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