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

KCal Library

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

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.6.1
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