2
This file is part of the KDE libraries
3
Copyright (c) 2005 S.R.Haque <srhaque@iee.org>.
5
This library is free software; you can redistribute it and/or
6
modify it under the terms of the GNU Library General Public
7
License version 2 as published by the Free Software Foundation.
9
This library is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
Library General Public License for more details.
14
You should have received a copy of the GNU Library General Public License
15
along with this library; see the file COPYING.LIB. If not, write to
16
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
Boston, MA 02110-1301, USA.
22
#include "ksttimezones.h"
25
#include <kstringhandler.h>
26
#include <ktempfile.h>
28
#include <qdatetime.h>
31
#include <qstringlist.h>
32
#include <qtextstream.h>
40
#define UTC_ZONE "UTC"
53
extern char ** environ;
56
void unsetenv(const char *name)
61
if (name == NULL || *name == '\0' || strchr (name, '=') != NULL) {
69
if (!strncmp (*ep, name, len) && (*ep)[len] == '=') {
70
/* Found it. Remove this pointer by moving later ones back. */
75
/* Continue the loop in case NAME appears again. */
82
#endif /* !HAVE_UNSETENV */
85
* Find out if the given standard (e.g. "GMT") and daylight savings time
86
* (e.g. "BST", but which may be empty) abbreviated timezone names match
89
* Thus, this class can be used as a heuristic when trying to lookup the
90
* real timezone from the abbreviated zone names.
92
class AbbreviationsMatch :
93
public KstTimezoneDetails
96
AbbreviationsMatch(const QString &stdZone, const QString &dstZone = "")
105
m_foundDst = m_dstZone.isEmpty();
110
return (m_foundStd && m_foundDst);
119
virtual void gotAbbreviation(int /*index*/, const QString &value)
121
if (m_stdZone == value)
125
if (m_dstZone == value)
133
* Internal dummy source for UTC timezone.
136
public KstTimezoneSource
140
KstTimezoneSource("")
144
virtual bool parse(const QString &/*zone*/, KstTimezoneDetails &/*dataReceiver*/) const
151
* Find offset at a particular point in time.
154
public KstTimezoneDetails
157
OffsetFind(unsigned dateTime)
159
m_dateTime = dateTime;
164
m_transitionTimeIndex = 0;
165
m_localTimeIndex = -1;
182
QString abbreviation()
189
int m_transitionTimeIndex;
190
int m_localTimeIndex;
196
virtual void gotTransitionTime(int index, unsigned transitionTime)
198
if (transitionTime <= m_dateTime)
200
// Remember the index of the transition time that relates to dateTime.
201
m_transitionTimeIndex = index;
205
virtual void gotLocalTimeIndex(int index, unsigned localTimeIndex)
207
if (index == m_transitionTimeIndex)
209
// Remember the index of the local time that relates to dateTime.
210
m_localTimeIndex = localTimeIndex;
214
virtual void gotLocalTime(int index, int gmtOff, bool isDst, unsigned abbrInd)
216
if (index == m_localTimeIndex)
218
// Remember the results that relate to gmtOffset.
221
m_abbrIndex = abbrInd;
225
virtual void gotAbbreviation(int index, const QString &value)
227
if (index == m_abbrIndex)
234
const float KstTimezone::UNKNOWN = 1000.0;
236
bool KstTimezone::isValidLatitude(float latitude)
238
return (latitude >= -90.0) && (latitude <= 90.0);
241
bool KstTimezone::isValidLongitude(float longitude)
243
return (longitude >= -180.0) && (longitude <= 180.0);
246
KstTimezone::KstTimezone(
247
KSharedPtr<KstTimezoneSource> db, const QString& name,
248
const QString &countryCode, float latitude, float longitude,
249
const QString &comment) :
252
m_countryCode(countryCode),
253
m_latitude(latitude),
254
m_longitude(longitude),
258
// Detect duff values.
259
if (m_latitude * m_latitude > 90 * 90)
260
m_latitude = UNKNOWN;
261
if (m_longitude * m_longitude > 180 * 180)
262
m_longitude = UNKNOWN;
265
KstTimezone::~KstTimezone()
267
// FIXME when needed:
271
QString KstTimezone::comment() const
276
QDateTime KstTimezone::convert(const KstTimezone *newZone, const QDateTime &dateTime) const
278
char *originalZone = ::getenv("TZ");
280
// Convert the given localtime to UTC.
281
::putenv(strdup(QString("TZ=:").append(m_name).utf8()));
283
unsigned utc = dateTime.toTime_t();
285
// Set the timezone and convert UTC to localtime.
286
::putenv(strdup(QString("TZ=:").append(newZone->name()).utf8()));
288
QDateTime remoteTime;
289
remoteTime.setTime_t(utc, Qt::LocalTime);
291
// Now restore things
298
::putenv(strdup(QString("TZ=").append(originalZone).utf8()));
304
QString KstTimezone::countryCode() const
306
return m_countryCode;
309
float KstTimezone::latitude() const
314
float KstTimezone::longitude() const
319
QString KstTimezone::name() const
324
int KstTimezone::offset(Qt::TimeSpec basisSpec) const
326
char *originalZone = ::getenv("TZ");
328
// Get the time in the current timezone.
329
QDateTime basisTime = QDateTime::currentDateTime(basisSpec);
331
// Set the timezone and find out what time it is there compared to the basis.
332
::putenv(strdup(QString("TZ=:").append(m_name).utf8()));
334
QDateTime remoteTime = QDateTime::currentDateTime(Qt::LocalTime);
335
int offset = remoteTime.secsTo(basisTime);
337
// Now restore things
344
::putenv(strdup(QString("TZ=").append(originalZone).utf8()));
350
int KstTimezone::offset(const QDateTime &dateTime) const
352
OffsetFind finder(dateTime.toTime_t());
356
result = finder.offset();
361
bool KstTimezone::parse(KstTimezoneDetails &dataReceiver) const
363
dataReceiver.parseStarted();
364
bool result = m_db->parse(m_name, dataReceiver);
365
dataReceiver.parseEnded();
369
KstTimezones::KstTimezones() :
374
// Create the database (and resolve m_zoneinfoDir!).
376
m_UTC = new KstTimezone(new DummySource(), UTC_ZONE);
380
KstTimezones::~KstTimezones()
382
// FIXME when needed:
385
// Autodelete behavior.
388
for (ZoneMap::ConstIterator it = m_zones->begin(); it != m_zones->end(); ++it)
396
void KstTimezones::add(KstTimezone *zone)
398
m_zones->insert(zone->name(), zone);
401
const KstTimezones::ZoneMap KstTimezones::allZones()
403
// Have we already done all the hard work? If not, create the cache.
406
m_zones = new ZoneMap();
408
// Go read the database.
410
// On Windows, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones
411
// is the place to look. The TZI binary value is the TIME_ZONE_INFORMATION structure.
413
// For Unix its all easy except knowing where to look. Try the LSB location first.
415
m_zoneinfoDir = "/usr/share/zoneinfo";
416
f.setName(m_zoneinfoDir + "/zone.tab");
417
if (!f.open(IO_ReadOnly))
419
m_zoneinfoDir = "/usr/lib/zoneinfo";
420
f.setName(m_zoneinfoDir + "/zone.tab");
421
if (!f.open(IO_ReadOnly))
423
m_zoneinfoDir = ::getenv("TZDIR");
424
f.setName(m_zoneinfoDir + "/zone.tab");
425
if (m_zoneinfoDir.isEmpty() || !f.open(IO_ReadOnly))
427
// Solaris support. Synthesise something that looks like a zone.tab.
429
// /bin/grep -h ^Zone /usr/share/lib/zoneinfo/src/* | /bin/awk '{print "??\t+9999+99999\t" $2}'
431
// where the country code is set to "??" and the lattitude/longitude
432
// values are dummies.
433
m_zoneinfoDir = "/usr/share/lib/zoneinfo";
435
KShellProcess reader;
436
reader << "/bin/grep" << "-h" << "^Zone" << m_zoneinfoDir << "/src/*" << temp.name() << "|" <<
437
"/bin/awk" << "'{print \"??\\t+9999+99999\\t\" $2}'";
438
// Note the use of blocking here...it is a trivial amount of data!
440
reader.start(KProcess::Block);
441
f.setName(temp.name());
442
if (!temp.status() || !f.open(IO_ReadOnly))
450
// Parse the zone.tab.
452
QRegExp lineSeparator("[ \t]");
453
QRegExp ordinateSeparator("[+-]");
454
KSharedPtr<KstTimezoneSource> db(new KstTimezoneSource(m_zoneinfoDir));
457
QString line = str.readLine();
458
if (line.isEmpty() || '#' == line[0])
460
QStringList tokens = KStringHandler::perlSplit(lineSeparator, line, 4);
461
if (tokens.count() < 3)
466
// Got three tokens. Now check for two ordinates plus first one is "".
467
QStringList ordinates = KStringHandler::perlSplit(ordinateSeparator, tokens[1], 2);
468
if (ordinates.count() < 2)
473
float latitude = convertCoordinate(ordinates[1]);
474
float longitude = convertCoordinate(ordinates[2]);
476
// Add entry to list.
477
if (tokens[0] == "??")
479
KstTimezone *timezone = new KstTimezone(db, tokens[2], tokens[0], latitude, longitude, tokens[3]);
487
* Convert sHHMM or sHHMMSS to a floating point number of degrees.
489
float KstTimezones::convertCoordinate(const QString &coordinate)
491
int value = coordinate.toInt();
496
if (coordinate.length() > 11)
498
degrees = value / 10000;
499
value -= degrees * 10000;
500
minutes = value / 100;
501
value -= minutes * 100;
506
degrees = value / 100;
507
value -= degrees * 100;
510
value = degrees * 3600 + minutes * 60 + seconds;
511
return value / 3600.0;
514
const KstTimezone *KstTimezones::local()
516
const KstTimezone *local = 0;
518
// First try the simplest solution of checking for well-formed TZ setting.
519
char *envZone = ::getenv("TZ");
522
if (envZone[0] == '\0')
527
if (envZone[0] == ':')
531
local = zone(envZone);
536
// Try to match /etc/localtime against the list of zoneinfo files.
538
f.setName("/etc/localtime");
539
if (f.open(IO_ReadOnly))
541
// Compute the MD5 sum of /etc/localtime.
545
QIODevice::Offset referenceSize = f.size();
546
QString referenceMd5Sum = context.hexDigest();
548
if (!m_zoneinfoDir.isEmpty())
550
// Compare it with each zoneinfo file.
551
for (ZoneMap::Iterator it = m_zones->begin(); it != m_zones->end(); ++it)
553
KstTimezone *zone = it.data();
554
f.setName(m_zoneinfoDir + '/' + zone->name());
555
if (f.open(IO_ReadOnly))
557
QIODevice::Offset candidateSize = f.size();
558
QString candidateMd5Sum;
559
if (candidateSize == referenceSize)
561
// Only do the heavy lifting for file sizes which match.
564
candidateMd5Sum = context.hexDigest();
567
if (candidateMd5Sum == referenceMd5Sum)
581
f.setName("/etc/timezone");
582
if (!f.open(IO_ReadOnly))
584
// Solaris support using /etc/default/init.
585
f.setName("/etc/default/init");
586
if (f.open(IO_ReadOnly))
589
ts.setEncoding(QTextStream::Latin1);
591
// Read the last line starting "TZ=".
594
fileZone = ts.readLine();
595
if (fileZone.startsWith("TZ="))
597
fileZone = fileZone.mid(3);
599
local = zone(fileZone);
608
ts.setEncoding(QTextStream::Latin1);
610
// Read the first line.
613
fileZone = ts.readLine();
615
local = zone(fileZone);
622
// None of the deterministic stuff above has worked: try a heuristic. We
623
// try to find a pair of matching timezone abbreviations...that way, we'll
624
// likely return a value in the user's own country.
625
if (!m_zoneinfoDir.isEmpty())
628
AbbreviationsMatch matcher(tzname[0], tzname[1]);
629
int bestOffset = INT_MAX;
630
for (ZoneMap::Iterator it = m_zones->begin(); it != m_zones->end(); ++it)
632
KstTimezone *zone = it.data();
633
int candidateOffset = QABS(zone->offset(Qt::LocalTime));
634
if (zone->parse(matcher) && matcher.test() && (candidateOffset < bestOffset))
636
bestOffset = candidateOffset;
646
const KstTimezone *KstTimezones::zone(const QString &name)
650
ZoneMap::ConstIterator it = m_zones->find(name);
651
if (it != m_zones->end())
658
KstTimezoneDetails::KstTimezoneDetails()
662
KstTimezoneDetails::~KstTimezoneDetails()
666
void KstTimezoneDetails::gotAbbreviation(int /*index*/, const QString &)
669
void KstTimezoneDetails::gotHeader(
670
unsigned, unsigned, unsigned,
671
unsigned, unsigned, unsigned)
674
void KstTimezoneDetails::gotLeapAdjustment(int /*index*/, unsigned, unsigned)
677
void KstTimezoneDetails::gotLocalTime(int /*index*/, int, bool, unsigned)
680
void KstTimezoneDetails::gotLocalTimeIndex(int /*index*/, unsigned)
683
void KstTimezoneDetails::gotIsStandard(int /*index*/, bool)
686
void KstTimezoneDetails::gotTransitionTime(int /*index*/, unsigned)
689
void KstTimezoneDetails::gotIsUTC(int /*index*/, bool)
692
void KstTimezoneDetails::parseEnded()
695
void KstTimezoneDetails::parseStarted()
698
KstTimezoneSource::KstTimezoneSource(const QString &db) :
703
KstTimezoneSource::~KstTimezoneSource()
707
QString KstTimezoneSource::db()
712
bool KstTimezoneSource::parse(const QString &zone, KstTimezoneDetails &dataReceiver) const
714
QFile f(m_db + '/' + zone);
715
if (!f.open(IO_ReadOnly))
720
// Structures that represent the zoneinfo file.
721
Q_UINT8 T, z, i_, f_;
731
Q_UINT32 transitionTime;
732
Q_UINT8 localTimeIndex;
740
Q_UINT32 leapSeconds;
745
str >> T >> z >> i_ >> f_;
747
for (i = 0; i < 4; i++)
748
str >> tzh.ttisgmtcnt;
749
str >> tzh.ttisgmtcnt >> tzh.ttisstdcnt >> tzh.leapcnt >> tzh.timecnt >> tzh.typecnt >> tzh.charcnt;
750
dataReceiver.gotHeader(tzh.ttisgmtcnt, tzh.ttisstdcnt, tzh.leapcnt, tzh.timecnt, tzh.typecnt, tzh.charcnt);
751
for (i = 0; i < tzh.timecnt; i++)
753
str >> transitionTime;
754
dataReceiver.gotTransitionTime(i, transitionTime);
756
for (i = 0; i < tzh.timecnt; i++)
758
// NB: these appear to be 1-based, not zero-based!
759
str >> localTimeIndex;
760
dataReceiver.gotLocalTimeIndex(i, localTimeIndex);
762
for (i = 0; i < tzh.typecnt; i++)
764
str >> tt.gmtoff >> tt.isdst >> tt.abbrind;
765
dataReceiver.gotLocalTime(i, tt.gmtoff, (tt.isdst != 0), tt.abbrind);
768
// Make sure we don't run foul of maliciously coded timezone abbreviations.
769
if (tzh.charcnt > 64)
773
QByteArray array(tzh.charcnt);
774
str.readRawBytes(array.data(), array.size());
775
char *abbrs = array.data();
776
if (abbrs[tzh.charcnt - 1] != 0)
778
// These abbrevations are corrupt!
782
while (abbr < abbrs + tzh.charcnt)
784
dataReceiver.gotAbbreviation((abbr - abbrs), abbr);
785
abbr += strlen(abbr) + 1;
787
for (i = 0; i < tzh.leapcnt; i++)
789
str >> leapTime >> leapSeconds;
790
dataReceiver.gotLeapAdjustment(i, leapTime, leapSeconds);
792
for (i = 0; i < tzh.ttisstdcnt; i++)
795
dataReceiver.gotIsStandard(i, (isStandard != 0));
797
for (i = 0; i < tzh.ttisgmtcnt; i++)
800
dataReceiver.gotIsUTC(i, (isUTC != 0));