2014-11-13 01:04:59 +02:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2022-10-01 11:29:43 +03:00
|
|
|
// 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.
|
|
|
|
|
2014-11-13 01:04:59 +02:00
|
|
|
#include "ktimezone.h"
|
|
|
|
|
|
|
|
#include <config.h>
|
2022-10-01 11:29:43 +03:00
|
|
|
#include <config-date.h>
|
2014-11-13 01:04:59 +02:00
|
|
|
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <climits>
|
|
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
#include <QtCore/QSet>
|
|
|
|
#include <QtCore/QSharedData>
|
|
|
|
#include <QtCore/QCoreApplication>
|
2022-10-01 15:50:56 +03:00
|
|
|
#include <QtCore/QFile>
|
2014-11-13 01:04:59 +02:00
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kglobal.h>
|
2022-10-01 15:50:56 +03:00
|
|
|
#include <ksystemtimezone.h>
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2022-10-01 15:50:56 +03:00
|
|
|
extern QString zoneinfoDir(); // in ksystemtimezone.cpp
|
2022-10-01 11:29:43 +03:00
|
|
|
|
|
|
|
/* 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
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2022-10-01 15:50:56 +03:00
|
|
|
// 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);
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2022-10-01 15:50:56 +03:00
|
|
|
mUtcSource = new KTimeZoneSource(zoneinfoDir());
|
2014-11-13 01:04:59 +02:00
|
|
|
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;
|
2022-09-24 18:21:05 +03:00
|
|
|
#ifdef ENABLE_TESTING
|
2014-11-13 01:04:59 +02:00
|
|
|
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
|
2022-09-24 18:21:05 +03:00
|
|
|
#ifdef ENABLE_TESTING
|
2014-11-13 01:04:59 +02:00
|
|
|
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
|
2022-09-24 18:21:05 +03:00
|
|
|
#ifdef ENABLE_TESTING
|
2014-11-13 01:04:59 +02:00
|
|
|
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
|
2022-09-24 18:21:05 +03:00
|
|
|
#ifdef ENABLE_TESTING
|
2014-11-13 01:04:59 +02:00
|
|
|
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);
|
2022-10-01 15:50:56 +03:00
|
|
|
return true;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
#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))
|
|
|
|
{}
|
|
|
|
|
2022-10-01 15:50:56 +03:00
|
|
|
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))
|
|
|
|
{}
|
|
|
|
|
2014-11-13 01:04:59 +02:00
|
|
|
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()
|
|
|
|
{
|
2022-10-01 15:50:56 +03:00
|
|
|
static KTimeZone utcZone(KTimeZonePrivate::utcSource(), QLatin1String("UTC"));
|
2014-11-13 01:04:59 +02:00
|
|
|
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:
|
2022-10-01 15:50:56 +03:00
|
|
|
KTimeZoneSourcePrivate()
|
|
|
|
: mUseZoneParse(true) {}
|
|
|
|
|
|
|
|
KTimeZoneSourcePrivate(const QString &loc)
|
|
|
|
: mUseZoneParse(true), mLocation(loc) {}
|
|
|
|
|
2014-11-13 01:04:59 +02:00
|
|
|
bool mUseZoneParse;
|
2022-10-01 15:50:56 +03:00
|
|
|
QString mLocation;
|
2014-11-13 01:04:59 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
KTimeZoneSource::KTimeZoneSource()
|
2022-10-01 15:50:56 +03:00
|
|
|
: d(new KTimeZoneSourcePrivate())
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2022-10-01 15:50:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
KTimeZoneSource::KTimeZoneSource(const QString &location)
|
|
|
|
: d(new KTimeZoneSourcePrivate(location))
|
|
|
|
{
|
|
|
|
if (location.length() > 1 && location.endsWith(QLatin1Char('/')))
|
|
|
|
d->mLocation.chop(1);
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
KTimeZoneSource::KTimeZoneSource(bool useZoneParse)
|
2022-10-01 15:50:56 +03:00
|
|
|
: d(new KTimeZoneSourcePrivate())
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
|
|
|
d->mUseZoneParse = useZoneParse;
|
|
|
|
}
|
|
|
|
|
|
|
|
KTimeZoneSource::~KTimeZoneSource()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
2022-10-01 15:50:56 +03:00
|
|
|
KTimeZoneData *KTimeZoneSource::parse(const KTimeZone &zone) const
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
|
|
|
Q_ASSERT(d->mUseZoneParse); // method should never be called if it isn't usable
|
2022-10-01 15:50:56 +03:00
|
|
|
|
|
|
|
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() << endl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
QDataStream str(&f);
|
|
|
|
|
|
|
|
// 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() << endl;
|
|
|
|
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 << endl;
|
|
|
|
|
|
|
|
// 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 << endl;
|
|
|
|
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] << endl;
|
|
|
|
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" << endl;
|
|
|
|
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 << endl;
|
|
|
|
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;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool KTimeZoneSource::useZoneParse() const
|
|
|
|
{
|
|
|
|
return d->mUseZoneParse;
|
|
|
|
}
|
|
|
|
|
2022-10-01 15:50:56 +03:00
|
|
|
QString KTimeZoneSource::location() const
|
|
|
|
{
|
|
|
|
return d->mLocation;
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
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())
|
2022-10-01 15:50:56 +03:00
|
|
|
return QByteArray("UTC");
|
2014-11-13 01:04:59 +02:00
|
|
|
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
|
|
|
|
{
|
2022-10-01 15:50:56 +03:00
|
|
|
return true;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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" << endl;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int i = d->leapChanges.count(); --i >= 0; )
|
|
|
|
{
|
|
|
|
if (d->leapChanges[i].dateTime() < utc)
|
|
|
|
return d->leapChanges[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return KTimeZone::LeapSeconds();
|
|
|
|
}
|