• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.10.5 API Reference
  • KDE Home
  • Contact Us
 

KCalCore Library

  • kcalcore
icaltimezones.cpp
1 /*
2  This file is part of the kcalcore library.
3 
4  Copyright (c) 2005-2007 David Jarvie <djarvie@kde.org>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License as published by the Free Software Foundation; either
9  version 2 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Library General Public License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 */
21 #include <config-kcalcore.h>
22 
23 #include "icaltimezones.h"
24 #include "icalformat.h"
25 #include "icalformat_p.h"
26 #include "recurrence.h"
27 #include "recurrencerule.h"
28 
29 #include <KDebug>
30 #include <KDateTime>
31 #include <KSystemTimeZone>
32 
33 #include <QtCore/QDateTime>
34 #include <QtCore/QFile>
35 #include <QtCore/QTextStream>
36 
37 extern "C" {
38  #include <ical.h>
39  #include <icaltimezone.h>
40 }
41 
42 #if defined(HAVE_UUID_UUID_H)
43 #include <uuid/uuid.h>
44 #endif
45 
46 #if defined(Q_OS_WINCE)
47 #include <Winbase.h>
48 #endif
49 using namespace KCalCore;
50 
51 // Minimum repetition counts for VTIMEZONE RRULEs
52 static const int minRuleCount = 5; // for any RRULE
53 static const int minPhaseCount = 8; // for separate STANDARD/DAYLIGHT component
54 
55 // Convert an ical time to QDateTime, preserving the UTC indicator
56 static QDateTime toQDateTime( const icaltimetype &t )
57 {
58  return QDateTime( QDate( t.year, t.month, t.day ),
59  QTime( t.hour, t.minute, t.second ),
60  (icaltime_is_utc( t ) ? Qt::UTC : Qt::LocalTime));
61 }
62 
63 // Maximum date for time zone data.
64 // It's not sensible to try to predict them very far in advance, because
65 // they can easily change. Plus, it limits the processing required.
66 static QDateTime MAX_DATE()
67 {
68  static QDateTime dt;
69  if ( !dt.isValid() ) {
70  dt = QDateTime( QDate::currentDate().addYears( 20 ), QTime( 0, 0, 0 ) );
71  }
72  return dt;
73 }
74 
75 static icaltimetype writeLocalICalDateTime( const QDateTime &utc, int offset )
76 {
77  const QDateTime local = utc.addSecs( offset );
78  icaltimetype t = icaltime_null_time();
79  t.year = local.date().year();
80  t.month = local.date().month();
81  t.day = local.date().day();
82  t.hour = local.time().hour();
83  t.minute = local.time().minute();
84  t.second = local.time().second();
85  t.is_date = 0;
86  t.zone = 0;
87  return t;
88 }
89 
90 namespace KCalCore {
91 
92 /******************************************************************************/
93 
94 //@cond PRIVATE
95 class ICalTimeZonesPrivate
96 {
97  public:
98  ICalTimeZonesPrivate() {}
99  ICalTimeZones::ZoneMap zones;
100 };
101 //@endcond
102 
103 ICalTimeZones::ICalTimeZones()
104  : d( new ICalTimeZonesPrivate )
105 {
106 }
107 
108 ICalTimeZones::ICalTimeZones( const ICalTimeZones &rhs )
109  : d( new ICalTimeZonesPrivate() )
110 {
111  d->zones = rhs.d->zones;
112 }
113 
114 ICalTimeZones &ICalTimeZones::operator=( const ICalTimeZones &rhs )
115 {
116  // check for self assignment
117  if ( &rhs == this ) {
118  return *this;
119  }
120  d->zones = rhs.d->zones;
121  return *this;
122 }
123 
124 ICalTimeZones::~ICalTimeZones()
125 {
126  delete d;
127 }
128 
129 const ICalTimeZones::ZoneMap ICalTimeZones::zones() const
130 {
131  return d->zones;
132 }
133 
134 bool ICalTimeZones::add( const ICalTimeZone &zone )
135 {
136  if ( !zone.isValid() ) {
137  return false;
138  }
139  if ( d->zones.find( zone.name() ) != d->zones.end() ) {
140  return false; // name already exists
141  }
142 
143  d->zones.insert( zone.name(), zone );
144  return true;
145 }
146 
147 ICalTimeZone ICalTimeZones::remove( const ICalTimeZone &zone )
148 {
149  if ( zone.isValid() ) {
150  for ( ZoneMap::Iterator it = d->zones.begin(), end = d->zones.end(); it != end; ++it ) {
151  if ( it.value() == zone ) {
152  d->zones.erase( it );
153  return ( zone == ICalTimeZone::utc() ) ? ICalTimeZone() : zone;
154  }
155  }
156  }
157  return ICalTimeZone();
158 }
159 
160 ICalTimeZone ICalTimeZones::remove( const QString &name )
161 {
162  if ( !name.isEmpty() ) {
163  ZoneMap::Iterator it = d->zones.find( name );
164  if ( it != d->zones.end() ) {
165  const ICalTimeZone zone = it.value();
166  d->zones.erase(it);
167  return ( zone == ICalTimeZone::utc() ) ? ICalTimeZone() : zone;
168  }
169  }
170  return ICalTimeZone();
171 }
172 
173 void ICalTimeZones::clear()
174 {
175  d->zones.clear();
176 }
177 
178 int ICalTimeZones::count()
179 {
180  return d->zones.count();
181 }
182 
183 ICalTimeZone ICalTimeZones::zone( const QString &name ) const
184 {
185  if ( !name.isEmpty() ) {
186  ZoneMap::ConstIterator it = d->zones.constFind( name );
187  if ( it != d->zones.constEnd() ) {
188  return it.value();
189  }
190  }
191  return ICalTimeZone(); // error
192 }
193 
194 ICalTimeZone ICalTimeZones::zone( const ICalTimeZone &zone ) const
195 {
196  if ( zone.isValid() ) {
197  QMapIterator<QString, ICalTimeZone> it(d->zones);
198  while ( it.hasNext() ) {
199  it.next();
200  const ICalTimeZone tz = it.value();
201  const QList<KTimeZone::Transition> list1 = tz.transitions();
202  const QList<KTimeZone::Transition> list2 = zone.transitions();
203  if ( list1.size() == list2.size() ) {
204  int i = 0;
205  int matches = 0;
206  for ( ; i < list1.size(); ++i ) {
207  const KTimeZone::Transition t1 = list1[ i ];
208  const KTimeZone::Transition t2 = list2[ i ];
209  if ( ( t1.time() == t2.time() ) &&
210  ( t1.phase().utcOffset() == t2.phase().utcOffset() ) &&
211  ( t1.phase().isDst() == t2.phase().isDst() ) ) {
212  matches++;
213  }
214  }
215  if ( matches == i ) {
216  // Existing zone has all the transitions of the given zone.
217  return tz;
218  }
219  }
220  }
221  }
222  return ICalTimeZone(); // not found
223 }
224 
225 /******************************************************************************/
226 
227 ICalTimeZoneBackend::ICalTimeZoneBackend()
228  : KTimeZoneBackend()
229 {}
230 
231 ICalTimeZoneBackend::ICalTimeZoneBackend( ICalTimeZoneSource *source,
232  const QString &name,
233  const QString &countryCode,
234  float latitude, float longitude,
235  const QString &comment )
236  : KTimeZoneBackend( source, name, countryCode, latitude, longitude, comment )
237 {}
238 
239 ICalTimeZoneBackend::ICalTimeZoneBackend( const KTimeZone &tz, const QDate &earliest )
240  : KTimeZoneBackend( 0, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment() )
241 {
242  Q_UNUSED( earliest );
243 }
244 
245 ICalTimeZoneBackend::~ICalTimeZoneBackend()
246 {}
247 
248 KTimeZoneBackend *ICalTimeZoneBackend::clone() const
249 {
250  return new ICalTimeZoneBackend( *this );
251 }
252 
253 QByteArray ICalTimeZoneBackend::type() const
254 {
255  return "ICalTimeZone";
256 }
257 
258 bool ICalTimeZoneBackend::hasTransitions( const KTimeZone *caller ) const
259 {
260  Q_UNUSED( caller );
261  return true;
262 }
263 
264 void ICalTimeZoneBackend::virtual_hook( int id, void *data )
265 {
266  Q_UNUSED( id );
267  Q_UNUSED( data );
268 }
269 
270 /******************************************************************************/
271 
272 ICalTimeZone::ICalTimeZone()
273  : KTimeZone( new ICalTimeZoneBackend() )
274 {}
275 
276 ICalTimeZone::ICalTimeZone( ICalTimeZoneSource *source, const QString &name,
277  ICalTimeZoneData *data )
278  : KTimeZone( new ICalTimeZoneBackend( source, name ) )
279 {
280  setData( data );
281 }
282 
283 ICalTimeZone::ICalTimeZone( const KTimeZone &tz, const QDate &earliest )
284  : KTimeZone( new ICalTimeZoneBackend( 0, tz.name(), tz.countryCode(),
285  tz.latitude(), tz.longitude(),
286  tz.comment() ) )
287 {
288  const KTimeZoneData *data = tz.data( true );
289  if ( data ) {
290  const ICalTimeZoneData *icaldata = dynamic_cast<const ICalTimeZoneData*>( data );
291  if ( icaldata ) {
292  setData( new ICalTimeZoneData( *icaldata ) );
293  } else {
294  setData( new ICalTimeZoneData( *data, tz, earliest ) );
295  }
296  }
297 }
298 
299 ICalTimeZone::~ICalTimeZone()
300 {}
301 
302 QString ICalTimeZone::city() const
303 {
304  const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() );
305  return dat ? dat->city() : QString();
306 }
307 
308 QByteArray ICalTimeZone::url() const
309 {
310  const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() );
311  return dat ? dat->url() : QByteArray();
312 }
313 
314 QDateTime ICalTimeZone::lastModified() const
315 {
316  const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() );
317  return dat ? dat->lastModified() : QDateTime();
318 }
319 
320 QByteArray ICalTimeZone::vtimezone() const
321 {
322  const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() );
323  return dat ? dat->vtimezone() : QByteArray();
324 }
325 
326 icaltimezone *ICalTimeZone::icalTimezone() const
327 {
328  const ICalTimeZoneData *dat = static_cast<const ICalTimeZoneData*>( data() );
329  return dat ? dat->icalTimezone() : 0;
330 }
331 
332 bool ICalTimeZone::update( const ICalTimeZone &other )
333 {
334  if ( !updateBase( other ) ) {
335  return false;
336  }
337 
338  KTimeZoneData *otherData = other.data() ? other.data()->clone() : 0;
339  setData( otherData, other.source() );
340  return true;
341 }
342 
343 ICalTimeZone ICalTimeZone::utc()
344 {
345  static ICalTimeZone utcZone;
346  if ( !utcZone.isValid() ) {
347  ICalTimeZoneSource tzs;
348  utcZone = tzs.parse( icaltimezone_get_utc_timezone() );
349  }
350  return utcZone;
351 }
352 
353 void ICalTimeZone::virtual_hook( int id, void *data )
354 {
355  Q_UNUSED( id );
356  Q_UNUSED( data );
357 }
358 /******************************************************************************/
359 
360 //@cond PRIVATE
361 class ICalTimeZoneDataPrivate
362 {
363  public:
364  ICalTimeZoneDataPrivate() : icalComponent( 0 ) {}
365 
366  ~ICalTimeZoneDataPrivate()
367  {
368  if ( icalComponent ) {
369  icalcomponent_free( icalComponent );
370  }
371  }
372 
373  icalcomponent *component() const { return icalComponent; }
374  void setComponent( icalcomponent *c )
375  {
376  if ( icalComponent ) {
377  icalcomponent_free( icalComponent );
378  }
379  icalComponent = c;
380  }
381 
382  QString location; // name of city for this time zone
383  QByteArray url; // URL of published VTIMEZONE definition (optional)
384  QDateTime lastModified; // time of last modification of the VTIMEZONE component (optional)
385 
386  private:
387  icalcomponent *icalComponent; // ical component representing this time zone
388 };
389 //@endcond
390 
391 ICalTimeZoneData::ICalTimeZoneData()
392  : d ( new ICalTimeZoneDataPrivate() )
393 {
394 }
395 
396 ICalTimeZoneData::ICalTimeZoneData( const ICalTimeZoneData &rhs )
397  : KTimeZoneData( rhs ),
398  d( new ICalTimeZoneDataPrivate() )
399 {
400  d->location = rhs.d->location;
401  d->url = rhs.d->url;
402  d->lastModified = rhs.d->lastModified;
403  d->setComponent( icalcomponent_new_clone( rhs.d->component() ) );
404 }
405 
406 #ifdef Q_OS_WINCE
407 // Helper function to convert Windows recurrences to a QDate
408 static QDate find_nth_weekday_in_month_of_year( int nth, int dayOfWeek, int month, int year ) {
409  const QDate first( year, month, 1 );
410  const int actualDayOfWeek = first.dayOfWeek();
411  QDate candidate = first.addDays( ( nth - 1 ) * 7 + dayOfWeek - actualDayOfWeek );
412  if ( nth == 5 ) {
413  if ( candidate.month() != month ) {
414  candidate = candidate.addDays( -7 );
415  }
416  }
417  return candidate;
418 }
419 #endif // Q_OS_WINCE
420 
421 ICalTimeZoneData::ICalTimeZoneData( const KTimeZoneData &rhs,
422  const KTimeZone &tz, const QDate &earliest )
423  : KTimeZoneData( rhs ),
424  d( new ICalTimeZoneDataPrivate() )
425 {
426  // VTIMEZONE RRULE types
427  enum {
428  DAY_OF_MONTH = 0x01,
429  WEEKDAY_OF_MONTH = 0x02,
430  LAST_WEEKDAY_OF_MONTH = 0x04
431  };
432 
433  if ( tz.type() == "KSystemTimeZone" ) {
434  // Try to fetch a system time zone in preference, on the grounds
435  // that system time zones are more likely to be up to date than
436  // built-in libical ones.
437  icalcomponent *c = 0;
438  const KTimeZone ktz = KSystemTimeZones::readZone( tz.name() );
439  if ( ktz.isValid() ) {
440  if ( ktz.data( true ) ) {
441  const ICalTimeZone icaltz( ktz, earliest );
442  icaltimezone *itz = icaltz.icalTimezone();
443  if ( itz ) {
444  c = icalcomponent_new_clone( icaltimezone_get_component( itz ) );
445  icaltimezone_free( itz, 1 );
446  }
447  }
448  }
449  if ( !c ) {
450  // Try to fetch a built-in libical time zone.
451  icaltimezone *itz = icaltimezone_get_builtin_timezone( tz.name().toUtf8() );
452  c = icalcomponent_new_clone( icaltimezone_get_component( itz ) );
453  }
454  if ( c ) {
455  // TZID in built-in libical time zones has a standard prefix.
456  // To make the VTIMEZONE TZID match TZID references in incidences
457  // (as required by RFC2445), strip off the prefix.
458  icalproperty *prop = icalcomponent_get_first_property( c, ICAL_TZID_PROPERTY );
459  if ( prop ) {
460  icalvalue *value = icalproperty_get_value( prop );
461  const char *tzid = icalvalue_get_text( value );
462  const QByteArray icalprefix = ICalTimeZoneSource::icalTzidPrefix();
463  const int len = icalprefix.size();
464  if ( !strncmp( icalprefix, tzid, len ) ) {
465  const char *s = strchr( tzid + len, '/' ); // find third '/'
466  if ( s ) {
467  const QByteArray tzidShort( s + 1 ); // deep copy (needed by icalvalue_set_text())
468  icalvalue_set_text( value, tzidShort );
469 
470  // Remove the X-LIC-LOCATION property, which is only used by libical
471  prop = icalcomponent_get_first_property( c, ICAL_X_PROPERTY );
472  const char *xname = icalproperty_get_x_name( prop );
473  if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) {
474  icalcomponent_remove_property( c, prop );
475  }
476  }
477  }
478  }
479  }
480  d->setComponent( c );
481  } else {
482  // Write the time zone data into an iCal component
483  icalcomponent *tzcomp = icalcomponent_new( ICAL_VTIMEZONE_COMPONENT );
484  icalcomponent_add_property( tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ) );
485 // icalcomponent_add_property(tzcomp, icalproperty_new_location( tz.name().toUtf8() ));
486 
487  // Compile an ordered list of transitions so that we can know the phases
488  // which occur before and after each transition.
489  QList<KTimeZone::Transition> transits = transitions();
490  if ( transits.isEmpty() ) {
491  // If there is no way to compile a complete list of transitions
492  // transitions() can return an empty list
493  // In that case try get one transition to write a valid VTIMEZONE entry.
494 #ifdef Q_OS_WINCE
495  TIME_ZONE_INFORMATION currentTimeZone;
496  GetTimeZoneInformation( &currentTimeZone );
497  if ( QString::fromWCharArray( currentTimeZone.StandardName ) != tz.name() ) {
498  kDebug() << "VTIMEZONE entry will be invalid for: " << tz.name();
499  } else {
500  const SYSTEMTIME std = currentTimeZone.StandardDate;
501  const SYSTEMTIME dlt = currentTimeZone.DaylightDate;
502 
503  // Create the according Phases
504  const KTimeZone::Phase standardPhase =
505  KTimeZone::Phase( ( currentTimeZone.Bias +
506  currentTimeZone.StandardBias ) * -60,
507  QByteArray(), false );
508  const KTimeZone::Phase daylightPhase =
509  KTimeZone::Phase( ( currentTimeZone.Bias +
510  currentTimeZone.DaylightBias ) * -60,
511  QByteArray(), true );
512  // Generate the transitions from the minimal to the maximal year that
513  // the calendar offers on WinCE
514  for ( int i = 2000; i <= 2050; i++ ) {
515  const QDateTime standardTime =
516  QDateTime( find_nth_weekday_in_month_of_year(
517  std.wDay,
518  std.wDayOfWeek ? std.wDayOfWeek : 7,
519  std.wMonth, i ),
520  QTime( std.wHour, std.wMinute,
521  std.wSecond, std.wMilliseconds ) );
522 
523  const QDateTime daylightTime =
524  QDateTime( find_nth_weekday_in_month_of_year(
525  dlt.wDay,
526  dlt.wDayOfWeek ? dlt.wDayOfWeek : 7,
527  dlt.wMonth, i ),
528  QTime( dlt.wHour, dlt.wMinute,
529  dlt.wSecond, dlt.wMilliseconds ) );
530 
531  transits << KTimeZone::Transition( standardTime, standardPhase )
532  << KTimeZone::Transition( daylightTime, daylightPhase );
533  }
534  }
535 #endif // Q_OS_WINCE
536  if ( transits.isEmpty() ) {
537  kDebug() << "No transition information available VTIMEZONE will be invalid.";
538  }
539  }
540  if ( earliest.isValid() ) {
541  // Remove all transitions earlier than those we are interested in
542  for ( int i = 0, end = transits.count(); i < end; ++i ) {
543  if ( transits.at( i ).time().date() >= earliest ) {
544  if ( i > 0 ) {
545  transits.erase( transits.begin(), transits.begin() + i );
546  }
547  break;
548  }
549  }
550  }
551  int trcount = transits.count();
552  QVector<bool> transitionsDone(trcount);
553  transitionsDone.fill( false );
554 
555  // Go through the list of transitions and create an iCal component for each
556  // distinct combination of phase after and UTC offset before the transition.
557  icaldatetimeperiodtype dtperiod;
558  dtperiod.period = icalperiodtype_null_period();
559  for ( ; ; ) {
560  int i = 0;
561  for ( ; i < trcount && transitionsDone[i]; ++i ) {
562  ;
563  }
564  if ( i >= trcount ) {
565  break;
566  }
567  // Found a phase combination which hasn't yet been processed
568  const int preOffset = ( i > 0 ) ?
569  transits.at( i - 1 ).phase().utcOffset() :
570  rhs.previousUtcOffset();
571  const KTimeZone::Phase phase = transits.at( i ).phase();
572  if ( phase.utcOffset() == preOffset ) {
573  transitionsDone[i] = true;
574  while ( ++i < trcount ) {
575  if ( transitionsDone[i] ||
576  transits.at( i ).phase() != phase ||
577  transits.at( i - 1 ).phase().utcOffset() != preOffset ) {
578  continue;
579  }
580  transitionsDone[i] = true;
581  }
582  continue;
583  }
584  icalcomponent *phaseComp =
585  icalcomponent_new( phase.isDst() ? ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT );
586  const QList<QByteArray> abbrevs = phase.abbreviations();
587  for ( int a = 0, aend = abbrevs.count(); a < aend; ++a ) {
588  icalcomponent_add_property( phaseComp,
589  icalproperty_new_tzname(
590  static_cast<const char*>( abbrevs[a]) ) );
591  }
592  if ( !phase.comment().isEmpty() ) {
593  icalcomponent_add_property( phaseComp,
594  icalproperty_new_comment( phase.comment().toUtf8() ) );
595  }
596  icalcomponent_add_property( phaseComp,
597  icalproperty_new_tzoffsetfrom( preOffset ) );
598  icalcomponent_add_property( phaseComp,
599  icalproperty_new_tzoffsetto( phase.utcOffset() ) );
600  // Create a component to hold initial RRULE if any, plus all RDATEs
601  icalcomponent *phaseComp1 = icalcomponent_new_clone( phaseComp );
602  icalcomponent_add_property( phaseComp1,
603  icalproperty_new_dtstart(
604  writeLocalICalDateTime( transits.at( i ).time(),
605  preOffset ) ) );
606  bool useNewRRULE = false;
607 
608  // Compile the list of UTC transition dates/times, and check
609  // if the list can be reduced to an RRULE instead of multiple RDATEs.
610  QTime time;
611  QDate date;
612  int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0; // avoid compiler warnings
613  int dayOfWeek = 0; // Monday = 1
614  int nthFromStart = 0; // nth (weekday) of month
615  int nthFromEnd = 0; // nth last (weekday) of month
616  int newRule;
617  int rule = 0;
618  QList<QDateTime> rdates;// dates which (probably) need to be written as RDATEs
619  QList<QDateTime> times;
620  QDateTime qdt = transits.at( i ).time(); // set 'qdt' for start of loop
621  times += qdt;
622  transitionsDone[i] = true;
623  do {
624  if ( !rule ) {
625  // Initialise data for detecting a new rule
626  rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH;
627  time = qdt.time();
628  date = qdt.date();
629  year = date.year();
630  month = date.month();
631  daysInMonth = date.daysInMonth();
632  dayOfWeek = date.dayOfWeek(); // Monday = 1
633  dayOfMonth = date.day();
634  nthFromStart = ( dayOfMonth - 1 ) / 7 + 1; // nth (weekday) of month
635  nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1; // nth last (weekday) of month
636  }
637  if ( ++i >= trcount ) {
638  newRule = 0;
639  times += QDateTime(); // append a dummy value since last value in list is ignored
640  } else {
641  if ( transitionsDone[i] ||
642  transits.at( i ).phase() != phase ||
643  transits.at( i - 1 ).phase().utcOffset() != preOffset ) {
644  continue;
645  }
646  transitionsDone[i] = true;
647  qdt = transits.at( i ).time();
648  if ( !qdt.isValid() ) {
649  continue;
650  }
651  newRule = rule;
652  times += qdt;
653  date = qdt.date();
654  if ( qdt.time() != time ||
655  date.month() != month ||
656  date.year() != ++year ) {
657  newRule = 0;
658  } else {
659  const int day = date.day();
660  if ( ( newRule & DAY_OF_MONTH ) && day != dayOfMonth ) {
661  newRule &= ~DAY_OF_MONTH;
662  }
663  if ( newRule & ( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ) ) {
664  if ( date.dayOfWeek() != dayOfWeek ) {
665  newRule &= ~( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH );
666  } else {
667  if ( ( newRule & WEEKDAY_OF_MONTH ) &&
668  ( day - 1 ) / 7 + 1 != nthFromStart ) {
669  newRule &= ~WEEKDAY_OF_MONTH;
670  }
671  if ( ( newRule & LAST_WEEKDAY_OF_MONTH ) &&
672  ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) {
673  newRule &= ~LAST_WEEKDAY_OF_MONTH;
674  }
675  }
676  }
677  }
678  }
679  if ( !newRule ) {
680  // The previous rule (if any) no longer applies.
681  // Write all the times up to but not including the current one.
682  // First check whether any of the last RDATE values fit this rule.
683  int yr = times[0].date().year();
684  while ( !rdates.isEmpty() ) {
685  qdt = rdates.last();
686  date = qdt.date();
687  if ( qdt.time() != time ||
688  date.month() != month ||
689  date.year() != --yr ) {
690  break;
691  }
692  const int day = date.day();
693  if ( rule & DAY_OF_MONTH ) {
694  if ( day != dayOfMonth ) {
695  break;
696  }
697  } else {
698  if ( date.dayOfWeek() != dayOfWeek ||
699  ( ( rule & WEEKDAY_OF_MONTH ) &&
700  ( day - 1 ) / 7 + 1 != nthFromStart ) ||
701  ( ( rule & LAST_WEEKDAY_OF_MONTH ) &&
702  ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) ) {
703  break;
704  }
705  }
706  times.prepend( qdt );
707  rdates.pop_back();
708  }
709  if ( times.count() > ( useNewRRULE ? minPhaseCount : minRuleCount ) ) {
710  // There are enough dates to combine into an RRULE
711  icalrecurrencetype r;
712  icalrecurrencetype_clear( &r );
713  r.freq = ICAL_YEARLY_RECURRENCE;
714  r.count = ( year >= 2030 ) ? 0 : times.count() - 1;
715  r.by_month[0] = month;
716  if ( rule & DAY_OF_MONTH ) {
717  r.by_month_day[0] = dayOfMonth;
718  } else if ( rule & WEEKDAY_OF_MONTH ) {
719  r.by_day[0] = ( dayOfWeek % 7 + 1 ) + ( nthFromStart * 8 ); // Sunday = 1
720  } else if ( rule & LAST_WEEKDAY_OF_MONTH ) {
721  r.by_day[0] = -( dayOfWeek % 7 + 1 ) - ( nthFromEnd * 8 ); // Sunday = 1
722  }
723  icalproperty *prop = icalproperty_new_rrule( r );
724  if ( useNewRRULE ) {
725  // This RRULE doesn't start from the phase start date, so set it into
726  // a new STANDARD/DAYLIGHT component in the VTIMEZONE.
727  icalcomponent *c = icalcomponent_new_clone( phaseComp );
728  icalcomponent_add_property(
729  c, icalproperty_new_dtstart( writeLocalICalDateTime( times[0], preOffset ) ) );
730  icalcomponent_add_property( c, prop );
731  icalcomponent_add_component( tzcomp, c );
732  } else {
733  icalcomponent_add_property( phaseComp1, prop );
734  }
735  } else {
736  // Save dates for writing as RDATEs
737  for ( int t = 0, tend = times.count() - 1; t < tend; ++t ) {
738  rdates += times[t];
739  }
740  }
741  useNewRRULE = true;
742  // All date/time values but the last have been added to the VTIMEZONE.
743  // Remove them from the list.
744  qdt = times.last(); // set 'qdt' for start of loop
745  times.clear();
746  times += qdt;
747  }
748  rule = newRule;
749  } while ( i < trcount );
750 
751  // Write remaining dates as RDATEs
752  for ( int rd = 0, rdend = rdates.count(); rd < rdend; ++rd ) {
753  dtperiod.time = writeLocalICalDateTime( rdates[rd], preOffset );
754  icalcomponent_add_property( phaseComp1, icalproperty_new_rdate( dtperiod ) );
755  }
756  icalcomponent_add_component( tzcomp, phaseComp1 );
757  icalcomponent_free( phaseComp );
758  }
759 
760  d->setComponent( tzcomp );
761  }
762 }
763 
764 ICalTimeZoneData::~ICalTimeZoneData()
765 {
766  delete d;
767 }
768 
769 ICalTimeZoneData &ICalTimeZoneData::operator=( const ICalTimeZoneData &rhs )
770 {
771  // check for self assignment
772  if ( &rhs == this ) {
773  return *this;
774  }
775 
776  KTimeZoneData::operator=( rhs );
777  d->location = rhs.d->location;
778  d->url = rhs.d->url;
779  d->lastModified = rhs.d->lastModified;
780  d->setComponent( icalcomponent_new_clone( rhs.d->component() ) );
781  return *this;
782 }
783 
784 KTimeZoneData *ICalTimeZoneData::clone() const
785 {
786  return new ICalTimeZoneData( *this );
787 }
788 
789 QString ICalTimeZoneData::city() const
790 {
791  return d->location;
792 }
793 
794 QByteArray ICalTimeZoneData::url() const
795 {
796  return d->url;
797 }
798 
799 QDateTime ICalTimeZoneData::lastModified() const
800 {
801  return d->lastModified;
802 }
803 
804 QByteArray ICalTimeZoneData::vtimezone() const
805 {
806  const QByteArray result( icalcomponent_as_ical_string( d->component() ) );
807  icalmemory_free_ring();
808  return result;
809 }
810 
811 icaltimezone *ICalTimeZoneData::icalTimezone() const
812 {
813  icaltimezone *icaltz = icaltimezone_new();
814  if ( !icaltz ) {
815  return 0;
816  }
817  icalcomponent *c = icalcomponent_new_clone( d->component() );
818  if ( !icaltimezone_set_component( icaltz, c ) ) {
819  icalcomponent_free( c );
820  icaltimezone_free( icaltz, 1 );
821  return 0;
822  }
823  return icaltz;
824 }
825 
826 bool ICalTimeZoneData::hasTransitions() const
827 {
828  return true;
829 }
830 
831 void ICalTimeZoneData::virtual_hook( int id, void *data )
832 {
833  Q_UNUSED( id );
834  Q_UNUSED( data );
835 }
836 
837 /******************************************************************************/
838 
839 //@cond PRIVATE
840 class ICalTimeZoneSourcePrivate
841 {
842  public:
843  static QList<QDateTime> parsePhase( icalcomponent *, bool daylight,
844  int &prevOffset, KTimeZone::Phase & );
845  static QByteArray icalTzidPrefix;
846 
847 #if defined(HAVE_UUID_UUID_H)
848  static void parseTransitions( const MSSystemTime &date, const KTimeZone::Phase &phase,
849  int prevOffset, QList<KTimeZone::Transition> &transitions );
850 #endif
851 };
852 
853 QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix;
854 //@endcond
855 
856 ICalTimeZoneSource::ICalTimeZoneSource()
857  : KTimeZoneSource( false ),
858  d( 0 )
859 {
860 }
861 
862 ICalTimeZoneSource::~ICalTimeZoneSource()
863 {
864 }
865 
866 bool ICalTimeZoneSource::parse( const QString &fileName, ICalTimeZones &zones )
867 {
868  QFile file( fileName );
869  if ( !file.open( QIODevice::ReadOnly ) ) {
870  return false;
871  }
872  QTextStream ts( &file );
873  ts.setCodec( "ISO 8859-1" );
874  const QByteArray text = ts.readAll().trimmed().toLatin1();
875  file.close();
876 
877  bool result = false;
878  icalcomponent *calendar = icalcomponent_new_from_string( text.data() );
879  if ( calendar ) {
880  if ( icalcomponent_isa( calendar ) == ICAL_VCALENDAR_COMPONENT ) {
881  result = parse( calendar, zones );
882  }
883  icalcomponent_free( calendar );
884  }
885  return result;
886 }
887 
888 bool ICalTimeZoneSource::parse( icalcomponent *calendar, ICalTimeZones &zones )
889 {
890  for ( icalcomponent *c = icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT );
891  c; c = icalcomponent_get_next_component( calendar, ICAL_VTIMEZONE_COMPONENT ) ) {
892  const ICalTimeZone zone = parse( c );
893  if ( !zone.isValid() ) {
894  return false;
895  }
896  ICalTimeZone oldzone = zones.zone( zone.name() );
897  if ( oldzone.isValid() ) {
898  // The zone already exists in the collection, so update the definition
899  // of the zone rather than using a newly created one.
900  oldzone.update( zone );
901  } else if ( !zones.add( zone ) ) {
902  return false;
903  }
904  }
905  return true;
906 }
907 
908 ICalTimeZone ICalTimeZoneSource::parse( icalcomponent *vtimezone )
909 {
910  QString name;
911  QString xlocation;
912  ICalTimeZoneData *data = new ICalTimeZoneData();
913 
914  // Read the fixed properties which can only appear once in VTIMEZONE
915  icalproperty *p = icalcomponent_get_first_property( vtimezone, ICAL_ANY_PROPERTY );
916  while ( p ) {
917  icalproperty_kind kind = icalproperty_isa( p );
918  switch ( kind ) {
919 
920  case ICAL_TZID_PROPERTY:
921  name = QString::fromUtf8( icalproperty_get_tzid( p ) );
922  break;
923 
924  case ICAL_TZURL_PROPERTY:
925  data->d->url = icalproperty_get_tzurl( p );
926  break;
927 
928  case ICAL_LOCATION_PROPERTY:
929  // This isn't mentioned in RFC2445, but libical reads it ...
930  data->d->location = QString::fromUtf8( icalproperty_get_location( p ) );
931  break;
932 
933  case ICAL_X_PROPERTY:
934  { // use X-LIC-LOCATION if LOCATION is missing
935  const char *xname = icalproperty_get_x_name( p );
936  if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) {
937  xlocation = QString::fromUtf8( icalproperty_get_x( p ) );
938  }
939  break;
940  }
941  case ICAL_LASTMODIFIED_PROPERTY:
942  {
943  const icaltimetype t = icalproperty_get_lastmodified(p);
944  if (icaltime_is_utc( t )) {
945  data->d->lastModified = toQDateTime( t );
946  } else {
947  kDebug() << "LAST-MODIFIED not UTC";
948  }
949  break;
950  }
951  default:
952  break;
953  }
954  p = icalcomponent_get_next_property( vtimezone, ICAL_ANY_PROPERTY );
955  }
956 
957  if ( name.isEmpty() ) {
958  kDebug() << "TZID missing";
959  delete data;
960  return ICalTimeZone();
961  }
962  if ( data->d->location.isEmpty() && !xlocation.isEmpty() ) {
963  data->d->location = xlocation;
964  }
965  const QString prefix = QString::fromUtf8( icalTzidPrefix() );
966  if ( name.startsWith( prefix ) ) {
967  // Remove the prefix from libical built in time zone TZID
968  const int i = name.indexOf( '/', prefix.length() );
969  if ( i > 0 ) {
970  name = name.mid( i + 1 );
971  }
972  }
973  //kDebug() << "---zoneId: \"" << name << '"';
974 
975  /*
976  * Iterate through all time zone rules for this VTIMEZONE,
977  * and create a Phase object containing details for each one.
978  */
979  int prevOffset = 0;
980  QList<KTimeZone::Transition> transitions;
981  QDateTime earliest;
982  QList<KTimeZone::Phase> phases;
983  for ( icalcomponent *c = icalcomponent_get_first_component( vtimezone, ICAL_ANY_COMPONENT );
984  c; c = icalcomponent_get_next_component( vtimezone, ICAL_ANY_COMPONENT ) ) {
985  int prevoff = 0;
986  KTimeZone::Phase phase;
987  QList<QDateTime> times;
988  icalcomponent_kind kind = icalcomponent_isa( c );
989  switch ( kind ) {
990 
991  case ICAL_XSTANDARD_COMPONENT:
992  //kDebug() << "---standard phase: found";
993  times = ICalTimeZoneSourcePrivate::parsePhase( c, false, prevoff, phase );
994  break;
995 
996  case ICAL_XDAYLIGHT_COMPONENT:
997  //kDebug() << "---daylight phase: found";
998  times = ICalTimeZoneSourcePrivate::parsePhase( c, true, prevoff, phase );
999  break;
1000 
1001  default:
1002  kDebug() << "Unknown component:" << int( kind );
1003  break;
1004  }
1005  const int tcount = times.count();
1006  if ( tcount ) {
1007  phases += phase;
1008  for ( int t = 0; t < tcount; ++t ) {
1009  transitions += KTimeZone::Transition( times[t], phase );
1010  }
1011  if ( !earliest.isValid() || times[0] < earliest ) {
1012  prevOffset = prevoff;
1013  earliest = times[0];
1014  }
1015  }
1016  }
1017  // Set phases used by the time zone, but note that VTIMEZONE doesn't contain
1018  // time zone abbreviation before first transition.
1019  data->setPhases( phases, prevOffset );
1020  // Remove any "duplicate" transitions, i.e. those where two consecutive
1021  // transitions have the same phase.
1022  qSort( transitions );
1023  for ( int t = 1, tend = transitions.count(); t < tend; ) {
1024  if ( transitions[t].phase() == transitions[t - 1].phase() ) {
1025  transitions.removeAt( t );
1026  --tend;
1027  } else {
1028  ++t;
1029  }
1030  }
1031  data->setTransitions( transitions );
1032 
1033  data->d->setComponent( icalcomponent_new_clone( vtimezone ) );
1034  //kDebug() << "VTIMEZONE" << name;
1035  return ICalTimeZone( this, name, data );
1036 }
1037 
1038 #if defined(HAVE_UUID_UUID_H)
1039 ICalTimeZone ICalTimeZoneSource::parse( MSTimeZone *tz, ICalTimeZones &zones )
1040 {
1041  const ICalTimeZone zone = parse( tz );
1042  if ( !zone.isValid() ) {
1043  return ICalTimeZone(); // error
1044  }
1045  const ICalTimeZone oldzone = zones.zone( zone );
1046  if ( oldzone.isValid() ) {
1047  // A similar zone already exists in the collection, so don't add this
1048  // new zone, return old zone instead.
1049  return oldzone;
1050  } else if ( zones.add( zone ) ) {
1051  // No similar zone, add and return new one.
1052  return zone;
1053  }
1054  return ICalTimeZone(); // error
1055 }
1056 
1057 ICalTimeZone ICalTimeZoneSource::parse( MSTimeZone *tz )
1058 {
1059  ICalTimeZoneData kdata;
1060 
1061  // General properties.
1062  uuid_t uuid;
1063  char suuid[64];
1064  uuid_generate_random( uuid );
1065  uuid_unparse( uuid, suuid );
1066  QString name = QString( suuid );
1067 
1068  // Create phases.
1069  QList<KTimeZone::Phase> phases;
1070 
1071  QList<QByteArray> standardAbbrevs;
1072  standardAbbrevs += tz->StandardName.toLatin1();
1073  const KTimeZone::Phase standardPhase(
1074  ( tz->Bias + tz->StandardBias ) * -60,
1075  standardAbbrevs, false,
1076  "Microsoft TIME_ZONE_INFORMATION" );
1077  phases += standardPhase;
1078 
1079  QList<QByteArray> daylightAbbrevs;
1080  daylightAbbrevs += tz->DaylightName.toLatin1();
1081  const KTimeZone::Phase daylightPhase(
1082  ( tz->Bias + tz->DaylightBias ) * -60,
1083  daylightAbbrevs, true,
1084  "Microsoft TIME_ZONE_INFORMATION" );
1085  phases += daylightPhase;
1086 
1087  // Set phases used by the time zone, but note that previous time zone
1088  // abbreviation is not known.
1089  const int prevOffset = tz->Bias * -60;
1090  kdata.setPhases( phases, prevOffset );
1091 
1092  // Create transitions
1093  QList<KTimeZone::Transition> transitions;
1094  ICalTimeZoneSourcePrivate::parseTransitions(
1095  tz->StandardDate, standardPhase, prevOffset, transitions );
1096  ICalTimeZoneSourcePrivate::parseTransitions(
1097  tz->DaylightDate, daylightPhase, prevOffset, transitions );
1098 
1099  qSort( transitions );
1100  kdata.setTransitions( transitions );
1101 
1102  ICalTimeZoneData *idata = new ICalTimeZoneData( kdata, KTimeZone( name ), QDate() );
1103 
1104  return ICalTimeZone( this, name, idata );
1105 }
1106 #endif // HAVE_UUID_UUID_H
1107 
1108 ICalTimeZone ICalTimeZoneSource::parse( const QString &name, const QStringList &tzList,
1109  ICalTimeZones &zones )
1110 {
1111  const ICalTimeZone zone = parse( name, tzList );
1112  if ( !zone.isValid() ) {
1113  return ICalTimeZone(); // error
1114  }
1115 
1116  ICalTimeZone oldzone = zones.zone( zone );
1117  // First off see if the zone is same as oldzone - _exactly_ same
1118  if ( oldzone.isValid() ) {
1119  return oldzone;
1120  }
1121 
1122  oldzone = zones.zone( name );
1123  if ( oldzone.isValid() ) {
1124  // The zone already exists, so update
1125  oldzone.update( zone );
1126  return zone;
1127  } else if ( zones.add( zone ) ) {
1128  // No similar zone, add and return new one.
1129  return zone;
1130  }
1131  return ICalTimeZone(); // error
1132 }
1133 
1134 ICalTimeZone ICalTimeZoneSource::parse( const QString &name, const QStringList &tzList )
1135 {
1136  ICalTimeZoneData kdata;
1137  QList<KTimeZone::Phase> phases;
1138  QList<KTimeZone::Transition> transitions;
1139  bool daylight;
1140 
1141  for ( QStringList::ConstIterator it = tzList.begin(); it != tzList.end(); ++it ) {
1142  QString value = *it;
1143  daylight = false;
1144  const QString tzName = value.mid( 0, value.indexOf( ";" ) );
1145  value = value.mid( ( value.indexOf( ";" ) + 1 ) );
1146  const QString tzOffset = value.mid( 0, value.indexOf( ";" ) );
1147  value = value.mid( ( value.indexOf( ";" ) + 1 ) );
1148  const QString tzDaylight = value.mid( 0, value.indexOf( ";" ) );
1149  const KDateTime tzDate = KDateTime::fromString( value.mid( ( value.lastIndexOf( ";" ) + 1 ) ) );
1150  if ( tzDaylight == "true" ) {
1151  daylight = true;
1152  }
1153 
1154  const KTimeZone::Phase tzPhase(
1155  tzOffset.toInt(),
1156  QByteArray( tzName.toLatin1() ), daylight, "VCAL_TZ_INFORMATION" );
1157  phases += tzPhase;
1158  transitions += KTimeZone::Transition( tzDate.dateTime(), tzPhase );
1159  }
1160 
1161  kdata.setPhases( phases, 0 );
1162  qSort( transitions );
1163  kdata.setTransitions( transitions );
1164 
1165  ICalTimeZoneData *idata = new ICalTimeZoneData( kdata, KTimeZone( name ), QDate() );
1166  return ICalTimeZone( this, name, idata );
1167 }
1168 
1169 #if defined(HAVE_UUID_UUID_H)
1170 //@cond PRIVATE
1171 void ICalTimeZoneSourcePrivate::parseTransitions( const MSSystemTime &date,
1172  const KTimeZone::Phase &phase, int prevOffset,
1173  QList<KTimeZone::Transition> &transitions )
1174 {
1175  // NOTE that we need to set start and end times and they cannot be
1176  // to far in either direction to avoid bloating the transitions list
1177  const KDateTime klocalStart( QDateTime( QDate( 2000, 1, 1 ), QTime( 0, 0, 0 ) ),
1178  KDateTime::Spec::ClockTime() );
1179  const KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() );
1180 
1181  if ( date.wYear ) {
1182  // Absolute change time.
1183  if ( date.wYear >= 1601 && date.wYear <= 30827 &&
1184  date.wMonth >= 1 && date.wMonth <= 12 &&
1185  date.wDay >= 1 && date.wDay <= 31 ) {
1186  const QDate dt( date.wYear, date.wMonth, date.wDay );
1187  const QTime tm( date.wHour, date.wMinute, date.wSecond, date.wMilliseconds );
1188  const QDateTime datetime( dt, tm );
1189  if ( datetime.isValid() ) {
1190  transitions += KTimeZone::Transition( datetime, phase );
1191  }
1192  }
1193  } else {
1194  // The normal way, for example: 'First Sunday in April at 02:00'.
1195  if ( date.wDayOfWeek >= 0 && date.wDayOfWeek <= 6 &&
1196  date.wMonth >= 1 && date.wMonth <= 12 &&
1197  date.wDay >= 1 && date.wDay <= 5 ) {
1198  RecurrenceRule r;
1199  r.setRecurrenceType( RecurrenceRule::rYearly );
1200  r.setDuration( -1 );
1201  r.setFrequency( 1 );
1202  QList<int> lst;
1203  lst.append( date.wMonth );
1204  r.setByMonths( lst );
1205  QList<RecurrenceRule::WDayPos> wdlst;
1206  RecurrenceRule::WDayPos pos;
1207  pos.setDay( date.wDayOfWeek ? date.wDayOfWeek : 7 );
1208  pos.setPos( date.wDay < 5 ? date.wDay : -1 );
1209  wdlst.append( pos );
1210  r.setByDays( wdlst );
1211  r.setStartDt( klocalStart );
1212  r.setWeekStart( 1 );
1213  const DateTimeList dtl = r.timesInInterval( klocalStart, maxTime );
1214  for ( int i = 0, end = dtl.count(); i < end; ++i ) {
1215  QDateTime utc = dtl[i].dateTime();
1216  utc.setTimeSpec( Qt::UTC );
1217  transitions += KTimeZone::Transition( utc.addSecs( -prevOffset ), phase );
1218  }
1219  }
1220  }
1221 }
1222 //@endcond
1223 #endif // HAVE_UUID_UUID_H
1224 
1225 ICalTimeZone ICalTimeZoneSource::parse( icaltimezone *tz )
1226 {
1227  /* Parse the VTIMEZONE component stored in the icaltimezone structure.
1228  * This is both easier and provides more complete information than
1229  * extracting already parsed data from icaltimezone.
1230  */
1231  return tz ? parse( icaltimezone_get_component( tz ) ) : ICalTimeZone();
1232 }
1233 
1234 //@cond PRIVATE
1235 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase( icalcomponent *c,
1236  bool daylight,
1237  int &prevOffset,
1238  KTimeZone::Phase &phase )
1239 {
1240  QList<QDateTime> transitions;
1241 
1242  // Read the observance data for this standard/daylight savings phase
1243  QList<QByteArray> abbrevs;
1244  QString comment;
1245  prevOffset = 0;
1246  int utcOffset = 0;
1247  bool recurs = false;
1248  bool found_dtstart = false;
1249  bool found_tzoffsetfrom = false;
1250  bool found_tzoffsetto = false;
1251  icaltimetype dtstart = icaltime_null_time();
1252 
1253  // Now do the ical reading.
1254  icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
1255  while ( p ) {
1256  icalproperty_kind kind = icalproperty_isa( p );
1257  switch ( kind ) {
1258 
1259  case ICAL_TZNAME_PROPERTY: // abbreviated name for this time offset
1260  {
1261  // TZNAME can appear multiple times in order to provide language
1262  // translations of the time zone offset name.
1263 
1264  // TODO: Does this cope with multiple language specifications?
1265  QByteArray tzname = icalproperty_get_tzname( p );
1266  // Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
1267  // strings, which is totally useless. So ignore those.
1268  if ( ( !daylight && tzname == "Standard Time" ) ||
1269  ( daylight && tzname == "Daylight Time" ) ) {
1270  break;
1271  }
1272  if ( !abbrevs.contains( tzname ) ) {
1273  abbrevs += tzname;
1274  }
1275  break;
1276  }
1277  case ICAL_DTSTART_PROPERTY: // local time at which phase starts
1278  dtstart = icalproperty_get_dtstart( p );
1279  found_dtstart = true;
1280  break;
1281 
1282  case ICAL_TZOFFSETFROM_PROPERTY: // UTC offset immediately before start of phase
1283  prevOffset = icalproperty_get_tzoffsetfrom( p );
1284  found_tzoffsetfrom = true;
1285  break;
1286 
1287  case ICAL_TZOFFSETTO_PROPERTY:
1288  utcOffset = icalproperty_get_tzoffsetto( p );
1289  found_tzoffsetto = true;
1290  break;
1291 
1292  case ICAL_COMMENT_PROPERTY:
1293  comment = QString::fromUtf8( icalproperty_get_comment( p ) );
1294  break;
1295 
1296  case ICAL_RDATE_PROPERTY:
1297  case ICAL_RRULE_PROPERTY:
1298  recurs = true;
1299  break;
1300 
1301  default:
1302  kDebug() << "Unknown property:" << int( kind );
1303  break;
1304  }
1305  p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
1306  }
1307 
1308  // Validate the phase data
1309  if ( !found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto ) {
1310  kDebug() << "DTSTART/TZOFFSETFROM/TZOFFSETTO missing";
1311  return transitions;
1312  }
1313 
1314  // Convert DTSTART to QDateTime, and from local time to UTC
1315  const QDateTime localStart = toQDateTime( dtstart ); // local time
1316  dtstart.second -= prevOffset;
1317  dtstart.zone = icaltimezone_get_utc_timezone();
1318  const QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) ); // UTC
1319 
1320  transitions += utcStart;
1321  if ( recurs ) {
1322  /* RDATE or RRULE is specified. There should only be one or the other, but
1323  * it doesn't really matter - the code can cope with both.
1324  * Note that we had to get DTSTART, TZOFFSETFROM, TZOFFSETTO before reading
1325  * recurrences.
1326  */
1327  const KDateTime klocalStart( localStart, KDateTime::Spec::ClockTime() );
1328  const KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() );
1329  Recurrence recur;
1330  icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
1331  while ( p ) {
1332  icalproperty_kind kind = icalproperty_isa( p );
1333  switch ( kind ) {
1334 
1335  case ICAL_RDATE_PROPERTY:
1336  {
1337  icaltimetype t = icalproperty_get_rdate( p ).time;
1338  if ( icaltime_is_date( t ) ) {
1339  // RDATE with a DATE value inherits the (local) time from DTSTART
1340  t.hour = dtstart.hour;
1341  t.minute = dtstart.minute;
1342  t.second = dtstart.second;
1343  t.is_date = 0;
1344  t.zone = 0; // dtstart is in local time
1345  }
1346  // RFC2445 states that RDATE must be in local time,
1347  // but we support UTC as well to be safe.
1348  if (!icaltime_is_utc( t )) {
1349  t.second -= prevOffset; // convert to UTC
1350  t.zone = icaltimezone_get_utc_timezone();
1351  t = icaltime_normalize( t );
1352  }
1353  transitions += toQDateTime( t );
1354  break;
1355  }
1356  case ICAL_RRULE_PROPERTY:
1357  {
1358  RecurrenceRule r;
1359  ICalFormat icf;
1360  ICalFormatImpl impl( &icf );
1361  impl.readRecurrence( icalproperty_get_rrule( p ), &r );
1362  r.setStartDt( klocalStart );
1363  // The end date time specified in an RRULE should be in UTC.
1364  // Convert to local time to avoid timesInInterval() getting things wrong.
1365  if ( r.duration() == 0 ) {
1366  KDateTime end( r.endDt() );
1367  if ( end.timeSpec() == KDateTime::Spec::UTC() ) {
1368  end.setTimeSpec( KDateTime::Spec::ClockTime() );
1369  r.setEndDt( end.addSecs( prevOffset ) );
1370  }
1371  }
1372  const DateTimeList dts = r.timesInInterval( klocalStart, maxTime );
1373  for ( int i = 0, end = dts.count(); i < end; ++i ) {
1374  QDateTime utc = dts[i].dateTime();
1375  utc.setTimeSpec( Qt::UTC );
1376  transitions += utc.addSecs( -prevOffset );
1377  }
1378  break;
1379  }
1380  default:
1381  break;
1382  }
1383  p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
1384  }
1385  qSortUnique( transitions );
1386  }
1387 
1388  phase = KTimeZone::Phase( utcOffset, abbrevs, daylight, comment );
1389  return transitions;
1390 }
1391 //@endcond
1392 
1393 ICalTimeZone ICalTimeZoneSource::standardZone( const QString &zone, bool icalBuiltIn )
1394 {
1395  if ( !icalBuiltIn ) {
1396  // Try to fetch a system time zone in preference, on the grounds
1397  // that system time zones are more likely to be up to date than
1398  // built-in libical ones.
1399  QString tzid = zone;
1400  const QString prefix = QString::fromUtf8( icalTzidPrefix() );
1401  if ( zone.startsWith( prefix ) ) {
1402  const int i = zone.indexOf( '/', prefix.length() );
1403  if ( i > 0 ) {
1404  tzid = zone.mid( i + 1 ); // strip off the libical prefix
1405  }
1406  }
1407  const KTimeZone ktz = KSystemTimeZones::readZone( tzid );
1408  if ( ktz.isValid() ) {
1409  if ( ktz.data( true ) ) {
1410  const ICalTimeZone icaltz( ktz );
1411  //kDebug() << zone << " read from system database";
1412  return icaltz;
1413  }
1414  }
1415  }
1416  // Try to fetch a built-in libical time zone.
1417  // First try to look it up as a geographical location (e.g. Europe/London)
1418  const QByteArray zoneName = zone.toUtf8();
1419  icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName );
1420  if ( !icaltz ) {
1421  // This will find it if it includes the libical prefix
1422  icaltz = icaltimezone_get_builtin_timezone_from_tzid( zoneName );
1423  if ( !icaltz ) {
1424  return ICalTimeZone();
1425  }
1426  }
1427  return parse( icaltz );
1428 }
1429 
1430 QByteArray ICalTimeZoneSource::icalTzidPrefix()
1431 {
1432  if ( ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty() ) {
1433  icaltimezone *icaltz = icaltimezone_get_builtin_timezone( "Europe/London" );
1434  const QByteArray tzid = icaltimezone_get_tzid( icaltz );
1435  if ( tzid.right( 13 ) == "Europe/London" ) {
1436  int i = tzid.indexOf( '/', 1 );
1437  if ( i > 0 ) {
1438  ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left( i + 1 );
1439  return ICalTimeZoneSourcePrivate::icalTzidPrefix;
1440  }
1441  }
1442  kError() << "failed to get libical TZID prefix";
1443  }
1444  return ICalTimeZoneSourcePrivate::icalTzidPrefix;
1445 }
1446 
1447 void ICalTimeZoneSource::virtual_hook( int id, void *data )
1448 {
1449  Q_UNUSED( id );
1450  Q_UNUSED( data );
1451  Q_ASSERT( false );
1452 }
1453 
1454 } // namespace KCalCore
KCalCore::ICalTimeZone::~ICalTimeZone
virtual ~ICalTimeZone()
Destructor.
Definition: icaltimezones.cpp:299
KCalCore::ICalTimeZoneData::city
QString city() const
Returns the name of the city for this time zone, if any.
Definition: icaltimezones.cpp:789
KCalCore::ICalTimeZones::clear
void clear()
Clears the collection.
Definition: icaltimezones.cpp:173
KCalCore::ICalTimeZones::remove
ICalTimeZone remove(const ICalTimeZone &zone)
Removes a time zone from the collection.
Definition: icaltimezones.cpp:147
KCalCore::ICalTimeZoneBackend::virtual_hook
virtual void virtual_hook(int id, void *data)
Definition: icaltimezones.cpp:264
KCalCore::RecurrenceRule::setFrequency
void setFrequency(int freq)
Sets the recurrence frequency, in terms of the recurrence time period type.
Definition: recurrencerule.cpp:1031
KCalCore::ICalTimeZone::ICalTimeZone
ICalTimeZone()
Constructs a null time zone.
Definition: icaltimezones.cpp:272
KCalCore::RecurrenceRule::WDayPos
structure for describing the n-th weekday of the month/year.
Definition: recurrencerule.h:68
KCalCore::ICalTimeZoneSource::virtual_hook
virtual void virtual_hook(int id, void *data)
Definition: icaltimezones.cpp:1447
KCalCore::ICalTimeZone::lastModified
QDateTime lastModified() const
Returns the LAST-MODIFIED time of the VTIMEZONE, if any.
Definition: icaltimezones.cpp:314
KCalCore::ICalTimeZone::virtual_hook
virtual void virtual_hook(int id, void *data)
Definition: icaltimezones.cpp:353
KCalCore::ICalTimeZoneBackend::ICalTimeZoneBackend
ICalTimeZoneBackend()
Implements ICalTimeZone::ICalTimeZone().
Definition: icaltimezones.cpp:227
KCalCore::ICalTimeZone::city
QString city() const
Returns the name of the city for this time zone, if any.
Definition: icaltimezones.cpp:302
KCalCore::ICalTimeZoneSource
A class which reads and parses iCalendar VTIMEZONE components, and accesses libical time zone data...
Definition: icaltimezones.h:405
KCalCore::RecurrenceRule::setDuration
void setDuration(int duration)
Sets the total number of times the event is to occur, including both the first and last...
Definition: recurrencerule.cpp:994
KCalCore::ICalTimeZoneData::ICalTimeZoneData
ICalTimeZoneData()
Default constructor.
Definition: icaltimezones.cpp:391
KCalCore::ICalTimeZoneSource::ICalTimeZoneSource
ICalTimeZoneSource()
Constructs an iCalendar time zone source.
Definition: icaltimezones.cpp:856
KCalCore::ICalTimeZoneBackend::type
virtual QByteArray type() const
Returns the class name of the data represented by this instance.
Definition: icaltimezones.cpp:253
KCalCore::ICalTimeZoneData::vtimezone
QByteArray vtimezone() const
Returns the VTIMEZONE string which represents this time zone.
Definition: icaltimezones.cpp:804
icalformat_p.h
This file is part of the API for handling calendar data and defines the internal ICalFormatImpl class...
KCalCore::ICalTimeZoneData::operator=
ICalTimeZoneData & operator=(const ICalTimeZoneData &rhs)
Assignment operator.
Definition: icaltimezones.cpp:769
KCalCore::ICalTimeZoneSource::standardZone
ICalTimeZone standardZone(const QString &zone, bool icalBuiltIn=false)
Creates an ICalTimeZone instance for a standard time zone.
Definition: icaltimezones.cpp:1393
KCalCore::ICalTimeZones::~ICalTimeZones
~ICalTimeZones()
Destructor.
Definition: icaltimezones.cpp:124
KCalCore::ICalTimeZone::utc
static ICalTimeZone utc()
Returns a standard UTC time zone, with name &quot;UTC&quot;.
Definition: icaltimezones.cpp:343
KCalCore::ICalTimeZoneData::hasTransitions
virtual bool hasTransitions() const
Return whether daylight saving transitions are available for the time zone.
Definition: icaltimezones.cpp:826
KCalCore::ICalFormatImpl
This class provides the libical dependent functions for ICalFormat.
Definition: icalformat_p.h:88
KCalCore::RecurrenceRule::setEndDt
void setEndDt(const KDateTime &endDateTime)
Sets the date and time of the last recurrence.
Definition: recurrencerule.cpp:984
KCalCore::ICalTimeZoneSource::~ICalTimeZoneSource
virtual ~ICalTimeZoneSource()
Destructor.
Definition: icaltimezones.cpp:862
KCalCore::ICalTimeZones::add
bool add(const ICalTimeZone &zone)
Adds a time zone to the collection.
Definition: icaltimezones.cpp:134
KCalCore::Recurrence
This class represents a recurrence rule for a calendar incidence.
Definition: recurrence.h:87
KCalCore::ICalTimeZoneBackend::hasTransitions
virtual bool hasTransitions(const KTimeZone *caller) const
Implements ICalTimeZone::hasTransitions().
Definition: icaltimezones.cpp:258
KCalCore::ICalTimeZoneData::url
QByteArray url() const
Returns the URL of the published VTIMEZONE definition, if any.
Definition: icaltimezones.cpp:794
KCalCore::ICalTimeZoneData::lastModified
QDateTime lastModified() const
Returns the LAST-MODIFIED time of the VTIMEZONE, if any.
Definition: icaltimezones.cpp:799
KCalCore::SortableList
A QList which can be sorted.
Definition: sortablelist.h:86
KCalCore::ICalFormat
iCalendar format implementation.
Definition: icalformat.h:58
KCalCore::ICalTimeZoneData
Parsed iCalendar VTIMEZONE data.
Definition: icaltimezones.h:564
KCalCore::ICalTimeZoneSource::icalTzidPrefix
static QByteArray icalTzidPrefix()
Returns the prefix string used in the TZID field in built-in libical time zones.
Definition: icaltimezones.cpp:1430
KCalCore::ICalTimeZoneSource::parse
ICalTimeZone parse(icalcomponent *vtimezone)
Creates an ICalTimeZone instance containing the detailed information parsed from an iCalendar VTIMEZO...
Definition: icaltimezones.cpp:908
KCalCore::ICalTimeZones::count
int count()
Returns the number of zones kept in memory.
Definition: icaltimezones.cpp:178
KCalCore::ICalTimeZone::vtimezone
QByteArray vtimezone() const
Returns the VTIMEZONE string which represents this time zone.
Definition: icaltimezones.cpp:320
KCalCore::ICalTimeZoneBackend::clone
virtual KTimeZoneBackend * clone() const
Creates a copy of this instance.
Definition: icaltimezones.cpp:248
KCalCore::RecurrenceRule::endDt
KDateTime endDt(bool *result=0) const
Returns the date and time of the last recurrence.
Definition: recurrencerule.cpp:953
KCalCore::ICalTimeZoneData::clone
virtual KTimeZoneData * clone() const
Creates a new copy of this object.
Definition: icaltimezones.cpp:784
KCalCore::ICalTimeZone::update
bool update(const ICalTimeZone &other)
Update the definition of the time zone to be identical to another ICalTimeZone instance.
Definition: icaltimezones.cpp:332
KCalCore::ICalTimeZoneData::~ICalTimeZoneData
virtual ~ICalTimeZoneData()
Destructor.
Definition: icaltimezones.cpp:764
KCalCore::ICalTimeZone::url
QByteArray url() const
Returns the URL of the published VTIMEZONE definition, if any.
Definition: icaltimezones.cpp:308
KCalCore::ICalTimeZoneBackend
Backend class for KICalTimeZone class.
Definition: icaltimezones.h:299
KCalCore::ICalTimeZoneData::icalTimezone
icaltimezone * icalTimezone() const
Returns the ICal timezone structure which represents this time zone.
Definition: icaltimezones.cpp:811
KCalCore::ICalTimeZones::ICalTimeZones
ICalTimeZones()
Constructs an empty time zone collection.
Definition: icaltimezones.cpp:103
KCalCore::RecurrenceRule::duration
int duration() const
Returns -1 if the event recurs infinitely, 0 if the end date is set, otherwise the total number of re...
Definition: recurrencerule.cpp:2145
KCalCore::ICalTimeZones
The ICalTimeZones class represents a time zone database which consists of a collection of individual ...
Definition: icaltimezones.h:65
KCalCore::ICalTimeZone
The ICalTimeZone class represents an iCalendar VTIMEZONE component.
Definition: icaltimezones.h:176
KCalCore::RecurrenceRule::setStartDt
void setStartDt(const KDateTime &start)
Sets the recurrence start date/time.
Definition: recurrencerule.cpp:1022
KCalCore::RecurrenceRule::timesInInterval
DateTimeList timesInInterval(const KDateTime &start, const KDateTime &end) const
Returns a list of all the times at which the recurrence will occur between two specified times...
Definition: recurrencerule.cpp:1739
KCalCore::ICalTimeZones::zones
const ZoneMap zones() const
Returns all the time zones defined in this collection.
Definition: icaltimezones.cpp:129
KCalCore::ICalTimeZones::operator=
ICalTimeZones & operator=(const ICalTimeZones &rhs)
Assignment operator.
Definition: icaltimezones.cpp:114
KCalCore::ICalTimeZone::icalTimezone
icaltimezone * icalTimezone() const
Returns the ICal timezone structure which represents this time zone.
Definition: icaltimezones.cpp:326
KCalCore::ICalTimeZoneData::virtual_hook
virtual void virtual_hook(int id, void *data)
Definition: icaltimezones.cpp:831
icalformat.h
This file is part of the API for handling calendar data and defines the ICalFormat class...
KCalCore::ICalTimeZones::zone
ICalTimeZone zone(const QString &name) const
Returns the time zone with the given name.
Definition: icaltimezones.cpp:183
KCalCore::_MSSystemTime
Placeholhers for Microsoft and ActiveSync timezone data.
Definition: icaltimezones.h:373
KCalCore::RecurrenceRule
This class represents a recurrence rule for a calendar incidence.
Definition: recurrencerule.h:43
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Wed Sep 8 2021 11:22:27 by doxygen 1.8.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.10.5 API Reference

Skip menu "kdepimlibs-4.10.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • 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