KCalCore Library
icaltimezones.cpp
00001 /* 00002 This file is part of the kcalcore library. 00003 00004 Copyright (c) 2005-2007 David Jarvie <djarvie@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License as published by the Free Software Foundation; either 00009 version 2 of the License, or (at your option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 Boston, MA 02110-1301, USA. 00020 */ 00021 #include <config-kcalcore.h> 00022 00023 #include "icaltimezones.h" 00024 #include "icalformat.h" 00025 #include "icalformat_p.h" 00026 #include "recurrence.h" 00027 #include "recurrencerule.h" 00028 00029 #include <KDebug> 00030 #include <KDateTime> 00031 #include <KSystemTimeZone> 00032 00033 #include <QtCore/QDateTime> 00034 #include <QtCore/QFile> 00035 #include <QtCore/QTextStream> 00036 00037 extern "C" { 00038 #include <ical.h> 00039 #include <icaltimezone.h> 00040 } 00041 00042 #if defined(HAVE_UUID_UUID_H) 00043 #include <uuid/uuid.h> 00044 #endif 00045 00046 using namespace KCalCore; 00047 00048 // Minimum repetition counts for VTIMEZONE RRULEs 00049 static const int minRuleCount = 5; // for any RRULE 00050 static const int minPhaseCount = 8; // for separate STANDARD/DAYLIGHT component 00051 00052 // Convert an ical time to QDateTime, preserving the UTC indicator 00053 static QDateTime toQDateTime( const icaltimetype &t ) 00054 { 00055 return QDateTime( QDate( t.year, t.month, t.day ), 00056 QTime( t.hour, t.minute, t.second ), 00057 ( t.is_utc ? Qt::UTC : Qt::LocalTime ) ); 00058 } 00059 00060 // Maximum date for time zone data. 00061 // It's not sensible to try to predict them very far in advance, because 00062 // they can easily change. Plus, it limits the processing required. 00063 static QDateTime MAX_DATE() 00064 { 00065 static QDateTime dt; 00066 if ( !dt.isValid() ) { 00067 dt = QDateTime( QDate::currentDate().addYears( 20 ), QTime( 0, 0, 0 ) ); 00068 } 00069 return dt; 00070 } 00071 00072 static icaltimetype writeLocalICalDateTime( const QDateTime &utc, int offset ) 00073 { 00074 QDateTime local = utc.addSecs( offset ); 00075 icaltimetype t = icaltime_null_time(); 00076 t.year = local.date().year(); 00077 t.month = local.date().month(); 00078 t.day = local.date().day(); 00079 t.hour = local.time().hour(); 00080 t.minute = local.time().minute(); 00081 t.second = local.time().second(); 00082 t.is_date = 0; 00083 t.zone = 0; 00084 t.is_utc = 0; 00085 return t; 00086 } 00087 00088 namespace KCalCore { 00089 00090 /******************************************************************************/ 00091 00092 //@cond PRIVATE 00093 class ICalTimeZonesPrivate 00094 { 00095 public: 00096 ICalTimeZonesPrivate() {} 00097 ICalTimeZones::ZoneMap zones; 00098 }; 00099 //@endcond 00100 00101 ICalTimeZones::ICalTimeZones() 00102 : d( new ICalTimeZonesPrivate ) 00103 { 00104 } 00105 00106 ICalTimeZones::ICalTimeZones( const ICalTimeZones &rhs ) 00107 : d( new ICalTimeZonesPrivate() ) 00108 { 00109 d->zones = rhs.d->zones; 00110 } 00111 00112 ICalTimeZones &ICalTimeZones::operator=( const ICalTimeZones &rhs ) 00113 { 00114 // check for self assignment 00115 if ( &rhs == this ) { 00116 return *this; 00117 } 00118 d->zones = rhs.d->zones; 00119 return *this; 00120 } 00121 00122 ICalTimeZones::~ICalTimeZones() 00123 { 00124 delete d; 00125 } 00126 00127 const ICalTimeZones::ZoneMap ICalTimeZones::zones() const 00128 { 00129 return d->zones; 00130 } 00131 00132 bool ICalTimeZones::add( const ICalTimeZone &zone ) 00133 { 00134 if ( !zone.isValid() ) { 00135 return false; 00136 } 00137 if ( d->zones.find( zone.name() ) != d->zones.end() ) { 00138 return false; // name already exists 00139 } 00140 00141 d->zones.insert( zone.name(), zone ); 00142 return true; 00143 } 00144 00145 ICalTimeZone ICalTimeZones::remove( const ICalTimeZone &zone ) 00146 { 00147 if ( zone.isValid() ) { 00148 for ( ZoneMap::Iterator it = d->zones.begin(), end = d->zones.end(); it != end; ++it ) { 00149 if ( it.value() == zone ) { 00150 d->zones.erase( it ); 00151 return ( zone == ICalTimeZone::utc() ) ? ICalTimeZone() : zone; 00152 } 00153 } 00154 } 00155 return ICalTimeZone(); 00156 } 00157 00158 ICalTimeZone ICalTimeZones::remove( const QString &name ) 00159 { 00160 if ( !name.isEmpty() ) { 00161 ZoneMap::Iterator it = d->zones.find( name ); 00162 if ( it != d->zones.end() ) { 00163 ICalTimeZone zone = it.value(); 00164 d->zones.erase(it); 00165 return ( zone == ICalTimeZone::utc() ) ? ICalTimeZone() : zone; 00166 } 00167 } 00168 return ICalTimeZone(); 00169 } 00170 00171 void ICalTimeZones::clear() 00172 { 00173 d->zones.clear(); 00174 } 00175 00176 int ICalTimeZones::count() 00177 { 00178 return d->zones.count(); 00179 } 00180 00181 ICalTimeZone ICalTimeZones::zone( const QString &name ) const 00182 { 00183 if ( !name.isEmpty() ) { 00184 ZoneMap::ConstIterator it = d->zones.constFind( name ); 00185 if ( it != d->zones.constEnd() ) { 00186 return it.value(); 00187 } 00188 } 00189 return ICalTimeZone(); // error 00190 } 00191 00192 ICalTimeZone ICalTimeZones::zone( const ICalTimeZone &zone ) const 00193 { 00194 if ( zone.isValid() ) { 00195 QMapIterator<QString, ICalTimeZone> it(d->zones); 00196 while ( it.hasNext() ) { 00197 it.next(); 00198 ICalTimeZone tz = it.value(); 00199 QList<KTimeZone::Transition> list1 = tz.transitions(); 00200 QList<KTimeZone::Transition> list2 = zone.transitions(); 00201 if ( list1.size() == list2.size() ) { 00202 int i = 0; 00203 int matches = 0; 00204 for ( ; i < list1.size(); ++i ) { 00205 KTimeZone::Transition t1 = list1.at( i ); 00206 KTimeZone::Transition t2 = list2.at( i ); 00207 if ( ( t1.time() == t2.time() ) && 00208 ( t1.phase().utcOffset() == t2.phase().utcOffset() ) && 00209 ( t1.phase().isDst() == t2.phase().isDst() ) ) { 00210 matches++; 00211 } 00212 } 00213 if ( matches == i ) { 00214 // Existing zone has all the transitions of the given zone. 00215 return tz; 00216 } 00217 } 00218 } 00219 } 00220 return ICalTimeZone(); // not found 00221 } 00222 00223 /******************************************************************************/ 00224 00225 ICalTimeZoneBackend::ICalTimeZoneBackend() 00226 : KTimeZoneBackend() 00227 {} 00228 00229 ICalTimeZoneBackend::ICalTimeZoneBackend( ICalTimeZoneSource *source, 00230 const QString &name, 00231 const QString &countryCode, 00232 float latitude, float longitude, 00233 const QString &comment ) 00234 : KTimeZoneBackend( source, name, countryCode, latitude, longitude, comment ) 00235 {} 00236 00237 ICalTimeZoneBackend::ICalTimeZoneBackend( const KTimeZone &tz, const QDate &earliest ) 00238 : KTimeZoneBackend( 0, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment() ) 00239 { 00240 Q_UNUSED( earliest ); 00241 } 00242 00243 ICalTimeZoneBackend::~ICalTimeZoneBackend() 00244 {} 00245 00246 KTimeZoneBackend *ICalTimeZoneBackend::clone() const 00247 { 00248 return new ICalTimeZoneBackend( *this ); 00249 } 00250 00251 QByteArray ICalTimeZoneBackend::type() const 00252 { 00253 return "ICalTimeZone"; 00254 } 00255 00256 bool ICalTimeZoneBackend::hasTransitions( const KTimeZone *caller ) const 00257 { 00258 Q_UNUSED( caller ); 00259 return true; 00260 } 00261 00262 void ICalTimeZoneBackend::virtual_hook( int id, void *data ) 00263 { 00264 Q_UNUSED( id ); 00265 Q_UNUSED( data ); 00266 } 00267 00268 /******************************************************************************/ 00269 00270 ICalTimeZone::ICalTimeZone() 00271 : KTimeZone( new ICalTimeZoneBackend() ) 00272 {} 00273 00274 ICalTimeZone::ICalTimeZone( ICalTimeZoneSource *source, const QString &name, 00275 ICalTimeZoneData *data ) 00276 : KTimeZone( new ICalTimeZoneBackend( source, name ) ) 00277 { 00278 setData( data ); 00279 } 00280 00281 ICalTimeZone::ICalTimeZone( const KTimeZone &tz, const QDate &earliest ) 00282 : KTimeZone( new ICalTimeZoneBackend( 0, tz.name(), tz.countryCode(), 00283 tz.latitude(), tz.longitude(), 00284 tz.comment() ) ) 00285 { 00286 const KTimeZoneData *data = tz.data( true ); 00287 if ( data ) { 00288 const ICalTimeZoneData *icaldata = dynamic_cast<const ICalTimeZoneData*>( data ); 00289 if ( icaldata ) { 00290 setData( new ICalTimeZoneData( *icaldata ) ); 00291 } else { 00292 setData( new ICalTimeZoneData( *data, tz, earliest ) ); 00293 } 00294 } 00295 } 00296 00297 ICalTimeZone::~ICalTimeZone() 00298 {} 00299 00300 QString ICalTimeZone::city() const 00301 { 00302 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00303 return dat ? dat->city() : QString(); 00304 } 00305 00306 QByteArray ICalTimeZone::url() const 00307 { 00308 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00309 return dat ? dat->url() : QByteArray(); 00310 } 00311 00312 QDateTime ICalTimeZone::lastModified() const 00313 { 00314 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00315 return dat ? dat->lastModified() : QDateTime(); 00316 } 00317 00318 QByteArray ICalTimeZone::vtimezone() const 00319 { 00320 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00321 return dat ? dat->vtimezone() : QByteArray(); 00322 } 00323 00324 icaltimezone *ICalTimeZone::icalTimezone() const 00325 { 00326 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00327 return dat ? dat->icalTimezone() : 0; 00328 } 00329 00330 bool ICalTimeZone::update( const ICalTimeZone &other ) 00331 { 00332 if ( !updateBase( other ) ) { 00333 return false; 00334 } 00335 00336 KTimeZoneData *otherData = other.data() ? other.data()->clone() : 0; 00337 setData( otherData, other.source() ); 00338 return true; 00339 } 00340 00341 ICalTimeZone ICalTimeZone::utc() 00342 { 00343 static ICalTimeZone utcZone; 00344 if ( !utcZone.isValid() ) { 00345 ICalTimeZoneSource tzs; 00346 utcZone = tzs.parse( icaltimezone_get_utc_timezone() ); 00347 } 00348 return utcZone; 00349 } 00350 00351 void ICalTimeZone::virtual_hook( int id, void *data ) 00352 { 00353 Q_UNUSED( id ); 00354 Q_UNUSED( data ); 00355 } 00356 /******************************************************************************/ 00357 00358 //@cond PRIVATE 00359 class ICalTimeZoneDataPrivate 00360 { 00361 public: 00362 ICalTimeZoneDataPrivate() : icalComponent(0) {} 00363 ~ICalTimeZoneDataPrivate() 00364 { 00365 if ( icalComponent ) { 00366 icalcomponent_free( icalComponent ); 00367 } 00368 } 00369 icalcomponent *component() const { return icalComponent; } 00370 void setComponent( icalcomponent *c ) 00371 { 00372 if ( icalComponent ) { 00373 icalcomponent_free( icalComponent ); 00374 } 00375 icalComponent = c; 00376 } 00377 QString location; // name of city for this time zone 00378 QByteArray url; // URL of published VTIMEZONE definition (optional) 00379 QDateTime lastModified; // time of last modification of the VTIMEZONE component (optional) 00380 private: 00381 icalcomponent *icalComponent; // ical component representing this time zone 00382 }; 00383 //@endcond 00384 00385 ICalTimeZoneData::ICalTimeZoneData() 00386 : d ( new ICalTimeZoneDataPrivate() ) 00387 { 00388 } 00389 00390 ICalTimeZoneData::ICalTimeZoneData( const ICalTimeZoneData &rhs ) 00391 : KTimeZoneData( rhs ), 00392 d( new ICalTimeZoneDataPrivate() ) 00393 { 00394 d->location = rhs.d->location; 00395 d->url = rhs.d->url; 00396 d->lastModified = rhs.d->lastModified; 00397 d->setComponent( icalcomponent_new_clone( rhs.d->component() ) ); 00398 } 00399 00400 ICalTimeZoneData::ICalTimeZoneData( const KTimeZoneData &rhs, 00401 const KTimeZone &tz, const QDate &earliest ) 00402 : KTimeZoneData( rhs ), 00403 d( new ICalTimeZoneDataPrivate() ) 00404 { 00405 // VTIMEZONE RRULE types 00406 enum { 00407 DAY_OF_MONTH = 0x01, 00408 WEEKDAY_OF_MONTH = 0x02, 00409 LAST_WEEKDAY_OF_MONTH = 0x04 00410 }; 00411 00412 if ( tz.type() == "KSystemTimeZone" ) { 00413 // Try to fetch a system time zone in preference, on the grounds 00414 // that system time zones are more likely to be up to date than 00415 // built-in libical ones. 00416 icalcomponent *c = 0; 00417 KTimeZone ktz = KSystemTimeZones::readZone( tz.name() ); 00418 if ( ktz.isValid() ) { 00419 if ( ktz.data(true) ) { 00420 ICalTimeZone icaltz( ktz, earliest ); 00421 icaltimezone *itz = icaltz.icalTimezone(); 00422 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) ); 00423 icaltimezone_free( itz, 1 ); 00424 } 00425 } 00426 if ( !c ) { 00427 // Try to fetch a built-in libical time zone. 00428 icaltimezone *itz = icaltimezone_get_builtin_timezone( tz.name().toUtf8() ); 00429 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) ); 00430 } 00431 if ( c ) { 00432 // TZID in built-in libical time zones has a standard prefix. 00433 // To make the VTIMEZONE TZID match TZID references in incidences 00434 // (as required by RFC2445), strip off the prefix. 00435 icalproperty *prop = icalcomponent_get_first_property( c, ICAL_TZID_PROPERTY ); 00436 if ( prop ) { 00437 icalvalue *value = icalproperty_get_value( prop ); 00438 const char *tzid = icalvalue_get_text( value ); 00439 QByteArray icalprefix = ICalTimeZoneSource::icalTzidPrefix(); 00440 int len = icalprefix.size(); 00441 if ( !strncmp( icalprefix, tzid, len ) ) { 00442 const char *s = strchr( tzid + len, '/' ); // find third '/' 00443 if ( s ) { 00444 QByteArray tzidShort( s + 1 ); // deep copy of string (needed by icalvalue_set_text()) 00445 icalvalue_set_text( value, tzidShort ); 00446 00447 // Remove the X-LIC-LOCATION property, which is only used by libical 00448 prop = icalcomponent_get_first_property( c, ICAL_X_PROPERTY ); 00449 const char *xname = icalproperty_get_x_name( prop ); 00450 if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) { 00451 icalcomponent_remove_property( c, prop ); 00452 } 00453 } 00454 } 00455 } 00456 } 00457 d->setComponent( c ); 00458 } else { 00459 // Write the time zone data into an iCal component 00460 icalcomponent *tzcomp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT); 00461 icalcomponent_add_property( tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ) ); 00462 // icalcomponent_add_property(tzcomp, icalproperty_new_location( tz.name().toUtf8() )); 00463 00464 // Compile an ordered list of transitions so that we can know the phases 00465 // which occur before and after each transition. 00466 QList<KTimeZone::Transition> transits = transitions(); 00467 if ( earliest.isValid() ) { 00468 // Remove all transitions earlier than those we are interested in 00469 for ( int i = 0, end = transits.count(); i < end; ++i ) { 00470 if ( transits[i].time().date() >= earliest ) { 00471 if ( i > 0 ) { 00472 transits.erase( transits.begin(), transits.begin() + i ); 00473 } 00474 break; 00475 } 00476 } 00477 } 00478 int trcount = transits.count(); 00479 QVector<bool> transitionsDone(trcount); 00480 transitionsDone.fill(false); 00481 00482 // Go through the list of transitions and create an iCal component for each 00483 // distinct combination of phase after and UTC offset before the transition. 00484 icaldatetimeperiodtype dtperiod; 00485 dtperiod.period = icalperiodtype_null_period(); 00486 for ( ; ; ) { 00487 int i = 0; 00488 for ( ; i < trcount && transitionsDone[i]; ++i ) { 00489 ; 00490 } 00491 if ( i >= trcount ) { 00492 break; 00493 } 00494 // Found a phase combination which hasn't yet been processed 00495 int preOffset = ( i > 0 ) ? transits[i - 1].phase().utcOffset() : rhs.previousUtcOffset(); 00496 KTimeZone::Phase phase = transits[i].phase(); 00497 if ( phase.utcOffset() == preOffset ) { 00498 transitionsDone[i] = true; 00499 while ( ++i < trcount ) { 00500 if ( transitionsDone[i] || 00501 transits[i].phase() != phase || 00502 transits[i - 1].phase().utcOffset() != preOffset ) { 00503 continue; 00504 } 00505 transitionsDone[i] = true; 00506 } 00507 continue; 00508 } 00509 icalcomponent *phaseComp = 00510 icalcomponent_new( phase.isDst() ? ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT ); 00511 QList<QByteArray> abbrevs = phase.abbreviations(); 00512 for ( int a = 0, aend = abbrevs.count(); a < aend; ++a ) { 00513 icalcomponent_add_property( phaseComp, 00514 icalproperty_new_tzname( 00515 static_cast<const char*>( abbrevs[a]) ) ); 00516 } 00517 if ( !phase.comment().isEmpty() ) { 00518 icalcomponent_add_property( phaseComp, 00519 icalproperty_new_comment( phase.comment().toUtf8() ) ); 00520 } 00521 icalcomponent_add_property( phaseComp, 00522 icalproperty_new_tzoffsetfrom( preOffset ) ); 00523 icalcomponent_add_property( phaseComp, 00524 icalproperty_new_tzoffsetto( phase.utcOffset() ) ); 00525 // Create a component to hold initial RRULE if any, plus all RDATEs 00526 icalcomponent *phaseComp1 = icalcomponent_new_clone( phaseComp ); 00527 icalcomponent_add_property( phaseComp1, 00528 icalproperty_new_dtstart( 00529 writeLocalICalDateTime( transits[i].time(), preOffset ) ) ); 00530 bool useNewRRULE = false; 00531 00532 // Compile the list of UTC transition dates/times, and check 00533 // if the list can be reduced to an RRULE instead of multiple RDATEs. 00534 QTime time; 00535 QDate date; 00536 int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0; // avoid compiler warnings 00537 int dayOfWeek = 0; // Monday = 1 00538 int nthFromStart = 0; // nth (weekday) of month 00539 int nthFromEnd = 0; // nth last (weekday) of month 00540 int newRule; 00541 int rule = 0; 00542 QList<QDateTime> rdates;// dates which (probably) need to be written as RDATEs 00543 QList<QDateTime> times; 00544 QDateTime qdt = transits[i].time(); // set 'qdt' for start of loop 00545 times += qdt; 00546 transitionsDone[i] = true; 00547 do { 00548 if ( !rule ) { 00549 // Initialise data for detecting a new rule 00550 rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH; 00551 time = qdt.time(); 00552 date = qdt.date(); 00553 year = date.year(); 00554 month = date.month(); 00555 daysInMonth = date.daysInMonth(); 00556 dayOfWeek = date.dayOfWeek(); // Monday = 1 00557 dayOfMonth = date.day(); 00558 nthFromStart = ( dayOfMonth - 1 ) / 7 + 1; // nth (weekday) of month 00559 nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1; // nth last (weekday) of month 00560 } 00561 if ( ++i >= trcount ) { 00562 newRule = 0; 00563 times += QDateTime(); // append a dummy value since last value in list is ignored 00564 } else { 00565 if ( transitionsDone[i] || 00566 transits[i].phase() != phase || 00567 transits[i - 1].phase().utcOffset() != preOffset ) { 00568 continue; 00569 } 00570 transitionsDone[i] = true; 00571 qdt = transits[i].time(); 00572 if ( !qdt.isValid() ) { 00573 continue; 00574 } 00575 newRule = rule; 00576 times += qdt; 00577 date = qdt.date(); 00578 if ( qdt.time() != time || 00579 date.month() != month || 00580 date.year() != ++year ) { 00581 newRule = 0; 00582 } else { 00583 int day = date.day(); 00584 if ( ( newRule & DAY_OF_MONTH ) && day != dayOfMonth ) { 00585 newRule &= ~DAY_OF_MONTH; 00586 } 00587 if ( newRule & ( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ) ) { 00588 if ( date.dayOfWeek() != dayOfWeek ) { 00589 newRule &= ~( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ); 00590 } else { 00591 if ( ( newRule & WEEKDAY_OF_MONTH ) && 00592 ( day - 1 ) / 7 + 1 != nthFromStart ) { 00593 newRule &= ~WEEKDAY_OF_MONTH; 00594 } 00595 if ( ( newRule & LAST_WEEKDAY_OF_MONTH ) && 00596 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) { 00597 newRule &= ~LAST_WEEKDAY_OF_MONTH; 00598 } 00599 } 00600 } 00601 } 00602 } 00603 if ( !newRule ) { 00604 // The previous rule (if any) no longer applies. 00605 // Write all the times up to but not including the current one. 00606 // First check whether any of the last RDATE values fit this rule. 00607 int yr = times[0].date().year(); 00608 while ( !rdates.isEmpty() ) { 00609 qdt = rdates.last(); 00610 date = qdt.date(); 00611 if ( qdt.time() != time || 00612 date.month() != month || 00613 date.year() != --yr ) { 00614 break; 00615 } 00616 int day = date.day(); 00617 if ( rule & DAY_OF_MONTH ) { 00618 if ( day != dayOfMonth ) { 00619 break; 00620 } 00621 } else { 00622 if ( date.dayOfWeek() != dayOfWeek || 00623 ( ( rule & WEEKDAY_OF_MONTH ) && 00624 ( day - 1 ) / 7 + 1 != nthFromStart ) || 00625 ( ( rule & LAST_WEEKDAY_OF_MONTH ) && 00626 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) ) { 00627 break; 00628 } 00629 } 00630 times.prepend( qdt ); 00631 rdates.pop_back(); 00632 } 00633 if ( times.count() > ( useNewRRULE ? minPhaseCount : minRuleCount ) ) { 00634 // There are enough dates to combine into an RRULE 00635 icalrecurrencetype r; 00636 icalrecurrencetype_clear( &r ); 00637 r.freq = ICAL_YEARLY_RECURRENCE; 00638 r.count = ( year >= 2030 ) ? 0 : times.count() - 1; 00639 r.by_month[0] = month; 00640 if ( rule & DAY_OF_MONTH ) { 00641 r.by_month_day[0] = dayOfMonth; 00642 } else if ( rule & WEEKDAY_OF_MONTH ) { 00643 r.by_day[0] = ( dayOfWeek % 7 + 1 ) + ( nthFromStart * 8 ); // Sunday = 1 00644 } else if ( rule & LAST_WEEKDAY_OF_MONTH ) { 00645 r.by_day[0] = -( dayOfWeek % 7 + 1 ) - ( nthFromEnd * 8 ); // Sunday = 1 00646 } 00647 icalproperty *prop = icalproperty_new_rrule( r ); 00648 if ( useNewRRULE ) { 00649 // This RRULE doesn't start from the phase start date, so set it into 00650 // a new STANDARD/DAYLIGHT component in the VTIMEZONE. 00651 icalcomponent *c = icalcomponent_new_clone( phaseComp ); 00652 icalcomponent_add_property( 00653 c, icalproperty_new_dtstart( writeLocalICalDateTime( times[0], preOffset ) ) ); 00654 icalcomponent_add_property( c, prop ); 00655 icalcomponent_add_component( tzcomp, c ); 00656 } else { 00657 icalcomponent_add_property( phaseComp1, prop ); 00658 } 00659 } else { 00660 // Save dates for writing as RDATEs 00661 for ( int t = 0, tend = times.count() - 1; t < tend; ++t ) { 00662 rdates += times[t]; 00663 } 00664 } 00665 useNewRRULE = true; 00666 // All date/time values but the last have been added to the VTIMEZONE. 00667 // Remove them from the list. 00668 qdt = times.last(); // set 'qdt' for start of loop 00669 times.clear(); 00670 times += qdt; 00671 } 00672 rule = newRule; 00673 } while ( i < trcount ); 00674 00675 // Write remaining dates as RDATEs 00676 for ( int rd = 0, rdend = rdates.count(); rd < rdend; ++rd ) { 00677 dtperiod.time = writeLocalICalDateTime( rdates[rd], preOffset ); 00678 icalcomponent_add_property( phaseComp1, icalproperty_new_rdate( dtperiod ) ); 00679 } 00680 icalcomponent_add_component( tzcomp, phaseComp1 ); 00681 icalcomponent_free( phaseComp ); 00682 } 00683 00684 d->setComponent( tzcomp ); 00685 } 00686 } 00687 00688 ICalTimeZoneData::~ICalTimeZoneData() 00689 { 00690 delete d; 00691 } 00692 00693 ICalTimeZoneData &ICalTimeZoneData::operator=( const ICalTimeZoneData &rhs ) 00694 { 00695 // check for self assignment 00696 if ( &rhs == this ) { 00697 return *this; 00698 } 00699 00700 KTimeZoneData::operator=( rhs ); 00701 d->location = rhs.d->location; 00702 d->url = rhs.d->url; 00703 d->lastModified = rhs.d->lastModified; 00704 d->setComponent( icalcomponent_new_clone( rhs.d->component() ) ); 00705 return *this; 00706 } 00707 00708 KTimeZoneData *ICalTimeZoneData::clone() const 00709 { 00710 return new ICalTimeZoneData( *this ); 00711 } 00712 00713 QString ICalTimeZoneData::city() const 00714 { 00715 return d->location; 00716 } 00717 00718 QByteArray ICalTimeZoneData::url() const 00719 { 00720 return d->url; 00721 } 00722 00723 QDateTime ICalTimeZoneData::lastModified() const 00724 { 00725 return d->lastModified; 00726 } 00727 00728 QByteArray ICalTimeZoneData::vtimezone() const 00729 { 00730 QByteArray result( icalcomponent_as_ical_string( d->component() ) ); 00731 icalmemory_free_ring(); 00732 return result; 00733 } 00734 00735 icaltimezone *ICalTimeZoneData::icalTimezone() const 00736 { 00737 icaltimezone *icaltz = icaltimezone_new(); 00738 if ( !icaltz ) { 00739 return 0; 00740 } 00741 icalcomponent *c = icalcomponent_new_clone( d->component() ); 00742 if ( !icaltimezone_set_component( icaltz, c ) ) { 00743 icalcomponent_free( c ); 00744 icaltimezone_free( icaltz, 1 ); 00745 return 0; 00746 } 00747 return icaltz; 00748 } 00749 00750 bool ICalTimeZoneData::hasTransitions() const 00751 { 00752 return true; 00753 } 00754 00755 void ICalTimeZoneData::virtual_hook( int id, void *data ) 00756 { 00757 Q_UNUSED( id ); 00758 Q_UNUSED( data ); 00759 } 00760 00761 /******************************************************************************/ 00762 00763 //@cond PRIVATE 00764 class ICalTimeZoneSourcePrivate 00765 { 00766 public: 00767 static QList<QDateTime> parsePhase( icalcomponent *, bool daylight, 00768 int &prevOffset, KTimeZone::Phase & ); 00769 static QByteArray icalTzidPrefix; 00770 00771 #if defined(HAVE_UUID_UUID_H) 00772 static void parseTransitions( const MSSystemTime &date, const KTimeZone::Phase &phase, 00773 int prevOffset, QList<KTimeZone::Transition> &transitions ); 00774 #endif 00775 }; 00776 00777 QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix; 00778 //@endcond 00779 00780 ICalTimeZoneSource::ICalTimeZoneSource() 00781 : KTimeZoneSource( false ), 00782 d( 0 ) 00783 { 00784 } 00785 00786 ICalTimeZoneSource::~ICalTimeZoneSource() 00787 { 00788 } 00789 00790 bool ICalTimeZoneSource::parse( const QString &fileName, ICalTimeZones &zones ) 00791 { 00792 QFile file( fileName ); 00793 if ( !file.open( QIODevice::ReadOnly ) ) { 00794 return false; 00795 } 00796 QTextStream ts( &file ); 00797 ts.setCodec( "ISO 8859-1" ); 00798 QByteArray text = ts.readAll().trimmed().toLatin1(); 00799 file.close(); 00800 00801 bool result = false; 00802 icalcomponent *calendar = icalcomponent_new_from_string( text.data() ); 00803 if ( calendar ) { 00804 if ( icalcomponent_isa( calendar ) == ICAL_VCALENDAR_COMPONENT ) { 00805 result = parse( calendar, zones ); 00806 } 00807 icalcomponent_free( calendar ); 00808 } 00809 return result; 00810 } 00811 00812 bool ICalTimeZoneSource::parse( icalcomponent *calendar, ICalTimeZones &zones ) 00813 { 00814 for ( icalcomponent *c = icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT ); 00815 c; c = icalcomponent_get_next_component( calendar, ICAL_VTIMEZONE_COMPONENT ) ) { 00816 ICalTimeZone zone = parse( c ); 00817 if ( !zone.isValid() ) { 00818 return false; 00819 } 00820 ICalTimeZone oldzone = zones.zone( zone.name() ); 00821 if ( oldzone.isValid() ) { 00822 // The zone already exists in the collection, so update the definition 00823 // of the zone rather than using a newly created one. 00824 oldzone.update( zone ); 00825 } else if ( !zones.add( zone ) ) { 00826 return false; 00827 } 00828 } 00829 return true; 00830 } 00831 00832 ICalTimeZone ICalTimeZoneSource::parse( icalcomponent *vtimezone ) 00833 { 00834 QString name; 00835 QString xlocation; 00836 ICalTimeZoneData *data = new ICalTimeZoneData(); 00837 00838 // Read the fixed properties which can only appear once in VTIMEZONE 00839 icalproperty *p = icalcomponent_get_first_property( vtimezone, ICAL_ANY_PROPERTY ); 00840 while ( p ) { 00841 icalproperty_kind kind = icalproperty_isa( p ); 00842 switch ( kind ) { 00843 00844 case ICAL_TZID_PROPERTY: 00845 name = QString::fromUtf8( icalproperty_get_tzid( p ) ); 00846 break; 00847 00848 case ICAL_TZURL_PROPERTY: 00849 data->d->url = icalproperty_get_tzurl( p ); 00850 break; 00851 00852 case ICAL_LOCATION_PROPERTY: 00853 // This isn't mentioned in RFC2445, but libical reads it ... 00854 data->d->location = QString::fromUtf8( icalproperty_get_location( p ) ); 00855 break; 00856 00857 case ICAL_X_PROPERTY: 00858 { // use X-LIC-LOCATION if LOCATION is missing 00859 const char *xname = icalproperty_get_x_name( p ); 00860 if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) { 00861 xlocation = QString::fromUtf8( icalproperty_get_x( p ) ); 00862 } 00863 break; 00864 } 00865 case ICAL_LASTMODIFIED_PROPERTY: 00866 { 00867 icaltimetype t = icalproperty_get_lastmodified(p); 00868 if ( t.is_utc ) { 00869 data->d->lastModified = toQDateTime( t ); 00870 } else { 00871 kDebug() << "LAST-MODIFIED not UTC"; 00872 } 00873 break; 00874 } 00875 default: 00876 break; 00877 } 00878 p = icalcomponent_get_next_property( vtimezone, ICAL_ANY_PROPERTY ); 00879 } 00880 00881 if ( name.isEmpty() ) { 00882 kDebug() << "TZID missing"; 00883 delete data; 00884 return ICalTimeZone(); 00885 } 00886 if ( data->d->location.isEmpty() && !xlocation.isEmpty() ) { 00887 data->d->location = xlocation; 00888 } 00889 QString prefix = QString::fromUtf8( icalTzidPrefix() ); 00890 if ( name.startsWith( prefix ) ) { 00891 // Remove the prefix from libical built in time zone TZID 00892 int i = name.indexOf( '/', prefix.length() ); 00893 if ( i > 0 ) { 00894 name = name.mid( i + 1 ); 00895 } 00896 } 00897 //kDebug() << "---zoneId: \"" << name << '"'; 00898 00899 /* 00900 * Iterate through all time zone rules for this VTIMEZONE, 00901 * and create a Phase object containing details for each one. 00902 */ 00903 int prevOffset = 0; 00904 QList<KTimeZone::Transition> transitions; 00905 QDateTime earliest; 00906 QList<KTimeZone::Phase> phases; 00907 for ( icalcomponent *c = icalcomponent_get_first_component( vtimezone, ICAL_ANY_COMPONENT ); 00908 c; c = icalcomponent_get_next_component( vtimezone, ICAL_ANY_COMPONENT ) ) 00909 { 00910 int prevoff = 0; 00911 KTimeZone::Phase phase; 00912 QList<QDateTime> times; 00913 icalcomponent_kind kind = icalcomponent_isa( c ); 00914 switch ( kind ) { 00915 00916 case ICAL_XSTANDARD_COMPONENT: 00917 //kDebug() << "---standard phase: found"; 00918 times = ICalTimeZoneSourcePrivate::parsePhase( c, false, prevoff, phase ); 00919 break; 00920 00921 case ICAL_XDAYLIGHT_COMPONENT: 00922 //kDebug() << "---daylight phase: found"; 00923 times = ICalTimeZoneSourcePrivate::parsePhase( c, true, prevoff, phase ); 00924 break; 00925 00926 default: 00927 kDebug() << "Unknown component:" << int( kind ); 00928 break; 00929 } 00930 int tcount = times.count(); 00931 if ( tcount ) { 00932 phases += phase; 00933 for ( int t = 0; t < tcount; ++t ) { 00934 transitions += KTimeZone::Transition( times[t], phase ); 00935 } 00936 if ( !earliest.isValid() || times[0] < earliest ) { 00937 prevOffset = prevoff; 00938 earliest = times[0]; 00939 } 00940 } 00941 } 00942 data->setPhases( phases, prevOffset ); 00943 // Remove any "duplicate" transitions, i.e. those where two consecutive 00944 // transitions have the same phase. 00945 qSort( transitions ); 00946 for ( int t = 1, tend = transitions.count(); t < tend; ) { 00947 if ( transitions[t].phase() == transitions[t - 1].phase() ) { 00948 transitions.removeAt( t ); 00949 --tend; 00950 } else { 00951 ++t; 00952 } 00953 } 00954 data->setTransitions( transitions ); 00955 00956 data->d->setComponent( icalcomponent_new_clone( vtimezone ) ); 00957 kDebug() << "VTIMEZONE" << name; 00958 return ICalTimeZone( this, name, data ); 00959 } 00960 00961 #if defined(HAVE_UUID_UUID_H) 00962 ICalTimeZone ICalTimeZoneSource::parse( MSTimeZone *tz, ICalTimeZones &zones ) 00963 { 00964 ICalTimeZone zone = parse( tz ); 00965 if ( !zone.isValid() ) { 00966 return ICalTimeZone(); // error 00967 } 00968 ICalTimeZone oldzone = zones.zone( zone ); 00969 if ( oldzone.isValid() ) { 00970 // A similar zone already exists in the collection, so don't add this 00971 // new zone, return old zone instead. 00972 return oldzone; 00973 } else if ( zones.add( zone ) ) { 00974 // No similar zone, add and return new one. 00975 return zone; 00976 } 00977 return ICalTimeZone(); // error 00978 } 00979 00980 ICalTimeZone ICalTimeZoneSource::parse( MSTimeZone *tz ) 00981 { 00982 ICalTimeZoneData kdata; 00983 00984 // General properties. 00985 uuid_t uuid; 00986 char suuid[64]; 00987 uuid_generate_random( uuid ); 00988 uuid_unparse( uuid, suuid ); 00989 QString name = QString( suuid ); 00990 00991 // Create phases. 00992 QList<KTimeZone::Phase> phases; 00993 00994 QList<QByteArray> standardAbbrevs; 00995 standardAbbrevs += tz->StandardName.toAscii(); 00996 KTimeZone::Phase standardPhase( ( tz->Bias + tz->StandardBias ) * -60, standardAbbrevs, false, 00997 "Microsoft TIME_ZONE_INFORMATION" ); 00998 phases += standardPhase; 00999 01000 QList<QByteArray> daylightAbbrevs; 01001 daylightAbbrevs += tz->DaylightName.toAscii(); 01002 KTimeZone::Phase daylightPhase( ( tz->Bias + tz->DaylightBias ) * -60, daylightAbbrevs, true, 01003 "Microsoft TIME_ZONE_INFORMATION" ); 01004 phases += daylightPhase; 01005 01006 int prevOffset = 0; 01007 kdata.setPhases( phases, prevOffset ); 01008 01009 // Create transitions 01010 QList<KTimeZone::Transition> transitions; 01011 ICalTimeZoneSourcePrivate::parseTransitions( 01012 tz->StandardDate, standardPhase, prevOffset, transitions ); 01013 ICalTimeZoneSourcePrivate::parseTransitions( 01014 tz->DaylightDate, daylightPhase, prevOffset, transitions ); 01015 01016 qSort( transitions ); 01017 kdata.setTransitions( transitions ); 01018 01019 ICalTimeZoneData *idata = new ICalTimeZoneData( kdata, KTimeZone( name ), QDate() ); 01020 01021 return ICalTimeZone( this, name, idata ); 01022 } 01023 #endif // HAVE_UUID_UUID_H 01024 01025 ICalTimeZone ICalTimeZoneSource::parse( const QString &name, const QStringList &tzList, 01026 ICalTimeZones &zones ) 01027 { 01028 ICalTimeZone zone = parse( name, tzList ); 01029 if ( !zone.isValid() ) { 01030 return ICalTimeZone(); // error 01031 } 01032 01033 ICalTimeZone oldzone = zones.zone( zone ); 01034 // First off see if the zone is same as oldzone - _exactly_ same 01035 if ( oldzone.isValid() ) { 01036 return oldzone; 01037 } 01038 01039 oldzone = zones.zone( name ); 01040 if ( oldzone.isValid() ) { 01041 // The zone already exists, so update 01042 oldzone.update( zone ); 01043 return zone; 01044 } else if ( zones.add( zone ) ) { 01045 // No similar zone, add and return new one. 01046 return zone; 01047 } 01048 return ICalTimeZone(); // error 01049 } 01050 01051 ICalTimeZone ICalTimeZoneSource::parse( const QString &name, const QStringList &tzList ) 01052 { 01053 ICalTimeZoneData kdata; 01054 QList<KTimeZone::Phase> phases; 01055 QList<KTimeZone::Transition> transitions; 01056 bool daylight; 01057 01058 for ( QStringList::ConstIterator it = tzList.begin(); it != tzList.end(); ++it ) { 01059 QString value = *it; 01060 daylight = false; 01061 QString tzName = value.mid( 0, value.indexOf( ";" ) ); 01062 value = value.mid( ( value.indexOf( ";" ) + 1 ) ); 01063 QString tzOffset = value.mid( 0, value.indexOf( ";" ) ); 01064 value = value.mid( ( value.indexOf( ";" ) + 1 ) ); 01065 QString tzDaylight = value.mid( 0, value.indexOf( ";" ) ); 01066 KDateTime tzDate = KDateTime::fromString( value.mid( ( value.lastIndexOf( ";" ) + 1 ) ) ); 01067 if ( tzDaylight == "true" ) { 01068 daylight = true; 01069 } 01070 01071 KTimeZone::Phase tzPhase( tzOffset.toInt(), 01072 QByteArray( tzName.toAscii() ), daylight, "VCAL_TZ_INFORMATION" ); 01073 phases += tzPhase; 01074 transitions += KTimeZone::Transition( tzDate.dateTime(), tzPhase ); 01075 } 01076 01077 kdata.setPhases( phases, 0 ); 01078 qSort( transitions ); 01079 kdata.setTransitions( transitions ); 01080 01081 ICalTimeZoneData *idata = new ICalTimeZoneData( kdata, KTimeZone( name ), QDate() ); 01082 return ICalTimeZone( this, name, idata ); 01083 } 01084 01085 #if defined(HAVE_UUID_UUID_H) 01086 //@cond PRIVATE 01087 void ICalTimeZoneSourcePrivate::parseTransitions( const MSSystemTime &date, 01088 const KTimeZone::Phase &phase, int prevOffset, 01089 QList<KTimeZone::Transition> &transitions ) 01090 { 01091 // NOTE that we need to set start and end times and they cannot be 01092 // to far in either direction to avoid bloating the transitions list 01093 KDateTime klocalStart( QDateTime( QDate( 2000, 1, 1 ), QTime( 0, 0, 0 ) ), 01094 KDateTime::Spec::ClockTime() ); 01095 KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() ); 01096 01097 if ( date.wYear ) { 01098 // Absolute change time. 01099 if ( date.wYear >= 1601 && date.wYear <= 30827 && 01100 date.wMonth >= 1 && date.wMonth <= 12 && 01101 date.wDay >= 1 && date.wDay <= 31 ) { 01102 QDate dt( date.wYear, date.wMonth, date.wDay ); 01103 QTime tm( date.wHour, date.wMinute, date.wSecond, date.wMilliseconds ); 01104 QDateTime datetime( dt, tm ); 01105 if ( datetime.isValid() ) { 01106 transitions += KTimeZone::Transition( datetime, phase ); 01107 } 01108 } 01109 } else { 01110 // The normal way, for example: 'First Sunday in April at 02:00'. 01111 if ( date.wDayOfWeek >= 0 && date.wDayOfWeek <= 6 && 01112 date.wMonth >= 1 && date.wMonth <= 12 && 01113 date.wDay >= 1 && date.wDay <= 5 ) { 01114 RecurrenceRule r; 01115 r.setRecurrenceType( RecurrenceRule::rYearly ); 01116 r.setDuration( -1 ); 01117 r.setFrequency( 1 ); 01118 QList<int> lst; 01119 lst.append( date.wMonth ); 01120 r.setByMonths( lst ); 01121 QList<RecurrenceRule::WDayPos> wdlst; 01122 RecurrenceRule::WDayPos pos; 01123 pos.setDay( date.wDayOfWeek ? date.wDayOfWeek : 7 ); 01124 pos.setPos( date.wDay < 5 ? date.wDay : -1 ); 01125 wdlst.append( pos ); 01126 r.setByDays( wdlst ); 01127 r.setStartDt( klocalStart ); 01128 r.setWeekStart( 1 ); 01129 DateTimeList dtl = r.timesInInterval( klocalStart, maxTime ); 01130 for ( int i = 0, end = dtl.count(); i < end; ++i ) { 01131 QDateTime utc = dtl[i].dateTime(); 01132 utc.setTimeSpec( Qt::UTC ); 01133 transitions += KTimeZone::Transition( utc.addSecs( -prevOffset ), phase ); 01134 } 01135 } 01136 } 01137 } 01138 //@endcond 01139 #endif // HAVE_UUID_UUID_H 01140 01141 ICalTimeZone ICalTimeZoneSource::parse( icaltimezone *tz ) 01142 { 01143 /* Parse the VTIMEZONE component stored in the icaltimezone structure. 01144 * This is both easier and provides more complete information than 01145 * extracting already parsed data from icaltimezone. 01146 */ 01147 return tz ? parse( icaltimezone_get_component( tz ) ) : ICalTimeZone(); 01148 } 01149 01150 //@cond PRIVATE 01151 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase( icalcomponent *c, 01152 bool daylight, 01153 int &prevOffset, 01154 KTimeZone::Phase &phase ) 01155 { 01156 QList<QDateTime> transitions; 01157 01158 // Read the observance data for this standard/daylight savings phase 01159 QList<QByteArray> abbrevs; 01160 QString comment; 01161 prevOffset = 0; 01162 int utcOffset = 0; 01163 bool recurs = false; 01164 bool found_dtstart = false; 01165 bool found_tzoffsetfrom = false; 01166 bool found_tzoffsetto = false; 01167 icaltimetype dtstart = icaltime_null_time(); 01168 01169 // Now do the ical reading. 01170 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY ); 01171 while ( p ) { 01172 icalproperty_kind kind = icalproperty_isa( p ); 01173 switch ( kind ) { 01174 01175 case ICAL_TZNAME_PROPERTY: // abbreviated name for this time offset 01176 { 01177 // TZNAME can appear multiple times in order to provide language 01178 // translations of the time zone offset name. 01179 01180 // TODO: Does this cope with multiple language specifications? 01181 QByteArray tzname = icalproperty_get_tzname( p ); 01182 // Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME 01183 // strings, which is totally useless. So ignore those. 01184 if ( ( !daylight && tzname == "Standard Time" ) || 01185 ( daylight && tzname == "Daylight Time" ) ) { 01186 break; 01187 } 01188 if ( !abbrevs.contains( tzname ) ) { 01189 abbrevs += tzname; 01190 } 01191 break; 01192 } 01193 case ICAL_DTSTART_PROPERTY: // local time at which phase starts 01194 dtstart = icalproperty_get_dtstart( p ); 01195 found_dtstart = true; 01196 break; 01197 01198 case ICAL_TZOFFSETFROM_PROPERTY: // UTC offset immediately before start of phase 01199 prevOffset = icalproperty_get_tzoffsetfrom( p ); 01200 found_tzoffsetfrom = true; 01201 break; 01202 01203 case ICAL_TZOFFSETTO_PROPERTY: 01204 utcOffset = icalproperty_get_tzoffsetto( p ); 01205 found_tzoffsetto = true; 01206 break; 01207 01208 case ICAL_COMMENT_PROPERTY: 01209 comment = QString::fromUtf8( icalproperty_get_comment( p ) ); 01210 break; 01211 01212 case ICAL_RDATE_PROPERTY: 01213 case ICAL_RRULE_PROPERTY: 01214 recurs = true; 01215 break; 01216 01217 default: 01218 kDebug() << "Unknown property:" << int( kind ); 01219 break; 01220 } 01221 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY ); 01222 } 01223 01224 // Validate the phase data 01225 if ( !found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto ) { 01226 kDebug() << "DTSTART/TZOFFSETFROM/TZOFFSETTO missing"; 01227 return transitions; 01228 } 01229 01230 // Convert DTSTART to QDateTime, and from local time to UTC 01231 QDateTime localStart = toQDateTime( dtstart ); // local time 01232 dtstart.second -= prevOffset; 01233 dtstart.is_utc = 1; 01234 QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) ); // UTC 01235 01236 transitions += utcStart; 01237 if ( recurs ) { 01238 /* RDATE or RRULE is specified. There should only be one or the other, but 01239 * it doesn't really matter - the code can cope with both. 01240 * Note that we had to get DTSTART, TZOFFSETFROM, TZOFFSETTO before reading 01241 * recurrences. 01242 */ 01243 KDateTime klocalStart( localStart, KDateTime::Spec::ClockTime() ); 01244 KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() ); 01245 Recurrence recur; 01246 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY ); 01247 while ( p ) { 01248 icalproperty_kind kind = icalproperty_isa( p ); 01249 switch ( kind ) { 01250 01251 case ICAL_RDATE_PROPERTY: 01252 { 01253 icaltimetype t = icalproperty_get_rdate(p).time; 01254 if ( icaltime_is_date( t ) ) { 01255 // RDATE with a DATE value inherits the (local) time from DTSTART 01256 t.hour = dtstart.hour; 01257 t.minute = dtstart.minute; 01258 t.second = dtstart.second; 01259 t.is_date = 0; 01260 t.is_utc = 0; // dtstart is in local time 01261 } 01262 // RFC2445 states that RDATE must be in local time, 01263 // but we support UTC as well to be safe. 01264 if ( !t.is_utc ) { 01265 t.second -= prevOffset; // convert to UTC 01266 t.is_utc = 1; 01267 t = icaltime_normalize( t ); 01268 } 01269 transitions += toQDateTime( t ); 01270 break; 01271 } 01272 case ICAL_RRULE_PROPERTY: 01273 { 01274 RecurrenceRule r; 01275 ICalFormat icf; 01276 ICalFormatImpl impl( &icf ); 01277 impl.readRecurrence( icalproperty_get_rrule( p ), &r ); 01278 r.setStartDt( klocalStart ); 01279 // The end date time specified in an RRULE should be in UTC. 01280 // Convert to local time to avoid timesInInterval() getting things wrong. 01281 if ( r.duration() == 0 ) { 01282 KDateTime end( r.endDt() ); 01283 if ( end.timeSpec() == KDateTime::Spec::UTC() ) { 01284 end.setTimeSpec( KDateTime::Spec::ClockTime() ); 01285 r.setEndDt( end.addSecs( prevOffset ) ); 01286 } 01287 } 01288 DateTimeList dts = r.timesInInterval( klocalStart, maxTime ); 01289 for ( int i = 0, end = dts.count(); i < end; ++i ) { 01290 QDateTime utc = dts[i].dateTime(); 01291 utc.setTimeSpec( Qt::UTC ); 01292 transitions += utc.addSecs( -prevOffset ); 01293 } 01294 break; 01295 } 01296 default: 01297 break; 01298 } 01299 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY ); 01300 } 01301 qSortUnique( transitions ); 01302 } 01303 01304 phase = KTimeZone::Phase( utcOffset, abbrevs, daylight, comment ); 01305 return transitions; 01306 } 01307 //@endcond 01308 01309 ICalTimeZone ICalTimeZoneSource::standardZone( const QString &zone, bool icalBuiltIn ) 01310 { 01311 if ( !icalBuiltIn ) { 01312 // Try to fetch a system time zone in preference, on the grounds 01313 // that system time zones are more likely to be up to date than 01314 // built-in libical ones. 01315 QString tzid = zone; 01316 QString prefix = QString::fromUtf8( icalTzidPrefix() ); 01317 if ( zone.startsWith( prefix ) ) { 01318 int i = zone.indexOf( '/', prefix.length() ); 01319 if ( i > 0 ) { 01320 tzid = zone.mid( i + 1 ); // strip off the libical prefix 01321 } 01322 } 01323 KTimeZone ktz = KSystemTimeZones::readZone( tzid ); 01324 if ( ktz.isValid() ) { 01325 if ( ktz.data( true ) ) { 01326 ICalTimeZone icaltz( ktz ); 01327 //kDebug() << zone << " read from system database"; 01328 return icaltz; 01329 } 01330 } 01331 } 01332 // Try to fetch a built-in libical time zone. 01333 // First try to look it up as a geographical location (e.g. Europe/London) 01334 QByteArray zoneName = zone.toUtf8(); 01335 icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName ); 01336 if ( !icaltz ) { 01337 // This will find it if it includes the libical prefix 01338 icaltz = icaltimezone_get_builtin_timezone_from_tzid( zoneName ); 01339 if ( !icaltz ) { 01340 return ICalTimeZone(); 01341 } 01342 } 01343 return parse( icaltz ); 01344 } 01345 01346 QByteArray ICalTimeZoneSource::icalTzidPrefix() 01347 { 01348 if ( ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty() ) { 01349 icaltimezone *icaltz = icaltimezone_get_builtin_timezone( "Europe/London" ); 01350 QByteArray tzid = icaltimezone_get_tzid( icaltz ); 01351 if ( tzid.right( 13 ) == "Europe/London" ) { 01352 int i = tzid.indexOf( '/', 1 ); 01353 if ( i > 0 ) { 01354 ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left( i + 1 ); 01355 return ICalTimeZoneSourcePrivate::icalTzidPrefix; 01356 } 01357 } 01358 kError() << "failed to get libical TZID prefix"; 01359 } 01360 return ICalTimeZoneSourcePrivate::icalTzidPrefix; 01361 } 01362 01363 void ICalTimeZoneSource::virtual_hook( int id, void *data ) 01364 { 01365 Q_UNUSED( id ); 01366 Q_UNUSED( data ); 01367 Q_ASSERT( false ); 01368 } 01369 01370 } // namespace KCalCore