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