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