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