kdelibs/kdecore/date/ktimezone.cpp

308 lines
8.4 KiB
C++
Raw Normal View History

2014-11-13 01:04:59 +02: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>
#include <ksystemtimezone.h>
#include <QFile>
#include <QDir>
2014-11-13 01:04:59 +02:00
extern QString zoneinfoDir(); // in ksystemtimezone.cpp
// for reference:
// https://man7.org/linux/man-pages/man5/tzfile.5.html
2014-11-13 01:04:59 +02:00
/******************************************************************************/
class KTimeZonePrivate
2014-11-13 01:04:59 +02:00
{
public:
KTimeZonePrivate();
KTimeZonePrivate(const QString &nam,
2014-11-13 01:04:59 +02:00
const QString &country, float lat, float lon, const QString &cmnt);
KTimeZonePrivate(const KTimeZonePrivate &rhs);
2014-11-13 01:04:59 +02:00
QString name;
QString countryCode;
QString comment;
float latitude;
float longitude;
int currentOffset;
QList<QByteArray> abbreviations;
2014-11-13 01:04:59 +02:00
};
KTimeZonePrivate::KTimeZonePrivate()
: latitude(KTimeZone::UNKNOWN),
longitude(KTimeZone::UNKNOWN),
currentOffset(KTimeZone::InvalidOffset)
{
}
2014-11-13 01:04:59 +02: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),
longitude(lon)
2014-11-13 01:04:59 +02:00
{
// Detect duff values.
if (latitude > 90 || latitude < -90) {
2014-11-13 01:04:59 +02:00
latitude = KTimeZone::UNKNOWN;
}
if (longitude > 180 || longitude < -180) {
2014-11-13 01:04:59 +02:00
longitude = KTimeZone::UNKNOWN;
}
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
kDebug() << "Parsing" << tzfile.fileName();
QDataStream tzstream(&tzfile);
tzstream.setByteOrder(QDataStream::BigEndian);
2014-11-13 01:04:59 +02: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
}
// 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;
// NOTE: should not be less than or or equal to zero
if (Q_UNLIKELY(tzh_typecnt <= 0)) {
kWarning() << "Invalid number of local time types" << tzfile.fileName();
return;
2014-11-13 01:04:59 +02:00
}
// skip transitions
const int toskip = (
(tzh_timecnt * (sizeof(quint32) + sizeof(quint8)))
);
tzstream.skipRawData(toskip);
// get local time
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
}
}
// get the zone abbreviations
QByteArray abbreviationsbuffer(tzh_charcnt + 1, '\0');
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
}
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
}
// qDebug() << Q_FUNC_INFO << tzfile.fileName() << currentOffset << abbreviationindex << abbreviations;
2014-11-13 01:04:59 +02: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()
: d(new KTimeZonePrivate())
{
}
2014-11-13 01:04:59 +02:00
KTimeZone::KTimeZone(const QString &name,
const QString &countryCode, float latitude, float longitude,
const QString &comment)
: d(new KTimeZonePrivate(name, countryCode, latitude, longitude, comment))
{
}
2014-11-13 01:04:59 +02:00
KTimeZone::KTimeZone(const KTimeZone &tz)
: d(new KTimeZonePrivate(*tz.d))
2014-11-13 01:04:59 +02:00
{
}
KTimeZone::~KTimeZone()
2014-11-13 01:04:59 +02:00
{
delete d;
2014-11-13 01:04:59 +02:00
}
KTimeZone &KTimeZone::operator=(const KTimeZone &tz)
{
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
{
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
{
// 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
{
return d->countryCode;
2014-11-13 01:04:59 +02:00
}
float KTimeZone::latitude() const
{
return d->latitude;
2014-11-13 01:04:59 +02:00
}
float KTimeZone::longitude() const
{
return d->longitude;
2014-11-13 01:04:59 +02:00
}
QString KTimeZone::comment() const
{
return d->comment;
2014-11-13 01:04:59 +02:00
}
QString KTimeZone::name() const
{
return d->name;
2014-11-13 01:04:59 +02:00
}
QList<QByteArray> KTimeZone::abbreviations() const
{
return d->abbreviations;
2014-11-13 01:04:59 +02:00
}
QByteArray KTimeZone::abbreviation(const QDateTime &utcDateTime) const
{
if (utcDateTime.timeSpec() != Qt::UTC) {
2014-11-13 01:04:59 +02:00
return QByteArray();
}
// TODO: actually check the date
return d->abbreviations[0];
2014-11-13 01:04:59 +02:00
}
QDateTime KTimeZone::toUtc(const QDateTime &zoneDateTime) const
{
if (!zoneDateTime.isValid() || zoneDateTime.timeSpec() != Qt::LocalTime) {
2014-11-13 01:04:59 +02:00
return QDateTime();
}
const int offset = currentOffset();
if (offset == KTimeZone::InvalidOffset) {
2014-11-13 01:04:59 +02:00
return QDateTime();
}
QDateTime dt = zoneDateTime.addSecs(-offset);
2014-11-13 01:04:59 +02:00
dt.setTimeSpec(Qt::UTC);
return dt;
2014-11-13 01:04:59 +02:00
}
QDateTime KTimeZone::toZoneTime(const QDateTime &utcDateTime) const
2014-11-13 01:04:59 +02:00
{
if (!utcDateTime.isValid() || utcDateTime.timeSpec() != Qt::UTC) {
QDateTime dt = utcDateTime;
2014-11-13 01:04:59 +02:00
dt.setTimeSpec(Qt::LocalTime);
return dt;
}
QDateTime dt = utcDateTime.addSecs(currentOffset());
dt.setTimeSpec(Qt::LocalTime);
return dt;
2014-11-13 01:04:59 +02:00
}
int KTimeZone::currentOffset() const
2014-11-13 01:04:59 +02:00
{
return d->currentOffset;
2014-11-13 01:04:59 +02:00
}
KTimeZone KTimeZone::utc()
{
static KTimeZone utcZone(QLatin1String("UTC"));
2014-11-13 01:04:59 +02:00
return utcZone;
}