• 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  ( 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  }
477  }
478  }
479  }
480  }
481  d->setComponent( c );
482  } else {
483  // Write the time zone data into an iCal component
484  icalcomponent *tzcomp = icalcomponent_new( ICAL_VTIMEZONE_COMPONENT );
485  icalcomponent_add_property( tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ) );
486 // icalcomponent_add_property(tzcomp, icalproperty_new_location( tz.name().toUtf8() ));
487 
488  // Compile an ordered list of transitions so that we can know the phases
489  // which occur before and after each transition.
490  QList<KTimeZone::Transition> transits = transitions();
491  if ( transits.isEmpty() ) {
492  // If there is no way to compile a complete list of transitions
493  // transitions() can return an empty list
494  // In that case try get one transition to write a valid VTIMEZONE entry.
495 #ifdef Q_OS_WINCE
496  TIME_ZONE_INFORMATION currentTimeZone;
497  GetTimeZoneInformation( &currentTimeZone );
498  if ( QString::fromWCharArray( currentTimeZone.StandardName ) != tz.name() ) {
499  kDebug() << "VTIMEZONE entry will be invalid for: " << tz.name();
500  } else {
501  const SYSTEMTIME std = currentTimeZone.StandardDate;
502  const SYSTEMTIME dlt = currentTimeZone.DaylightDate;
503 
504  // Create the according Phases
505  const KTimeZone::Phase standardPhase =
506  KTimeZone::Phase( ( currentTimeZone.Bias +
507  currentTimeZone.StandardBias ) * -60,
508  QByteArray(), false );
509  const KTimeZone::Phase daylightPhase =
510  KTimeZone::Phase( ( currentTimeZone.Bias +
511  currentTimeZone.DaylightBias ) * -60,
512  QByteArray(), true );
513  // Generate the transitions from the minimal to the maximal year that
514  // the calendar offers on WinCE
515  for ( int i = 2000; i <= 2050; i++ ) {
516  const QDateTime standardTime =
517  QDateTime( find_nth_weekday_in_month_of_year(
518  std.wDay,
519  std.wDayOfWeek ? std.wDayOfWeek : 7,
520  std.wMonth, i ),
521  QTime( std.wHour, std.wMinute,
522  std.wSecond, std.wMilliseconds ) );
523 
524  const QDateTime daylightTime =
525  QDateTime( find_nth_weekday_in_month_of_year(
526  dlt.wDay,
527  dlt.wDayOfWeek ? dlt.wDayOfWeek : 7,
528  dlt.wMonth, i ),
529  QTime( dlt.wHour, dlt.wMinute,
530  dlt.wSecond, dlt.wMilliseconds ) );
531 
532  transits << KTimeZone::Transition( standardTime, standardPhase )
533  << KTimeZone::Transition( daylightTime, daylightPhase );
534  }
535  }
536 #endif // Q_OS_WINCE
537  if ( transits.isEmpty() ) {
538  kDebug() << "No transition information available VTIMEZONE will be invalid.";
539  }
540  }
541  if ( earliest.isValid() ) {
542  // Remove all transitions earlier than those we are interested in
543  for ( int i = 0, end = transits.count(); i < end; ++i ) {
544  if ( transits.at( i ).time().date() >= earliest ) {
545  if ( i > 0 ) {
546  transits.erase( transits.begin(), transits.begin() + i );
547  }
548  break;
549  }
550  }
551  }
552  int trcount = transits.count();
553  QVector<bool> transitionsDone(trcount);
554  transitionsDone.fill( false );
555 
556  // Go through the list of transitions and create an iCal component for each
557  // distinct combination of phase after and UTC offset before the transition.
558  icaldatetimeperiodtype dtperiod;
559  dtperiod.period = icalperiodtype_null_period();
560  for ( ; ; ) {
561  int i = 0;
562  for ( ; i < trcount && transitionsDone[i]; ++i ) {
563  ;
564  }
565  if ( i >= trcount ) {
566  break;
567  }
568  // Found a phase combination which hasn't yet been processed
569  const int preOffset = ( i > 0 ) ?
570  transits.at( i - 1 ).phase().utcOffset() :
571  rhs.previousUtcOffset();
572  const KTimeZone::Phase phase = transits.at( i ).phase();
573  if ( phase.utcOffset() == preOffset ) {
574  transitionsDone[i] = true;
575  while ( ++i < trcount ) {
576  if ( transitionsDone[i] ||
577  transits.at( i ).phase() != phase ||
578  transits.at( i - 1 ).phase().utcOffset() != preOffset ) {
579  continue;
580  }
581  transitionsDone[i] = true;
582  }
583  continue;
584  }
585  icalcomponent *phaseComp =
586  icalcomponent_new( phase.isDst() ? ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT );
587  const QList<QByteArray> abbrevs = phase.abbreviations();
588  for ( int a = 0, aend = abbrevs.count(); a < aend; ++a ) {
589  icalcomponent_add_property( phaseComp,
590  icalproperty_new_tzname(
591  static_cast<const char*>( abbrevs[a]) ) );
592  }
593  if ( !phase.comment().isEmpty() ) {
594  icalcomponent_add_property( phaseComp,
595  icalproperty_new_comment( phase.comment().toUtf8() ) );
596  }
597  icalcomponent_add_property( phaseComp,
598  icalproperty_new_tzoffsetfrom( preOffset ) );
599  icalcomponent_add_property( phaseComp,
600  icalproperty_new_tzoffsetto( phase.utcOffset() ) );
601  // Create a component to hold initial RRULE if any, plus all RDATEs
602  icalcomponent *phaseComp1 = icalcomponent_new_clone( phaseComp );
603  icalcomponent_add_property( phaseComp1,
604  icalproperty_new_dtstart(
605  writeLocalICalDateTime( transits.at( i ).time(),
606  preOffset ) ) );
607  bool useNewRRULE = false;
608 
609  // Compile the list of UTC transition dates/times, and check
610  // if the list can be reduced to an RRULE instead of multiple RDATEs.
611  QTime time;
612  QDate date;
613  int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0; // avoid compiler warnings
614  int dayOfWeek = 0; // Monday = 1
615  int nthFromStart = 0; // nth (weekday) of month
616  int nthFromEnd = 0; // nth last (weekday) of month
617  int newRule;
618  int rule = 0;
619  QList<QDateTime> rdates;// dates which (probably) need to be written as RDATEs
620  QList<QDateTime> times;
621  QDateTime qdt = transits.at( i ).time(); // set 'qdt' for start of loop
622  times += qdt;
623  transitionsDone[i] = true;
624  do {
625  if ( !rule ) {
626  // Initialise data for detecting a new rule
627  rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH;
628  time = qdt.time();
629  date = qdt.date();
630  year = date.year();
631  month = date.month();
632  daysInMonth = date.daysInMonth();
633  dayOfWeek = date.dayOfWeek(); // Monday = 1
634  dayOfMonth = date.day();
635  nthFromStart = ( dayOfMonth - 1 ) / 7 + 1; // nth (weekday) of month
636  nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1; // nth last (weekday) of month
637  }
638  if ( ++i >= trcount ) {
639  newRule = 0;
640  times += QDateTime(); // append a dummy value since last value in list is ignored
641  } else {
642  if ( transitionsDone[i] ||
643  transits.at( i ).phase() != phase ||
644  transits.at( i - 1 ).phase().utcOffset() != preOffset ) {
645  continue;
646  }
647  transitionsDone[i] = true;
648  qdt = transits.at( i ).time();
649  if ( !qdt.isValid() ) {
650  continue;
651  }
652  newRule = rule;
653  times += qdt;
654  date = qdt.date();
655  if ( qdt.time() != time ||
656  date.month() != month ||
657  date.year() != ++year ) {
658  newRule = 0;
659  } else {
660  const int day = date.day();
661  if ( ( newRule & DAY_OF_MONTH ) && day != dayOfMonth ) {
662  newRule &= ~DAY_OF_MONTH;
663  }
664  if ( newRule & ( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ) ) {
665  if ( date.dayOfWeek() != dayOfWeek ) {
666  newRule &= ~( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH );
667  } else {
668  if ( ( newRule & WEEKDAY_OF_MONTH ) &&
669  ( day - 1 ) / 7 + 1 != nthFromStart ) {
670  newRule &= ~WEEKDAY_OF_MONTH;
671  }
672  if ( ( newRule & LAST_WEEKDAY_OF_MONTH ) &&
673  ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) {
674  newRule &= ~LAST_WEEKDAY_OF_MONTH;
675  }
676  }
677  }
678  }
679  }
680  if ( !newRule ) {
681  // The previous rule (if any) no longer applies.
682  // Write all the times up to but not including the current one.
683  // First check whether any of the last RDATE values fit this rule.
684  int yr = times[0].date().year();
685  while ( !rdates.isEmpty() ) {
686  qdt = rdates.last();
687  date = qdt.date();
688  if ( qdt.time() != time ||
689  date.month() != month ||
690  date.year() != --yr ) {
691  break;
692  }
693  const int day = date.day();
694  if ( rule & DAY_OF_MONTH ) {
695  if ( day != dayOfMonth ) {
696  break;
697  }
698  } else {
699  if ( date.dayOfWeek() != dayOfWeek ||
700  ( ( rule & WEEKDAY_OF_MONTH ) &&
701  ( day - 1 ) / 7 + 1 != nthFromStart ) ||
702  ( ( rule & LAST_WEEKDAY_OF_MONTH ) &&
703  ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) ) {
704  break;
705  }
706  }
707  times.prepend( qdt );
708  rdates.pop_back();
709  }
710  if ( times.count() > ( useNewRRULE ? minPhaseCount : minRuleCount ) ) {
711  // There are enough dates to combine into an RRULE
712  icalrecurrencetype r;
713  icalrecurrencetype_clear( &r );
714  r.freq = ICAL_YEARLY_RECURRENCE;
715  r.count = ( year >= 2030 ) ? 0 : times.count() - 1;
716  r.by_month[0] = month;
717  if ( rule & DAY_OF_MONTH ) {
718  r.by_month_day[0] = dayOfMonth;
719  } else if ( rule & WEEKDAY_OF_MONTH ) {
720  r.by_day[0] = ( dayOfWeek % 7 + 1 ) + ( nthFromStart * 8 ); // Sunday = 1
721  } else if ( rule & LAST_WEEKDAY_OF_MONTH ) {
722  r.by_day[0] = -( dayOfWeek % 7 + 1 ) - ( nthFromEnd * 8 ); // Sunday = 1
723  }
724  icalproperty *prop = icalproperty_new_rrule( r );
725  if ( useNewRRULE ) {
726  // This RRULE doesn't start from the phase start date, so set it into
727  // a new STANDARD/DAYLIGHT component in the VTIMEZONE.
728  icalcomponent *c = icalcomponent_new_clone( phaseComp );
729  icalcomponent_add_property(
730  c, icalproperty_new_dtstart( writeLocalICalDateTime( times[0], preOffset ) ) );
731  icalcomponent_add_property( c, prop );
732  icalcomponent_add_component( tzcomp, c );
733  } else {
734  icalcomponent_add_property( phaseComp1, prop );
735  }
736  } else {
737  // Save dates for writing as RDATEs
738  for ( int t = 0, tend = times.count() - 1; t < tend; ++t ) {
739  rdates += times[t];
740  }
741  }
742  useNewRRULE = true;
743  // All date/time values but the last have been added to the VTIMEZONE.
744  // Remove them from the list.
745  qdt = times.last(); // set 'qdt' for start of loop
746  times.clear();
747  times += qdt;
748  }
749  rule = newRule;
750  } while ( i < trcount );
751 
752  // Write remaining dates as RDATEs
753  for ( int rd = 0, rdend = rdates.count(); rd < rdend; ++rd ) {
754  dtperiod.time = writeLocalICalDateTime( rdates[rd], preOffset );
755  icalcomponent_add_property( phaseComp1, icalproperty_new_rdate( dtperiod ) );
756  }
757  icalcomponent_add_component( tzcomp, phaseComp1 );
758  icalcomponent_free( phaseComp );
759  }
760 
761  d->setComponent( tzcomp );
762  }
763 }
764 
765 ICalTimeZoneData::~ICalTimeZoneData()
766 {
767  delete d;
768 }
769 
770 ICalTimeZoneData &ICalTimeZoneData::operator=( const ICalTimeZoneData &rhs )
771 {
772  // check for self assignment
773  if ( &rhs == this ) {
774  return *this;
775  }
776 
777  KTimeZoneData::operator=( rhs );
778  d->location = rhs.d->location;
779  d->url = rhs.d->url;
780  d->lastModified = rhs.d->lastModified;
781  d->setComponent( icalcomponent_new_clone( rhs.d->component() ) );
782  return *this;
783 }
784 
785 KTimeZoneData *ICalTimeZoneData::clone() const
786 {
787  return new ICalTimeZoneData( *this );
788 }
789 
790 QString ICalTimeZoneData::city() const
791 {
792  return d->location;
793 }
794 
795 QByteArray ICalTimeZoneData::url() const
796 {
797  return d->url;
798 }
799 
800 QDateTime ICalTimeZoneData::lastModified() const
801 {
802  return d->lastModified;
803 }
804 
805 QByteArray ICalTimeZoneData::vtimezone() const
806 {
807  const QByteArray result( icalcomponent_as_ical_string( d->component() ) );
808  icalmemory_free_ring();
809  return result;
810 }
811 
812 icaltimezone *ICalTimeZoneData::icalTimezone() const
813 {
814  icaltimezone *icaltz = icaltimezone_new();
815  if ( !icaltz ) {
816  return 0;
817  }
818  icalcomponent *c = icalcomponent_new_clone( d->component() );
819  if ( !icaltimezone_set_component( icaltz, c ) ) {
820  icalcomponent_free( c );
821  icaltimezone_free( icaltz, 1 );
822  return 0;
823  }
824  return icaltz;
825 }
826 
827 bool ICalTimeZoneData::hasTransitions() const
828 {
829  return true;
830 }
831 
832 void ICalTimeZoneData::virtual_hook( int id, void *data )
833 {
834  Q_UNUSED( id );
835  Q_UNUSED( data );
836 }
837 
838 /******************************************************************************/
839 
840 //@cond PRIVATE
841 class ICalTimeZoneSourcePrivate
842 {
843  public:
844  static QList<QDateTime> parsePhase( icalcomponent *, bool daylight,
845  int &prevOffset, KTimeZone::Phase & );
846  static QByteArray icalTzidPrefix;
847 
848 #if defined(HAVE_UUID_UUID_H)
849  static void parseTransitions( const MSSystemTime &date, const KTimeZone::Phase &phase,
850  int prevOffset, QList<KTimeZone::Transition> &transitions );
851 #endif
852 };
853 
854 QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix;
855 //@endcond
856 
857 ICalTimeZoneSource::ICalTimeZoneSource()
858  : KTimeZoneSource( false ),
859  d( 0 )
860 {
861 }
862 
863 ICalTimeZoneSource::~ICalTimeZoneSource()
864 {
865 }
866 
867 bool ICalTimeZoneSource::parse( const QString &fileName, ICalTimeZones &zones )
868 {
869  QFile file( fileName );
870  if ( !file.open( QIODevice::ReadOnly ) ) {
871  return false;
872  }
873  QTextStream ts( &file );
874  ts.setCodec( "ISO 8859-1" );
875  const QByteArray text = ts.readAll().trimmed().toLatin1();
876  file.close();
877 
878  bool result = false;
879  icalcomponent *calendar = icalcomponent_new_from_string( text.data() );
880  if ( calendar ) {
881  if ( icalcomponent_isa( calendar ) == ICAL_VCALENDAR_COMPONENT ) {
882  result = parse( calendar, zones );
883  }
884  icalcomponent_free( calendar );
885  }
886  return result;
887 }
888 
889 bool ICalTimeZoneSource::parse( icalcomponent *calendar, ICalTimeZones &zones )
890 {
891  for ( icalcomponent *c = icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT );
892  c; c = icalcomponent_get_next_component( calendar, ICAL_VTIMEZONE_COMPONENT ) ) {
893  const ICalTimeZone zone = parse( c );
894  if ( !zone.isValid() ) {
895  return false;
896  }
897  ICalTimeZone oldzone = zones.zone( zone.name() );
898  if ( oldzone.isValid() ) {
899  // The zone already exists in the collection, so update the definition
900  // of the zone rather than using a newly created one.
901  oldzone.update( zone );
902  } else if ( !zones.add( zone ) ) {
903  return false;
904  }
905  }
906  return true;
907 }
908 
909 ICalTimeZone ICalTimeZoneSource::parse( icalcomponent *vtimezone )
910 {
911  QString name;
912  QString xlocation;
913  ICalTimeZoneData *data = new ICalTimeZoneData();
914 
915  // Read the fixed properties which can only appear once in VTIMEZONE
916  icalproperty *p = icalcomponent_get_first_property( vtimezone, ICAL_ANY_PROPERTY );
917  while ( p ) {
918  icalproperty_kind kind = icalproperty_isa( p );
919  switch ( kind ) {
920 
921  case ICAL_TZID_PROPERTY:
922  name = QString::fromUtf8( icalproperty_get_tzid( p ) );
923  break;
924 
925  case ICAL_TZURL_PROPERTY:
926  data->d->url = icalproperty_get_tzurl( p );
927  break;
928 
929  case ICAL_LOCATION_PROPERTY:
930  // This isn't mentioned in RFC2445, but libical reads it ...
931  data->d->location = QString::fromUtf8( icalproperty_get_location( p ) );
932  break;
933 
934  case ICAL_X_PROPERTY:
935  { // use X-LIC-LOCATION if LOCATION is missing
936  const char *xname = icalproperty_get_x_name( p );
937  if ( xname && !strcmp( xname, "X-LIC-LOCATION" ) ) {
938  xlocation = QString::fromUtf8( icalproperty_get_x( p ) );
939  }
940  break;
941  }
942  case ICAL_LASTMODIFIED_PROPERTY:
943  {
944  const icaltimetype t = icalproperty_get_lastmodified(p);
945  if ( t.is_utc ) {
946  data->d->lastModified = toQDateTime( t );
947  } else {
948  kDebug() << "LAST-MODIFIED not UTC";
949  }
950  break;
951  }
952  default:
953  break;
954  }
955  p = icalcomponent_get_next_property( vtimezone, ICAL_ANY_PROPERTY );
956  }
957 
958  if ( name.isEmpty() ) {
959  kDebug() << "TZID missing";
960  delete data;
961  return ICalTimeZone();
962  }
963  if ( data->d->location.isEmpty() && !xlocation.isEmpty() ) {
964  data->d->location = xlocation;
965  }
966  const QString prefix = QString::fromUtf8( icalTzidPrefix() );
967  if ( name.startsWith( prefix ) ) {
968  // Remove the prefix from libical built in time zone TZID
969  const int i = name.indexOf( '/', prefix.length() );
970  if ( i > 0 ) {
971  name = name.mid( i + 1 );
972  }
973  }
974  //kDebug() << "---zoneId: \"" << name << '"';
975 
976  /*
977  * Iterate through all time zone rules for this VTIMEZONE,
978  * and create a Phase object containing details for each one.
979  */
980  int prevOffset = 0;
981  QList<KTimeZone::Transition> transitions;
982  QDateTime earliest;
983  QList<KTimeZone::Phase> phases;
984  for ( icalcomponent *c = icalcomponent_get_first_component( vtimezone, ICAL_ANY_COMPONENT );
985  c; c = icalcomponent_get_next_component( vtimezone, ICAL_ANY_COMPONENT ) ) {
986  int prevoff = 0;
987  KTimeZone::Phase phase;
988  QList<QDateTime> times;
989  icalcomponent_kind kind = icalcomponent_isa( c );
990  switch ( kind ) {
991 
992  case ICAL_XSTANDARD_COMPONENT:
993  //kDebug() << "---standard phase: found";
994  times = ICalTimeZoneSourcePrivate::parsePhase( c, false, prevoff, phase );
995  break;
996 
997  case ICAL_XDAYLIGHT_COMPONENT:
998  //kDebug() << "---daylight phase: found";
999  times = ICalTimeZoneSourcePrivate::parsePhase( c, true, prevoff, phase );
1000  break;
1001 
1002  default:
1003  kDebug() << "Unknown component:" << int( kind );
1004  break;
1005  }
1006  const int tcount = times.count();
1007  if ( tcount ) {
1008  phases += phase;
1009  for ( int t = 0; t < tcount; ++t ) {
1010  transitions += KTimeZone::Transition( times[t], phase );
1011  }
1012  if ( !earliest.isValid() || times[0] < earliest ) {
1013  prevOffset = prevoff;
1014  earliest = times[0];
1015  }
1016  }
1017  }
1018  // Set phases used by the time zone, but note that VTIMEZONE doesn't contain
1019  // time zone abbreviation before first transition.
1020  data->setPhases( phases, prevOffset );
1021  // Remove any "duplicate" transitions, i.e. those where two consecutive
1022  // transitions have the same phase.
1023  qSort( transitions );
1024  for ( int t = 1, tend = transitions.count(); t < tend; ) {
1025  if ( transitions[t].phase() == transitions[t - 1].phase() ) {
1026  transitions.removeAt( t );
1027  --tend;
1028  } else {
1029  ++t;
1030  }
1031  }
1032  data->setTransitions( transitions );
1033 
1034  data->d->setComponent( icalcomponent_new_clone( vtimezone ) );
1035  //kDebug() << "VTIMEZONE" << name;
1036  return ICalTimeZone( this, name, data );
1037 }
1038 
1039 #if defined(HAVE_UUID_UUID_H)
1040 ICalTimeZone ICalTimeZoneSource::parse( MSTimeZone *tz, ICalTimeZones &zones )
1041 {
1042  const ICalTimeZone zone = parse( tz );
1043  if ( !zone.isValid() ) {
1044  return ICalTimeZone(); // error
1045  }
1046  const ICalTimeZone oldzone = zones.zone( zone );
1047  if ( oldzone.isValid() ) {
1048  // A similar zone already exists in the collection, so don't add this
1049  // new zone, return old zone instead.
1050  return oldzone;
1051  } else if ( zones.add( zone ) ) {
1052  // No similar zone, add and return new one.
1053  return zone;
1054  }
1055  return ICalTimeZone(); // error
1056 }
1057 
1058 ICalTimeZone ICalTimeZoneSource::parse( MSTimeZone *tz )
1059 {
1060  ICalTimeZoneData kdata;
1061 
1062  // General properties.
1063  uuid_t uuid;
1064  char suuid[64];
1065  uuid_generate_random( uuid );
1066  uuid_unparse( uuid, suuid );
1067  QString name = QString( suuid );
1068 
1069  // Create phases.
1070  QList<KTimeZone::Phase> phases;
1071 
1072  QList<QByteArray> standardAbbrevs;
1073  standardAbbrevs += tz->StandardName.toLatin1();
1074  const KTimeZone::Phase standardPhase(
1075  ( tz->Bias + tz->StandardBias ) * -60,
1076  standardAbbrevs, false,
1077  "Microsoft TIME_ZONE_INFORMATION" );
1078  phases += standardPhase;
1079 
1080  QList<QByteArray> daylightAbbrevs;
1081  daylightAbbrevs += tz->DaylightName.toLatin1();
1082  const KTimeZone::Phase daylightPhase(
1083  ( tz->Bias + tz->DaylightBias ) * -60,
1084  daylightAbbrevs, true,
1085  "Microsoft TIME_ZONE_INFORMATION" );
1086  phases += daylightPhase;
1087 
1088  // Set phases used by the time zone, but note that previous time zone
1089  // abbreviation is not known.
1090  const int prevOffset = tz->Bias * -60;
1091  kdata.setPhases( phases, prevOffset );
1092 
1093  // Create transitions
1094  QList<KTimeZone::Transition> transitions;
1095  ICalTimeZoneSourcePrivate::parseTransitions(
1096  tz->StandardDate, standardPhase, prevOffset, transitions );
1097  ICalTimeZoneSourcePrivate::parseTransitions(
1098  tz->DaylightDate, daylightPhase, prevOffset, transitions );
1099 
1100  qSort( transitions );
1101  kdata.setTransitions( transitions );
1102 
1103  ICalTimeZoneData *idata = new ICalTimeZoneData( kdata, KTimeZone( name ), QDate() );
1104 
1105  return ICalTimeZone( this, name, idata );
1106 }
1107 #endif // HAVE_UUID_UUID_H
1108 
1109 ICalTimeZone ICalTimeZoneSource::parse( const QString &name, const QStringList &tzList,
1110  ICalTimeZones &zones )
1111 {
1112  const ICalTimeZone zone = parse( name, tzList );
1113  if ( !zone.isValid() ) {
1114  return ICalTimeZone(); // error
1115  }
1116 
1117  ICalTimeZone oldzone = zones.zone( zone );
1118  // First off see if the zone is same as oldzone - _exactly_ same
1119  if ( oldzone.isValid() ) {
1120  return oldzone;
1121  }
1122 
1123  oldzone = zones.zone( name );
1124  if ( oldzone.isValid() ) {
1125  // The zone already exists, so update
1126  oldzone.update( zone );
1127  return zone;
1128  } else if ( zones.add( zone ) ) {
1129  // No similar zone, add and return new one.
1130  return zone;
1131  }
1132  return ICalTimeZone(); // error
1133 }
1134 
1135 ICalTimeZone ICalTimeZoneSource::parse( const QString &name, const QStringList &tzList )
1136 {
1137  ICalTimeZoneData kdata;
1138  QList<KTimeZone::Phase> phases;
1139  QList<KTimeZone::Transition> transitions;
1140  bool daylight;
1141 
1142  for ( QStringList::ConstIterator it = tzList.begin(); it != tzList.end(); ++it ) {
1143  QString value = *it;
1144  daylight = false;
1145  const QString tzName = value.mid( 0, value.indexOf( ";" ) );
1146  value = value.mid( ( value.indexOf( ";" ) + 1 ) );
1147  const QString tzOffset = value.mid( 0, value.indexOf( ";" ) );
1148  value = value.mid( ( value.indexOf( ";" ) + 1 ) );
1149  const QString tzDaylight = value.mid( 0, value.indexOf( ";" ) );
1150  const KDateTime tzDate = KDateTime::fromString( value.mid( ( value.lastIndexOf( ";" ) + 1 ) ) );
1151  if ( tzDaylight == "true" ) {
1152  daylight = true;
1153  }
1154 
1155  const KTimeZone::Phase tzPhase(
1156  tzOffset.toInt(),
1157  QByteArray( tzName.toLatin1() ), daylight, "VCAL_TZ_INFORMATION" );
1158  phases += tzPhase;
1159  transitions += KTimeZone::Transition( tzDate.dateTime(), tzPhase );
1160  }
1161 
1162  kdata.setPhases( phases, 0 );
1163  qSort( transitions );
1164  kdata.setTransitions( transitions );
1165 
1166  ICalTimeZoneData *idata = new ICalTimeZoneData( kdata, KTimeZone( name ), QDate() );
1167  return ICalTimeZone( this, name, idata );
1168 }
1169 
1170 #if defined(HAVE_UUID_UUID_H)
1171 //@cond PRIVATE
1172 void ICalTimeZoneSourcePrivate::parseTransitions( const MSSystemTime &date,
1173  const KTimeZone::Phase &phase, int prevOffset,
1174  QList<KTimeZone::Transition> &transitions )
1175 {
1176  // NOTE that we need to set start and end times and they cannot be
1177  // to far in either direction to avoid bloating the transitions list
1178  const KDateTime klocalStart( QDateTime( QDate( 2000, 1, 1 ), QTime( 0, 0, 0 ) ),
1179  KDateTime::Spec::ClockTime() );
1180  const KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() );
1181 
1182  if ( date.wYear ) {
1183  // Absolute change time.
1184  if ( date.wYear >= 1601 && date.wYear <= 30827 &&
1185  date.wMonth >= 1 && date.wMonth <= 12 &&
1186  date.wDay >= 1 && date.wDay <= 31 ) {
1187  const QDate dt( date.wYear, date.wMonth, date.wDay );
1188  const QTime tm( date.wHour, date.wMinute, date.wSecond, date.wMilliseconds );
1189  const QDateTime datetime( dt, tm );
1190  if ( datetime.isValid() ) {
1191  transitions += KTimeZone::Transition( datetime, phase );
1192  }
1193  }
1194  } else {
1195  // The normal way, for example: 'First Sunday in April at 02:00'.
1196  if ( date.wDayOfWeek >= 0 && date.wDayOfWeek <= 6 &&
1197  date.wMonth >= 1 && date.wMonth <= 12 &&
1198  date.wDay >= 1 && date.wDay <= 5 ) {
1199  RecurrenceRule r;
1200  r.setRecurrenceType( RecurrenceRule::rYearly );
1201  r.setDuration( -1 );
1202  r.setFrequency( 1 );
1203  QList<int> lst;
1204  lst.append( date.wMonth );
1205  r.setByMonths( lst );
1206  QList<RecurrenceRule::WDayPos> wdlst;
1207  RecurrenceRule::WDayPos pos;
1208  pos.setDay( date.wDayOfWeek ? date.wDayOfWeek : 7 );
1209  pos.setPos( date.wDay < 5 ? date.wDay : -1 );
1210  wdlst.append( pos );
1211  r.setByDays( wdlst );
1212  r.setStartDt( klocalStart );
1213  r.setWeekStart( 1 );
1214  const DateTimeList dtl = r.timesInInterval( klocalStart, maxTime );
1215  for ( int i = 0, end = dtl.count(); i < end; ++i ) {
1216  QDateTime utc = dtl[i].dateTime();
1217  utc.setTimeSpec( Qt::UTC );
1218  transitions += KTimeZone::Transition( utc.addSecs( -prevOffset ), phase );
1219  }
1220  }
1221  }
1222 }
1223 //@endcond
1224 #endif // HAVE_UUID_UUID_H
1225 
1226 ICalTimeZone ICalTimeZoneSource::parse( icaltimezone *tz )
1227 {
1228  /* Parse the VTIMEZONE component stored in the icaltimezone structure.
1229  * This is both easier and provides more complete information than
1230  * extracting already parsed data from icaltimezone.
1231  */
1232  return tz ? parse( icaltimezone_get_component( tz ) ) : ICalTimeZone();
1233 }
1234 
1235 //@cond PRIVATE
1236 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase( icalcomponent *c,
1237  bool daylight,
1238  int &prevOffset,
1239  KTimeZone::Phase &phase )
1240 {
1241  QList<QDateTime> transitions;
1242 
1243  // Read the observance data for this standard/daylight savings phase
1244  QList<QByteArray> abbrevs;
1245  QString comment;
1246  prevOffset = 0;
1247  int utcOffset = 0;
1248  bool recurs = false;
1249  bool found_dtstart = false;
1250  bool found_tzoffsetfrom = false;
1251  bool found_tzoffsetto = false;
1252  icaltimetype dtstart = icaltime_null_time();
1253 
1254  // Now do the ical reading.
1255  icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
1256  while ( p ) {
1257  icalproperty_kind kind = icalproperty_isa( p );
1258  switch ( kind ) {
1259 
1260  case ICAL_TZNAME_PROPERTY: // abbreviated name for this time offset
1261  {
1262  // TZNAME can appear multiple times in order to provide language
1263  // translations of the time zone offset name.
1264 
1265  // TODO: Does this cope with multiple language specifications?
1266  QByteArray tzname = icalproperty_get_tzname( p );
1267  // Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
1268  // strings, which is totally useless. So ignore those.
1269  if ( ( !daylight && tzname == "Standard Time" ) ||
1270  ( daylight && tzname == "Daylight Time" ) ) {
1271  break;
1272  }
1273  if ( !abbrevs.contains( tzname ) ) {
1274  abbrevs += tzname;
1275  }
1276  break;
1277  }
1278  case ICAL_DTSTART_PROPERTY: // local time at which phase starts
1279  dtstart = icalproperty_get_dtstart( p );
1280  found_dtstart = true;
1281  break;
1282 
1283  case ICAL_TZOFFSETFROM_PROPERTY: // UTC offset immediately before start of phase
1284  prevOffset = icalproperty_get_tzoffsetfrom( p );
1285  found_tzoffsetfrom = true;
1286  break;
1287 
1288  case ICAL_TZOFFSETTO_PROPERTY:
1289  utcOffset = icalproperty_get_tzoffsetto( p );
1290  found_tzoffsetto = true;
1291  break;
1292 
1293  case ICAL_COMMENT_PROPERTY:
1294  comment = QString::fromUtf8( icalproperty_get_comment( p ) );
1295  break;
1296 
1297  case ICAL_RDATE_PROPERTY:
1298  case ICAL_RRULE_PROPERTY:
1299  recurs = true;
1300  break;
1301 
1302  default:
1303  kDebug() << "Unknown property:" << int( kind );
1304  break;
1305  }
1306  p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
1307  }
1308 
1309  // Validate the phase data
1310  if ( !found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto ) {
1311  kDebug() << "DTSTART/TZOFFSETFROM/TZOFFSETTO missing";
1312  return transitions;
1313  }
1314 
1315  // Convert DTSTART to QDateTime, and from local time to UTC
1316  const QDateTime localStart = toQDateTime( dtstart ); // local time
1317  dtstart.second -= prevOffset;
1318  dtstart.is_utc = 1;
1319  const QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) ); // UTC
1320 
1321  transitions += utcStart;
1322  if ( recurs ) {
1323  /* RDATE or RRULE is specified. There should only be one or the other, but
1324  * it doesn't really matter - the code can cope with both.
1325  * Note that we had to get DTSTART, TZOFFSETFROM, TZOFFSETTO before reading
1326  * recurrences.
1327  */
1328  const KDateTime klocalStart( localStart, KDateTime::Spec::ClockTime() );
1329  const KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() );
1330  Recurrence recur;
1331  icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
1332  while ( p ) {
1333  icalproperty_kind kind = icalproperty_isa( p );
1334  switch ( kind ) {
1335 
1336  case ICAL_RDATE_PROPERTY:
1337  {
1338  icaltimetype t = icalproperty_get_rdate( p ).time;
1339  if ( icaltime_is_date( t ) ) {
1340  // RDATE with a DATE value inherits the (local) time from DTSTART
1341  t.hour = dtstart.hour;
1342  t.minute = dtstart.minute;
1343  t.second = dtstart.second;
1344  t.is_date = 0;
1345  t.is_utc = 0; // dtstart is in local time
1346  }
1347  // RFC2445 states that RDATE must be in local time,
1348  // but we support UTC as well to be safe.
1349  if ( !t.is_utc ) {
1350  t.second -= prevOffset; // convert to UTC
1351  t.is_utc = 1;
1352  t = icaltime_normalize( t );
1353  }
1354  transitions += toQDateTime( t );
1355  break;
1356  }
1357  case ICAL_RRULE_PROPERTY:
1358  {
1359  RecurrenceRule r;
1360  ICalFormat icf;
1361  ICalFormatImpl impl( &icf );
1362  impl.readRecurrence( icalproperty_get_rrule( p ), &r );
1363  r.setStartDt( klocalStart );
1364  // The end date time specified in an RRULE should be in UTC.
1365  // Convert to local time to avoid timesInInterval() getting things wrong.
1366  if ( r.duration() == 0 ) {
1367  KDateTime end( r.endDt() );
1368  if ( end.timeSpec() == KDateTime::Spec::UTC() ) {
1369  end.setTimeSpec( KDateTime::Spec::ClockTime() );
1370  r.setEndDt( end.addSecs( prevOffset ) );
1371  }
1372  }
1373  const DateTimeList dts = r.timesInInterval( klocalStart, maxTime );
1374  for ( int i = 0, end = dts.count(); i < end; ++i ) {
1375  QDateTime utc = dts[i].dateTime();
1376  utc.setTimeSpec( Qt::UTC );
1377  transitions += utc.addSecs( -prevOffset );
1378  }
1379  break;
1380  }
1381  default:
1382  break;
1383  }
1384  p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
1385  }
1386  qSortUnique( transitions );
1387  }
1388 
1389  phase = KTimeZone::Phase( utcOffset, abbrevs, daylight, comment );
1390  return transitions;
1391 }
1392 //@endcond
1393 
1394 ICalTimeZone ICalTimeZoneSource::standardZone( const QString &zone, bool icalBuiltIn )
1395 {
1396  if ( !icalBuiltIn ) {
1397  // Try to fetch a system time zone in preference, on the grounds
1398  // that system time zones are more likely to be up to date than
1399  // built-in libical ones.
1400  QString tzid = zone;
1401  const QString prefix = QString::fromUtf8( icalTzidPrefix() );
1402  if ( zone.startsWith( prefix ) ) {
1403  const int i = zone.indexOf( '/', prefix.length() );
1404  if ( i > 0 ) {
1405  tzid = zone.mid( i + 1 ); // strip off the libical prefix
1406  }
1407  }
1408  const KTimeZone ktz = KSystemTimeZones::readZone( tzid );
1409  if ( ktz.isValid() ) {
1410  if ( ktz.data( true ) ) {
1411  const ICalTimeZone icaltz( ktz );
1412  //kDebug() << zone << " read from system database";
1413  return icaltz;
1414  }
1415  }
1416  }
1417  // Try to fetch a built-in libical time zone.
1418  // First try to look it up as a geographical location (e.g. Europe/London)
1419  const QByteArray zoneName = zone.toUtf8();
1420  icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName );
1421  if ( !icaltz ) {
1422  // This will find it if it includes the libical prefix
1423  icaltz = icaltimezone_get_builtin_timezone_from_tzid( zoneName );
1424  if ( !icaltz ) {
1425  return ICalTimeZone();
1426  }
1427  }
1428  return parse( icaltz );
1429 }
1430 
1431 QByteArray ICalTimeZoneSource::icalTzidPrefix()
1432 {
1433  if ( ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty() ) {
1434  icaltimezone *icaltz = icaltimezone_get_builtin_timezone( "Europe/London" );
1435  const QByteArray tzid = icaltimezone_get_tzid( icaltz );
1436  if ( tzid.right( 13 ) == "Europe/London" ) {
1437  int i = tzid.indexOf( '/', 1 );
1438  if ( i > 0 ) {
1439  ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left( i + 1 );
1440  return ICalTimeZoneSourcePrivate::icalTzidPrefix;
1441  }
1442  }
1443  kError() << "failed to get libical TZID prefix";
1444  }
1445  return ICalTimeZoneSourcePrivate::icalTzidPrefix;
1446 }
1447 
1448 void ICalTimeZoneSource::virtual_hook( int id, void *data )
1449 {
1450  Q_UNUSED( id );
1451  Q_UNUSED( data );
1452  Q_ASSERT( false );
1453 }
1454 
1455 } // namespace KCalCore
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:24:51 by doxygen 1.8.3.1 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