• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

KCalCore Library

icaltimezones.cpp

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

KCalCore Library

Skip menu "KCalCore 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"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • 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.3
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