kdelibs/kdecore/date/ktimezone.cpp
Ivailo Monev 9b41127ecb generic: prepare for Katie changes
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-11-13 00:40:18 +02:00

1727 lines
49 KiB
C++

/*
This file is part of the KDE libraries
Copyright (c) 2005-2008,2011 David Jarvie <djarvie@kde.org>
Copyright (c) 2005 S.R.Haque <srhaque@iee.org>.
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 <config.h>
#include <config-date.h>
#include <sys/time.h>
#include <time.h>
#include <climits>
#include <cstdlib>
#include <QtCore/QSet>
#include <QtCore/QSharedData>
#include <QtCore/QCoreApplication>
#include <QtCore/QFile>
#include <kdebug.h>
#include <kglobal.h>
#include <ksystemtimezone.h>
extern QString zoneinfoDir(); // in ksystemtimezone.cpp
/* 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
}
// Use this replacement for QDateTime::setTime_t(uint) since our time
// values are signed.
static QDateTime fromTime_t(qint32 seconds)
{
static const QDate epochDate(1970,1,1);
static const QTime epochTime(0,0,0);
int days = seconds / 86400;
seconds -= days * 86400;
if (seconds < 0)
{
--days;
seconds += 86400;
}
return QDateTime(epochDate.addDays(days), epochTime.addSecs(seconds), Qt::UTC);
}
/******************************************************************************/
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<QByteArray> &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<QByteArray> 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<KTimeZone::Phase> phases;
QList<KTimeZone::Transition> transitions;
QList<KTimeZone::LeapSeconds> leapChanges;
QList<int> utcOffsets;
QList<QByteArray> 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(zoneinfoDir());
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<KTimeZone::Transition> 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<KTimeZone::Transition> 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 true;
}
/******************************************************************************/
#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(KTimeZoneSource *source, const QString &name,
const QString &countryCode, float latitude, float longitude,
const QString &comment)
: d(new KTimeZoneBackend(source, name, countryCode, latitude, longitude, comment))
{}
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<QByteArray> KTimeZone::abbreviations() const
{
if (!data(true))
return QList<QByteArray>();
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<int> KTimeZone::utcOffsets() const
{
if (!data(true))
return QList<int>();
return d->d->data->utcOffsets();
}
QList<KTimeZone::Phase> KTimeZone::phases() const
{
if (!data(true))
return QList<KTimeZone::Phase>();
return d->d->data->phases();
}
bool KTimeZone::hasTransitions() const
{
return d->hasTransitions(this);
}
QList<KTimeZone::Transition> KTimeZone::transitions(const QDateTime &start, const QDateTime &end) const
{
if (!data(true))
return QList<KTimeZone::Transition>();
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<QDateTime> KTimeZone::transitionTimes(const Phase &phase, const QDateTime &start, const QDateTime &end) const
{
if (!data(true))
return QList<QDateTime>();
return d->d->data->transitionTimes(phase, start, end);
}
QList<KTimeZone::LeapSeconds> KTimeZone::leapSecondChanges() const
{
if (!data(true))
return QList<KTimeZone::LeapSeconds>();
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(KTimeZonePrivate::utcSource(), 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<time_t>(t64);
if (static_cast<qint64>(t) != t64)
return InvalidTime_t;
return t;
}
/******************************************************************************/
class KTimeZoneSourcePrivate
{
public:
KTimeZoneSourcePrivate()
: mUseZoneParse(true) {}
KTimeZoneSourcePrivate(const QString &loc)
: mUseZoneParse(true), mLocation(loc) {}
bool mUseZoneParse;
QString mLocation;
};
KTimeZoneSource::KTimeZoneSource()
: d(new KTimeZoneSourcePrivate())
{
}
KTimeZoneSource::KTimeZoneSource(const QString &location)
: d(new KTimeZoneSourcePrivate(location))
{
if (location.length() > 1 && location.endsWith(QLatin1Char('/')))
d->mLocation.chop(1);
}
KTimeZoneSource::KTimeZoneSource(bool useZoneParse)
: d(new KTimeZoneSourcePrivate())
{
d->mUseZoneParse = useZoneParse;
}
KTimeZoneSource::~KTimeZoneSource()
{
delete d;
}
// for reference:
// https://man7.org/linux/man-pages/man5/tzfile.5.html
KTimeZoneData *KTimeZoneSource::parse(const KTimeZone &zone) const
{
Q_ASSERT(d->mUseZoneParse); // method should never be called if it isn't usable
quint32 abbrCharCount; // the number of characters of time zone abbreviation strings
quint32 ttisgmtcnt;
quint8 is;
quint8 T_, Z_, i_, f_; // tzfile identifier prefix
QString path = zone.name();
if (!path.startsWith(QLatin1Char('/')))
{
if (d->mLocation == QLatin1String("/"))
path.prepend(d->mLocation);
else
path = d->mLocation + QLatin1Char('/') + path;
}
QFile f(path);
if (!f.open(QIODevice::ReadOnly))
{
kError() << "Cannot open " << f.fileName();
return 0;
}
QDataStream str(&f);
str.setByteOrder(QDataStream::BigEndian);
// Read the file type identifier
str >> T_ >> Z_ >> i_ >> f_;
if (T_ != 'T' || Z_ != 'Z' || i_ != 'i' || f_ != 'f')
{
kError() << "Not a TZFILE: " << f.fileName();
return 0;
}
// Discard 16 bytes reserved for future use
unsigned i;
for (i = 0; i < 4; ++i)
str >> ttisgmtcnt;
KTimeZoneData* data = new KTimeZoneData();
// Read the sizes of arrays held in the file
quint32 nTransitionTimes;
quint32 nLocalTimeTypes;
quint32 nLeapSecondAdjusts;
quint32 nIsStandard;
quint32 nIsUtc;
str >> nIsUtc
>> nIsStandard
>> nLeapSecondAdjusts
>> nTransitionTimes
>> nLocalTimeTypes
>> abbrCharCount;
// kDebug() << "header: " << nIsUtc << ", " << nIsStandard << ", " << nLeapSecondAdjusts << ", " <<
// nTransitionTimes << ", " << nLocalTimeTypes << ", " << abbrCharCount;
// Read the transition times, at which the rules for computing local time change
struct TransitionTime
{
qint32 time; // time (as returned by time(2)) at which the rules for computing local time change
quint8 localTimeIndex; // index into the LocalTimeType array
};
// kDebug()<<"Reading zone "<<zone.name();
TransitionTime *transitionTimes = new TransitionTime[nTransitionTimes];
for (i = 0; i < nTransitionTimes; ++i)
{
str >> transitionTimes[i].time;
}
for (i = 0; i < nTransitionTimes; ++i)
{
str >> transitionTimes[i].localTimeIndex;
// kDebug() << "Transition time "<<i<<": "<<transitionTimes[i].time<<" lt index="<<(int)transitionTimes[i].localTimeIndex;
}
// Read the local time types
struct LocalTimeType
{
qint32 gmtoff; // number of seconds to be added to UTC
bool isdst; // whether tm_isdst should be set by localtime(3)
quint8 abbrIndex; // index into the list of time zone abbreviations
bool isutc; // transition times are in UTC. If UTC, isstd is ignored.
bool isstd; // if true, transition times are in standard time;
// if false, transition times are in wall clock time,
// i.e. standard time or daylight savings time
// whichever is current before the transition
};
LocalTimeType *localTimeTypes = new LocalTimeType[nLocalTimeTypes];
LocalTimeType *ltt = localTimeTypes;
for (i = 0; i < nLocalTimeTypes; ++ltt, ++i)
{
str >> ltt->gmtoff;
str >> is;
ltt->isdst = (is != 0);
str >> ltt->abbrIndex;
// kDebug() << "local type: " << ltt->gmtoff << ", " << is << ", " << ltt->abbrIndex;
ltt->isstd = false; // default if no data
ltt->isutc = false; // default if no data
}
// Read the timezone abbreviations. They are stored as null terminated strings in
// a character array.
// Make sure we don't fall foul of maliciously coded time zone abbreviations.
if (abbrCharCount > 64)
{
kError() << "excessive length for timezone abbreviations: " << abbrCharCount;
delete data;
delete[] transitionTimes;
delete[] localTimeTypes;
return 0;
}
QByteArray array(abbrCharCount, 0);
str.readRawData(array.data(), array.size());
const char *abbrs = array.constData();
if (abbrs[abbrCharCount - 1] != 0)
{
// These abbreviations are corrupt!
kError() << "timezone abbreviations not null terminated: " << abbrs[abbrCharCount - 1];
delete data;
delete[] transitionTimes;
delete[] localTimeTypes;
return 0;
}
quint8 n = 0;
QList<QByteArray> abbreviations;
for (i = 0; i < abbrCharCount; ++n, i += strlen(abbrs + i) + 1)
{
abbreviations += QByteArray(abbrs + i);
// Convert the LocalTimeTypes pointer to a sequential index
ltt = localTimeTypes;
for (unsigned j = 0; j < nLocalTimeTypes; ++ltt, ++j)
{
if (ltt->abbrIndex == i)
ltt->abbrIndex = n;
}
}
// Read the leap second adjustments
qint32 t;
quint32 s;
QList<KTimeZone::LeapSeconds> leapChanges;
for (i = 0; i < nLeapSecondAdjusts; ++i)
{
str >> t >> s;
// kDebug() << "leap entry: " << t << ", " << s;
// Don't use QDateTime::setTime_t() because it takes an unsigned argument
leapChanges += KTimeZone::LeapSeconds(fromTime_t(t), static_cast<int>(s));
}
data->setLeapSecondChanges(leapChanges);
// Read the standard/wall time indicators.
// These are true if the transition times associated with local time types
// are specified as standard time, false if wall clock time.
for (i = 0; i < nIsStandard; ++i)
{
str >> is;
localTimeTypes[i].isstd = (is != 0);
// kDebug() << "standard: " << is;
}
// Read the UTC/local time indicators.
// These are true if the transition times associated with local time types
// are specified as UTC, false if local time.
for (i = 0; i < nIsUtc; ++i)
{
str >> is;
localTimeTypes[i].isutc = (is != 0);
// kDebug() << "UTC: " << is;
}
// Find the starting offset from UTC to use before the first transition time.
// This is first non-daylight savings local time type, or if there is none,
// the first local time type.
LocalTimeType* firstLtt = 0;
ltt = localTimeTypes;
for (i = 0; i < nLocalTimeTypes; ++ltt, ++i)
{
if (!ltt->isdst)
{
firstLtt = ltt;
break;
}
}
// Compile the time type data into a list of KTimeZone::Phase instances.
// Also check for local time types which are identical (this does happen)
// and use the same Phase instance for each.
QByteArray abbrev;
QList<KTimeZone::Phase> phases;
QList<QByteArray> phaseAbbrevs;
QVector<int> lttLookup(nLocalTimeTypes);
ltt = localTimeTypes;
for (i = 0; i < nLocalTimeTypes; ++ltt, ++i)
{
if (ltt->abbrIndex >= abbreviations.count())
{
kError() << "KTimeZoneSource::parse(): abbreviation index out of range";
abbrev = "???";
}
else
abbrev = abbreviations[ltt->abbrIndex];
// Check for an identical Phase
int phindex = 0;
for (int j = 0, jend = phases.count(); j < jend; ++j, ++phindex)
{
if (ltt->gmtoff == phases[j].utcOffset()
&& (bool)ltt->isdst == phases[j].isDst()
&& abbrev == phaseAbbrevs[j])
break;
}
lttLookup[i] = phindex;
if (phindex == phases.count())
{
phases += KTimeZone::Phase(ltt->gmtoff, abbrev, ltt->isdst);
phaseAbbrevs += abbrev;
}
}
KTimeZone::Phase prePhase(firstLtt->gmtoff,
(firstLtt->abbrIndex < abbreviations.count() ? abbreviations[firstLtt->abbrIndex] : ""),
false);
data->setPhases(phases, prePhase);
// Compile the transition list
QList<KTimeZone::Transition> transitions;
TransitionTime *tt = transitionTimes;
for (i = 0; i < nTransitionTimes; ++tt, ++i)
{
if (tt->localTimeIndex >= nLocalTimeTypes)
{
kError() << "KTimeZoneSource::parse(): transition ignored: local time type out of range: " << (int)tt->localTimeIndex << " > " << nLocalTimeTypes;
continue;
}
// Convert local transition times to UTC
ltt = &localTimeTypes[tt->localTimeIndex];
const KTimeZone::Phase phase = phases[lttLookup[tt->localTimeIndex]];
// kDebug(161) << "Transition time "<<i<<": "<<fromTime_t(tt->time)<<", offset="<<phase.utcOffset()/60;
transitions += KTimeZone::Transition(fromTime_t(tt->time), phase);
}
data->setTransitions(transitions);
// for(int xxx=1;xxx<data->transitions().count();xxx++)
// kDebug(161) << "Transition time "<<xxx<<": "<<data->transitions()[xxx].time()<<", offset="<<data->transitions()[xxx].phase().utcOffset()/60;
delete[] localTimeTypes;
delete[] transitionTimes;
return data;
}
bool KTimeZoneSource::useZoneParse() const
{
return d->mUseZoneParse;
}
QString KTimeZoneSource::location() const
{
return d->mLocation;
}
/******************************************************************************/
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<QByteArray> KTimeZoneData::abbreviations() const
{
if (d->abbreviations.isEmpty())
{
for (int i = 0, end = d->phases.count(); i < end; ++i)
{
const QList<QByteArray> 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 QByteArray("UTC");
const KTimeZone::Transition *tr = transition(utcDateTime);
const QList<QByteArray> abbrevs = tr ? tr->phase().abbreviations()
: d->prePhase.abbreviations();
if (abbrevs.isEmpty())
return QByteArray();
return abbrevs[0];
}
QList<int> 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<KTimeZone::Phase> KTimeZoneData::phases() const
{
return d->phases;
}
void KTimeZoneData::setPhases(const QList<KTimeZone::Phase> &phases, const KTimeZone::Phase& previousPhase)
{
d->phases = phases;
d->prePhase = previousPhase;
}
void KTimeZoneData::setPhases(const QList<KTimeZone::Phase> &phases, int previousUtcOffset)
{
d->phases = phases;
d->prePhase = KTimeZone::Phase(previousUtcOffset, QByteArray(), false);
}
bool KTimeZoneData::hasTransitions() const
{
return true;
}
QList<KTimeZone::Transition> KTimeZoneData::transitions(const QDateTime &start, const QDateTime &end) const
{
int ixstart, ixend;
if (!d->transitionIndexes(start, end, ixstart, ixend))
return QList<KTimeZone::Transition>(); // 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<KTimeZone::Transition> &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<QDateTime> KTimeZoneData::transitionTimes(const KTimeZone::Phase &phase, const QDateTime &start, const QDateTime &end) const
{
QList<QDateTime> 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<KTimeZone::LeapSeconds> KTimeZoneData::leapSecondChanges() const
{
return d->leapChanges;
}
void KTimeZoneData::setLeapSecondChanges(const QList<KTimeZone::LeapSeconds> &adjusts)
{
d->leapChanges = adjusts;
}
KTimeZone::LeapSeconds KTimeZoneData::leapSecondChange(const QDateTime &utc) const
{
if (utc.timeSpec() != Qt::UTC)
kError() << "KTimeZoneData::leapSecondChange(): non-UTC time specified";
else
{
for (int i = d->leapChanges.count(); --i >= 0; )
{
if (d->leapChanges[i].dateTime() < utc)
return d->leapChanges[i];
}
}
return KTimeZone::LeapSeconds();
}