data/ICalTime.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "ICalendar.h" | ||
| 4 | |||
| 5 | #include <QDateTime> | ||
| 6 | #include <QHash> | ||
| 7 | #include <QList> | ||
| 8 | #include <QString> | ||
| 9 | #include <QTimeZone> | ||
| 10 | |||
| 11 | // iCalendar DATE / DATE-TIME values and time-zone resolution (Sprint 93 | ||
| 12 | // S93.1.2, CAL-TZ). | ||
| 13 | // | ||
| 14 | // MailJD keeps three kinds of DATE-TIME values, distinguished by the | ||
| 15 | // QDateTime it produces and by the zone id stored next to the instant in | ||
| 16 | // calendar.db (`tz_start`/`tz_end`): | ||
| 17 | // "" UTC ("…Z") QDateTime in UTC | ||
| 18 | // "floating" no zone (local wall clock) QDateTime in local time | ||
| 19 | // "<zone id>" TZID, resolved to an IANA zone QDateTime in that zone | ||
| 20 | // DATE values (all-day) are stored at UTC midnight with an empty zone id. | ||
| 21 | // | ||
| 22 | // TZID resolution order: IANA id (also as the tail of a "/mozilla.org/…" | ||
| 23 | // path), Windows zone name, the resource's VTIMEZONE (matched to the IANA | ||
| 24 | // zone with identical transitions over ±2 years, preferring X-LIC-LOCATION, | ||
| 25 | // the system zone and zones whose city appears in the TZID), else a fixed | ||
| 26 | // offset from that VTIMEZONE; an unknown TZID without definition is | ||
| 27 | // treated as floating. Every fallback logs under mailjd.calendar.tz. | ||
| 28 | namespace ICalTime { | ||
| 29 | |||
| 30 | inline const QString kFloating = QStringLiteral("floating"); | ||
| 31 | |||
| 32 | class ZoneResolver { | ||
| 33 | public: | ||
| 34 | 508 | ZoneResolver() = default; | |
| 35 | // `calendar` is the VCALENDAR whose VTIMEZONE components define custom ids. | ||
| 36 | explicit ZoneResolver(const ICal::Component &calendar); | ||
| 37 | // Invalid QTimeZone = treat the value as floating. | ||
| 38 | QTimeZone resolve(const QString &tzid) const; | ||
| 39 | |||
| 40 | private: | ||
| 41 | QHash<QString, ICal::Component> m_definitions; // unescaped TZID -> VTIMEZONE | ||
| 42 | mutable QHash<QString, QTimeZone> m_cache; | ||
| 43 | }; | ||
| 44 | |||
| 45 | struct Value { | ||
| 46 | QDateTime dateTime; // see the table above | ||
| 47 | bool dateOnly = false; // VALUE=DATE | ||
| 48 | bool floating = false; | ||
| 49 | 1504 | bool valid() const { return dateTime.isValid(); } | |
| 50 | }; | ||
| 51 | |||
| 52 | // One DATE / DATE-TIME text with the TZID of its property. | ||
| 53 | Value parseValue(const QString &text, const QString &tzid, bool forceDate, | ||
| 54 | const ZoneResolver &resolver); | ||
| 55 | // A property (DTSTART, DTEND, RECURRENCE-ID, …) incl. VALUE/TZID params. | ||
| 56 | Value parseProperty(const ICal::Property &property, const ZoneResolver &resolver); | ||
| 57 | // Multi-valued EXDATE / RDATE property; PERIOD values use their start. | ||
| 58 | QList<Value> parseList(const ICal::Property &property, const ZoneResolver &resolver); | ||
| 59 | |||
| 60 | // Zone id as stored (see table); "" for UTC and DATE values. | ||
| 61 | QString zoneId(const QDateTime &dateTime, bool dateOnly); | ||
| 62 | // Rebuilds a stored value: the instant in its zone (or local / UTC). | ||
| 63 | QDateTime fromStored(qint64 secondsSinceEpoch, const QString &zoneId); | ||
| 64 | // The QTimeZone a stored zone id denotes (invalid for "" and floating). | ||
| 65 | QTimeZone zoneForId(const QString &zoneId); | ||
| 66 | |||
| 67 | // Renders `dateTime` into `property` (value + VALUE/TZID parameters). | ||
| 68 | // `tzid` is the parameter text to use for zoned values (the original TZID | ||
| 69 | // when the zone did not change, else the IANA id). | ||
| 70 | void writeProperty(ICal::Property *property, const QDateTime &dateTime, bool dateOnly, | ||
| 71 | const QString &tzid); | ||
| 72 | QString formatValue(const QDateTime &dateTime, bool dateOnly); | ||
| 73 | |||
| 74 | // RFC 5545 basic-format DATE ("yyyyMMdd", exactly 8 characters) and | ||
| 75 | // DATE-TIME ("yyyyMMddTHHmmss", exactly 15) read as a plain wall clock: no | ||
| 76 | // zone, no DST adjustment. False for other text or an invalid date/time. | ||
| 77 | // QDateTime::fromString() is not used: it builds a local time first (moving | ||
| 78 | // wall clocks inside a DST gap of the system zone) and costs ~0.3 ms a call. | ||
| 79 | bool parseBasicDate(QStringView text, QDate *date); | ||
| 80 | bool parseBasicDateTime(QStringView text, QDate *date, QTime *time); | ||
| 81 | |||
| 82 | // Wall clock of `naive` (date+time, zone ignored) in `zone`; a time in a DST | ||
| 83 | // gap moves forward by the gap, a time in a fold takes the earlier instant | ||
| 84 | // (RFC 5545 §3.3.5). Invalid zone = local time (floating). | ||
| 85 | QDateTime fromWallClock(const QDate &date, const QTime &time, const QTimeZone &zone); | ||
| 86 | |||
| 87 | // A VTIMEZONE for `zone` around `year`: STANDARD/DAYLIGHT with yearly RRULEs | ||
| 88 | // when the zone follows a fixed yearly pattern, else explicit transitions | ||
| 89 | // for year-1 … year+5. | ||
| 90 | ICal::Component vtimezoneFor(const QTimeZone &zone, int year); | ||
| 91 | |||
| 92 | } // namespace ICalTime | ||
| 93 |