/* This file is part of the KDE libraries Copyright (c) 2005 S.R.Haque . This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define UTC_ZONE "UTC" /** * Find out if the given standard (e.g. "GMT") and daylight savings time * (e.g. "BST", but which may be empty) abbreviated timezone names match * this timezone. * * Thus, this class can be used as a heuristic when trying to lookup the * real timezone from the abbreviated zone names. */ class AbbreviationsMatch : public KTimezoneDetails { public: AbbreviationsMatch(const TQString &stdZone, const TQString &dstZone = "") { m_stdZone = stdZone; m_dstZone = dstZone; } void parseStarted() { m_foundStd = false; m_foundDst = m_dstZone.isEmpty(); } bool test() { return (m_foundStd && m_foundDst); } private: bool m_foundStd; bool m_foundDst; TQString m_stdZone; TQString m_dstZone; virtual void gotAbbreviation(int /*index*/, const TQString &value) { if (m_stdZone == value) { m_foundStd = true; } if (m_dstZone == value) { m_foundDst = true; } } }; /** * Internal dummy source for UTC timezone. */ class DummySource : public KTimezoneSource { public: DummySource() : KTimezoneSource("") { } virtual bool parse(const TQString &/*zone*/, KTimezoneDetails &/*dataReceiver*/) const { return true; } }; /** * Find offset at a particular point in time. */ class OffsetFind : public KTimezoneDetails { public: OffsetFind(unsigned dateTime) { m_dateTime = dateTime; } void parseStarted() { m_transitionTimeIndex = 0; m_localTimeIndex = -1; m_abbrIndex = -1; m_offset = 0; m_isDst = false; m_abbr = UTC_ZONE; } int offset() { return m_offset; } bool isDst() { return m_isDst; } TQString abbreviation() { return m_abbr; } private: unsigned m_dateTime; int m_transitionTimeIndex; int m_localTimeIndex; int m_abbrIndex; int m_offset; bool m_isDst; TQString m_abbr; virtual void gotTransitionTime(int index, unsigned transitionTime) { if (transitionTime <= m_dateTime) { // Remember the index of the transition time that relates to dateTime. m_transitionTimeIndex = index; } } virtual void gotLocalTimeIndex(int index, unsigned localTimeIndex) { if (index == m_transitionTimeIndex) { // Remember the index of the local time that relates to dateTime. m_localTimeIndex = localTimeIndex; } } virtual void gotLocalTime(int index, int gmtOff, bool isDst, unsigned abbrInd) { if (index == m_localTimeIndex) { // Remember the results that relate to gmtOffset. m_offset = gmtOff; m_isDst = isDst; m_abbrIndex = abbrInd; } } virtual void gotAbbreviation(int index, const TQString &value) { if (index == m_abbrIndex) { m_abbr = value; } } }; const float KTimezone::UNKNOWN = 1000.0; bool KTimezone::isValidLatitude(float latitude) { return (latitude >= -90.0) && (latitude <= 90.0); } bool KTimezone::isValidLongitude(float longitude) { return (longitude >= -180.0) && (longitude <= 180.0); } KTimezone::KTimezone( KSharedPtr db, const TQString& name, const TQString &countryCode, float latitude, float longitude, const TQString &comment) : m_db(db), m_name(name), m_countryCode(countryCode), m_latitude(latitude), m_longitude(longitude), m_comment(comment), d(0) { // Detect duff values. if (m_latitude * m_latitude > 90 * 90) m_latitude = UNKNOWN; if (m_longitude * m_longitude > 180 * 180) m_longitude = UNKNOWN; } KTimezone::~KTimezone() { // FIXME when needed: // delete d; } TQString KTimezone::comment() const { return m_comment; } TQDateTime KTimezone::convert(const KTimezone *newZone, const TQDateTime &dateTime) const { char *originalZone = ::getenv("TZ"); // Convert the given localtime to UTC. ::setenv("TZ", m_name.utf8(), 1); tzset(); unsigned utc = dateTime.toTime_t(); // Set the timezone and convert UTC to localtime. ::setenv("TZ", newZone->name().utf8(), 1); tzset(); TQDateTime remoteTime; remoteTime.setTime_t(utc, Qt::LocalTime); // Now restore things if (!originalZone) { ::unsetenv("TZ"); } else { ::setenv("TZ", originalZone, 1); } tzset(); return remoteTime; } TQString KTimezone::countryCode() const { return m_countryCode; } float KTimezone::latitude() const { return m_latitude; } float KTimezone::longitude() const { return m_longitude; } TQString KTimezone::name() const { return m_name; } int KTimezone::offset(Qt::TimeSpec basisSpec) const { char *originalZone = ::getenv("TZ"); // Get the time in the current timezone. TQDateTime basisTime = TQDateTime::tqcurrentDateTime(basisSpec); // Set the timezone and tqfind out what time it is there compared to the basis. ::setenv("TZ", m_name.utf8(), 1); tzset(); TQDateTime remoteTime = TQDateTime::tqcurrentDateTime(Qt::LocalTime); int offset = remoteTime.secsTo(basisTime); // Now restore things if (!originalZone) { ::unsetenv("TZ"); } else { ::setenv("TZ", originalZone, 1); } tzset(); return offset; } int KTimezone::offset(const TQDateTime &dateTime) const { OffsetFind tqfinder(dateTime.toTime_t()); int result = 0; if (parse(tqfinder)) { result = tqfinder.offset(); } return result; } bool KTimezone::parse(KTimezoneDetails &dataReceiver) const { dataReceiver.parseStarted(); bool result = m_db->parse(m_name, dataReceiver); dataReceiver.parseEnded(); return result; } KTimezones::KTimezones() : m_zoneinfoDir(), m_zones(0), d(0) { // Create the database (and resolve m_zoneinfoDir!). allZones(); m_UTC = new KTimezone(new DummySource(), UTC_ZONE); add(m_UTC); } KTimezones::~KTimezones() { // FIXME when needed: // delete d; // Autodelete behavior. if (m_zones) { for (ZoneMap::ConstIterator it = m_zones->begin(); it != m_zones->end(); ++it) { delete it.data(); } } delete m_zones; } void KTimezones::add(KTimezone *zone) { m_zones->insert(zone->name(), zone); } const KTimezones::ZoneMap KTimezones::allZones() { // Have we already done all the hard work? If not, create the cache. if (m_zones) return *m_zones; m_zones = new ZoneMap(); // Go read the database. // // On Windows, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones // is the place to look. The TZI binary value is the TIME_ZONE_INFORMATION structure. // // For Unix its all easy except knowing where to look. Try the LSB location first. TQFile f; m_zoneinfoDir = "/usr/share/zoneinfo"; f.setName(m_zoneinfoDir + "/zone.tab"); if (!f.open(IO_ReadOnly)) { kdDebug() << "Can't open " << f.name() << endl; m_zoneinfoDir = "/usr/lib/zoneinfo"; f.setName(m_zoneinfoDir + "/zone.tab"); if (!f.open(IO_ReadOnly)) { kdDebug() << "Can't open " << f.name() << endl; m_zoneinfoDir = ::getenv("TZDIR"); f.setName(m_zoneinfoDir + "/zone.tab"); if (m_zoneinfoDir.isEmpty() || !f.open(IO_ReadOnly)) { kdDebug() << "Can't open " << f.name() << endl; // Solaris support. Synthesise something that looks like a zone.tab. // // /bin/grep -h ^Zone /usr/share/lib/zoneinfo/src/* | /bin/awk '{print "??\t+9999+99999\t" $2}' // // where the country code is set to "??" and the lattitude/longitude // values are dummies. m_zoneinfoDir = "/usr/share/lib/zoneinfo"; KTempFile temp; KShellProcess reader; reader << "/bin/grep" << "-h" << "^Zone" << m_zoneinfoDir << "/src/*" << temp.name() << "|" << "/bin/awk" << "'{print \"??\\t+9999+99999\\t\" $2}'"; // Note the use of blocking here...it is a trivial amount of data! temp.close(); reader.start(KProcess::Block); f.setName(temp.name()); if (!temp.status() || !f.open(IO_ReadOnly)) { kdDebug() << "Can't open " << f.name() << endl; return *m_zones; } } } } // Parse the zone.tab. TQTextStream str(&f); TQRegExp lineSeparator("[ \t]"); TQRegExp ordinateSeparator("[+-]"); KSharedPtr db(new KTimezoneSource(m_zoneinfoDir)); while (!str.atEnd()) { TQString line = str.readLine(); if (line.isEmpty() || '#' == line[0]) continue; TQStringList tokens = KStringHandler::perlSplit(lineSeparator, line, 4); if (tokens.count() < 3) { kdError() << "invalid record: " << line << endl; continue; } // Got three tokens. Now check for two ordinates plus first one is "". TQStringList ordinates = KStringHandler::perlSplit(ordinateSeparator, tokens[1], 2); if (ordinates.count() < 2) { kdError() << "invalid coordinates: " << tokens[1] << endl; continue; } float latitude = convertCoordinate(ordinates[1]); float longitude = convertCoordinate(ordinates[2]); // Add entry to list. if (tokens[0] == "??") tokens[0] = ""; KTimezone *timezone = new KTimezone(db, tokens[2], tokens[0], latitude, longitude, tokens[3]); add(timezone); } f.close(); return *m_zones; } /** * Convert sHHMM or sHHMMSS to a floating point number of degrees. */ float KTimezones::convertCoordinate(const TQString &coordinate) { int value = coordinate.toInt(); int degrees = 0; int minutes = 0; int seconds = 0; if (coordinate.length() > 11) { degrees = value / 10000; value -= degrees * 10000; minutes = value / 100; value -= minutes * 100; seconds = value; } else { degrees = value / 100; value -= degrees * 100; minutes = value; } value = degrees * 3600 + minutes * 60 + seconds; return value / 3600.0; } const KTimezone *KTimezones::local() { const KTimezone *local = 0; // First try the simplest solution of checking for well-formed TZ setting. char *envZone = ::getenv("TZ"); if (envZone) { if (envZone[0] == '\0') { return m_UTC; } else if (envZone[0] == ':') { envZone++; } local = zone(envZone); } if (local) return local; // Try to match /etc/localtime against the list of zoneinfo files. TQFile f; f.setName("/etc/localtime"); if (f.open(IO_ReadOnly)) { // Compute the MD5 sum of /etc/localtime. KMD5 context(""); context.reset(); context.update(f); TQIODevice::Offset referenceSize = f.size(); TQString referenceMd5Sum = context.hexDigest(); f.close(); if (!m_zoneinfoDir.isEmpty()) { // Compare it with each zoneinfo file. for (ZoneMap::Iterator it = m_zones->begin(); it != m_zones->end(); ++it) { KTimezone *zone = it.data(); f.setName(m_zoneinfoDir + '/' + zone->name()); if (f.open(IO_ReadOnly)) { TQIODevice::Offset candidateSize = f.size(); TQString candidateMd5Sum; if (candidateSize == referenceSize) { // Only do the heavy lifting for file sizes which match. context.reset(); context.update(f); candidateMd5Sum = context.hexDigest(); } f.close(); if (candidateMd5Sum == referenceMd5Sum) { // kdError() << "local=" << zone->name() << endl; local = zone; break; } } } } } if (local) return local; // BSD support. TQString fileZone; f.setName("/etc/timezone"); if (!f.open(IO_ReadOnly)) { kdDebug() << "Can't open " << f.name() << endl; // Solaris support using /etc/default/init. f.setName("/etc/default/init"); if (!f.open(IO_ReadOnly)) { kdDebug() << "Can't open " << f.name() << endl; } else { TQTextStream ts(&f); ts.setEncoding(TQTextStream::Latin1); // Read the last line starting "TZ=". while (!ts.atEnd()) { fileZone = ts.readLine(); if (fileZone.startsWith("TZ=")) { fileZone = fileZone.mid(3); // kdError() << "local=" << fileZone << endl; local = zone(fileZone); } } f.close(); } } else { TQTextStream ts(&f); ts.setEncoding(TQTextStream::Latin1); // Read the first line. if (!ts.atEnd()) { fileZone = ts.readLine(); // kdError() << "local=" << fileZone << endl; local = zone(fileZone); } f.close(); } if (local) return local; // None of the deterministic stuff above has worked: try a heuristic. We // try to tqfind a pair of matching timezone abbreviations...that way, we'll // likely return a value in the user's own country. if (!m_zoneinfoDir.isEmpty()) { tzset(); AbbreviationsMatch matcher(tzname[0], tzname[1]); int bestOffset = INT_MAX; for (ZoneMap::Iterator it = m_zones->begin(); it != m_zones->end(); ++it) { KTimezone *zone = it.data(); int candidateOffset = QABS(zone->offset(Qt::LocalTime)); if (zone->parse(matcher) && matcher.test() && (candidateOffset < bestOffset)) { // kdError() << "local=" << zone->name() << endl; bestOffset = candidateOffset; local = zone; } } } if (local) return local; return m_UTC; } const KTimezone *KTimezones::zone(const TQString &name) { if (name.isEmpty()) return m_UTC; ZoneMap::ConstIterator it = m_zones->tqfind(name); if (it != m_zones->end()) return it.data(); // Error. return 0; } KTimezoneDetails::KTimezoneDetails() { } KTimezoneDetails::~KTimezoneDetails() { } void KTimezoneDetails::gotAbbreviation(int /*index*/, const TQString &) {} void KTimezoneDetails::gotHeader( unsigned, unsigned, unsigned, unsigned, unsigned, unsigned) {} void KTimezoneDetails::gotLeapAdjustment(int /*index*/, unsigned, unsigned) {} void KTimezoneDetails::gotLocalTime(int /*index*/, int, bool, unsigned) {} void KTimezoneDetails::gotLocalTimeIndex(int /*index*/, unsigned) {} void KTimezoneDetails::gotIsStandard(int /*index*/, bool) {} void KTimezoneDetails::gotTransitionTime(int /*index*/, unsigned) {} void KTimezoneDetails::gotIsUTC(int /*index*/, bool) {} void KTimezoneDetails::parseEnded() {} void KTimezoneDetails::parseStarted() {} KTimezoneSource::KTimezoneSource(const TQString &db) : m_db(db) { } KTimezoneSource::~KTimezoneSource() { } TQString KTimezoneSource::db() { return m_db; } bool KTimezoneSource::parse(const TQString &zone, KTimezoneDetails &dataReceiver) const { TQFile f(m_db + '/' + zone); if (!f.open(IO_ReadOnly)) { kdError() << "Cannot open " << f.name() << endl; return false; } // Structures that represent the zoneinfo file. TQ_UINT8 T, z, i_, f_; struct { TQ_UINT32 ttisgmtcnt; TQ_UINT32 ttisstdcnt; TQ_UINT32 leapcnt; TQ_UINT32 timecnt; TQ_UINT32 typecnt; TQ_UINT32 charcnt; } tzh; TQ_UINT32 transitionTime; TQ_UINT8 localTimeIndex; struct { TQ_INT32 gmtoff; TQ_INT8 isdst; TQ_UINT8 abbrind; } tt; TQ_UINT32 leapTime; TQ_UINT32 leapSeconds; TQ_UINT8 isStandard; TQ_UINT8 isUTC; TQDataStream str(&f); str >> T >> z >> i_ >> f_; // kdError() << "signature: " << TQChar(T) << TQChar(z) << TQChar(i_) << TQChar(f_) << endl; unsigned i; for (i = 0; i < 4; i++) str >> tzh.ttisgmtcnt; str >> tzh.ttisgmtcnt >> tzh.ttisstdcnt >> tzh.leapcnt >> tzh.timecnt >> tzh.typecnt >> tzh.charcnt; // kdError() << "header: " << tzh.ttisgmtcnt << ", " << tzh.ttisstdcnt << ", " << tzh.leapcnt << ", " << // tzh.timecnt << ", " << tzh.typecnt << ", " << tzh.charcnt << endl; dataReceiver.gotHeader(tzh.ttisgmtcnt, tzh.ttisstdcnt, tzh.leapcnt, tzh.timecnt, tzh.typecnt, tzh.charcnt); for (i = 0; i < tzh.timecnt; i++) { str >> transitionTime; dataReceiver.gotTransitionTime(i, transitionTime); } for (i = 0; i < tzh.timecnt; i++) { // NB: these appear to be 1-based, not zero-based! str >> localTimeIndex; dataReceiver.gotLocalTimeIndex(i, localTimeIndex); } for (i = 0; i < tzh.typecnt; i++) { str >> tt.gmtoff >> tt.isdst >> tt.abbrind; // kdError() << "local type: " << tt.gmtoff << ", " << tt.isdst << ", " << tt.abbrind << endl; dataReceiver.gotLocalTime(i, tt.gmtoff, (tt.isdst != 0), tt.abbrind); } // Make sure we don't run foul of maliciously coded timezone abbreviations. if (tzh.charcnt > 64) { kdError() << "excessive length for timezone abbreviations: " << tzh.charcnt << endl; return false; } TQByteArray array(tzh.charcnt); str.readRawBytes(array.data(), array.size()); char *abbrs = array.data(); if (abbrs[tzh.charcnt - 1] != 0) { // These abbrevations are corrupt! kdError() << "timezone abbreviations not terminated: " << abbrs[tzh.charcnt - 1] << endl; return false; } char *abbr = abbrs; while (abbr < abbrs + tzh.charcnt) { // kdError() << "abbr: " << abbr << endl; dataReceiver.gotAbbreviation((abbr - abbrs), abbr); abbr += strlen(abbr) + 1; } for (i = 0; i < tzh.leapcnt; i++) { str >> leapTime >> leapSeconds; // kdError() << "leap entry: " << leapTime << ", " << leapSeconds << endl; dataReceiver.gotLeapAdjustment(i, leapTime, leapSeconds); } for (i = 0; i < tzh.ttisstdcnt; i++) { str >> isStandard; // kdError() << "standard: " << isStandard << endl; dataReceiver.gotIsStandard(i, (isStandard != 0)); } for (i = 0; i < tzh.ttisgmtcnt; i++) { str >> isUTC; // kdError() << "UTC: " << isUTC << endl; dataReceiver.gotIsUTC(i, (isUTC != 0)); } return true; }