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