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

KCalCore Library

Skip menu "KCalCore Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File 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.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal