KCal Library
icaltimezones.cpp
00001 /* 00002 This file is part of the kcal 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 00022 #include "icaltimezones.h" 00023 #include "icalformat.h" 00024 #include "icalformat_p.h" 00025 00026 extern "C" { 00027 #include <libical/ical.h> 00028 #include <libical/icaltimezone.h> 00029 } 00030 #include <ksystemtimezone.h> 00031 #include <kdatetime.h> 00032 #include <kdebug.h> 00033 00034 #include <QtCore/QDateTime> 00035 #include <QtCore/QString> 00036 #include <QtCore/QList> 00037 #include <QtCore/QVector> 00038 #include <QtCore/QSet> 00039 #include <QtCore/QFile> 00040 #include <QtCore/QTextStream> 00041 00042 using namespace KCal; 00043 00044 // Minimum repetition counts for VTIMEZONE RRULEs 00045 static const int minRuleCount = 5; // for any RRULE 00046 static const int minPhaseCount = 8; // for separate STANDARD/DAYLIGHT component 00047 00048 // Convert an ical time to QDateTime, preserving the UTC indicator 00049 static QDateTime toQDateTime( const icaltimetype &t ) 00050 { 00051 return QDateTime( QDate( t.year, t.month, t.day ), 00052 QTime( t.hour, t.minute, t.second ), 00053 ( t.is_utc ? Qt::UTC : Qt::LocalTime ) ); 00054 } 00055 00056 // Maximum date for time zone data. 00057 // It's not sensible to try to predict them very far in advance, because 00058 // they can easily change. Plus, it limits the processing required. 00059 static QDateTime MAX_DATE() 00060 { 00061 static QDateTime dt; 00062 if ( !dt.isValid() ) { 00063 dt = QDateTime( QDate::currentDate().addYears( 20 ), QTime( 0, 0, 0 ) ); 00064 } 00065 return dt; 00066 } 00067 00068 static icaltimetype writeLocalICalDateTime( const QDateTime &utc, int offset ) 00069 { 00070 QDateTime local = utc.addSecs( offset ); 00071 icaltimetype t = icaltime_null_time(); 00072 t.year = local.date().year(); 00073 t.month = local.date().month(); 00074 t.day = local.date().day(); 00075 t.hour = local.time().hour(); 00076 t.minute = local.time().minute(); 00077 t.second = local.time().second(); 00078 t.is_date = 0; 00079 t.zone = 0; 00080 t.is_utc = 0; 00081 return t; 00082 } 00083 00084 namespace KCal { 00085 00086 /******************************************************************************/ 00087 00088 //@cond PRIVATE 00089 class ICalTimeZonesPrivate 00090 { 00091 public: 00092 ICalTimeZonesPrivate() {} 00093 ICalTimeZones::ZoneMap zones; 00094 }; 00095 //@endcond 00096 00097 ICalTimeZones::ICalTimeZones() 00098 : d( new ICalTimeZonesPrivate ) 00099 { 00100 } 00101 00102 ICalTimeZones::~ICalTimeZones() 00103 { 00104 delete d; 00105 } 00106 00107 const ICalTimeZones::ZoneMap ICalTimeZones::zones() const 00108 { 00109 return d->zones; 00110 } 00111 00112 bool ICalTimeZones::add( const ICalTimeZone &zone ) 00113 { 00114 if ( !zone.isValid() ) { 00115 return false; 00116 } 00117 if ( d->zones.find( zone.name() ) != d->zones.end() ) { 00118 return false; // name already exists 00119 } 00120 00121 d->zones.insert( zone.name(), zone ); 00122 return true; 00123 } 00124 00125 ICalTimeZone ICalTimeZones::remove( const ICalTimeZone &zone ) 00126 { 00127 if ( zone.isValid() ) { 00128 for ( ZoneMap::Iterator it = d->zones.begin(), end = d->zones.end(); it != end; ++it ) { 00129 if ( it.value() == zone ) { 00130 d->zones.erase( it ); 00131 return ( zone == ICalTimeZone::utc() ) ? ICalTimeZone() : zone; 00132 } 00133 } 00134 } 00135 return ICalTimeZone(); 00136 } 00137 00138 ICalTimeZone ICalTimeZones::remove( const QString &name ) 00139 { 00140 if ( !name.isEmpty() ) { 00141 ZoneMap::Iterator it = d->zones.find( name ); 00142 if ( it != d->zones.end() ) { 00143 ICalTimeZone zone = it.value(); 00144 d->zones.erase(it); 00145 return ( zone == ICalTimeZone::utc() ) ? ICalTimeZone() : zone; 00146 } 00147 } 00148 return ICalTimeZone(); 00149 } 00150 00151 void ICalTimeZones::clear() 00152 { 00153 d->zones.clear(); 00154 } 00155 00156 ICalTimeZone ICalTimeZones::zone( const QString &name ) const 00157 { 00158 if ( !name.isEmpty() ) { 00159 ZoneMap::ConstIterator it = d->zones.constFind( name ); 00160 if ( it != d->zones.constEnd() ) { 00161 return it.value(); 00162 } 00163 } 00164 return ICalTimeZone(); // error 00165 } 00166 00167 /******************************************************************************/ 00168 00169 ICalTimeZoneBackend::ICalTimeZoneBackend() 00170 : KTimeZoneBackend() 00171 {} 00172 00173 ICalTimeZoneBackend::ICalTimeZoneBackend( ICalTimeZoneSource *source, 00174 const QString &name, 00175 const QString &countryCode, 00176 float latitude, float longitude, 00177 const QString &comment ) 00178 : KTimeZoneBackend( source, name, countryCode, latitude, longitude, comment ) 00179 {} 00180 00181 ICalTimeZoneBackend::ICalTimeZoneBackend( const KTimeZone &tz, const QDate &earliest ) 00182 : KTimeZoneBackend( 0, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment() ) 00183 { 00184 Q_UNUSED( earliest ); 00185 } 00186 00187 ICalTimeZoneBackend::~ICalTimeZoneBackend() 00188 {} 00189 00190 KTimeZoneBackend *ICalTimeZoneBackend::clone() const 00191 { 00192 return new ICalTimeZoneBackend( *this ); 00193 } 00194 00195 QByteArray ICalTimeZoneBackend::type() const 00196 { 00197 return "ICalTimeZone"; 00198 } 00199 00200 bool ICalTimeZoneBackend::hasTransitions( const KTimeZone *caller ) const 00201 { 00202 Q_UNUSED( caller ); 00203 return true; 00204 } 00205 00206 /******************************************************************************/ 00207 00208 ICalTimeZone::ICalTimeZone() 00209 : KTimeZone( new ICalTimeZoneBackend() ) 00210 {} 00211 00212 ICalTimeZone::ICalTimeZone( ICalTimeZoneSource *source, const QString &name, 00213 ICalTimeZoneData *data ) 00214 : KTimeZone( new ICalTimeZoneBackend( source, name ) ) 00215 { 00216 setData( data ); 00217 } 00218 00219 ICalTimeZone::ICalTimeZone( const KTimeZone &tz, const QDate &earliest ) 00220 : KTimeZone( new ICalTimeZoneBackend( 0, tz.name(), tz.countryCode(), 00221 tz.latitude(), tz.longitude(), 00222 tz.comment() ) ) 00223 { 00224 const KTimeZoneData *data = tz.data( true ); 00225 if ( data ) { 00226 const ICalTimeZoneData *icaldata = dynamic_cast<const ICalTimeZoneData*>( data ); 00227 if ( icaldata ) { 00228 setData( new ICalTimeZoneData( *icaldata ) ); 00229 } else { 00230 setData( new ICalTimeZoneData( *data, tz, earliest ) ); 00231 } 00232 } 00233 } 00234 00235 ICalTimeZone::~ICalTimeZone() 00236 {} 00237 00238 QString ICalTimeZone::city() const 00239 { 00240 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00241 return dat ? dat->city() : QString(); 00242 } 00243 00244 QByteArray ICalTimeZone::url() const 00245 { 00246 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00247 return dat ? dat->url() : QByteArray(); 00248 } 00249 00250 QDateTime ICalTimeZone::lastModified() const 00251 { 00252 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00253 return dat ? dat->lastModified() : QDateTime(); 00254 } 00255 00256 QByteArray ICalTimeZone::vtimezone() const 00257 { 00258 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00259 return dat ? dat->vtimezone() : QByteArray(); 00260 } 00261 00262 icaltimezone *ICalTimeZone::icalTimezone() const 00263 { 00264 const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() ); 00265 return dat ? dat->icalTimezone() : 0; 00266 } 00267 00268 bool ICalTimeZone::update( const ICalTimeZone &other ) 00269 { 00270 if ( !updateBase( other ) ) { 00271 return false; 00272 } 00273 00274 KTimeZoneData *otherData = other.data() ? other.data()->clone() : 0; 00275 setData( otherData, other.source() ); 00276 return true; 00277 } 00278 00279 ICalTimeZone ICalTimeZone::utc() 00280 { 00281 static ICalTimeZone utcZone; 00282 if ( !utcZone.isValid() ) { 00283 ICalTimeZoneSource tzs; 00284 utcZone = tzs.parse( icaltimezone_get_utc_timezone() ); 00285 } 00286 return utcZone; 00287 } 00288 00289 /******************************************************************************/ 00290 00291 //@cond PRIVATE 00292 class ICalTimeZoneDataPrivate 00293 { 00294 public: 00295 ICalTimeZoneDataPrivate() : icalComponent(0) {} 00296 ~ICalTimeZoneDataPrivate() 00297 { 00298 if ( icalComponent ) { 00299 icalcomponent_free( icalComponent ); 00300 } 00301 } 00302 icalcomponent *component() const { return icalComponent; } 00303 void setComponent( icalcomponent *c ) 00304 { 00305 if ( icalComponent ) { 00306 icalcomponent_free( icalComponent ); 00307 } 00308 icalComponent = c; 00309 } 00310 QString location; // name of city for this time zone 00311 QByteArray url; // URL of published VTIMEZONE definition (optional) 00312 QDateTime lastModified; // time of last modification of the VTIMEZONE component (optional) 00313 private: 00314 icalcomponent *icalComponent; // ical component representing this time zone 00315 }; 00316 //@endcond 00317 00318 ICalTimeZoneData::ICalTimeZoneData() 00319 : d ( new ICalTimeZoneDataPrivate() ) 00320 { 00321 } 00322 00323 ICalTimeZoneData::ICalTimeZoneData( const ICalTimeZoneData &rhs ) 00324 : KTimeZoneData( rhs ), 00325 d( new ICalTimeZoneDataPrivate() ) 00326 { 00327 d->location = rhs.d->location; 00328 d->url = rhs.d->url; 00329 d->lastModified = rhs.d->lastModified; 00330 d->setComponent( icalcomponent_new_clone( rhs.d->component() ) ); 00331 } 00332 00333 ICalTimeZoneData::ICalTimeZoneData( const KTimeZoneData &rhs, 00334 const KTimeZone &tz, const QDate &earliest ) 00335 : KTimeZoneData( rhs ), 00336 d( new ICalTimeZoneDataPrivate() ) 00337 { 00338 // VTIMEZONE RRULE types 00339 enum { 00340 DAY_OF_MONTH = 0x01, 00341 WEEKDAY_OF_MONTH = 0x02, 00342 LAST_WEEKDAY_OF_MONTH = 0x04 00343 }; 00344 00345 if ( tz.type() == "KSystemTimeZone" ) { 00346 // Try to fetch a system time zone in preference, on the grounds 00347 // that system time zones are more likely to be up to date than 00348 // built-in libical ones. 00349 icalcomponent *c = 0; 00350 KTimeZone ktz = KSystemTimeZones::readZone( tz.name() ); 00351 if ( ktz.isValid() ) { 00352 if ( ktz.data(true) ) { 00353 ICalTimeZone icaltz( ktz, earliest ); 00354 icaltimezone *itz = icaltz.icalTimezone(); 00355 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) ); 00356 icaltimezone_free( itz, 1 ); 00357 } 00358 } 00359 if ( !c ) { 00360 // Try to fetch a built-in libical time zone. 00361 icaltimezone *itz = icaltimezone_get_builtin_timezone( tz.name().toUtf8() ); 00362 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) ); 00363 } 00364 if ( c ) { 00365 // TZID in built-in libical time zones has a standard prefix. 00366 // To make the VTIMEZONE TZID match TZID references in incidences 00367 // (as required by RFC2445), strip off the prefix. 00368 icalproperty *prop = icalcomponent_get_first_property( c, ICAL_TZID_PROPERTY ); 00369 if ( prop ) { 00370 icalvalue *value = icalproperty_get_value( prop ); 00371 const char *tzid = icalvalue_get_text( value ); 00372 QByteArray icalprefix = ICalTimeZoneSource::icalTzidPrefix(); 00373 int len = icalprefix.size(); 00374 if ( !strncmp( icalprefix, tzid, len ) ) { 00375 const char *s = strchr( tzid + len, '/' ); // find third '/' 00376 if ( s ) { 00377 QByteArray tzidShort( s + 1 ); // deep copy of string (needed by icalvalue_set_text()) 00378 icalvalue_set_text( value, tzidShort ); 00379 00380 // Remove the X-LIC-LOCATION property, which is only used by libical 00381 prop = icalcomponent_get_first_property( c, ICAL_X_PROPERTY ); 00382 const char *xname = icalproperty_get_x_name( prop ); 00383 if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) { 00384 icalcomponent_remove_property( c, prop ); 00385 } 00386 } 00387 } 00388 } 00389 } 00390 d->setComponent( c ); 00391 } else { 00392 // Write the time zone data into an iCal component 00393 icalcomponent *tzcomp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT); 00394 icalcomponent_add_property( tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ) ); 00395 // icalcomponent_add_property(tzcomp, icalproperty_new_location( tz.name().toUtf8() )); 00396 00397 // Compile an ordered list of transitions so that we can know the phases 00398 // which occur before and after each transition. 00399 QList<KTimeZone::Transition> transits = transitions(); 00400 if ( earliest.isValid() ) { 00401 // Remove all transitions earlier than those we are interested in 00402 for ( int i = 0, end = transits.count(); i < end; ++i ) { 00403 if ( transits[i].time().date() >= earliest ) { 00404 if ( i > 0 ) { 00405 transits.erase( transits.begin(), transits.begin() + i ); 00406 } 00407 break; 00408 } 00409 } 00410 } 00411 int trcount = transits.count(); 00412 QVector<bool> transitionsDone(trcount); 00413 transitionsDone.fill(false); 00414 00415 // Go through the list of transitions and create an iCal component for each 00416 // distinct combination of phase after and UTC offset before the transition. 00417 icaldatetimeperiodtype dtperiod; 00418 dtperiod.period = icalperiodtype_null_period(); 00419 for ( ; ; ) { 00420 int i = 0; 00421 for ( ; i < trcount && transitionsDone[i]; ++i ) { 00422 ; 00423 } 00424 if ( i >= trcount ) { 00425 break; 00426 } 00427 // Found a phase combination which hasn't yet been processed 00428 int preOffset = ( i > 0 ) ? transits[i - 1].phase().utcOffset() : rhs.previousUtcOffset(); 00429 KTimeZone::Phase phase = transits[i].phase(); 00430 if ( phase.utcOffset() == preOffset ) { 00431 transitionsDone[i] = true; 00432 while ( ++i < trcount ) { 00433 if ( transitionsDone[i] || 00434 transits[i].phase() != phase || 00435 transits[i - 1].phase().utcOffset() != preOffset ) { 00436 continue; 00437 } 00438 transitionsDone[i] = true; 00439 } 00440 continue; 00441 } 00442 icalcomponent *phaseComp = 00443 icalcomponent_new( phase.isDst() ? ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT ); 00444 QList<QByteArray> abbrevs = phase.abbreviations(); 00445 for ( int a = 0, aend = abbrevs.count(); a < aend; ++a ) { 00446 icalcomponent_add_property( phaseComp, 00447 icalproperty_new_tzname( 00448 static_cast<const char*>( abbrevs[a]) ) ); 00449 } 00450 if ( !phase.comment().isEmpty() ) { 00451 icalcomponent_add_property( phaseComp, 00452 icalproperty_new_comment( phase.comment().toUtf8() ) ); 00453 } 00454 icalcomponent_add_property( phaseComp, 00455 icalproperty_new_tzoffsetfrom( preOffset ) ); 00456 icalcomponent_add_property( phaseComp, 00457 icalproperty_new_tzoffsetto( phase.utcOffset() ) ); 00458 // Create a component to hold initial RRULE if any, plus all RDATEs 00459 icalcomponent *phaseComp1 = icalcomponent_new_clone( phaseComp ); 00460 icalcomponent_add_property( phaseComp1, 00461 icalproperty_new_dtstart( 00462 writeLocalICalDateTime( transits[i].time(), preOffset ) ) ); 00463 bool useNewRRULE = false; 00464 00465 // Compile the list of UTC transition dates/times, and check 00466 // if the list can be reduced to an RRULE instead of multiple RDATEs. 00467 QTime time; 00468 QDate date; 00469 int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0; // avoid compiler warnings 00470 int dayOfWeek = 0; // Monday = 1 00471 int nthFromStart = 0; // nth (weekday) of month 00472 int nthFromEnd = 0; // nth last (weekday) of month 00473 int newRule; 00474 int rule = 0; 00475 QList<QDateTime> rdates;// dates which (probably) need to be written as RDATEs 00476 QList<QDateTime> times; 00477 QDateTime qdt = transits[i].time(); // set 'qdt' for start of loop 00478 times += qdt; 00479 transitionsDone[i] = true; 00480 do { 00481 if ( !rule ) { 00482 // Initialise data for detecting a new rule 00483 rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH; 00484 time = qdt.time(); 00485 date = qdt.date(); 00486 year = date.year(); 00487 month = date.month(); 00488 daysInMonth = date.daysInMonth(); 00489 dayOfWeek = date.dayOfWeek(); // Monday = 1 00490 dayOfMonth = date.day(); 00491 nthFromStart = ( dayOfMonth - 1 ) / 7 + 1; // nth (weekday) of month 00492 nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1; // nth last (weekday) of month 00493 } 00494 if ( ++i >= trcount ) { 00495 newRule = 0; 00496 times += QDateTime(); // append a dummy value since last value in list is ignored 00497 } else { 00498 if ( transitionsDone[i] || 00499 transits[i].phase() != phase || 00500 transits[i - 1].phase().utcOffset() != preOffset ) { 00501 continue; 00502 } 00503 transitionsDone[i] = true; 00504 qdt = transits[i].time(); 00505 if ( !qdt.isValid() ) { 00506 continue; 00507 } 00508 newRule = rule; 00509 times += qdt; 00510 date = qdt.date(); 00511 if ( qdt.time() != time || 00512 date.month() != month || 00513 date.year() != ++year ) { 00514 newRule = 0; 00515 } else { 00516 int day = date.day(); 00517 if ( ( newRule & DAY_OF_MONTH ) && day != dayOfMonth ) { 00518 newRule &= ~DAY_OF_MONTH; 00519 } 00520 if ( newRule & ( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ) ) { 00521 if ( date.dayOfWeek() != dayOfWeek ) { 00522 newRule &= ~( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ); 00523 } else { 00524 if ( ( newRule & WEEKDAY_OF_MONTH ) && 00525 ( day - 1 ) / 7 + 1 != nthFromStart ) { 00526 newRule &= ~WEEKDAY_OF_MONTH; 00527 } 00528 if ( ( newRule & LAST_WEEKDAY_OF_MONTH ) && 00529 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) { 00530 newRule &= ~LAST_WEEKDAY_OF_MONTH; 00531 } 00532 } 00533 } 00534 } 00535 } 00536 if ( !newRule ) { 00537 // The previous rule (if any) no longer applies. 00538 // Write all the times up to but not including the current one. 00539 // First check whether any of the last RDATE values fit this rule. 00540 int yr = times[0].date().year(); 00541 while ( !rdates.isEmpty() ) { 00542 qdt = rdates.last(); 00543 date = qdt.date(); 00544 if ( qdt.time() != time || 00545 date.month() != month || 00546 date.year() != --yr ) { 00547 break; 00548 } 00549 int day = date.day(); 00550 if ( rule & DAY_OF_MONTH ) { 00551 if ( day != dayOfMonth ) { 00552 break; 00553 } 00554 } else { 00555 if ( date.dayOfWeek() != dayOfWeek || 00556 ( ( rule & WEEKDAY_OF_MONTH ) && 00557 ( day - 1 ) / 7 + 1 != nthFromStart ) || 00558 ( ( rule & LAST_WEEKDAY_OF_MONTH ) && 00559 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) ) { 00560 break; 00561 } 00562 } 00563 times.prepend( qdt ); 00564 rdates.pop_back(); 00565 } 00566 if ( times.count() > ( useNewRRULE ? minPhaseCount : minRuleCount ) ) { 00567 // There are enough dates to combine into an RRULE 00568 icalrecurrencetype r; 00569 icalrecurrencetype_clear( &r ); 00570 r.freq = ICAL_YEARLY_RECURRENCE; 00571 r.count = ( year >= 2030 ) ? 0 : times.count() - 1; 00572 r.by_month[0] = month; 00573 if ( rule & DAY_OF_MONTH ) { 00574 r.by_month_day[0] = dayOfMonth; 00575 } else if ( rule & WEEKDAY_OF_MONTH ) { 00576 r.by_day[0] = ( dayOfWeek % 7 + 1 ) + ( nthFromStart * 8 ); // Sunday = 1 00577 } else if ( rule & LAST_WEEKDAY_OF_MONTH ) { 00578 r.by_day[0] = -( dayOfWeek % 7 + 1 ) - ( nthFromEnd * 8 ); // Sunday = 1 00579 } 00580 icalproperty *prop = icalproperty_new_rrule( r ); 00581 if ( useNewRRULE ) { 00582 // This RRULE doesn't start from the phase start date, so set it into 00583 // a new STANDARD/DAYLIGHT component in the VTIMEZONE. 00584 icalcomponent *c = icalcomponent_new_clone( phaseComp ); 00585 icalcomponent_add_property( 00586 c, icalproperty_new_dtstart( writeLocalICalDateTime( times[0], preOffset ) ) ); 00587 icalcomponent_add_property( c, prop ); 00588 icalcomponent_add_component( tzcomp, c ); 00589 } else { 00590 icalcomponent_add_property( phaseComp1, prop ); 00591 } 00592 } else { 00593 // Save dates for writing as RDATEs 00594 for ( int t = 0, tend = times.count() - 1; t < tend; ++t ) { 00595 rdates += times[t]; 00596 } 00597 } 00598 useNewRRULE = true; 00599 // All date/time values but the last have been added to the VTIMEZONE. 00600 // Remove them from the list. 00601 qdt = times.last(); // set 'qdt' for start of loop 00602 times.clear(); 00603 times += qdt; 00604 } 00605 rule = newRule; 00606 } while ( i < trcount ); 00607 00608 // Write remaining dates as RDATEs 00609 for ( int rd = 0, rdend = rdates.count(); rd < rdend; ++rd ) { 00610 dtperiod.time = writeLocalICalDateTime( rdates[rd], preOffset ); 00611 icalcomponent_add_property( phaseComp1, icalproperty_new_rdate( dtperiod ) ); 00612 } 00613 icalcomponent_add_component( tzcomp, phaseComp1 ); 00614 icalcomponent_free( phaseComp ); 00615 } 00616 00617 d->setComponent( tzcomp ); 00618 } 00619 } 00620 00621 ICalTimeZoneData::~ICalTimeZoneData() 00622 { 00623 delete d; 00624 } 00625 00626 ICalTimeZoneData &ICalTimeZoneData::operator=( const ICalTimeZoneData &rhs ) 00627 { 00628 // check for self assignment 00629 if ( &rhs == this ) { 00630 return *this; 00631 } 00632 00633 KTimeZoneData::operator=( rhs ); 00634 d->location = rhs.d->location; 00635 d->url = rhs.d->url; 00636 d->lastModified = rhs.d->lastModified; 00637 d->setComponent( icalcomponent_new_clone( rhs.d->component() ) ); 00638 return *this; 00639 } 00640 00641 KTimeZoneData *ICalTimeZoneData::clone() const 00642 { 00643 return new ICalTimeZoneData( *this ); 00644 } 00645 00646 QString ICalTimeZoneData::city() const 00647 { 00648 return d->location; 00649 } 00650 00651 QByteArray ICalTimeZoneData::url() const 00652 { 00653 return d->url; 00654 } 00655 00656 QDateTime ICalTimeZoneData::lastModified() const 00657 { 00658 return d->lastModified; 00659 } 00660 00661 QByteArray ICalTimeZoneData::vtimezone() const 00662 { 00663 QByteArray result( icalcomponent_as_ical_string( d->component() ) ); 00664 icalmemory_free_ring(); 00665 return result; 00666 } 00667 00668 icaltimezone *ICalTimeZoneData::icalTimezone() const 00669 { 00670 icaltimezone *icaltz = icaltimezone_new(); 00671 if ( !icaltz ) { 00672 return 0; 00673 } 00674 icalcomponent *c = icalcomponent_new_clone( d->component() ); 00675 if ( !icaltimezone_set_component( icaltz, c ) ) { 00676 icalcomponent_free( c ); 00677 icaltimezone_free( icaltz, 1 ); 00678 return 0; 00679 } 00680 return icaltz; 00681 } 00682 00683 bool ICalTimeZoneData::hasTransitions() const 00684 { 00685 return true; 00686 } 00687 00688 /******************************************************************************/ 00689 00690 //@cond PRIVATE 00691 class ICalTimeZoneSourcePrivate 00692 { 00693 public: 00694 static QList<QDateTime> parsePhase( icalcomponent *, bool daylight, 00695 int &prevOffset, KTimeZone::Phase & ); 00696 static QByteArray icalTzidPrefix; 00697 }; 00698 00699 QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix; 00700 //@endcond 00701 00702 ICalTimeZoneSource::ICalTimeZoneSource() 00703 : KTimeZoneSource( false ), 00704 d( 0 ) 00705 { 00706 } 00707 00708 ICalTimeZoneSource::~ICalTimeZoneSource() 00709 { 00710 } 00711 00712 bool ICalTimeZoneSource::parse( const QString &fileName, ICalTimeZones &zones ) 00713 { 00714 QFile file( fileName ); 00715 if ( !file.open( QIODevice::ReadOnly ) ) { 00716 return false; 00717 } 00718 QTextStream ts( &file ); 00719 ts.setCodec( "ISO 8859-1" ); 00720 QByteArray text = ts.readAll().trimmed().toLatin1(); 00721 file.close(); 00722 00723 bool result = false; 00724 icalcomponent *calendar = icalcomponent_new_from_string( text.data() ); 00725 if ( calendar ) { 00726 if ( icalcomponent_isa( calendar ) == ICAL_VCALENDAR_COMPONENT ) { 00727 result = parse( calendar, zones ); 00728 } 00729 icalcomponent_free( calendar ); 00730 } 00731 return result; 00732 } 00733 00734 bool ICalTimeZoneSource::parse( icalcomponent *calendar, ICalTimeZones &zones ) 00735 { 00736 for ( icalcomponent *c = icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT ); 00737 c; c = icalcomponent_get_next_component( calendar, ICAL_VTIMEZONE_COMPONENT ) ) { 00738 ICalTimeZone zone = parse( c ); 00739 if ( !zone.isValid() ) { 00740 return false; 00741 } 00742 ICalTimeZone oldzone = zones.zone( zone.name() ); 00743 if ( oldzone.isValid() ) { 00744 // The zone already exists in the collection, so update the definition 00745 // of the zone rather than using a newly created one. 00746 oldzone.update( zone ); 00747 } else if ( !zones.add( zone ) ) { 00748 return false; 00749 } 00750 } 00751 return true; 00752 } 00753 00754 ICalTimeZone ICalTimeZoneSource::parse( icalcomponent *vtimezone ) 00755 { 00756 QString name; 00757 QString xlocation; 00758 ICalTimeZoneData *data = new ICalTimeZoneData(); 00759 00760 // Read the fixed properties which can only appear once in VTIMEZONE 00761 icalproperty *p = icalcomponent_get_first_property( vtimezone, ICAL_ANY_PROPERTY ); 00762 while ( p ) { 00763 icalproperty_kind kind = icalproperty_isa( p ); 00764 switch ( kind ) { 00765 00766 case ICAL_TZID_PROPERTY: 00767 name = QString::fromUtf8( icalproperty_get_tzid( p ) ); 00768 break; 00769 00770 case ICAL_TZURL_PROPERTY: 00771 data->d->url = icalproperty_get_tzurl( p ); 00772 break; 00773 00774 case ICAL_LOCATION_PROPERTY: 00775 // This isn't mentioned in RFC2445, but libical reads it ... 00776 data->d->location = QString::fromUtf8( icalproperty_get_location( p ) ); 00777 break; 00778 00779 case ICAL_X_PROPERTY: 00780 { // use X-LIC-LOCATION if LOCATION is missing 00781 const char *xname = icalproperty_get_x_name( p ); 00782 if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) { 00783 xlocation = QString::fromUtf8( icalproperty_get_x( p ) ); 00784 } 00785 break; 00786 } 00787 case ICAL_LASTMODIFIED_PROPERTY: 00788 { 00789 icaltimetype t = icalproperty_get_lastmodified(p); 00790 if ( t.is_utc ) { 00791 data->d->lastModified = toQDateTime( t ); 00792 } else { 00793 kDebug() << "LAST-MODIFIED not UTC"; 00794 } 00795 break; 00796 } 00797 default: 00798 break; 00799 } 00800 p = icalcomponent_get_next_property( vtimezone, ICAL_ANY_PROPERTY ); 00801 } 00802 00803 if ( name.isEmpty() ) { 00804 kDebug() << "TZID missing"; 00805 delete data; 00806 return ICalTimeZone(); 00807 } 00808 if ( data->d->location.isEmpty() && !xlocation.isEmpty() ) { 00809 data->d->location = xlocation; 00810 } 00811 QString prefix = QString::fromUtf8( icalTzidPrefix() ); 00812 if ( name.startsWith( prefix ) ) { 00813 // Remove the prefix from libical built in time zone TZID 00814 int i = name.indexOf( '/', prefix.length() ); 00815 if ( i > 0 ) { 00816 name = name.mid( i + 1 ); 00817 } 00818 } 00819 //kDebug() << "---zoneId: \"" << name << '"'; 00820 00821 /* 00822 * Iterate through all time zone rules for this VTIMEZONE, 00823 * and create a Phase object containing details for each one. 00824 */ 00825 int prevOffset = 0; 00826 QList<KTimeZone::Transition> transitions; 00827 QDateTime earliest; 00828 QList<KTimeZone::Phase> phases; 00829 for ( icalcomponent *c = icalcomponent_get_first_component( vtimezone, ICAL_ANY_COMPONENT ); 00830 c; c = icalcomponent_get_next_component( vtimezone, ICAL_ANY_COMPONENT ) ) 00831 { 00832 int prevoff; 00833 KTimeZone::Phase phase; 00834 QList<QDateTime> times; 00835 icalcomponent_kind kind = icalcomponent_isa( c ); 00836 switch ( kind ) { 00837 00838 case ICAL_XSTANDARD_COMPONENT: 00839 //kDebug() << "---standard phase: found"; 00840 times = ICalTimeZoneSourcePrivate::parsePhase( c, false, prevoff, phase ); 00841 break; 00842 00843 case ICAL_XDAYLIGHT_COMPONENT: 00844 //kDebug() << "---daylight phase: found"; 00845 times = ICalTimeZoneSourcePrivate::parsePhase( c, true, prevoff, phase ); 00846 break; 00847 00848 default: 00849 kDebug() << "Unknown component:" << kind; 00850 break; 00851 } 00852 int tcount = times.count(); 00853 if ( tcount ) { 00854 phases += phase; 00855 for ( int t = 0; t < tcount; ++t ) { 00856 transitions += KTimeZone::Transition( times[t], phase ); 00857 } 00858 if ( !earliest.isValid() || times[0] < earliest ) { 00859 prevOffset = prevoff; 00860 earliest = times[0]; 00861 } 00862 } 00863 } 00864 data->setPhases( phases, prevOffset ); 00865 // Remove any "duplicate" transitions, i.e. those where two consecutive 00866 // transitions have the same phase. 00867 qSort( transitions ); 00868 for ( int t = 1, tend = transitions.count(); t < tend; ) { 00869 if ( transitions[t].phase() == transitions[t - 1].phase() ) { 00870 transitions.removeAt( t ); 00871 --tend; 00872 } else { 00873 ++t; 00874 } 00875 } 00876 data->setTransitions( transitions ); 00877 00878 data->d->setComponent( icalcomponent_new_clone( vtimezone ) ); 00879 kDebug() << "VTIMEZONE" << name; 00880 return ICalTimeZone( this, name, data ); 00881 } 00882 00883 ICalTimeZone ICalTimeZoneSource::parse( icaltimezone *tz ) 00884 { 00885 /* Parse the VTIMEZONE component stored in the icaltimezone structure. 00886 * This is both easier and provides more complete information than 00887 * extracting already parsed data from icaltimezone. 00888 */ 00889 return tz ? parse( icaltimezone_get_component( tz ) ) : ICalTimeZone(); 00890 } 00891 00892 //@cond PRIVATE 00893 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase( icalcomponent *c, 00894 bool daylight, 00895 int &prevOffset, 00896 KTimeZone::Phase &phase ) 00897 { 00898 QList<QDateTime> transitions; 00899 00900 // Read the observance data for this standard/daylight savings phase 00901 QList<QByteArray> abbrevs; 00902 QString comment; 00903 prevOffset = 0; 00904 int utcOffset = 0; 00905 bool recurs = false; 00906 bool found_dtstart = false; 00907 bool found_tzoffsetfrom = false; 00908 bool found_tzoffsetto = false; 00909 icaltimetype dtstart = icaltime_null_time(); 00910 00911 // Now do the ical reading. 00912 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY ); 00913 while ( p ) { 00914 icalproperty_kind kind = icalproperty_isa( p ); 00915 switch ( kind ) { 00916 00917 case ICAL_TZNAME_PROPERTY: // abbreviated name for this time offset 00918 { 00919 // TZNAME can appear multiple times in order to provide language 00920 // translations of the time zone offset name. 00921 00922 // TODO: Does this cope with multiple language specifications? 00923 QByteArray tzname = icalproperty_get_tzname( p ); 00924 // Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME 00925 // strings, which is totally useless. So ignore those. 00926 if ( ( !daylight && tzname == "Standard Time" ) || 00927 ( daylight && tzname == "Daylight Time" ) ) { 00928 break; 00929 } 00930 if ( !abbrevs.contains( tzname ) ) { 00931 abbrevs += tzname; 00932 } 00933 break; 00934 } 00935 case ICAL_DTSTART_PROPERTY: // local time at which phase starts 00936 dtstart = icalproperty_get_dtstart( p ); 00937 found_dtstart = true; 00938 break; 00939 00940 case ICAL_TZOFFSETFROM_PROPERTY: // UTC offset immediately before start of phase 00941 prevOffset = icalproperty_get_tzoffsetfrom( p ); 00942 found_tzoffsetfrom = true; 00943 break; 00944 00945 case ICAL_TZOFFSETTO_PROPERTY: 00946 utcOffset = icalproperty_get_tzoffsetto( p ); 00947 found_tzoffsetto = true; 00948 break; 00949 00950 case ICAL_COMMENT_PROPERTY: 00951 comment = QString::fromUtf8( icalproperty_get_comment( p ) ); 00952 break; 00953 00954 case ICAL_RDATE_PROPERTY: 00955 case ICAL_RRULE_PROPERTY: 00956 recurs = true; 00957 break; 00958 00959 default: 00960 kDebug() << "Unknown property:" << kind; 00961 break; 00962 } 00963 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY ); 00964 } 00965 00966 // Validate the phase data 00967 if ( !found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto ) { 00968 kDebug() << "DTSTART/TZOFFSETFROM/TZOFFSETTO missing"; 00969 return transitions; 00970 } 00971 00972 // Convert DTSTART to QDateTime, and from local time to UTC 00973 QDateTime localStart = toQDateTime( dtstart ); // local time 00974 dtstart.second -= prevOffset; 00975 dtstart.is_utc = 1; 00976 QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) ); // UTC 00977 00978 transitions += utcStart; 00979 if ( recurs ) { 00980 /* RDATE or RRULE is specified. There should only be one or the other, but 00981 * it doesn't really matter - the code can cope with both. 00982 * Note that we had to get DTSTART, TZOFFSETFROM, TZOFFSETTO before reading 00983 * recurrences. 00984 */ 00985 KDateTime klocalStart( localStart, KDateTime::Spec::ClockTime() ); 00986 KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() ); 00987 Recurrence recur; 00988 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY ); 00989 while ( p ) { 00990 icalproperty_kind kind = icalproperty_isa( p ); 00991 switch ( kind ) { 00992 00993 case ICAL_RDATE_PROPERTY: 00994 { 00995 icaltimetype t = icalproperty_get_rdate(p).time; 00996 if ( icaltime_is_date( t ) ) { 00997 // RDATE with a DATE value inherits the (local) time from DTSTART 00998 t.hour = dtstart.hour; 00999 t.minute = dtstart.minute; 01000 t.second = dtstart.second; 01001 t.is_date = 0; 01002 t.is_utc = 0; // dtstart is in local time 01003 } 01004 // RFC2445 states that RDATE must be in local time, 01005 // but we support UTC as well to be safe. 01006 if ( !t.is_utc ) { 01007 t.second -= prevOffset; // convert to UTC 01008 t.is_utc = 1; 01009 t = icaltime_normalize( t ); 01010 } 01011 transitions += toQDateTime( t ); 01012 break; 01013 } 01014 case ICAL_RRULE_PROPERTY: 01015 { 01016 RecurrenceRule r; 01017 ICalFormat icf; 01018 ICalFormatImpl impl( &icf ); 01019 impl.readRecurrence( icalproperty_get_rrule( p ), &r ); 01020 r.setStartDt( klocalStart ); 01021 // The end date time specified in an RRULE should be in UTC. 01022 // Convert to local time to avoid timesInInterval() getting things wrong. 01023 if ( r.duration() == 0 ) { 01024 KDateTime end( r.endDt() ); 01025 if ( end.timeSpec() == KDateTime::Spec::UTC() ) { 01026 end.setTimeSpec( KDateTime::Spec::ClockTime() ); 01027 r.setEndDt( end.addSecs( prevOffset ) ); 01028 } 01029 } 01030 DateTimeList dts = r.timesInInterval( klocalStart, maxTime ); 01031 for ( int i = 0, end = dts.count(); i < end; ++i ) { 01032 QDateTime utc = dts[i].dateTime(); 01033 utc.setTimeSpec( Qt::UTC ); 01034 transitions += utc.addSecs( -prevOffset ); 01035 } 01036 break; 01037 } 01038 default: 01039 break; 01040 } 01041 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY ); 01042 } 01043 qSortUnique( transitions ); 01044 } 01045 01046 phase = KTimeZone::Phase( utcOffset, abbrevs, daylight, comment ); 01047 return transitions; 01048 } 01049 //@endcond 01050 01051 ICalTimeZone ICalTimeZoneSource::standardZone( const QString &zone, bool icalBuiltIn ) 01052 { 01053 if ( !icalBuiltIn ) { 01054 // Try to fetch a system time zone in preference, on the grounds 01055 // that system time zones are more likely to be up to date than 01056 // built-in libical ones. 01057 QString tzid = zone; 01058 QString prefix = QString::fromUtf8( icalTzidPrefix() ); 01059 if ( zone.startsWith( prefix ) ) { 01060 int i = zone.indexOf( '/', prefix.length() ); 01061 if ( i > 0 ) { 01062 tzid = zone.mid( i + 1 ); // strip off the libical prefix 01063 } 01064 } 01065 KTimeZone ktz = KSystemTimeZones::readZone( tzid ); 01066 if ( ktz.isValid() ) { 01067 if ( ktz.data( true ) ) { 01068 ICalTimeZone icaltz( ktz ); 01069 //kDebug() << zone << " read from system database"; 01070 return icaltz; 01071 } 01072 } 01073 } 01074 // Try to fetch a built-in libical time zone. 01075 // First try to look it up as a geographical location (e.g. Europe/London) 01076 QByteArray zoneName = zone.toUtf8(); 01077 icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName ); 01078 if ( !icaltz ) { 01079 // This will find it if it includes the libical prefix 01080 icaltz = icaltimezone_get_builtin_timezone_from_tzid( zoneName ); 01081 if ( !icaltz ) { 01082 return ICalTimeZone(); 01083 } 01084 } 01085 return parse( icaltz ); 01086 } 01087 01088 QByteArray ICalTimeZoneSource::icalTzidPrefix() 01089 { 01090 if ( ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty() ) { 01091 icaltimezone *icaltz = icaltimezone_get_builtin_timezone( "Europe/London" ); 01092 QByteArray tzid = icaltimezone_get_tzid( icaltz ); 01093 if ( tzid.right( 13 ) == "Europe/London" ) { 01094 int i = tzid.indexOf( '/', 1 ); 01095 if ( i > 0 ) { 01096 ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left( i + 1 ); 01097 return ICalTimeZoneSourcePrivate::icalTzidPrefix; 01098 } 01099 } 01100 kError() << "failed to get libical TZID prefix"; 01101 } 01102 return ICalTimeZoneSourcePrivate::icalTzidPrefix; 01103 } 01104 01105 } // namespace KCal
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 22:19:47 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 22:19:47 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.