00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include <config.h>
00021
00022 #include <ktimezones.h>
00023 #include <kdebug.h>
00024 #include <kmdcodec.h>
00025 #include <kprocess.h>
00026 #include <kstringhandler.h>
00027 #include <ktempfile.h>
00028
00029 #include <qdatetime.h>
00030 #include <qfile.h>
00031 #include <qregexp.h>
00032 #include <qstringlist.h>
00033 #include <qtextstream.h>
00034
00035 #include <cerrno>
00036 #include <climits>
00037 #include <cstdlib>
00038 #include <cstring>
00039 #include <ctime>
00040
00041 #define UTC_ZONE "UTC"
00042
00051 class AbbreviationsMatch :
00052 public KTimezoneDetails
00053 {
00054 public:
00055 AbbreviationsMatch(const QString &stdZone, const QString &dstZone = "")
00056 {
00057 m_stdZone = stdZone;
00058 m_dstZone = dstZone;
00059 }
00060
00061 void parseStarted()
00062 {
00063 m_foundStd = false;
00064 m_foundDst = m_dstZone.isEmpty();
00065 }
00066
00067 bool test()
00068 {
00069 return (m_foundStd && m_foundDst);
00070 }
00071
00072 private:
00073 bool m_foundStd;
00074 bool m_foundDst;
00075 QString m_stdZone;
00076 QString m_dstZone;
00077
00078 virtual void gotAbbreviation(int , const QString &value)
00079 {
00080 if (m_stdZone == value)
00081 {
00082 m_foundStd = true;
00083 }
00084 if (m_dstZone == value)
00085 {
00086 m_foundDst = true;
00087 }
00088 }
00089 };
00090
00094 class DummySource :
00095 public KTimezoneSource
00096 {
00097 public:
00098 DummySource() :
00099 KTimezoneSource("")
00100 {
00101 }
00102
00103 virtual bool parse(const QString &, KTimezoneDetails &) const
00104 {
00105 return true;
00106 }
00107 };
00108
00112 class OffsetFind :
00113 public KTimezoneDetails
00114 {
00115 public:
00116 OffsetFind(unsigned dateTime)
00117 {
00118 m_dateTime = dateTime;
00119 }
00120
00121 void parseStarted()
00122 {
00123 m_transitionTimeIndex = 0;
00124 m_localTimeIndex = -1;
00125 m_abbrIndex = -1;
00126 m_offset = 0;
00127 m_isDst = false;
00128 m_abbr = UTC_ZONE;
00129 }
00130
00131 int offset()
00132 {
00133 return m_offset;
00134 }
00135
00136 bool isDst()
00137 {
00138 return m_isDst;
00139 }
00140
00141 QString abbreviation()
00142 {
00143 return m_abbr;
00144 }
00145
00146 private:
00147 unsigned m_dateTime;
00148 int m_transitionTimeIndex;
00149 int m_localTimeIndex;
00150 int m_abbrIndex;
00151 int m_offset;
00152 bool m_isDst;
00153 QString m_abbr;
00154
00155 virtual void gotTransitionTime(int index, unsigned transitionTime)
00156 {
00157 if (transitionTime <= m_dateTime)
00158 {
00159
00160 m_transitionTimeIndex = index;
00161 }
00162 }
00163
00164 virtual void gotLocalTimeIndex(int index, unsigned localTimeIndex)
00165 {
00166 if (index == m_transitionTimeIndex)
00167 {
00168
00169 m_localTimeIndex = localTimeIndex;
00170 }
00171 }
00172
00173 virtual void gotLocalTime(int index, int gmtOff, bool isDst, unsigned abbrInd)
00174 {
00175 if (index == m_localTimeIndex)
00176 {
00177
00178 m_offset = gmtOff;
00179 m_isDst = isDst;
00180 m_abbrIndex = abbrInd;
00181 }
00182 }
00183
00184 virtual void gotAbbreviation(int index, const QString &value)
00185 {
00186 if (index == m_abbrIndex)
00187 {
00188 m_abbr = value;
00189 }
00190 }
00191 };
00192
00193 const float KTimezone::UNKNOWN = 1000.0;
00194
00195 bool KTimezone::isValidLatitude(float latitude)
00196 {
00197 return (latitude >= -90.0) && (latitude <= 90.0);
00198 }
00199
00200 bool KTimezone::isValidLongitude(float longitude)
00201 {
00202 return (longitude >= -180.0) && (longitude <= 180.0);
00203 }
00204
00205 KTimezone::KTimezone(
00206 KSharedPtr<KTimezoneSource> db, const QString& name,
00207 const QString &countryCode, float latitude, float longitude,
00208 const QString &comment) :
00209 m_db(db),
00210 m_name(name),
00211 m_countryCode(countryCode),
00212 m_latitude(latitude),
00213 m_longitude(longitude),
00214 m_comment(comment),
00215 d(0)
00216 {
00217
00218 if (m_latitude * m_latitude > 90 * 90)
00219 m_latitude = UNKNOWN;
00220 if (m_longitude * m_longitude > 180 * 180)
00221 m_longitude = UNKNOWN;
00222 }
00223
00224 KTimezone::~KTimezone()
00225 {
00226
00227
00228 }
00229
00230 QString KTimezone::comment() const
00231 {
00232 return m_comment;
00233 }
00234
00235 QDateTime KTimezone::convert(const KTimezone *newZone, const QDateTime &dateTime) const
00236 {
00237 char *originalZone = ::getenv("TZ");
00238
00239
00240 ::setenv("TZ", m_name.utf8(), 1);
00241 tzset();
00242 unsigned utc = dateTime.toTime_t();
00243
00244
00245 ::setenv("TZ", newZone->name().utf8(), 1);
00246 tzset();
00247 QDateTime remoteTime;
00248 remoteTime.setTime_t(utc, Qt::LocalTime);
00249
00250
00251 if (!originalZone)
00252 {
00253 ::unsetenv("TZ");
00254 }
00255 else
00256 {
00257 ::setenv("TZ", originalZone, 1);
00258 }
00259 tzset();
00260 return remoteTime;
00261 }
00262
00263 QString KTimezone::countryCode() const
00264 {
00265 return m_countryCode;
00266 }
00267
00268 float KTimezone::latitude() const
00269 {
00270 return m_latitude;
00271 }
00272
00273 float KTimezone::longitude() const
00274 {
00275 return m_longitude;
00276 }
00277
00278 QString KTimezone::name() const
00279 {
00280 return m_name;
00281 }
00282
00283 int KTimezone::offset(Qt::TimeSpec basisSpec) const
00284 {
00285 char *originalZone = ::getenv("TZ");
00286
00287
00288 QDateTime basisTime = QDateTime::currentDateTime(basisSpec);
00289
00290
00291 ::setenv("TZ", m_name.utf8(), 1);
00292 tzset();
00293 QDateTime remoteTime = QDateTime::currentDateTime(Qt::LocalTime);
00294 int offset = remoteTime.secsTo(basisTime);
00295
00296
00297 if (!originalZone)
00298 {
00299 ::unsetenv("TZ");
00300 }
00301 else
00302 {
00303 ::setenv("TZ", originalZone, 1);
00304 }
00305 tzset();
00306 return offset;
00307 }
00308
00309 int KTimezone::offset(const QDateTime &dateTime) const
00310 {
00311 OffsetFind finder(dateTime.toTime_t());
00312 int result = 0;
00313 if (parse(finder))
00314 {
00315 result = finder.offset();
00316 }
00317 return result;
00318 }
00319
00320 bool KTimezone::parse(KTimezoneDetails &dataReceiver) const
00321 {
00322 dataReceiver.parseStarted();
00323 bool result = m_db->parse(m_name, dataReceiver);
00324 dataReceiver.parseEnded();
00325 return result;
00326 }
00327
00328 KTimezones::KTimezones() :
00329 m_zoneinfoDir(),
00330 m_zones(0),
00331 d(0)
00332 {
00333
00334 allZones();
00335 m_UTC = new KTimezone(new DummySource(), UTC_ZONE);
00336 add(m_UTC);
00337 }
00338
00339 KTimezones::~KTimezones()
00340 {
00341
00342
00343
00344
00345 if (m_zones)
00346 {
00347 for (ZoneMap::ConstIterator it = m_zones->begin(); it != m_zones->end(); ++it)
00348 {
00349 delete it.data();
00350 }
00351 }
00352 delete m_zones;
00353 }
00354
00355 void KTimezones::add(KTimezone *zone)
00356 {
00357 m_zones->insert(zone->name(), zone);
00358 }
00359
00360 const KTimezones::ZoneMap KTimezones::allZones()
00361 {
00362
00363 if (m_zones)
00364 return *m_zones;
00365 m_zones = new ZoneMap();
00366
00367
00368
00369
00370
00371
00372
00373 QFile f;
00374 m_zoneinfoDir = "/usr/share/zoneinfo";
00375 f.setName(m_zoneinfoDir + "/zone.tab");
00376 if (!f.open(IO_ReadOnly))
00377 {
00378 kdDebug() << "Can't open " << f.name() << endl;
00379 m_zoneinfoDir = "/usr/lib/zoneinfo";
00380 f.setName(m_zoneinfoDir + "/zone.tab");
00381 if (!f.open(IO_ReadOnly))
00382 {
00383 kdDebug() << "Can't open " << f.name() << endl;
00384 m_zoneinfoDir = ::getenv("TZDIR");
00385 f.setName(m_zoneinfoDir + "/zone.tab");
00386 if (m_zoneinfoDir.isEmpty() || !f.open(IO_ReadOnly))
00387 {
00388 kdDebug() << "Can't open " << f.name() << endl;
00389
00390
00391
00392
00393
00394
00395
00396 m_zoneinfoDir = "/usr/share/lib/zoneinfo";
00397 KTempFile temp;
00398 KShellProcess reader;
00399 reader << "/bin/grep" << "-h" << "^Zone" << m_zoneinfoDir << "/src/*" << temp.name() << "|" <<
00400 "/bin/awk" << "'{print \"??\\t+9999+99999\\t\" $2}'";
00401
00402 temp.close();
00403 reader.start(KProcess::Block);
00404 f.setName(temp.name());
00405 if (!temp.status() || !f.open(IO_ReadOnly))
00406 {
00407 kdDebug() << "Can't open " << f.name() << endl;
00408 return *m_zones;
00409 }
00410 }
00411 }
00412 }
00413
00414
00415 QTextStream str(&f);
00416 QRegExp lineSeparator("[ \t]");
00417 QRegExp ordinateSeparator("[+-]");
00418 KSharedPtr<KTimezoneSource> db(new KTimezoneSource(m_zoneinfoDir));
00419 while (!str.atEnd())
00420 {
00421 QString line = str.readLine();
00422 if (line.isEmpty() || '#' == line[0])
00423 continue;
00424 QStringList tokens = KStringHandler::perlSplit(lineSeparator, line, 4);
00425 if (tokens.count() < 3)
00426 {
00427 kdError() << "invalid record: " << line << endl;
00428 continue;
00429 }
00430
00431
00432 QStringList ordinates = KStringHandler::perlSplit(ordinateSeparator, tokens[1], 2);
00433 if (ordinates.count() < 2)
00434 {
00435 kdError() << "invalid coordinates: " << tokens[1] << endl;
00436 continue;
00437 }
00438
00439 float latitude = convertCoordinate(ordinates[1]);
00440 float longitude = convertCoordinate(ordinates[2]);
00441
00442
00443 if (tokens[0] == "??")
00444 tokens[0] = "";
00445 KTimezone *timezone = new KTimezone(db, tokens[2], tokens[0], latitude, longitude, tokens[3]);
00446 add(timezone);
00447 }
00448 f.close();
00449 return *m_zones;
00450 }
00451
00455 float KTimezones::convertCoordinate(const QString &coordinate)
00456 {
00457 int value = coordinate.toInt();
00458 int degrees = 0;
00459 int minutes = 0;
00460 int seconds = 0;
00461
00462 if (coordinate.length() > 11)
00463 {
00464 degrees = value / 10000;
00465 value -= degrees * 10000;
00466 minutes = value / 100;
00467 value -= minutes * 100;
00468 seconds = value;
00469 }
00470 else
00471 {
00472 degrees = value / 100;
00473 value -= degrees * 100;
00474 minutes = value;
00475 }
00476 value = degrees * 3600 + minutes * 60 + seconds;
00477 return value / 3600.0;
00478 }
00479
00480 const KTimezone *KTimezones::local()
00481 {
00482 const KTimezone *local = 0;
00483
00484
00485 char *envZone = ::getenv("TZ");
00486 if (envZone)
00487 {
00488 if (envZone[0] == '\0')
00489 {
00490 return m_UTC;
00491 }
00492 else
00493 if (envZone[0] == ':')
00494 {
00495 envZone++;
00496 }
00497 local = zone(envZone);
00498 }
00499 if (local)
00500 return local;
00501
00502
00503 QFile f;
00504 f.setName("/etc/localtime");
00505 if (f.open(IO_ReadOnly))
00506 {
00507
00508 KMD5 context("");
00509 context.reset();
00510 context.update(f);
00511 QIODevice::Offset referenceSize = f.size();
00512 QString referenceMd5Sum = context.hexDigest();
00513 f.close();
00514 if (!m_zoneinfoDir.isEmpty())
00515 {
00516
00517 for (ZoneMap::Iterator it = m_zones->begin(); it != m_zones->end(); ++it)
00518 {
00519 KTimezone *zone = it.data();
00520 f.setName(m_zoneinfoDir + '/' + zone->name());
00521 if (f.open(IO_ReadOnly))
00522 {
00523 QIODevice::Offset candidateSize = f.size();
00524 QString candidateMd5Sum;
00525 if (candidateSize == referenceSize)
00526 {
00527
00528 context.reset();
00529 context.update(f);
00530 candidateMd5Sum = context.hexDigest();
00531 }
00532 f.close();
00533 if (candidateMd5Sum == referenceMd5Sum)
00534 {
00535
00536 local = zone;
00537 break;
00538 }
00539 }
00540 }
00541 }
00542 }
00543 if (local)
00544 return local;
00545
00546
00547 QString fileZone;
00548 f.setName("/etc/timezone");
00549 if (!f.open(IO_ReadOnly))
00550 {
00551 kdDebug() << "Can't open " << f.name() << endl;
00552
00553
00554 f.setName("/etc/default/init");
00555 if (!f.open(IO_ReadOnly))
00556 {
00557 kdDebug() << "Can't open " << f.name() << endl;
00558 }
00559 else
00560 {
00561 QTextStream ts(&f);
00562 ts.setEncoding(QTextStream::Latin1);
00563
00564
00565 while (!ts.atEnd())
00566 {
00567 fileZone = ts.readLine();
00568 if (fileZone.startsWith("TZ="))
00569 {
00570 fileZone = fileZone.mid(3);
00571
00572
00573 local = zone(fileZone);
00574 }
00575 }
00576 f.close();
00577 }
00578 }
00579 else
00580 {
00581 QTextStream ts(&f);
00582 ts.setEncoding(QTextStream::Latin1);
00583
00584
00585 if (!ts.atEnd())
00586 {
00587 fileZone = ts.readLine();
00588
00589
00590 local = zone(fileZone);
00591 }
00592 f.close();
00593 }
00594 if (local)
00595 return local;
00596
00597
00598
00599
00600 if (!m_zoneinfoDir.isEmpty())
00601 {
00602 tzset();
00603 AbbreviationsMatch matcher(tzname[0], tzname[1]);
00604 int bestOffset = INT_MAX;
00605 for (ZoneMap::Iterator it = m_zones->begin(); it != m_zones->end(); ++it)
00606 {
00607 KTimezone *zone = it.data();
00608 int candidateOffset = QABS(zone->offset(Qt::LocalTime));
00609 if (zone->parse(matcher) && matcher.test() && (candidateOffset < bestOffset))
00610 {
00611
00612 bestOffset = candidateOffset;
00613 local = zone;
00614 }
00615 }
00616 }
00617 if (local)
00618 return local;
00619 return m_UTC;
00620 }
00621
00622 const KTimezone *KTimezones::zone(const QString &name)
00623 {
00624 if (name.isEmpty())
00625 return m_UTC;
00626 ZoneMap::ConstIterator it = m_zones->find(name);
00627 if (it != m_zones->end())
00628 return it.data();
00629
00630
00631 return 0;
00632 }
00633
00634 KTimezoneDetails::KTimezoneDetails()
00635 {
00636 }
00637
00638 KTimezoneDetails::~KTimezoneDetails()
00639 {
00640 }
00641
00642 void KTimezoneDetails::gotAbbreviation(int , const QString &)
00643 {}
00644
00645 void KTimezoneDetails::gotHeader(
00646 unsigned, unsigned, unsigned,
00647 unsigned, unsigned, unsigned)
00648 {}
00649
00650 void KTimezoneDetails::gotLeapAdjustment(int , unsigned, unsigned)
00651 {}
00652
00653 void KTimezoneDetails::gotLocalTime(int , int, bool, unsigned)
00654 {}
00655
00656 void KTimezoneDetails::gotLocalTimeIndex(int , unsigned)
00657 {}
00658
00659 void KTimezoneDetails::gotIsStandard(int , bool)
00660 {}
00661
00662 void KTimezoneDetails::gotTransitionTime(int , unsigned)
00663 {}
00664
00665 void KTimezoneDetails::gotIsUTC(int , bool)
00666 {}
00667
00668 void KTimezoneDetails::parseEnded()
00669 {}
00670
00671 void KTimezoneDetails::parseStarted()
00672 {}
00673
00674 KTimezoneSource::KTimezoneSource(const QString &db) :
00675 m_db(db)
00676 {
00677 }
00678
00679 KTimezoneSource::~KTimezoneSource()
00680 {
00681 }
00682
00683 QString KTimezoneSource::db()
00684 {
00685 return m_db;
00686 }
00687
00688 bool KTimezoneSource::parse(const QString &zone, KTimezoneDetails &dataReceiver) const
00689 {
00690 QFile f(m_db + '/' + zone);
00691 if (!f.open(IO_ReadOnly))
00692 {
00693 kdError() << "Cannot open " << f.name() << endl;
00694 return false;
00695 }
00696
00697
00698 Q_UINT8 T, z, i_, f_;
00699 struct
00700 {
00701 Q_UINT32 ttisgmtcnt;
00702 Q_UINT32 ttisstdcnt;
00703 Q_UINT32 leapcnt;
00704 Q_UINT32 timecnt;
00705 Q_UINT32 typecnt;
00706 Q_UINT32 charcnt;
00707 } tzh;
00708 Q_UINT32 transitionTime;
00709 Q_UINT8 localTimeIndex;
00710 struct
00711 {
00712 Q_INT32 gmtoff;
00713 Q_INT8 isdst;
00714 Q_UINT8 abbrind;
00715 } tt;
00716 Q_UINT32 leapTime;
00717 Q_UINT32 leapSeconds;
00718 Q_UINT8 isStandard;
00719 Q_UINT8 isUTC;
00720
00721 QDataStream str(&f);
00722 str >> T >> z >> i_ >> f_;
00723
00724 unsigned i;
00725 for (i = 0; i < 4; i++)
00726 str >> tzh.ttisgmtcnt;
00727 str >> tzh.ttisgmtcnt >> tzh.ttisstdcnt >> tzh.leapcnt >> tzh.timecnt >> tzh.typecnt >> tzh.charcnt;
00728
00729
00730 dataReceiver.gotHeader(tzh.ttisgmtcnt, tzh.ttisstdcnt, tzh.leapcnt, tzh.timecnt, tzh.typecnt, tzh.charcnt);
00731 for (i = 0; i < tzh.timecnt; i++)
00732 {
00733 str >> transitionTime;
00734 dataReceiver.gotTransitionTime(i, transitionTime);
00735 }
00736 for (i = 0; i < tzh.timecnt; i++)
00737 {
00738
00739 str >> localTimeIndex;
00740 dataReceiver.gotLocalTimeIndex(i, localTimeIndex);
00741 }
00742 for (i = 0; i < tzh.typecnt; i++)
00743 {
00744 str >> tt.gmtoff >> tt.isdst >> tt.abbrind;
00745
00746 dataReceiver.gotLocalTime(i, tt.gmtoff, (tt.isdst != 0), tt.abbrind);
00747 }
00748
00749
00750 if (tzh.charcnt > 64)
00751 {
00752 kdError() << "excessive length for timezone abbreviations: " << tzh.charcnt << endl;
00753 return false;
00754 }
00755 QByteArray array(tzh.charcnt);
00756 str.readRawBytes(array.data(), array.size());
00757 char *abbrs = array.data();
00758 if (abbrs[tzh.charcnt - 1] != 0)
00759 {
00760
00761 kdError() << "timezone abbreviations not terminated: " << abbrs[tzh.charcnt - 1] << endl;
00762 return false;
00763 }
00764 char *abbr = abbrs;
00765 while (abbr < abbrs + tzh.charcnt)
00766 {
00767
00768 dataReceiver.gotAbbreviation((abbr - abbrs), abbr);
00769 abbr += strlen(abbr) + 1;
00770 }
00771 for (i = 0; i < tzh.leapcnt; i++)
00772 {
00773 str >> leapTime >> leapSeconds;
00774
00775 dataReceiver.gotLeapAdjustment(i, leapTime, leapSeconds);
00776 }
00777 for (i = 0; i < tzh.ttisstdcnt; i++)
00778 {
00779 str >> isStandard;
00780
00781 dataReceiver.gotIsStandard(i, (isStandard != 0));
00782 }
00783 for (i = 0; i < tzh.ttisgmtcnt; i++)
00784 {
00785 str >> isUTC;
00786
00787 dataReceiver.gotIsUTC(i, (isUTC != 0));
00788 }
00789 return true;
00790 }