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

KDE's Doxygen guidelines are available online.

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

kdepimlibs-4.8.5 API Reference

Skip menu "kdepimlibs-4.8.5 API Reference"
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal