• Skip to content
  • Skip to link menu
KDE 4.0 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

KCal Library

icaltimezones.cpp

00001 /*
00002     This file is part of the kcal library.
00003 
00004     Copyright (c) 2005-2007 David Jarvie <djarvie@kde.org>
00005 
00006     This library is free software; you can redistribute it and/or
00007     modify it under the terms of the GNU Library General Public
00008     License as published by the Free Software Foundation; either
00009     version 2 of the License, or (at your option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     Library General Public License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to
00018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019     Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include "icaltimezones.h"
00023 
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 // Minimum repetition counts for VTIMEZONE RRULEs
00048 static const int minRuleCount = 5;   // for any RRULE
00049 static const int minPhaseCount = 8;  // for separate STANDARD/DAYLIGHT component
00050 
00051 // Convert an ical time to QDateTime, preserving the UTC indicator
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 // Maximum date for time zone data.
00060 // It's not sensible to try to predict them very far in advance, because
00061 // they can easily change. Plus, it limits the processing required.
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 //@cond PRIVATE
00093 class ICalTimeZonesPrivate
00094 {
00095 public:
00096   ICalTimeZonesPrivate() {}
00097 
00098   ICalTimeZones::ZoneMap zones;
00099 };
00100 //@endcond
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;    // name already exists
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();   // error
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 //@cond PRIVATE
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;       // name of city for this time zone
00303     QByteArray    url;            // URL of published VTIMEZONE definition (optional)
00304     QDateTime     lastModified;   // time of last modification of the VTIMEZONE component (optional)
00305 private:
00306     icalcomponent *icalComponent; // ical component representing this time zone
00307 };
00308 //@endcond
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   // VTIMEZONE RRULE types
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     // Try to fetch a system time zone in preference, on the grounds
00338     // that system time zones are more likely to be up to date than
00339     // built-in libical ones.
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       // Try to fetch a built-in libical time zone.
00352       icaltimezone *itz = icaltimezone_get_builtin_timezone( tz.name().toUtf8() );
00353       c = icalcomponent_new_clone( icaltimezone_get_component( itz ) );
00354     }
00355     if ( c ) {
00356       // TZID in built-in libical time zones has a standard prefix.
00357       // To make the VTIMEZONE TZID match TZID references in incidences
00358       // (as required by RFC2445), strip off the prefix.
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, '/' );    // find third '/'
00367           if ( s ) {
00368             QByteArray tzidShort( s + 1 );    // deep copy of string (needed by icalvalue_set_text())
00369             icalvalue_set_text( value, tzidShort );
00370 
00371             // Remove the X-LIC-LOCATION property, which is only used by libical
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     // Write the time zone data into an iCal component
00384     icalcomponent *tzcomp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT);
00385     icalcomponent_add_property(tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ));
00386 //    icalcomponent_add_property(tzcomp, icalproperty_new_location( tz.name().toUtf8() ));
00387 
00388     // Compile an ordered list of transitions so that we can know the phases
00389     // which occur before and after each transition.
00390     QList<KTimeZone::Transition> transits = transitions();
00391     if ( earliest.isValid() ) {
00392       // Remove all transitions earlier than those we are interested in
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     // Go through the list of transitions and create an iCal component for each
00406     // distinct combination of phase after and UTC offset before the transition.
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       // Found a phase combination which hasn't yet been processed
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       // Create a component to hold initial RRULE if any, plus all RDATEs
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       // Compile the list of UTC transition dates/times, and
00445       // check if the list can be reduced to an RRULE instead of multiple RDATEs.
00446       QTime time;
00447       QDate date;
00448       int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0;  // initialise to avoid compiler warnings
00449       int dayOfWeek = 0;      // Monday = 1
00450       int nthFromStart = 0;   // nth (weekday) of month
00451       int nthFromEnd = 0;     // nth last (weekday) of month
00452       int newRule;
00453       int rule = 0;
00454       QList<QDateTime> rdates;   // dates which (probably) need to be written as RDATEs
00455       QList<QDateTime> times;
00456       QDateTime qdt = transits[i].time();   // set 'qdt' for start of loop
00457       times += qdt;
00458       transitionsDone[i] = true;
00459       do {
00460         if (!rule) {
00461           // Initialise data for detecting a new rule
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();   // Monday = 1
00469           dayOfMonth   = date.day();
00470           nthFromStart = (dayOfMonth - 1)/7 + 1;   // nth (weekday) of month
00471           nthFromEnd   = (daysInMonth - dayOfMonth)/7 + 1;   // nth last (weekday) of month
00472         }
00473         if (++i >= trcount) {
00474           newRule = 0;
00475           times += QDateTime();   // append a dummy value since last value in list is ignored
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           // The previous rule (if any) no longer applies.
00510           // Write all the times up to but not including the current one.
00511           // First check whether any of the last RDATE values fit this rule.
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             // There are enough dates to combine into an RRULE
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);   // Sunday = 1
00544             else if (rule & LAST_WEEKDAY_OF_MONTH)
00545               r.by_day[0] = -(dayOfWeek % 7 + 1) - (nthFromEnd * 8);   // Sunday = 1
00546             icalproperty *prop = icalproperty_new_rrule(r);
00547             if (useNewRRULE) {
00548               // This RRULE doesn't start from the phase start date, so set it into
00549               // a new STANDARD/DAYLIGHT component in the VTIMEZONE.
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             // Save dates for writing as RDATEs
00560             for ( int t = 0, tend = times.count() - 1;  t < tend;  ++t ) {
00561               rdates += times[t];
00562             }
00563           }
00564           useNewRRULE = true;
00565           // All date/time values but the last have been added to the VTIMEZONE.
00566           // Remove them from the list.
00567           qdt = times.last();   // set 'qdt' for start of loop
00568           times.clear();
00569           times += qdt;
00570         }
00571         rule = newRule;
00572       } while (i < trcount);
00573 
00574       // Write remaining dates as RDATEs
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 //@cond PRIVATE
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 //@endcond
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       // The zone already exists in the collection, so update the definition
00701       // of the zone rather than using a newly created one.
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   // Read the fixed properties which can only appear once in VTIMEZONE
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         // This isn't mentioned in RFC2445, but libical reads it ...
00732         data->d->location = QString::fromUtf8(icalproperty_get_location(p));
00733         break;
00734 
00735       case ICAL_X_PROPERTY: {   // use X-LIC-LOCATION if LOCATION is missing
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     // Remove the prefix from libical built in time zone TZID
00766     int i = name.indexOf('/', prefix.length());
00767     if (i > 0)
00768       name = name.mid(i + 1);
00769   }
00770   //kDebug(5800) << "---zoneId: \"" << name << '"';
00771 
00772   /*
00773    * Iterate through all time zone rules for this VTIMEZONE,
00774    * and create a Phase object containing details for each one.
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         //kDebug(5800) << "---standard phase: found";
00791         times = ICalTimeZoneSourcePrivate::parsePhase(c, false, prevoff, phase);
00792         break;
00793 
00794       case ICAL_XDAYLIGHT_COMPONENT:
00795         //kDebug(5800) << "---daylight phase: found";
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   // Remove any "duplicate" transitions, i.e. those where two consecutive
00816   // transitions have the same phase.
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   /* Parse the VTIMEZONE component stored in the icaltimezone structure.
00836    * This is both easier and provides more complete information than
00837    * extracting already parsed data from icaltimezone.
00838    */
00839   return parse(icaltimezone_get_component(tz));
00840 }
00841 
00842 //@cond PRIVATE
00843 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase(icalcomponent *c, bool daylight, int &prevOffset,
00844                                       KTimeZone::Phase &phase)
00845 {
00846   QList<QDateTime> transitions;
00847 
00848   // Read the observance data for this standard/daylight savings phase
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   // Now do the ical reading.
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:     // abbreviated name for this time offset
00866       {
00867         // TZNAME can appear multiple times in order to provide language
00868         // translations of the time zone offset name.
00869 #ifdef __GNUC__
00870 #warning Does this cope with multiple language specifications?
00871 #endif
00872         QByteArray tzname = icalproperty_get_tzname(p);
00873         // Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
00874         // strings, which is totally useless. So ignore those.
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:      // local time at which phase starts
00883         dtstart = icalproperty_get_dtstart(p);
00884         found_dtstart = true;
00885         break;
00886 
00887       case ICAL_TZOFFSETFROM_PROPERTY:    // UTC offset immediately before start of phase
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   // Validate the phase data
00914   if (!found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto) {
00915     kDebug(5800) << "ICalTimeZoneSource::readPhase(): DTSTART/TZOFFSETFROM/TZOFFSETTO missing";
00916     return transitions;
00917   }
00918 
00919   // Convert DTSTART to QDateTime, and from local time to UTC
00920   QDateTime localStart = toQDateTime(dtstart);   // local time
00921   dtstart.second -= prevOffset;
00922   dtstart.is_utc = 1;
00923   QDateTime utcStart = toQDateTime(icaltime_normalize(dtstart));   // UTC
00924 
00925   transitions += utcStart;
00926   if (recurs) {
00927     /* RDATE or RRULE is specified. There should only be one or the other, but
00928      * it doesn't really matter - the code can cope with both.
00929      * Note that we had to get DTSTART, TZOFFSETFROM, TZOFFSETTO before reading
00930      * recurrences.
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             // RDATE with a DATE value inherits the (local) time from DTSTART
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;    // dtstart is in local time
00950           }
00951           // RFC2445 states that RDATE must be in local time,
00952           // but we support UTC as well to be safe.
00953           if (!t.is_utc) {
00954             t.second -= prevOffset;    // convert to UTC
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           // The end date time specified in an RRULE should be in UTC.
00969           // Convert to local time to avoid timesInInterval() getting things wrong.
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 //@endcond
00997 
00998 ICalTimeZone ICalTimeZoneSource::standardZone(const QString &zone, bool icalBuiltIn)
00999 {
01000   if ( !icalBuiltIn ) {
01001     // Try to fetch a system time zone in preference, on the grounds
01002     // that system time zones are more likely to be up to date than
01003     // built-in libical ones.
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 );   // strip off the libical prefix
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   // Try to fetch a built-in libical time zone.
01021   // First try to look it up as a geographical location (e.g. Europe/London)
01022   QByteArray zoneName = zone.toUtf8();
01023   icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName );
01024   if ( !icaltz ) {
01025     // This will find it if it includes the libical prefix
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 }  // namespace KCal

KCal Library

Skip menu "KCal Library"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • kabc
  • kblog
  • kcal
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal