00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "icaltimezones.h"
00023 #include "icalformat.h"
00024 #include "icalformat_p.h"
00025
00026 extern "C" {
00027 #include <ical.h>
00028 #include <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
00045 static const int minRuleCount = 5;
00046 static const int minPhaseCount = 8;
00047
00048
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
00057
00058
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
00089 class ICalTimeZonesPrivate
00090 {
00091 public:
00092 ICalTimeZonesPrivate() {}
00093 ICalTimeZones::ZoneMap zones;
00094 };
00095
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;
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();
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
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;
00311 QByteArray url;
00312 QDateTime lastModified;
00313 private:
00314 icalcomponent *icalComponent;
00315 };
00316
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
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
00347
00348
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
00361 icaltimezone *itz = icaltimezone_get_builtin_timezone( tz.name().toUtf8() );
00362 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) );
00363 }
00364 if ( c ) {
00365
00366
00367
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, '/' );
00376 if ( s ) {
00377 QByteArray tzidShort( s + 1 );
00378 icalvalue_set_text( value, tzidShort );
00379
00380
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
00393 icalcomponent *tzcomp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT);
00394 icalcomponent_add_property( tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ) );
00395
00396
00397
00398
00399 QList<KTimeZone::Transition> transits = transitions();
00400 if ( earliest.isValid() ) {
00401
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
00416
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
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
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
00466
00467 QTime time;
00468 QDate date;
00469 int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0;
00470 int dayOfWeek = 0;
00471 int nthFromStart = 0;
00472 int nthFromEnd = 0;
00473 int newRule;
00474 int rule = 0;
00475 QList<QDateTime> rdates;
00476 QList<QDateTime> times;
00477 QDateTime qdt = transits[i].time();
00478 times += qdt;
00479 transitionsDone[i] = true;
00480 do {
00481 if ( !rule ) {
00482
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();
00490 dayOfMonth = date.day();
00491 nthFromStart = ( dayOfMonth - 1 ) / 7 + 1;
00492 nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1;
00493 }
00494 if ( ++i >= trcount ) {
00495 newRule = 0;
00496 times += QDateTime();
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
00538
00539
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
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 );
00577 } else if ( rule & LAST_WEEKDAY_OF_MONTH ) {
00578 r.by_day[0] = -( dayOfWeek % 7 + 1 ) - ( nthFromEnd * 8 );
00579 }
00580 icalproperty *prop = icalproperty_new_rrule( r );
00581 if ( useNewRRULE ) {
00582
00583
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
00594 for ( int t = 0, tend = times.count() - 1; t < tend; ++t ) {
00595 rdates += times[t];
00596 }
00597 }
00598 useNewRRULE = true;
00599
00600
00601 qdt = times.last();
00602 times.clear();
00603 times += qdt;
00604 }
00605 rule = newRule;
00606 } while ( i < trcount );
00607
00608
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
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 return icalcomponent_as_ical_string( d->component() );
00664 }
00665
00666 icaltimezone *ICalTimeZoneData::icalTimezone() const
00667 {
00668 icaltimezone *icaltz = icaltimezone_new();
00669 if ( !icaltz ) {
00670 return 0;
00671 }
00672 icalcomponent *c = icalcomponent_new_clone( d->component() );
00673 if ( !icaltimezone_set_component( icaltz, c ) ) {
00674 icalcomponent_free( c );
00675 icaltimezone_free( icaltz, 1 );
00676 return 0;
00677 }
00678 return icaltz;
00679 }
00680
00681 bool ICalTimeZoneData::hasTransitions() const
00682 {
00683 return true;
00684 }
00685
00686
00687
00688
00689 class ICalTimeZoneSourcePrivate
00690 {
00691 public:
00692 static QList<QDateTime> parsePhase( icalcomponent *, bool daylight,
00693 int &prevOffset, KTimeZone::Phase & );
00694 static QByteArray icalTzidPrefix;
00695 };
00696
00697 QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix;
00698
00699
00700 ICalTimeZoneSource::ICalTimeZoneSource()
00701 : KTimeZoneSource( false ),
00702 d( 0 )
00703 {
00704 }
00705
00706 ICalTimeZoneSource::~ICalTimeZoneSource()
00707 {
00708 }
00709
00710 bool ICalTimeZoneSource::parse( const QString &fileName, ICalTimeZones &zones )
00711 {
00712 QFile file( fileName );
00713 if ( !file.open( QIODevice::ReadOnly ) ) {
00714 return false;
00715 }
00716 QTextStream ts( &file );
00717 ts.setCodec( "ISO 8859-1" );
00718 QByteArray text = ts.readAll().trimmed().toLatin1();
00719 file.close();
00720
00721 bool result = false;
00722 icalcomponent *calendar = icalcomponent_new_from_string( text.data() );
00723 if ( calendar ) {
00724 if ( icalcomponent_isa( calendar ) == ICAL_VCALENDAR_COMPONENT ) {
00725 result = parse( calendar, zones );
00726 }
00727 icalcomponent_free( calendar );
00728 }
00729 return result;
00730 }
00731
00732 bool ICalTimeZoneSource::parse( icalcomponent *calendar, ICalTimeZones &zones )
00733 {
00734 for ( icalcomponent *c = icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT );
00735 c; c = icalcomponent_get_next_component( calendar, ICAL_VTIMEZONE_COMPONENT ) ) {
00736 ICalTimeZone zone = parse( c );
00737 if ( !zone.isValid() ) {
00738 return false;
00739 }
00740 ICalTimeZone oldzone = zones.zone( zone.name() );
00741 if ( oldzone.isValid() ) {
00742
00743
00744 oldzone.update( zone );
00745 } else if ( !zones.add( zone ) ) {
00746 return false;
00747 }
00748 }
00749 return true;
00750 }
00751
00752 ICalTimeZone ICalTimeZoneSource::parse( icalcomponent *vtimezone )
00753 {
00754 QString name;
00755 QString xlocation;
00756 ICalTimeZoneData *data = new ICalTimeZoneData();
00757
00758
00759 icalproperty *p = icalcomponent_get_first_property( vtimezone, ICAL_ANY_PROPERTY );
00760 while ( p ) {
00761 icalproperty_kind kind = icalproperty_isa( p );
00762 switch ( kind ) {
00763
00764 case ICAL_TZID_PROPERTY:
00765 name = QString::fromUtf8( icalproperty_get_tzid( p ) );
00766 break;
00767
00768 case ICAL_TZURL_PROPERTY:
00769 data->d->url = icalproperty_get_tzurl( p );
00770 break;
00771
00772 case ICAL_LOCATION_PROPERTY:
00773
00774 data->d->location = QString::fromUtf8( icalproperty_get_location( p ) );
00775 break;
00776
00777 case ICAL_X_PROPERTY:
00778 {
00779 const char *xname = icalproperty_get_x_name( p );
00780 if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) {
00781 xlocation = QString::fromUtf8( icalproperty_get_x( p ) );
00782 }
00783 break;
00784 }
00785 case ICAL_LASTMODIFIED_PROPERTY:
00786 {
00787 icaltimetype t = icalproperty_get_lastmodified(p);
00788 if ( t.is_utc ) {
00789 data->d->lastModified = toQDateTime( t );
00790 } else {
00791 kDebug() << "LAST-MODIFIED not UTC";
00792 }
00793 break;
00794 }
00795 default:
00796 break;
00797 }
00798 p = icalcomponent_get_next_property( vtimezone, ICAL_ANY_PROPERTY );
00799 }
00800
00801 if ( name.isEmpty() ) {
00802 kDebug() << "TZID missing";
00803 delete data;
00804 return ICalTimeZone();
00805 }
00806 if ( data->d->location.isEmpty() && !xlocation.isEmpty() ) {
00807 data->d->location = xlocation;
00808 }
00809 QString prefix = QString::fromUtf8( icalTzidPrefix() );
00810 if ( name.startsWith( prefix ) ) {
00811
00812 int i = name.indexOf( '/', prefix.length() );
00813 if ( i > 0 ) {
00814 name = name.mid( i + 1 );
00815 }
00816 }
00817
00818
00819
00820
00821
00822
00823 int prevOffset = 0;
00824 QList<KTimeZone::Transition> transitions;
00825 QDateTime earliest;
00826 QList<KTimeZone::Phase> phases;
00827 for ( icalcomponent *c = icalcomponent_get_first_component( vtimezone, ICAL_ANY_COMPONENT );
00828 c; c = icalcomponent_get_next_component( vtimezone, ICAL_ANY_COMPONENT ) )
00829 {
00830 int prevoff;
00831 KTimeZone::Phase phase;
00832 QList<QDateTime> times;
00833 icalcomponent_kind kind = icalcomponent_isa( c );
00834 switch ( kind ) {
00835
00836 case ICAL_XSTANDARD_COMPONENT:
00837
00838 times = ICalTimeZoneSourcePrivate::parsePhase( c, false, prevoff, phase );
00839 break;
00840
00841 case ICAL_XDAYLIGHT_COMPONENT:
00842
00843 times = ICalTimeZoneSourcePrivate::parsePhase( c, true, prevoff, phase );
00844 break;
00845
00846 default:
00847 kDebug() << "Unknown component:" << kind;
00848 break;
00849 }
00850 int tcount = times.count();
00851 if ( tcount ) {
00852 phases += phase;
00853 for ( int t = 0; t < tcount; ++t ) {
00854 transitions += KTimeZone::Transition( times[t], phase );
00855 }
00856 if ( !earliest.isValid() || times[0] < earliest ) {
00857 prevOffset = prevoff;
00858 earliest = times[0];
00859 }
00860 }
00861 }
00862 data->setPhases( phases, prevOffset );
00863
00864
00865 qSort( transitions );
00866 for ( int t = 1, tend = transitions.count(); t < tend; ) {
00867 if ( transitions[t].phase() == transitions[t - 1].phase() ) {
00868 transitions.removeAt( t );
00869 --tend;
00870 } else {
00871 ++t;
00872 }
00873 }
00874 data->setTransitions( transitions );
00875
00876 data->d->setComponent( icalcomponent_new_clone( vtimezone ) );
00877 kDebug() << "VTIMEZONE" << name;
00878 return ICalTimeZone( this, name, data );
00879 }
00880
00881 ICalTimeZone ICalTimeZoneSource::parse( icaltimezone *tz )
00882 {
00883
00884
00885
00886
00887 return tz ? parse( icaltimezone_get_component( tz ) ) : ICalTimeZone();
00888 }
00889
00890
00891 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase( icalcomponent *c,
00892 bool daylight,
00893 int &prevOffset,
00894 KTimeZone::Phase &phase )
00895 {
00896 QList<QDateTime> transitions;
00897
00898
00899 QList<QByteArray> abbrevs;
00900 QString comment;
00901 prevOffset = 0;
00902 int utcOffset = 0;
00903 bool recurs = false;
00904 bool found_dtstart = false;
00905 bool found_tzoffsetfrom = false;
00906 bool found_tzoffsetto = false;
00907 icaltimetype dtstart = icaltime_null_time();
00908
00909
00910 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
00911 while ( p ) {
00912 icalproperty_kind kind = icalproperty_isa( p );
00913 switch ( kind ) {
00914
00915 case ICAL_TZNAME_PROPERTY:
00916 {
00917
00918
00919 #ifdef __GNUC__
00920 #warning Does this cope with multiple language specifications?
00921 #endif
00922 QByteArray tzname = icalproperty_get_tzname( p );
00923
00924
00925 if ( ( !daylight && tzname == "Standard Time" ) ||
00926 ( daylight && tzname == "Daylight Time" ) ) {
00927 break;
00928 }
00929 if ( !abbrevs.contains( tzname ) ) {
00930 abbrevs += tzname;
00931 }
00932 break;
00933 }
00934 case ICAL_DTSTART_PROPERTY:
00935 dtstart = icalproperty_get_dtstart( p );
00936 found_dtstart = true;
00937 break;
00938
00939 case ICAL_TZOFFSETFROM_PROPERTY:
00940 prevOffset = icalproperty_get_tzoffsetfrom( p );
00941 found_tzoffsetfrom = true;
00942 break;
00943
00944 case ICAL_TZOFFSETTO_PROPERTY:
00945 utcOffset = icalproperty_get_tzoffsetto( p );
00946 found_tzoffsetto = true;
00947 break;
00948
00949 case ICAL_COMMENT_PROPERTY:
00950 comment = QString::fromUtf8( icalproperty_get_comment( p ) );
00951 break;
00952
00953 case ICAL_RDATE_PROPERTY:
00954 case ICAL_RRULE_PROPERTY:
00955 recurs = true;
00956 break;
00957
00958 default:
00959 kDebug() << "Unknown property:" << kind;
00960 break;
00961 }
00962 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
00963 }
00964
00965
00966 if ( !found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto ) {
00967 kDebug() << "DTSTART/TZOFFSETFROM/TZOFFSETTO missing";
00968 return transitions;
00969 }
00970
00971
00972 QDateTime localStart = toQDateTime( dtstart );
00973 dtstart.second -= prevOffset;
00974 dtstart.is_utc = 1;
00975 QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) );
00976
00977 transitions += utcStart;
00978 if ( recurs ) {
00979
00980
00981
00982
00983
00984 KDateTime klocalStart( localStart, KDateTime::Spec::ClockTime() );
00985 KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() );
00986 Recurrence recur;
00987 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
00988 while ( p ) {
00989 icalproperty_kind kind = icalproperty_isa( p );
00990 switch ( kind ) {
00991
00992 case ICAL_RDATE_PROPERTY:
00993 {
00994 icaltimetype t = icalproperty_get_rdate(p).time;
00995 if ( icaltime_is_date( t ) ) {
00996
00997 t.hour = dtstart.hour;
00998 t.minute = dtstart.minute;
00999 t.second = dtstart.second;
01000 t.is_date = 0;
01001 t.is_utc = 0;
01002 }
01003
01004
01005 if ( !t.is_utc ) {
01006 t.second -= prevOffset;
01007 t.is_utc = 1;
01008 t = icaltime_normalize( t );
01009 }
01010 transitions += toQDateTime( t );
01011 break;
01012 }
01013 case ICAL_RRULE_PROPERTY:
01014 {
01015 RecurrenceRule r;
01016 ICalFormat icf;
01017 ICalFormatImpl impl( &icf );
01018 impl.readRecurrence( icalproperty_get_rrule( p ), &r );
01019 r.setStartDt( klocalStart );
01020
01021
01022 if ( r.duration() == 0 ) {
01023 KDateTime end( r.endDt() );
01024 if ( end.timeSpec() == KDateTime::Spec::UTC() ) {
01025 end.setTimeSpec( KDateTime::Spec::ClockTime() );
01026 r.setEndDt( end.addSecs( prevOffset ) );
01027 }
01028 }
01029 DateTimeList dts = r.timesInInterval( klocalStart, maxTime );
01030 for ( int i = 0, end = dts.count(); i < end; ++i ) {
01031 QDateTime utc = dts[i].dateTime();
01032 utc.setTimeSpec( Qt::UTC );
01033 transitions += utc.addSecs( -prevOffset );
01034 }
01035 break;
01036 }
01037 default:
01038 break;
01039 }
01040 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
01041 }
01042 qSortUnique( transitions );
01043 }
01044
01045 phase = KTimeZone::Phase( utcOffset, abbrevs, daylight, comment );
01046 return transitions;
01047 }
01048
01049
01050 ICalTimeZone ICalTimeZoneSource::standardZone( const QString &zone, bool icalBuiltIn )
01051 {
01052 if ( !icalBuiltIn ) {
01053
01054
01055
01056 QString tzid = zone;
01057 QString prefix = QString::fromUtf8( icalTzidPrefix() );
01058 if ( zone.startsWith( prefix ) ) {
01059 int i = zone.indexOf( '/', prefix.length() );
01060 if ( i > 0 ) {
01061 tzid = zone.mid( i + 1 );
01062 }
01063 }
01064 KTimeZone ktz = KSystemTimeZones::readZone( tzid );
01065 if ( ktz.isValid() ) {
01066 if ( ktz.data( true ) ) {
01067 ICalTimeZone icaltz( ktz );
01068 kDebug() << zone << " read from system database";
01069 return icaltz;
01070 }
01071 }
01072 }
01073
01074
01075 QByteArray zoneName = zone.toUtf8();
01076 icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName );
01077 if ( !icaltz ) {
01078
01079 icaltz = icaltimezone_get_builtin_timezone_from_tzid( zoneName );
01080 if ( !icaltz ) {
01081 return ICalTimeZone();
01082 }
01083 }
01084 return parse( icaltz );
01085 }
01086
01087 QByteArray ICalTimeZoneSource::icalTzidPrefix()
01088 {
01089 if ( ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty() ) {
01090 icaltimezone *icaltz = icaltimezone_get_builtin_timezone( "Europe/London" );
01091 QByteArray tzid = icaltimezone_get_tzid( icaltz );
01092 if ( tzid.right( 13 ) == "Europe/London" ) {
01093 int i = tzid.indexOf( '/', 1 );
01094 if ( i > 0 ) {
01095 ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left( i + 1 );
01096 return ICalTimeZoneSourcePrivate::icalTzidPrefix;
01097 }
01098 }
01099 kError() << "failed to get libical TZID prefix";
01100 }
01101 return ICalTimeZoneSourcePrivate::icalTzidPrefix;
01102 }
01103
01104 }