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