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

Skip menu "kdepimlibs-4.11.3 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