kdelibs/kdecore/date/ktzfiletimezone.cpp
2014-11-13 01:04:59 +02:00

407 lines
13 KiB
C++

/*
This file is part of the KDE libraries
Copyright (c) 2005-2008 David Jarvie <djarvie@kde.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.
*/
#include "ktzfiletimezone.h"
#include <config.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include <QtCore/QFile>
#include <QtCore/QDataStream>
#include <QtCore/QVector>
#include <kdebug.h>
// 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);
}
/******************************************************************************/
KTzfileTimeZoneBackend::KTzfileTimeZoneBackend(KTzfileTimeZoneSource *source, const QString &name,
const QString &countryCode, float latitude, float longitude, const QString &comment)
: KTimeZoneBackend(source, name, countryCode, latitude, longitude, comment)
{}
KTzfileTimeZoneBackend::~KTzfileTimeZoneBackend()
{}
KTimeZoneBackend *KTzfileTimeZoneBackend::clone() const
{
return new KTzfileTimeZoneBackend(*this);
}
QByteArray KTzfileTimeZoneBackend::type() const
{
return "KTzfileTimeZone";
}
bool KTzfileTimeZoneBackend::hasTransitions(const KTimeZone *caller) const
{
Q_UNUSED(caller)
return true;
}
/******************************************************************************/
KTzfileTimeZone::KTzfileTimeZone(KTzfileTimeZoneSource *source, const QString &name,
const QString &countryCode, float latitude, float longitude,
const QString &comment)
: KTimeZone(new KTzfileTimeZoneBackend(source, name, countryCode, latitude, longitude, comment))
{}
KTzfileTimeZone::~KTzfileTimeZone()
{}
/******************************************************************************/
class KTzfileTimeZoneDataPrivate
{
public:
};
KTzfileTimeZoneData::KTzfileTimeZoneData()
// : d(new KTzfileTimeZoneDataPrivate)
{ }
KTzfileTimeZoneData::KTzfileTimeZoneData(const KTzfileTimeZoneData &rhs)
: KTimeZoneData(rhs)
// d(new KTzfileTimeZoneDataPrivate)
{
}
KTzfileTimeZoneData::~KTzfileTimeZoneData()
{
// delete d;
}
KTzfileTimeZoneData &KTzfileTimeZoneData::operator=(const KTzfileTimeZoneData &rhs)
{
KTimeZoneData::operator=(rhs);
return *this;
}
KTimeZoneData *KTzfileTimeZoneData::clone() const
{
return new KTzfileTimeZoneData(*this);
}
bool KTzfileTimeZoneData::hasTransitions() const
{
return true;
}
/******************************************************************************/
class KTzfileTimeZoneSourcePrivate
{
public:
KTzfileTimeZoneSourcePrivate(const QString &loc)
: location(loc) {}
~KTzfileTimeZoneSourcePrivate() {}
QString location;
};
KTzfileTimeZoneSource::KTzfileTimeZoneSource(const QString &location)
: d(new KTzfileTimeZoneSourcePrivate(location))
{
if (location.length() > 1 && location.endsWith(QLatin1Char('/')))
d->location.chop(1);
}
KTzfileTimeZoneSource::~KTzfileTimeZoneSource()
{
delete d;
}
QString KTzfileTimeZoneSource::location() const
{
return d->location;
}
KTimeZoneData* KTzfileTimeZoneSource::parse(const KTimeZone &zone) const
{
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->location == QLatin1String("/"))
path.prepend(d->location);
else
path = d->location + 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;
KTzfileTimeZoneData* data = new KTzfileTimeZoneData;
// 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() << "KTzfileTimeZoneSource::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() << "KTzfileTimeZoneSource::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;
}