• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.11.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 {
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 }
865 
866 ICalTimeZoneSource::~ICalTimeZoneSource()
867 {
868 }
869 
870 bool ICalTimeZoneSource::parse(const QString &fileName, ICalTimeZones &zones)
871 {
872  QFile file(fileName);
873  if (!file.open(QIODevice::ReadOnly)) {
874  return false;
875  }
876  QTextStream ts(&file);
877  ts.setCodec("ISO 8859-1");
878  const QByteArray text = ts.readAll().trimmed().toLatin1();
879  file.close();
880 
881  bool result = false;
882  icalcomponent *calendar = icalcomponent_new_from_string(text.data());
883  if (calendar) {
884  if (icalcomponent_isa(calendar) == ICAL_VCALENDAR_COMPONENT) {
885  result = parse(calendar, zones);
886  }
887  icalcomponent_free(calendar);
888  }
889  return result;
890 }
891 
892 bool ICalTimeZoneSource::parse(icalcomponent *calendar, ICalTimeZones &zones)
893 {
894  for (icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VTIMEZONE_COMPONENT);
895  c; c = icalcomponent_get_next_component(calendar, ICAL_VTIMEZONE_COMPONENT)) {
896  const ICalTimeZone zone = parse(c);
897  if (!zone.isValid()) {
898  return false;
899  }
900  ICalTimeZone oldzone = zones.zone(zone.name());
901  if (oldzone.isValid()) {
902  // The zone already exists in the collection, so update the definition
903  // of the zone rather than using a newly created one.
904  oldzone.update(zone);
905  } else if (!zones.add(zone)) {
906  return false;
907  }
908  }
909  return true;
910 }
911 
912 ICalTimeZone ICalTimeZoneSource::parse(icalcomponent *vtimezone)
913 {
914  QString name;
915  QString xlocation;
916  ICalTimeZoneData *data = new ICalTimeZoneData();
917 
918  // Read the fixed properties which can only appear once in VTIMEZONE
919  icalproperty *p = icalcomponent_get_first_property(vtimezone, ICAL_ANY_PROPERTY);
920  while (p) {
921  icalproperty_kind kind = icalproperty_isa(p);
922  switch (kind) {
923 
924  case ICAL_TZID_PROPERTY:
925  name = QString::fromUtf8(icalproperty_get_tzid(p));
926  break;
927 
928  case ICAL_TZURL_PROPERTY:
929  data->d->url = icalproperty_get_tzurl(p);
930  break;
931 
932  case ICAL_LOCATION_PROPERTY:
933  // This isn't mentioned in RFC2445, but libical reads it ...
934  data->d->location = QString::fromUtf8(icalproperty_get_location(p));
935  break;
936 
937  case ICAL_X_PROPERTY:
938  { // use X-LIC-LOCATION if LOCATION is missing
939  const char *xname = icalproperty_get_x_name(p);
940  if (xname && !strcmp(xname, "X-LIC-LOCATION")) {
941  xlocation = QString::fromUtf8(icalproperty_get_x(p));
942  }
943  break;
944  }
945  case ICAL_LASTMODIFIED_PROPERTY:
946  {
947  const icaltimetype t = icalproperty_get_lastmodified(p);
948  if (t.is_utc) {
949  data->d->lastModified = toQDateTime(t);
950  } else {
951  kDebug() << "LAST-MODIFIED not UTC";
952  }
953  break;
954  }
955  default:
956  break;
957  }
958  p = icalcomponent_get_next_property(vtimezone, ICAL_ANY_PROPERTY);
959  }
960 
961  if (name.isEmpty()) {
962  kDebug() << "TZID missing";
963  delete data;
964  return ICalTimeZone();
965  }
966  if (data->d->location.isEmpty() && !xlocation.isEmpty()) {
967  data->d->location = xlocation;
968  }
969  const QString prefix = QString::fromUtf8(icalTzidPrefix());
970  if (name.startsWith(prefix)) {
971  // Remove the prefix from libical built in time zone TZID
972  const int i = name.indexOf('/', prefix.length());
973  if (i > 0) {
974  name = name.mid(i + 1);
975  }
976  }
977  //kDebug() << "---zoneId: \"" << name << '"';
978 
979  /*
980  * Iterate through all time zone rules for this VTIMEZONE,
981  * and create a Phase object containing details for each one.
982  */
983  int prevOffset = 0;
984  QList<KTimeZone::Transition> transitions;
985  QDateTime earliest;
986  QList<KTimeZone::Phase> phases;
987  for (icalcomponent *c = icalcomponent_get_first_component(vtimezone, ICAL_ANY_COMPONENT);
988  c; c = icalcomponent_get_next_component(vtimezone, ICAL_ANY_COMPONENT)) {
989  int prevoff = 0;
990  KTimeZone::Phase phase;
991  QList<QDateTime> times;
992  icalcomponent_kind kind = icalcomponent_isa(c);
993  switch (kind) {
994 
995  case ICAL_XSTANDARD_COMPONENT:
996  //kDebug() << "---standard phase: found";
997  times = ICalTimeZoneSourcePrivate::parsePhase(c, false, prevoff, phase);
998  break;
999 
1000  case ICAL_XDAYLIGHT_COMPONENT:
1001  //kDebug() << "---daylight phase: found";
1002  times = ICalTimeZoneSourcePrivate::parsePhase(c, true, prevoff, phase);
1003  break;
1004 
1005  default:
1006  kDebug() << "Unknown component:" << int(kind);
1007  break;
1008  }
1009  const int tcount = times.count();
1010  if (tcount) {
1011  phases += phase;
1012  for (int t = 0; t < tcount; ++t) {
1013  transitions += KTimeZone::Transition(times[t], phase);
1014  }
1015  if (!earliest.isValid() || times[0] < earliest) {
1016  prevOffset = prevoff;
1017  earliest = times[0];
1018  }
1019  }
1020  }
1021  // Set phases used by the time zone, but note that VTIMEZONE doesn't contain
1022  // time zone abbreviation before first transition.
1023  data->setPhases(phases, prevOffset);
1024  // Remove any "duplicate" transitions, i.e. those where two consecutive
1025  // transitions have the same phase.
1026  qSort(transitions);
1027  for (int t = 1, tend = transitions.count(); t < tend;) {
1028  if (transitions[t].phase() == transitions[t - 1].phase()) {
1029  transitions.removeAt(t);
1030  --tend;
1031  } else {
1032  ++t;
1033  }
1034  }
1035  data->setTransitions(transitions);
1036 
1037  data->d->setComponent(icalcomponent_new_clone(vtimezone));
1038  //kDebug() << "VTIMEZONE" << name;
1039  return ICalTimeZone(this, name, data);
1040 }
1041 
1042 #if defined(HAVE_UUID_UUID_H)
1043 ICalTimeZone ICalTimeZoneSource::parse(MSTimeZone *tz, ICalTimeZones &zones)
1044 {
1045  const ICalTimeZone zone = parse(tz);
1046  if (!zone.isValid()) {
1047  return ICalTimeZone(); // error
1048  }
1049  const ICalTimeZone oldzone = zones.zone(zone);
1050  if (oldzone.isValid()) {
1051  // A similar zone already exists in the collection, so don't add this
1052  // new zone, return old zone instead.
1053  return oldzone;
1054  } else if (zones.add(zone)) {
1055  // No similar zone, add and return new one.
1056  return zone;
1057  }
1058  return ICalTimeZone(); // error
1059 }
1060 
1061 ICalTimeZone ICalTimeZoneSource::parse(MSTimeZone *tz)
1062 {
1063  ICalTimeZoneData kdata;
1064 
1065  // General properties.
1066  uuid_t uuid;
1067  char suuid[64];
1068  uuid_generate_random(uuid);
1069  uuid_unparse(uuid, suuid);
1070  QString name = QString(suuid);
1071 
1072  // Create phases.
1073  QList<KTimeZone::Phase> phases;
1074 
1075  QList<QByteArray> standardAbbrevs;
1076  standardAbbrevs += tz->StandardName.toLatin1();
1077  const KTimeZone::Phase standardPhase(
1078  (tz->Bias + tz->StandardBias) * -60,
1079  standardAbbrevs, false,
1080  "Microsoft TIME_ZONE_INFORMATION");
1081  phases += standardPhase;
1082 
1083  QList<QByteArray> daylightAbbrevs;
1084  daylightAbbrevs += tz->DaylightName.toLatin1();
1085  const KTimeZone::Phase daylightPhase(
1086  (tz->Bias + tz->DaylightBias) * -60,
1087  daylightAbbrevs, true,
1088  "Microsoft TIME_ZONE_INFORMATION");
1089  phases += daylightPhase;
1090 
1091  // Set phases used by the time zone, but note that previous time zone
1092  // abbreviation is not known.
1093  const int prevOffset = tz->Bias * -60;
1094  kdata.setPhases(phases, prevOffset);
1095 
1096  // Create transitions
1097  QList<KTimeZone::Transition> transitions;
1098  ICalTimeZoneSourcePrivate::parseTransitions(
1099  tz->StandardDate, standardPhase, prevOffset, transitions);
1100  ICalTimeZoneSourcePrivate::parseTransitions(
1101  tz->DaylightDate, daylightPhase, prevOffset, transitions);
1102 
1103  qSort(transitions);
1104  kdata.setTransitions(transitions);
1105 
1106  ICalTimeZoneData *idata = new ICalTimeZoneData(kdata, KTimeZone(name), QDate());
1107 
1108  return ICalTimeZone(this, name, idata);
1109 }
1110 #endif // HAVE_UUID_UUID_H
1111 
1112 ICalTimeZone ICalTimeZoneSource::parse(const QString &name, const QStringList &tzList,
1113  ICalTimeZones &zones)
1114 {
1115  const ICalTimeZone zone = parse(name, tzList);
1116  if (!zone.isValid()) {
1117  return ICalTimeZone(); // error
1118  }
1119 
1120  ICalTimeZone oldzone = zones.zone(zone);
1121  // First off see if the zone is same as oldzone - _exactly_ same
1122  if (oldzone.isValid()) {
1123  return oldzone;
1124  }
1125 
1126  oldzone = zones.zone(name);
1127  if (oldzone.isValid()) {
1128  // The zone already exists, so update
1129  oldzone.update(zone);
1130  return zone;
1131  } else if (zones.add(zone)) {
1132  // No similar zone, add and return new one.
1133  return zone;
1134  }
1135  return ICalTimeZone(); // error
1136 }
1137 
1138 ICalTimeZone ICalTimeZoneSource::parse(const QString &name, const QStringList &tzList)
1139 {
1140  ICalTimeZoneData kdata;
1141  QList<KTimeZone::Phase> phases;
1142  QList<KTimeZone::Transition> transitions;
1143  bool daylight;
1144 
1145  for (QStringList::ConstIterator it = tzList.begin(); it != tzList.end(); ++it) {
1146  QString value = *it;
1147  daylight = false;
1148  const QString tzName = value.mid(0, value.indexOf(";"));
1149  value = value.mid((value.indexOf(";") + 1));
1150  const QString tzOffset = value.mid(0, value.indexOf(";"));
1151  value = value.mid((value.indexOf(";") + 1));
1152  const QString tzDaylight = value.mid(0, value.indexOf(";"));
1153  const KDateTime tzDate = KDateTime::fromString(value.mid((value.lastIndexOf(";") + 1)));
1154  if (tzDaylight == "true") {
1155  daylight = true;
1156  }
1157 
1158  const KTimeZone::Phase tzPhase(
1159  tzOffset.toInt(),
1160  QByteArray(tzName.toLatin1()), daylight, "VCAL_TZ_INFORMATION");
1161  phases += tzPhase;
1162  transitions += KTimeZone::Transition(tzDate.dateTime(), tzPhase);
1163  }
1164 
1165  kdata.setPhases(phases, 0);
1166  qSort(transitions);
1167  kdata.setTransitions(transitions);
1168 
1169  ICalTimeZoneData *idata = new ICalTimeZoneData(kdata, KTimeZone(name), QDate());
1170  return ICalTimeZone(this, name, idata);
1171 }
1172 
1173 #if defined(HAVE_UUID_UUID_H)
1174 //@cond PRIVATE
1175 void ICalTimeZoneSourcePrivate::parseTransitions(const MSSystemTime &date,
1176  const KTimeZone::Phase &phase, int prevOffset,
1177  QList<KTimeZone::Transition> &transitions)
1178 {
1179  // NOTE that we need to set start and end times and they cannot be
1180  // to far in either direction to avoid bloating the transitions list
1181  const KDateTime klocalStart(QDateTime(QDate(2000, 1, 1), QTime(0, 0, 0)),
1182  KDateTime::Spec::ClockTime());
1183  const KDateTime maxTime(MAX_DATE(), KDateTime::Spec::ClockTime());
1184 
1185  if (date.wYear) {
1186  // Absolute change time.
1187  if (date.wYear >= 1601 && date.wYear <= 30827 &&
1188  date.wMonth >= 1 && date.wMonth <= 12 &&
1189  date.wDay >= 1 && date.wDay <= 31) {
1190  const QDate dt(date.wYear, date.wMonth, date.wDay);
1191  const QTime tm(date.wHour, date.wMinute, date.wSecond, date.wMilliseconds);
1192  const QDateTime datetime(dt, tm);
1193  if (datetime.isValid()) {
1194  transitions += KTimeZone::Transition(datetime, phase);
1195  }
1196  }
1197  } else {
1198  // The normal way, for example: 'First Sunday in April at 02:00'.
1199  if (date.wDayOfWeek >= 0 && date.wDayOfWeek <= 6 &&
1200  date.wMonth >= 1 && date.wMonth <= 12 &&
1201  date.wDay >= 1 && date.wDay <= 5) {
1202  RecurrenceRule r;
1203  r.setRecurrenceType(RecurrenceRule::rYearly);
1204  r.setDuration(-1);
1205  r.setFrequency(1);
1206  QList<int> lst;
1207  lst.append(date.wMonth);
1208  r.setByMonths(lst);
1209  QList<RecurrenceRule::WDayPos> wdlst;
1210  RecurrenceRule::WDayPos pos;
1211  pos.setDay(date.wDayOfWeek ? date.wDayOfWeek : 7);
1212  pos.setPos(date.wDay < 5 ? date.wDay : -1);
1213  wdlst.append(pos);
1214  r.setByDays(wdlst);
1215  r.setStartDt(klocalStart);
1216  r.setWeekStart(1);
1217  const DateTimeList dtl = r.timesInInterval(klocalStart, maxTime);
1218  for (int i = 0, end = dtl.count(); i < end; ++i) {
1219  QDateTime utc = dtl[i].dateTime();
1220  utc.setTimeSpec(Qt::UTC);
1221  transitions += KTimeZone::Transition(utc.addSecs(-prevOffset), phase);
1222  }
1223  }
1224  }
1225 }
1226 //@endcond
1227 #endif // HAVE_UUID_UUID_H
1228 
1229 ICalTimeZone ICalTimeZoneSource::parse(icaltimezone *tz)
1230 {
1231  /* Parse the VTIMEZONE component stored in the icaltimezone structure.
1232  * This is both easier and provides more complete information than
1233  * extracting already parsed data from icaltimezone.
1234  */
1235  return tz ? parse(icaltimezone_get_component(tz)) : ICalTimeZone();
1236 }
1237 
1238 //@cond PRIVATE
1239 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase(icalcomponent *c,
1240  bool daylight,
1241  int &prevOffset,
1242  KTimeZone::Phase &phase)
1243 {
1244  QList<QDateTime> transitions;
1245 
1246  // Read the observance data for this standard/daylight savings phase
1247  QList<QByteArray> abbrevs;
1248  QString comment;
1249  prevOffset = 0;
1250  int utcOffset = 0;
1251  bool recurs = false;
1252  bool found_dtstart = false;
1253  bool found_tzoffsetfrom = false;
1254  bool found_tzoffsetto = false;
1255  icaltimetype dtstart = icaltime_null_time();
1256 
1257  // Now do the ical reading.
1258  icalproperty *p = icalcomponent_get_first_property(c, ICAL_ANY_PROPERTY);
1259  while (p) {
1260  icalproperty_kind kind = icalproperty_isa(p);
1261  switch (kind) {
1262 
1263  case ICAL_TZNAME_PROPERTY: // abbreviated name for this time offset
1264  {
1265  // TZNAME can appear multiple times in order to provide language
1266  // translations of the time zone offset name.
1267 
1268  // TODO: Does this cope with multiple language specifications?
1269  QByteArray tzname = icalproperty_get_tzname(p);
1270  // Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME
1271  // strings, which is totally useless. So ignore those.
1272  if ((!daylight && tzname == "Standard Time") ||
1273  (daylight && tzname == "Daylight Time")) {
1274  break;
1275  }
1276  if (!abbrevs.contains(tzname)) {
1277  abbrevs += tzname;
1278  }
1279  break;
1280  }
1281  case ICAL_DTSTART_PROPERTY: // local time at which phase starts
1282  dtstart = icalproperty_get_dtstart(p);
1283  found_dtstart = true;
1284  break;
1285 
1286  case ICAL_TZOFFSETFROM_PROPERTY: // UTC offset immediately before start of phase
1287  prevOffset = icalproperty_get_tzoffsetfrom(p);
1288  found_tzoffsetfrom = true;
1289  break;
1290 
1291  case ICAL_TZOFFSETTO_PROPERTY:
1292  utcOffset = icalproperty_get_tzoffsetto(p);
1293  found_tzoffsetto = true;
1294  break;
1295 
1296  case ICAL_COMMENT_PROPERTY:
1297  comment = QString::fromUtf8(icalproperty_get_comment(p));
1298  break;
1299 
1300  case ICAL_RDATE_PROPERTY:
1301  case ICAL_RRULE_PROPERTY:
1302  recurs = true;
1303  break;
1304 
1305  default:
1306  kDebug() << "Unknown property:" << int(kind);
1307  break;
1308  }
1309  p = icalcomponent_get_next_property(c, ICAL_ANY_PROPERTY);
1310  }
1311 
1312  // Validate the phase data
1313  if (!found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto) {
1314  kDebug() << "DTSTART/TZOFFSETFROM/TZOFFSETTO missing";
1315  return transitions;
1316  }
1317 
1318  // Convert DTSTART to QDateTime, and from local time to UTC
1319  const QDateTime localStart = toQDateTime(dtstart); // local time
1320  dtstart.second -= prevOffset;
1321  dtstart.is_utc = 1;
1322  const QDateTime utcStart = toQDateTime(icaltime_normalize(dtstart)); // UTC
1323 
1324  transitions += utcStart;
1325  if (recurs) {
1326  /* RDATE or RRULE is specified. There should only be one or the other, but
1327  * it doesn't really matter - the code can cope with both.
1328  * Note that we had to get DTSTART, TZOFFSETFROM, TZOFFSETTO before reading
1329  * recurrences.
1330  */
1331  const KDateTime klocalStart(localStart, KDateTime::Spec::ClockTime());
1332  const KDateTime maxTime(MAX_DATE(), KDateTime::Spec::ClockTime());
1333  Recurrence recur;
1334  icalproperty *p = icalcomponent_get_first_property(c, ICAL_ANY_PROPERTY);
1335  while (p) {
1336  icalproperty_kind kind = icalproperty_isa(p);
1337  switch (kind) {
1338 
1339  case ICAL_RDATE_PROPERTY:
1340  {
1341  icaltimetype t = icalproperty_get_rdate(p).time;
1342  if (icaltime_is_date(t)) {
1343  // RDATE with a DATE value inherits the (local) time from DTSTART
1344  t.hour = dtstart.hour;
1345  t.minute = dtstart.minute;
1346  t.second = dtstart.second;
1347  t.is_date = 0;
1348  t.is_utc = 0; // dtstart is in local time
1349  }
1350  // RFC2445 states that RDATE must be in local time,
1351  // but we support UTC as well to be safe.
1352  if (!t.is_utc) {
1353  t.second -= prevOffset; // convert to UTC
1354  t.is_utc = 1;
1355  t = icaltime_normalize(t);
1356  }
1357  transitions += toQDateTime(t);
1358  break;
1359  }
1360  case ICAL_RRULE_PROPERTY:
1361  {
1362  RecurrenceRule r;
1363  ICalFormat icf;
1364  ICalFormatImpl impl(&icf);
1365  impl.readRecurrence(icalproperty_get_rrule(p), &r);
1366  r.setStartDt(klocalStart);
1367  // The end date time specified in an RRULE should be in UTC.
1368  // Convert to local time to avoid timesInInterval() getting things wrong.
1369  if (r.duration() == 0) {
1370  KDateTime end(r.endDt());
1371  if (end.timeSpec() == KDateTime::Spec::UTC()) {
1372  end.setTimeSpec(KDateTime::Spec::ClockTime());
1373  r.setEndDt(end.addSecs(prevOffset));
1374  }
1375  }
1376  const DateTimeList dts = r.timesInInterval(klocalStart, maxTime);
1377  for (int i = 0, end = dts.count(); i < end; ++i) {
1378  QDateTime utc = dts[i].dateTime();
1379  utc.setTimeSpec(Qt::UTC);
1380  transitions += utc.addSecs(-prevOffset);
1381  }
1382  break;
1383  }
1384  default:
1385  break;
1386  }
1387  p = icalcomponent_get_next_property(c, ICAL_ANY_PROPERTY);
1388  }
1389  qSortUnique(transitions);
1390  }
1391 
1392  phase = KTimeZone::Phase(utcOffset, abbrevs, daylight, comment);
1393  return transitions;
1394 }
1395 //@endcond
1396 
1397 ICalTimeZone ICalTimeZoneSource::standardZone(const QString &zone, bool icalBuiltIn)
1398 {
1399  if (!icalBuiltIn) {
1400  // Try to fetch a system time zone in preference, on the grounds
1401  // that system time zones are more likely to be up to date than
1402  // built-in libical ones.
1403  QString tzid = zone;
1404  const QString prefix = QString::fromUtf8(icalTzidPrefix());
1405  if (zone.startsWith(prefix)) {
1406  const int i = zone.indexOf('/', prefix.length());
1407  if (i > 0) {
1408  tzid = zone.mid(i + 1); // strip off the libical prefix
1409  }
1410  }
1411  const KTimeZone ktz = KSystemTimeZones::readZone(tzid);
1412  if (ktz.isValid()) {
1413  if (ktz.data(true)) {
1414  const ICalTimeZone icaltz(ktz);
1415  //kDebug() << zone << " read from system database";
1416  return icaltz;
1417  }
1418  }
1419  }
1420  // Try to fetch a built-in libical time zone.
1421  // First try to look it up as a geographical location (e.g. Europe/London)
1422  const QByteArray zoneName = zone.toUtf8();
1423  icaltimezone *icaltz = icaltimezone_get_builtin_timezone(zoneName);
1424  if (!icaltz) {
1425  // This will find it if it includes the libical prefix
1426  icaltz = icaltimezone_get_builtin_timezone_from_tzid(zoneName);
1427  if (!icaltz) {
1428  return ICalTimeZone();
1429  }
1430  }
1431  return parse(icaltz);
1432 }
1433 
1434 QByteArray ICalTimeZoneSource::icalTzidPrefix()
1435 {
1436  if (ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty()) {
1437  icaltimezone *icaltz = icaltimezone_get_builtin_timezone("Europe/London");
1438  const QByteArray tzid = icaltimezone_get_tzid(icaltz);
1439  if (tzid.right(13) == "Europe/London") {
1440  int i = tzid.indexOf('/', 1);
1441  if (i > 0) {
1442  ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left(i + 1);
1443  return ICalTimeZoneSourcePrivate::icalTzidPrefix;
1444  }
1445  }
1446  kError() << "failed to get libical TZID prefix";
1447  }
1448  return ICalTimeZoneSourcePrivate::icalTzidPrefix;
1449 }
1450 
1451 void ICalTimeZoneSource::virtual_hook(int id, void *data)
1452 {
1453  Q_UNUSED(id);
1454  Q_UNUSED(data);
1455  Q_ASSERT(false);
1456 }
1457 
1458 } // namespace KCalCore
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Thu Jan 9 2014 17:48:40 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.11.5 API Reference

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