2014-11-13 01:04:59 +02:00
|
|
|
/*
|
2023-07-22 04:00:09 +03:00
|
|
|
This file is part of the KDE libraries
|
|
|
|
Copyright (C) 2023 Ivailo Monev <xakepa10@gmail.com>
|
|
|
|
|
|
|
|
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.
|
2014-11-13 01:04:59 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "ktimezone.h"
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
2022-10-01 15:50:56 +03:00
|
|
|
#include <ksystemtimezone.h>
|
2023-07-22 02:29:37 +03:00
|
|
|
#include <QFile>
|
|
|
|
#include <QDir>
|
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
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
// for reference:
|
|
|
|
// https://man7.org/linux/man-pages/man5/tzfile.5.html
|
2014-11-13 01:04:59 +02:00
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
class KTimeZonePrivate
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
|
|
|
public:
|
2023-07-22 02:29:37 +03:00
|
|
|
KTimeZonePrivate();
|
|
|
|
KTimeZonePrivate(const QString &nam,
|
2014-11-13 01:04:59 +02:00
|
|
|
const QString &country, float lat, float lon, const QString &cmnt);
|
2023-07-22 02:29:37 +03:00
|
|
|
KTimeZonePrivate(const KTimeZonePrivate &rhs);
|
2014-11-13 01:04:59 +02:00
|
|
|
|
|
|
|
QString name;
|
|
|
|
QString countryCode;
|
|
|
|
QString comment;
|
2023-07-22 02:29:37 +03:00
|
|
|
float latitude;
|
|
|
|
float longitude;
|
|
|
|
int currentOffset;
|
|
|
|
QList<QByteArray> abbreviations;
|
2014-11-13 01:04:59 +02:00
|
|
|
};
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
KTimeZonePrivate::KTimeZonePrivate()
|
|
|
|
: latitude(KTimeZone::UNKNOWN),
|
|
|
|
longitude(KTimeZone::UNKNOWN),
|
|
|
|
currentOffset(KTimeZone::InvalidOffset)
|
|
|
|
{
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
KTimeZonePrivate::KTimeZonePrivate(const QString &nam,
|
|
|
|
const QString &country, float lat, float lon, const QString &cmnt)
|
|
|
|
: name(nam),
|
2014-11-13 01:04:59 +02:00
|
|
|
countryCode(country.toUpper()),
|
|
|
|
comment(cmnt),
|
|
|
|
latitude(lat),
|
2023-07-22 02:29:37 +03:00
|
|
|
longitude(lon)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
|
|
|
// Detect duff values.
|
2023-07-22 02:29:37 +03:00
|
|
|
if (latitude > 90 || latitude < -90) {
|
2014-11-13 01:04:59 +02:00
|
|
|
latitude = KTimeZone::UNKNOWN;
|
2023-07-22 02:29:37 +03:00
|
|
|
}
|
|
|
|
if (longitude > 180 || longitude < -180) {
|
2014-11-13 01:04:59 +02:00
|
|
|
longitude = KTimeZone::UNKNOWN;
|
|
|
|
}
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
QFile tzfile(zoneinfoDir() + QDir::separator() + name);
|
|
|
|
if (!tzfile.open(QFile::ReadOnly)) {
|
|
|
|
kWarning() << "Could not open" << tzfile.fileName();
|
|
|
|
return;
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
kDebug() << "Parsing" << tzfile.fileName();
|
|
|
|
QDataStream tzstream(&tzfile);
|
|
|
|
tzstream.setByteOrder(QDataStream::BigEndian);
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
char tzmagic[5];
|
|
|
|
::memset(tzmagic, 0, sizeof(tzmagic) * sizeof(char));
|
|
|
|
tzstream.readRawData(tzmagic, 4);
|
|
|
|
if (qstrcmp(tzmagic, "TZif") != 0) {
|
|
|
|
kWarning() << "Invalid magic bits" << tzfile.fileName() << tzmagic;
|
|
|
|
return;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
// 1 bit for version plus 15 reserved
|
|
|
|
tzstream.skipRawData(16);
|
|
|
|
|
|
|
|
Q_ASSERT(sizeof(quint8) == 1);
|
|
|
|
Q_ASSERT(sizeof(quint32) == 4);
|
|
|
|
quint32 tzh_ttisutcnt = 0;
|
|
|
|
quint32 tzh_ttisstdcnt = 0;
|
|
|
|
quint32 tzh_leapcnt = 0;
|
|
|
|
quint32 tzh_timecnt = 0;
|
|
|
|
quint32 tzh_typecnt = 0;
|
|
|
|
quint32 tzh_charcnt = 0;
|
|
|
|
tzstream >> tzh_ttisutcnt;
|
|
|
|
tzstream >> tzh_ttisstdcnt;
|
|
|
|
tzstream >> tzh_leapcnt;
|
|
|
|
tzstream >> tzh_timecnt;
|
|
|
|
tzstream >> tzh_typecnt;
|
|
|
|
tzstream >> tzh_charcnt;
|
|
|
|
|
2023-07-22 06:33:18 +03:00
|
|
|
// NOTE: should not be less than or or equal to zero
|
|
|
|
if (Q_UNLIKELY(tzh_typecnt <= 0)) {
|
2023-07-22 02:29:37 +03:00
|
|
|
kWarning() << "Invalid number of local time types" << tzfile.fileName();
|
|
|
|
return;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
2023-07-22 02:29:37 +03:00
|
|
|
|
|
|
|
// skip transitions
|
|
|
|
const int toskip = (
|
|
|
|
(tzh_timecnt * (sizeof(quint32) + sizeof(quint8)))
|
|
|
|
);
|
|
|
|
tzstream.skipRawData(toskip);
|
|
|
|
|
2023-07-22 06:33:18 +03:00
|
|
|
// get local time
|
2023-07-22 02:29:37 +03:00
|
|
|
qint32 tt_utoff = 0;
|
|
|
|
quint8 tt_isdst = 0;
|
|
|
|
quint8 tt_desigidx = 0;
|
|
|
|
quint8 abbreviationindex = 0;
|
|
|
|
for (quint32 i = 0; i < tzh_typecnt; i++) {
|
|
|
|
tzstream >> tt_utoff >> tt_isdst >> tt_desigidx;
|
|
|
|
if (!tt_isdst) {
|
|
|
|
currentOffset = tt_utoff;
|
|
|
|
abbreviationindex = tt_desigidx;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
// get the zone abbreviations
|
2023-07-22 06:33:18 +03:00
|
|
|
QByteArray abbreviationsbuffer(tzh_charcnt + 1, '\0');
|
2023-07-22 02:29:37 +03:00
|
|
|
tzstream.readRawData(abbreviationsbuffer.data(), abbreviationsbuffer.size());
|
|
|
|
foreach (const QByteArray &abbreviation, abbreviationsbuffer.split('\0')) {
|
|
|
|
if (abbreviation.isEmpty()) {
|
|
|
|
continue;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
2023-07-22 02:29:37 +03:00
|
|
|
abbreviations.append(abbreviation);
|
|
|
|
}
|
|
|
|
if (abbreviationindex >= 0 && abbreviationindex < abbreviationsbuffer.size()) {
|
|
|
|
QByteArray timetabbreviation = abbreviationsbuffer.mid(
|
|
|
|
abbreviationindex, qstrlen(abbreviationsbuffer.constData() + abbreviationindex)
|
|
|
|
);
|
|
|
|
// move the chosen local time abbreviation to the front, if not there already
|
|
|
|
for (int i = 1; i < abbreviations.size(); i++) {
|
|
|
|
if (abbreviations.at(i) == timetabbreviation) {
|
|
|
|
abbreviations.move(i, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
kWarning() << "Invalid abbreviation index" << tzfile.fileName() << abbreviationindex << abbreviationsbuffer.size();
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
2023-07-22 02:29:37 +03:00
|
|
|
// qDebug() << Q_FUNC_INFO << tzfile.fileName() << currentOffset << abbreviationindex << abbreviations;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
KTimeZonePrivate::KTimeZonePrivate(const KTimeZonePrivate &rhs)
|
|
|
|
: name(rhs.name),
|
|
|
|
countryCode(rhs.countryCode),
|
|
|
|
comment(rhs.comment),
|
|
|
|
latitude(rhs.latitude),
|
|
|
|
longitude(rhs.longitude),
|
|
|
|
currentOffset(rhs.currentOffset),
|
|
|
|
abbreviations(rhs.abbreviations)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
const int KTimeZone::InvalidOffset = 0x80000000;
|
|
|
|
const float KTimeZone::UNKNOWN = 1000.0;
|
|
|
|
|
|
|
|
KTimeZone::KTimeZone()
|
2023-07-22 02:29:37 +03:00
|
|
|
: d(new KTimeZonePrivate())
|
|
|
|
{
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
KTimeZone::KTimeZone(const QString &name,
|
|
|
|
const QString &countryCode, float latitude, float longitude,
|
|
|
|
const QString &comment)
|
|
|
|
: d(new KTimeZonePrivate(name, countryCode, latitude, longitude, comment))
|
|
|
|
{
|
|
|
|
}
|
2022-10-01 15:50:56 +03:00
|
|
|
|
2014-11-13 01:04:59 +02:00
|
|
|
KTimeZone::KTimeZone(const KTimeZone &tz)
|
2023-07-22 02:29:37 +03:00
|
|
|
: d(new KTimeZonePrivate(*tz.d))
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
KTimeZone::~KTimeZone()
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
delete d;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
KTimeZone &KTimeZone::operator=(const KTimeZone &tz)
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
if (d != tz.d) {
|
|
|
|
d->name = tz.name();
|
|
|
|
d->countryCode = tz.countryCode();
|
|
|
|
d->comment = tz.comment();
|
|
|
|
d->latitude = tz.latitude();
|
|
|
|
d->longitude = tz.longitude();
|
|
|
|
d->currentOffset = tz.currentOffset();
|
|
|
|
d->abbreviations = tz.abbreviations();
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTimeZone::operator==(const KTimeZone &rhs) const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
return (
|
|
|
|
name() == rhs.name()
|
|
|
|
&& countryCode() == rhs.countryCode()
|
|
|
|
&& comment() == rhs.comment()
|
|
|
|
&& latitude() == rhs.latitude()
|
|
|
|
&& longitude() == rhs.longitude()
|
|
|
|
&& currentOffset() == rhs.currentOffset()
|
|
|
|
&& abbreviations() == rhs.abbreviations()
|
|
|
|
);
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool KTimeZone::isValid() const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
// TODO: just because the name is not empty does not mean the time zone is valid
|
|
|
|
return !d->name.isEmpty();
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QString KTimeZone::countryCode() const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
return d->countryCode;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
float KTimeZone::latitude() const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
return d->latitude;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
float KTimeZone::longitude() const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
return d->longitude;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QString KTimeZone::comment() const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
return d->comment;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QString KTimeZone::name() const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
return d->name;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QList<QByteArray> KTimeZone::abbreviations() const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
return d->abbreviations;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray KTimeZone::abbreviation(const QDateTime &utcDateTime) const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
if (utcDateTime.timeSpec() != Qt::UTC) {
|
2014-11-13 01:04:59 +02:00
|
|
|
return QByteArray();
|
|
|
|
}
|
2023-07-22 06:33:18 +03:00
|
|
|
// TODO: actually check the date
|
2023-07-22 02:29:37 +03:00
|
|
|
return d->abbreviations[0];
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QDateTime KTimeZone::toUtc(const QDateTime &zoneDateTime) const
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
if (!zoneDateTime.isValid() || zoneDateTime.timeSpec() != Qt::LocalTime) {
|
2014-11-13 01:04:59 +02:00
|
|
|
return QDateTime();
|
2023-07-22 02:29:37 +03:00
|
|
|
}
|
|
|
|
const int offset = currentOffset();
|
|
|
|
if (offset == KTimeZone::InvalidOffset) {
|
2014-11-13 01:04:59 +02:00
|
|
|
return QDateTime();
|
2023-07-22 02:29:37 +03:00
|
|
|
}
|
|
|
|
QDateTime dt = zoneDateTime.addSecs(-offset);
|
2014-11-13 01:04:59 +02:00
|
|
|
dt.setTimeSpec(Qt::UTC);
|
2023-07-22 02:29:37 +03:00
|
|
|
return dt;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
QDateTime KTimeZone::toZoneTime(const QDateTime &utcDateTime) const
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
if (!utcDateTime.isValid() || utcDateTime.timeSpec() != Qt::UTC) {
|
2023-06-01 00:27:06 +03:00
|
|
|
QDateTime dt = utcDateTime;
|
2014-11-13 01:04:59 +02:00
|
|
|
dt.setTimeSpec(Qt::LocalTime);
|
|
|
|
return dt;
|
|
|
|
}
|
2023-07-22 02:29:37 +03:00
|
|
|
QDateTime dt = utcDateTime.addSecs(currentOffset());
|
2023-06-01 00:27:06 +03:00
|
|
|
dt.setTimeSpec(Qt::LocalTime);
|
|
|
|
return dt;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2023-07-22 02:29:37 +03:00
|
|
|
int KTimeZone::currentOffset() const
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
return d->currentOffset;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
KTimeZone KTimeZone::utc()
|
|
|
|
{
|
2023-07-22 02:29:37 +03:00
|
|
|
static KTimeZone utcZone(QLatin1String("UTC"));
|
2014-11-13 01:04:59 +02:00
|
|
|
return utcZone;
|
|
|
|
}
|