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