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

KCalCore Library

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

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

Search



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