/* This file is part of the KDE libraries Copyright (c) 2005-2008,2011 David Jarvie Copyright (c) 2005 S.R.Haque . This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // This file requires HAVE_STRUCT_TM_TM_ZONE to be defined if struct tm member tm_zone is available. // This file requires HAVE_TM_GMTOFF to be defined if struct tm member tm_gmtoff is available. #include "ktimezone.h" #include #include #include #include #include #include #include #include #include #include #include /* Return the offset to UTC in the current time zone at the specified UTC time. * The thread-safe function localtime_r() is used in preference if available. */ static int gmtoff(time_t t) { #ifdef _POSIX_THREAD_SAFE_FUNCTIONS tm tmtime; if (!localtime_r(&t, &tmtime)) return 0; #ifdef HAVE_TM_GMTOFF return tmtime.tm_gmtoff; #else int lwday = tmtime.tm_wday; int lt = 3600*tmtime.tm_hour + 60*tmtime.tm_min + tmtime.tm_sec; if (!gmtime_r(&t, &tmtime)) return 0; int uwday = tmtime.tm_wday; int ut = 3600*tmtime.tm_hour + 60*tmtime.tm_min + tmtime.tm_sec; #endif #else tm *tmtime = localtime(&t); if (!tmtime) return 0; #ifdef HAVE_TM_GMTOFF return tmtime->tm_gmtoff; #else int lwday = tmtime->tm_wday; int lt = 3600*tmtime->tm_hour + 60*tmtime->tm_min + tmtime->tm_sec; tmtime = gmtime(&t); int uwday = tmtime->tm_wday; int ut = 3600*tmtime->tm_hour + 60*tmtime->tm_min + tmtime->tm_sec; #endif #endif #ifndef HAVE_TM_GMTOFF if (lwday != uwday) { // Adjust for different day if (lwday == uwday + 1 || (lwday == 0 && uwday == 6)) lt += 24*3600; else lt -= 24*3600; } return lt - ut; #endif } /******************************************************************************/ class KTimeZonesPrivate { public: KTimeZonesPrivate() {} KTimeZones::ZoneMap zones; }; KTimeZones::KTimeZones() : d(new KTimeZonesPrivate) { } KTimeZones::~KTimeZones() { delete d; } const KTimeZones::ZoneMap KTimeZones::zones() const { return d->zones; } bool KTimeZones::add(const KTimeZone &zone) { if (!zone.isValid()) return false; if (d->zones.find(zone.name()) != d->zones.end()) return false; // name already exists d->zones.insert(zone.name(), zone); return true; } KTimeZone KTimeZones::remove(const KTimeZone &zone) { if (zone.isValid()) { for (ZoneMap::Iterator it = d->zones.begin(), end = d->zones.end(); it != end; ++it) { if (it.value() == zone) { d->zones.erase(it); return zone; } } } return KTimeZone(); } KTimeZone KTimeZones::remove(const QString &name) { if (!name.isEmpty()) { ZoneMap::Iterator it = d->zones.find(name); if (it != d->zones.end()) { KTimeZone zone = it.value(); d->zones.erase(it); return zone; } } return KTimeZone(); } void KTimeZones::clear() { d->zones.clear(); } KTimeZone KTimeZones::zone(const QString &name) const { if (!name.isEmpty()) { ZoneMap::ConstIterator it = d->zones.constFind(name); if (it != d->zones.constEnd()) return it.value(); if (name == KTimeZone::utc().name()) return KTimeZone::utc(); } return KTimeZone(); // error } /******************************************************************************/ class KTimeZonePhasePrivate : public QSharedData { public: QByteArray abbreviations; // time zone abbreviations (zero-delimited) QString comment; // optional comment int utcOffset; // seconds to add to UTC bool dst; // true if daylight savings time explicit KTimeZonePhasePrivate(int offset = 0, bool ds = false) : QSharedData(), utcOffset(offset), dst(ds) {} KTimeZonePhasePrivate(const KTimeZonePhasePrivate& rhs) : QSharedData(rhs), abbreviations(rhs.abbreviations), comment(rhs.comment), utcOffset(rhs.utcOffset), dst(rhs.dst) {} bool operator==(const KTimeZonePhasePrivate &rhs) const { return abbreviations == rhs.abbreviations && comment == rhs.comment && utcOffset == rhs.utcOffset && dst == rhs.dst; } }; KTimeZone::Phase::Phase() : d(new KTimeZonePhasePrivate) { } KTimeZone::Phase::Phase(int utcOffset, const QByteArray &abbrevs, bool dst, const QString &cmt) : d(new KTimeZonePhasePrivate(utcOffset, dst)) { d->abbreviations = abbrevs; d->comment = cmt; } KTimeZone::Phase::Phase(int utcOffset, const QList &abbrevs, bool dst, const QString &cmt) : d(new KTimeZonePhasePrivate(utcOffset, dst)) { for (int i = 0, end = abbrevs.count(); i < end; ++i) { if (i > 0) d->abbreviations += '\0'; d->abbreviations += abbrevs[i]; } d->comment = cmt; } KTimeZone::Phase::Phase(const KTimeZone::Phase &rhs) : d(rhs.d) { } KTimeZone::Phase::~Phase() { } KTimeZone::Phase &KTimeZone::Phase::operator=(const KTimeZone::Phase &rhs) { d = rhs.d; return *this; } bool KTimeZone::Phase::operator==(const KTimeZone::Phase &rhs) const { return d == rhs.d || *d == *rhs.d; } int KTimeZone::Phase::utcOffset() const { return d->utcOffset; } QList KTimeZone::Phase::abbreviations() const { return d->abbreviations.split('\0'); } bool KTimeZone::Phase::isDst() const { return d->dst; } QString KTimeZone::Phase::comment() const { return d->comment; } /******************************************************************************/ class KTimeZoneTransitionPrivate { public: QDateTime time; KTimeZone::Phase phase; }; KTimeZone::Transition::Transition() : d(new KTimeZoneTransitionPrivate) { } KTimeZone::Transition::Transition(const QDateTime &t, const KTimeZone::Phase &p) : d(new KTimeZoneTransitionPrivate) { d->time = t; d->phase = p; } KTimeZone::Transition::Transition(const KTimeZone::Transition &t) : d(new KTimeZoneTransitionPrivate) { d->time = t.d->time; d->phase = t.d->phase; } KTimeZone::Transition::~Transition() { delete d; } KTimeZone::Transition &KTimeZone::Transition::operator=(const KTimeZone::Transition &t) { d->time = t.d->time; d->phase = t.d->phase; return *this; } bool KTimeZone::Transition::operator<(const KTimeZone::Transition &rhs) const { return d->time < rhs.d->time; } QDateTime KTimeZone::Transition::time() const { return d->time; } KTimeZone::Phase KTimeZone::Transition::phase() const { return d->phase; } /******************************************************************************/ class KTimeZoneDataPrivate { public: QList phases; QList transitions; QList leapChanges; QList utcOffsets; QList abbreviations; KTimeZone::Phase prePhase; // phase to use before the first transition KTimeZoneDataPrivate() {} // Find the last transition before a specified UTC or local date/time. int transitionIndex(const QDateTime &dt) const; bool transitionIndexes(const QDateTime &start, const QDateTime &end, int &ixstart, int &ixend) const; bool isSecondOccurrence(const QDateTime &utcLocalTime, int transitionIndex) const; }; /******************************************************************************/ class KTimeZonePrivate : public QSharedData { public: KTimeZonePrivate() : source(0), data(0), refCount(1), cachedTransitionIndex(-1) {} KTimeZonePrivate(KTimeZoneSource *src, const QString& nam, const QString &country, float lat, float lon, const QString &cmnt); KTimeZonePrivate(const KTimeZonePrivate &); ~KTimeZonePrivate() { delete data; } KTimeZonePrivate &operator=(const KTimeZonePrivate &); static KTimeZoneSource *utcSource(); static void cleanup(); KTimeZoneSource *source; QString name; QString countryCode; QString comment; float latitude; float longitude; mutable KTimeZoneData *data; int refCount; // holds the number of KTimeZoneBackend instances using the KTimeZonePrivate instance as a d-pointer. int cachedTransitionIndex; QDateTime cachedTransitionStartZoneTime; QDateTime cachedTransitionEndZoneTime; bool cachedTransitionTimesValid; private: static KTimeZoneSource *mUtcSource; }; KTimeZoneSource *KTimeZonePrivate::mUtcSource = 0; KTimeZonePrivate::KTimeZonePrivate(KTimeZoneSource *src, const QString& nam, const QString &country, float lat, float lon, const QString &cmnt) : source(src), name(nam), countryCode(country.toUpper()), comment(cmnt), latitude(lat), longitude(lon), data(0), refCount(1), cachedTransitionIndex(-1) { // Detect duff values. if (latitude > 90 || latitude < -90) latitude = KTimeZone::UNKNOWN; if (longitude > 180 || longitude < -180) longitude = KTimeZone::UNKNOWN; } KTimeZonePrivate::KTimeZonePrivate(const KTimeZonePrivate &rhs) : QSharedData(rhs), source(rhs.source), name(rhs.name), countryCode(rhs.countryCode), comment(rhs.comment), latitude(rhs.latitude), longitude(rhs.longitude), refCount(1), cachedTransitionIndex(rhs.cachedTransitionIndex), cachedTransitionStartZoneTime(rhs.cachedTransitionStartZoneTime), cachedTransitionEndZoneTime(rhs.cachedTransitionEndZoneTime), cachedTransitionTimesValid(rhs.cachedTransitionTimesValid) { if (rhs.data) data = rhs.data->clone(); else data = 0; } KTimeZonePrivate &KTimeZonePrivate::operator=(const KTimeZonePrivate &rhs) { // Changing the contents of a KTimeZonePrivate instance by means of operator=() doesn't affect how // many references to it are held. source = rhs.source; name = rhs.name; countryCode = rhs.countryCode; comment = rhs.comment; latitude = rhs.latitude; longitude = rhs.longitude; cachedTransitionIndex = rhs.cachedTransitionIndex; cachedTransitionStartZoneTime = rhs.cachedTransitionStartZoneTime; cachedTransitionEndZoneTime = rhs.cachedTransitionEndZoneTime; cachedTransitionTimesValid = rhs.cachedTransitionTimesValid; delete data; if (rhs.data) data = rhs.data->clone(); else data = 0; // refCount is unchanged return *this; } KTimeZoneSource *KTimeZonePrivate::utcSource() { if (!mUtcSource) { mUtcSource = new KTimeZoneSource; qAddPostRoutine(KTimeZonePrivate::cleanup); } return mUtcSource; } void KTimeZonePrivate::cleanup() { delete mUtcSource; } /******************************************************************************/ K_GLOBAL_STATIC(KTimeZonePrivate, s_emptyTimeZonePrivate) KTimeZoneBackend::KTimeZoneBackend() : d(&*s_emptyTimeZonePrivate) { ++d->refCount; } KTimeZoneBackend::KTimeZoneBackend(const QString &name) : d(new KTimeZonePrivate(KTimeZonePrivate::utcSource(), name, QString(), KTimeZone::UNKNOWN, KTimeZone::UNKNOWN, QString())) {} KTimeZoneBackend::KTimeZoneBackend(KTimeZoneSource *source, const QString &name, const QString &countryCode, float latitude, float longitude, const QString &comment) : d(new KTimeZonePrivate(source, name, countryCode, latitude, longitude, comment)) {} KTimeZoneBackend::KTimeZoneBackend(const KTimeZoneBackend &other) : d(other.d) { ++d->refCount; } KTimeZoneBackend::~KTimeZoneBackend() { if (d && --d->refCount == 0) delete d; d = 0; } KTimeZoneBackend &KTimeZoneBackend::operator=(const KTimeZoneBackend &other) { if (d != other.d) { if (--d->refCount == 0) delete d; d = other.d; ++d->refCount; } return *this; } QByteArray KTimeZoneBackend::type() const { return "KTimeZone"; } KTimeZoneBackend *KTimeZoneBackend::clone() const { return new KTimeZoneBackend(*this); } int KTimeZoneBackend::offsetAtZoneTime(const KTimeZone* caller, const QDateTime &zoneDateTime, int *secondOffset) const { if (!zoneDateTime.isValid() || zoneDateTime.timeSpec() != Qt::LocalTime) // check for invalid time { if (secondOffset) *secondOffset = 0; return 0; } const QList transitions = caller->transitions(); int index = d->cachedTransitionIndex; if (index >= 0 && index < transitions.count()) { // There is a cached transition - check whether zoneDateTime uses it. // Caching is used because this method has been found to consume // significant CPU in real life applications. if (!d->cachedTransitionTimesValid) { const int offset = transitions[index].phase().utcOffset(); const int preoffset = (index > 0) ? transitions[index - 1].phase().utcOffset() : d->data ? d->data->previousUtcOffset() : 0; d->cachedTransitionStartZoneTime = transitions[index].time().addSecs(qMax(offset, preoffset)); if (index + 1 < transitions.count()) { const int postoffset = transitions[index + 1].phase().utcOffset(); d->cachedTransitionEndZoneTime = transitions[index + 1].time().addSecs(qMin(offset, postoffset)); } d->cachedTransitionTimesValid = true; } QDateTime dtutc = zoneDateTime; dtutc.setTimeSpec(Qt::UTC); if (dtutc >= d->cachedTransitionStartZoneTime && (index + 1 >= transitions.count() || dtutc < d->cachedTransitionEndZoneTime)) { // The time falls within the cached transition limits, so return its UTC offset const int offset = transitions[index].phase().utcOffset(); if (secondOffset) *secondOffset = offset; #ifdef ENABLE_TESTING kDebug(161) << "-> Using cache"; // enable the debug area to see this in the tests #endif return offset; } } // The time doesn't fall within the cached transition, or there isn't a cached transition #ifdef ENABLE_TESTING kDebug(161) << "-> No cache"; // enable the debug area to see this in the tests #endif bool validTime; int secondIndex = -1; index = caller->transitionIndex(zoneDateTime, (secondOffset ? &secondIndex : 0), &validTime); const KTimeZone::Transition* tr = (index >= 0) ? &transitions[index] : 0; const int offset = tr ? tr->phase().utcOffset() : validTime ? (d->data ? d->data->previousUtcOffset() : 0) : KTimeZone::InvalidOffset; if (secondOffset) *secondOffset = (secondIndex >= 0) ? transitions.at(secondIndex).phase().utcOffset() : offset; // Cache transition data for subsequent date/time values which occur after the same transition. d->cachedTransitionIndex = index; d->cachedTransitionTimesValid = false; return offset; } int KTimeZoneBackend::offsetAtUtc(const KTimeZone* caller, const QDateTime &utcDateTime) const { if (!utcDateTime.isValid() || utcDateTime.timeSpec() != Qt::UTC) // check for invalid time return 0; const QList transitions = caller->transitions(); int index = d->cachedTransitionIndex; if (index >= 0 && index < transitions.count()) { // There is a cached transition - check whether utcDateTime uses it. if (utcDateTime >= transitions[index].time() && (index + 1 >= transitions.count() || utcDateTime < transitions[index + 1].time())) { // The time falls within the cached transition, so return its UTC offset #ifdef ENABLE_TESTING kDebug(161) << "Using cache"; // enable the debug area to see this in the tests #endif return transitions[index].phase().utcOffset(); } } // The time doesn't fall within the cached transition, or there isn't a cached transition #ifdef ENABLE_TESTING kDebug(161) << "No cache"; // enable the debug area to see this in the tests #endif index = caller->transitionIndex(utcDateTime); d->cachedTransitionIndex = index; // cache transition data d->cachedTransitionTimesValid = false; const KTimeZone::Transition* tr = (index >= 0) ? &transitions.at(index) : 0; return tr ? tr->phase().utcOffset() : (d->data ? d->data->previousUtcOffset() : 0); } int KTimeZoneBackend::offset(const KTimeZone* caller, time_t t) const { return offsetAtUtc(caller, KTimeZone::fromTime_t(t)); } bool KTimeZoneBackend::isDstAtUtc(const KTimeZone* caller, const QDateTime &utcDateTime) const { if (!utcDateTime.isValid() || utcDateTime.timeSpec() != Qt::UTC) // check for invalid time return false; const KTimeZone::Transition *tr = caller->transition(utcDateTime); if (!tr) return false; return tr->phase().isDst(); } bool KTimeZoneBackend::isDst(const KTimeZone* caller, time_t t) const { return isDstAtUtc(caller, KTimeZone::fromTime_t(t)); } bool KTimeZoneBackend::hasTransitions(const KTimeZone* caller) const { Q_UNUSED(caller); return false; } /******************************************************************************/ #if SIZEOF_TIME_T == 8 const time_t KTimeZone::InvalidTime_t = 0x800000000000000LL; #else const time_t KTimeZone::InvalidTime_t = 0x80000000; #endif const int KTimeZone::InvalidOffset = 0x80000000; const float KTimeZone::UNKNOWN = 1000.0; KTimeZone::KTimeZone() : d(new KTimeZoneBackend()) {} KTimeZone::KTimeZone(const QString &name) : d(new KTimeZoneBackend(name)) {} KTimeZone::KTimeZone(const KTimeZone &tz) : d(tz.d->clone()) {} KTimeZone::~KTimeZone() { delete d; } KTimeZone::KTimeZone(KTimeZoneBackend *impl) : d(impl) { // 'impl' should be a newly constructed object, with refCount = 1 Q_ASSERT(d->d->refCount == 1 || d->d == &*s_emptyTimeZonePrivate); } KTimeZone &KTimeZone::operator=(const KTimeZone &tz) { if (d != tz.d) { delete d; d = tz.d->clone(); } return *this; } bool KTimeZone::operator==(const KTimeZone &rhs) const { return d->d == rhs.d->d; } QByteArray KTimeZone::type() const { return d->type(); } bool KTimeZone::isValid() const { return !d->d->name.isEmpty(); } QString KTimeZone::countryCode() const { return d->d->countryCode; } float KTimeZone::latitude() const { return d->d->latitude; } float KTimeZone::longitude() const { return d->d->longitude; } QString KTimeZone::comment() const { return d->d->comment; } QString KTimeZone::name() const { return d->d->name; } QList KTimeZone::abbreviations() const { if (!data(true)) return QList(); return d->d->data->abbreviations(); } QByteArray KTimeZone::abbreviation(const QDateTime &utcDateTime) const { if (utcDateTime.timeSpec() != Qt::UTC || !data(true)) return QByteArray(); return d->d->data->abbreviation(utcDateTime); } QList KTimeZone::utcOffsets() const { if (!data(true)) return QList(); return d->d->data->utcOffsets(); } QList KTimeZone::phases() const { if (!data(true)) return QList(); return d->d->data->phases(); } bool KTimeZone::hasTransitions() const { return d->hasTransitions(this); } QList KTimeZone::transitions(const QDateTime &start, const QDateTime &end) const { if (!data(true)) return QList(); return d->d->data->transitions(start, end); } const KTimeZone::Transition *KTimeZone::transition(const QDateTime &dt, const Transition **secondTransition, bool *validTime) const { if (!data(true)) { if (validTime) *validTime = false; return 0; } return d->d->data->transition(dt, secondTransition, validTime); } int KTimeZone::transitionIndex(const QDateTime &dt, int *secondIndex, bool *validTime) const { if (!data(true)) { if (validTime) *validTime = false; return -1; } return d->d->data->transitionIndex(dt, secondIndex, validTime); } QList KTimeZone::transitionTimes(const Phase &phase, const QDateTime &start, const QDateTime &end) const { if (!data(true)) return QList(); return d->d->data->transitionTimes(phase, start, end); } QList KTimeZone::leapSecondChanges() const { if (!data(true)) return QList(); return d->d->data->leapSecondChanges(); } KTimeZoneSource *KTimeZone::source() const { return d->d->source; } const KTimeZoneData *KTimeZone::data(bool create) const { if (!isValid()) return 0; if (create && !d->d->data && d->d->source->useZoneParse()) d->d->data = d->d->source->parse(*this); return d->d->data; } void KTimeZone::setData(KTimeZoneData *data, KTimeZoneSource *source) { if (!isValid()) return; delete d->d->data; d->d->data = data; if (source) d->d->source = source; } bool KTimeZone::updateBase(const KTimeZone &other) { if (d->d->name.isEmpty() || d->d->name != other.d->d->name) return false; d->d->countryCode = other.d->d->countryCode; d->d->comment = other.d->d->comment; d->d->latitude = other.d->d->latitude; d->d->longitude = other.d->d->longitude; return true; } bool KTimeZone::parse() const { if (!isValid()) return false; if (d->d->source->useZoneParse()) { delete d->d->data; d->d->data = d->d->source->parse(*this); } return d->d->data; } QDateTime KTimeZone::toUtc(const QDateTime &zoneDateTime) const { if (!zoneDateTime.isValid() || zoneDateTime.timeSpec() != Qt::LocalTime) return QDateTime(); const int secs = offsetAtZoneTime(zoneDateTime); if (secs == InvalidOffset) return QDateTime(); QDateTime dt = zoneDateTime; dt.setTimeSpec(Qt::UTC); return dt.addSecs(-secs); } QDateTime KTimeZone::toZoneTime(const QDateTime &utcDateTime, bool *secondOccurrence) const { if (secondOccurrence) *secondOccurrence = false; if (!utcDateTime.isValid() || utcDateTime.timeSpec() != Qt::UTC) // check for invalid time return QDateTime(); // Convert UTC to local time if (hasTransitions()) { if (!data(true)) { // No data - default to UTC QDateTime dt = utcDateTime; dt.setTimeSpec(Qt::LocalTime); return dt; } const KTimeZoneData *data = d->d->data; const int index = data->transitionIndex(utcDateTime); const int secs = (index >= 0) ? data->transitions().at(index).phase().utcOffset() : data->previousUtcOffset(); QDateTime dt = utcDateTime.addSecs(secs); if (secondOccurrence) { // Check whether the local time occurs twice around a daylight savings time // shift, and if so, whether it's the first or second occurrence. *secondOccurrence = data->d->isSecondOccurrence(dt, index); } dt.setTimeSpec(Qt::LocalTime); return dt; } else { const int secs = offsetAtUtc(utcDateTime); QDateTime dt = utcDateTime.addSecs(secs); dt.setTimeSpec(Qt::LocalTime); if (secondOccurrence) { // Check whether the local time occurs twice around a daylight savings time // shift, and if so, whether it's the first or second occurrence. *secondOccurrence = (secs != offsetAtZoneTime(dt)); } return dt; } } QDateTime KTimeZone::convert(const KTimeZone &newZone, const QDateTime &zoneDateTime) const { if (newZone == *this) { if (zoneDateTime.timeSpec() != Qt::LocalTime) return QDateTime(); return zoneDateTime; } return newZone.toZoneTime(toUtc(zoneDateTime)); } int KTimeZone::offsetAtZoneTime(const QDateTime &zoneDateTime, int *secondOffset) const { return d->offsetAtZoneTime(this, zoneDateTime, secondOffset); } int KTimeZone::offsetAtUtc(const QDateTime &utcDateTime) const { return d->offsetAtUtc(this, utcDateTime); } int KTimeZone::offset(time_t t) const { return d->offset(this, t); } int KTimeZone::currentOffset(Qt::TimeSpec basis) const { // Get current offset of this time zone to UTC const time_t now = time(0); const int secs = offset(now); switch (basis) { case Qt::LocalTime: // Return the current offset of this time zone to the local system time return secs - gmtoff(now); case Qt::UTC: // Return the current offset of this time zone to UTC return secs; default: break; } return 0; } bool KTimeZone::isDstAtUtc(const QDateTime &utcDateTime) const { return d->isDstAtUtc(this, utcDateTime); } bool KTimeZone::isDst(time_t t) const { return d->isDst(this, t); } KTimeZone KTimeZone::utc() { static KTimeZone utcZone(QLatin1String("UTC")); return utcZone; } QDateTime KTimeZone::fromTime_t(time_t t) { static const int secondsADay = 86400; static const QDate epochDate(1970,1,1); static const QTime epochTime(0,0,0); int days = t / secondsADay; int secs; if (t >= 0) secs = t % secondsADay; else { secs = secondsADay - (-t % secondsADay); --days; } return QDateTime(epochDate.addDays(days), epochTime.addSecs(secs), Qt::UTC); } time_t KTimeZone::toTime_t(const QDateTime &utcDateTime) { static const QDate epochDate(1970,1,1); static const QTime epochTime(0,0,0); if (utcDateTime.timeSpec() != Qt::UTC) return InvalidTime_t; const qint64 days = epochDate.daysTo(utcDateTime.date()); const qint64 secs = epochTime.secsTo(utcDateTime.time()); const qint64 t64 = days * 86400 + secs; const time_t t = static_cast(t64); if (static_cast(t) != t64) return InvalidTime_t; return t; } /******************************************************************************/ class KTimeZoneSourcePrivate { public: bool mUseZoneParse; }; KTimeZoneSource::KTimeZoneSource() : d(new KTimeZoneSourcePrivate) { d->mUseZoneParse = true; } KTimeZoneSource::KTimeZoneSource(bool useZoneParse) : d(new KTimeZoneSourcePrivate) { d->mUseZoneParse = useZoneParse; } KTimeZoneSource::~KTimeZoneSource() { delete d; } KTimeZoneData *KTimeZoneSource::parse(const KTimeZone &) const { Q_ASSERT(d->mUseZoneParse); // method should never be called if it isn't usable return new KTimeZoneData; } bool KTimeZoneSource::useZoneParse() const { return d->mUseZoneParse; } /******************************************************************************/ class KTimeZoneLeapSecondsPrivate { public: QDateTime dt; // UTC time when this change occurred QString comment; // optional comment int seconds; // number of leap seconds }; KTimeZone::LeapSeconds::LeapSeconds() : d(new KTimeZoneLeapSecondsPrivate) { } KTimeZone::LeapSeconds::LeapSeconds(const QDateTime &utc, int leap, const QString &cmt) : d(new KTimeZoneLeapSecondsPrivate) { if (utc.timeSpec() == Qt::UTC) // invalid if start time is not UTC { d->dt = utc; d->comment = cmt; d->seconds = leap; } } KTimeZone::LeapSeconds::LeapSeconds(const KTimeZone::LeapSeconds &c) : d(new KTimeZoneLeapSecondsPrivate) { d->dt = c.d->dt; d->comment = c.d->comment; d->seconds = c.d->seconds; } KTimeZone::LeapSeconds::~LeapSeconds() { delete d; } KTimeZone::LeapSeconds &KTimeZone::LeapSeconds::operator=(const KTimeZone::LeapSeconds &c) { d->dt = c.d->dt; d->comment = c.d->comment; d->seconds = c.d->seconds; return *this; } bool KTimeZone::LeapSeconds::operator<(const KTimeZone::LeapSeconds& c) const { return d->dt < c.d->dt; } QDateTime KTimeZone::LeapSeconds::dateTime() const { return d->dt; } bool KTimeZone::LeapSeconds::isValid() const { return d->dt.isValid(); } int KTimeZone::LeapSeconds::leapSeconds() const { return d->seconds; } QString KTimeZone::LeapSeconds::comment() const { return d->comment; } /******************************************************************************/ int KTimeZoneDataPrivate::transitionIndex(const QDateTime &dt) const { // Do a binary search to find the last transition before this date/time int start = -1; int end = transitions.count(); if (dt.timeSpec() == Qt::UTC) { while (end - start > 1) { int i = (start + end) / 2; if (dt < transitions[i].time()) end = i; else start = i; } } else { QDateTime dtutc = dt; dtutc.setTimeSpec(Qt::UTC); while (end - start > 1) { const int i = (start + end) / 2; if (dtutc.addSecs(-transitions[i].phase().utcOffset()) < transitions[i].time()) end = i; else start = i; } } return end ? start : -1; } // Find the indexes to the transitions at or after start, and before or at end. // start and end must be UTC. // Reply = false if none. bool KTimeZoneDataPrivate::transitionIndexes(const QDateTime &start, const QDateTime &end, int &ixstart, int &ixend) const { ixstart = 0; if (start.isValid() && start.timeSpec() == Qt::UTC) { ixstart = transitionIndex(start); if (ixstart < 0) ixstart = 0; else if (transitions[ixstart].time() < start) { if (++ixstart >= transitions.count()) return false; // there are no transitions at/after 'start' } } ixend = -1; if (end.isValid() && end.timeSpec() == Qt::UTC) { ixend = transitionIndex(end); if (ixend < 0) return false; // there are no transitions at/before 'end' } return true; } /* Check if it's a local time which occurs both before and after the specified * transition (for which it has to span a daylight saving to standard time change). * @param utcLocalTime local time set to Qt::UTC */ bool KTimeZoneDataPrivate::isSecondOccurrence(const QDateTime &utcLocalTime, int transitionIndex) const { if (transitionIndex < 0) return false; const int offset = transitions[transitionIndex].phase().utcOffset(); const int prevoffset = (transitionIndex > 0) ? transitions[transitionIndex-1].phase().utcOffset() : prePhase.utcOffset(); const int phaseDiff = prevoffset - offset; if (phaseDiff <= 0) return false; // Find how long after the start of the latest phase 'dt' is const qint64 afterStart = transitions[transitionIndex].time().msecsTo(utcLocalTime)/1000 - offset; return (afterStart < phaseDiff); } KTimeZoneData::KTimeZoneData() : d(new KTimeZoneDataPrivate) { } KTimeZoneData::KTimeZoneData(const KTimeZoneData &c) : d(new KTimeZoneDataPrivate) { d->phases = c.d->phases; d->transitions = c.d->transitions; d->leapChanges = c.d->leapChanges; d->utcOffsets = c.d->utcOffsets; d->abbreviations = c.d->abbreviations; d->prePhase = c.d->prePhase; } KTimeZoneData::~KTimeZoneData() { delete d; } KTimeZoneData &KTimeZoneData::operator=(const KTimeZoneData &c) { d->phases = c.d->phases; d->transitions = c.d->transitions; d->leapChanges = c.d->leapChanges; d->utcOffsets = c.d->utcOffsets; d->abbreviations = c.d->abbreviations; d->prePhase = c.d->prePhase; return *this; } KTimeZoneData *KTimeZoneData::clone() const { return new KTimeZoneData(*this); } QList KTimeZoneData::abbreviations() const { if (d->abbreviations.isEmpty()) { for (int i = 0, end = d->phases.count(); i < end; ++i) { const QList abbrevs = d->phases[i].abbreviations(); for (int j = 0, jend = abbrevs.count(); j < jend; ++j) if (!d->abbreviations.contains(abbrevs[j])) d->abbreviations.append(abbrevs[j]); } if (d->abbreviations.isEmpty()) d->abbreviations += "UTC"; } return d->abbreviations; } QByteArray KTimeZoneData::abbreviation(const QDateTime &utcDateTime) const { if (d->phases.isEmpty()) return "UTC"; const KTimeZone::Transition *tr = transition(utcDateTime); const QList abbrevs = tr ? tr->phase().abbreviations() : d->prePhase.abbreviations(); if (abbrevs.isEmpty()) return QByteArray(); return abbrevs[0]; } QList KTimeZoneData::utcOffsets() const { if (d->utcOffsets.isEmpty()) { for (int i = 0, end = d->phases.count(); i < end; ++i) { const int offset = d->phases[i].utcOffset(); if (!d->utcOffsets.contains(offset)) d->utcOffsets.append(offset); } if (d->utcOffsets.isEmpty()) d->utcOffsets += 0; else qSort(d->utcOffsets); } return d->utcOffsets; } QList KTimeZoneData::phases() const { return d->phases; } void KTimeZoneData::setPhases(const QList &phases, const KTimeZone::Phase& previousPhase) { d->phases = phases; d->prePhase = previousPhase; } void KTimeZoneData::setPhases(const QList &phases, int previousUtcOffset) { d->phases = phases; d->prePhase = KTimeZone::Phase(previousUtcOffset, QByteArray(), false); } bool KTimeZoneData::hasTransitions() const { return false; } QList KTimeZoneData::transitions(const QDateTime &start, const QDateTime &end) const { int ixstart, ixend; if (!d->transitionIndexes(start, end, ixstart, ixend)) return QList(); // there are no transitions within the time period if (ixend >= 0) return d->transitions.mid(ixstart, ixend - ixstart + 1); if (ixstart > 0) return d->transitions.mid(ixstart); return d->transitions; } void KTimeZoneData::setTransitions(const QList &transitions) { d->transitions = transitions; } int KTimeZoneData::previousUtcOffset() const { return d->prePhase.utcOffset(); } const KTimeZone::Transition *KTimeZoneData::transition(const QDateTime &dt, const KTimeZone::Transition **secondTransition, bool *validTime) const { int secondIndex; const int index = transitionIndex(dt, (secondTransition ? &secondIndex : 0), validTime); if (secondTransition) *secondTransition = (secondIndex >= 0) ? &d->transitions[secondIndex] : 0; return (index >= 0) ? &d->transitions[index] : 0; } int KTimeZoneData::transitionIndex(const QDateTime &dt, int *secondIndex, bool *validTime) const { if (validTime) *validTime = true; // Find the last transition before this date/time int index = d->transitionIndex(dt); if (dt.timeSpec() == Qt::UTC) { if (secondIndex) *secondIndex = index; return index; } else { /* Check whether the specified local time actually occurs. * Find the start of the next phase, and check if it falls in the gap * between the two phases. */ QDateTime dtutc = dt; dtutc.setTimeSpec(Qt::UTC); const int count = d->transitions.count(); const int next = (index >= 0) ? index + 1 : 0; if (next < count) { KTimeZone::Phase nextPhase = d->transitions[next].phase(); const int offset = (index >= 0) ? d->transitions[index].phase().utcOffset() : d->prePhase.utcOffset(); const int phaseDiff = nextPhase.utcOffset() - offset; if (phaseDiff > 0) { // Get UTC equivalent as if 'dt' was in the next phase if (dtutc.msecsTo(d->transitions[next].time())/1000 + nextPhase.utcOffset() <= phaseDiff) { // The time falls in the gap between the two phases, // so return an invalid value. if (validTime) *validTime = false; if (secondIndex) *secondIndex = -1; return -1; } } } if (index < 0) { // The specified time is before the first phase if (secondIndex) *secondIndex = -1; return -1; } /* Check if it's a local time which occurs both before and after the 'latest' * phase start time (for which it has to span a daylight saving to standard * time change). */ bool duplicate = true; if (d->isSecondOccurrence(dtutc, index)) { // 'dt' occurs twice if (secondIndex) { *secondIndex = index; duplicate = false; } // Get the transition containing the first occurrence of 'dt' if (index <= 0) return -1; // first occurrence of 'dt' is just before the first transition --index; } if (secondIndex && duplicate) *secondIndex = index; return index; } } QList KTimeZoneData::transitionTimes(const KTimeZone::Phase &phase, const QDateTime &start, const QDateTime &end) const { QList times; int ixstart, ixend; if (d->transitionIndexes(start, end, ixstart, ixend)) { if (ixend < 0) ixend = d->transitions.count() - 1; while (ixstart <= ixend) { if (d->transitions[ixstart].phase() == phase) times += d->transitions[ixstart].time(); } } return times; } QList KTimeZoneData::leapSecondChanges() const { return d->leapChanges; } void KTimeZoneData::setLeapSecondChanges(const QList &adjusts) { d->leapChanges = adjusts; } KTimeZone::LeapSeconds KTimeZoneData::leapSecondChange(const QDateTime &utc) const { if (utc.timeSpec() != Qt::UTC) kError() << "KTimeZoneData::leapSecondChange(): non-UTC time specified" << endl; else { for (int i = d->leapChanges.count(); --i >= 0; ) { if (d->leapChanges[i].dateTime() < utc) return d->leapChanges[i]; } } return KTimeZone::LeapSeconds(); }