File indexing completed on 2023-09-24 04:04:57
0001 /* 0002 This file is part of the KDE libraries 0003 Copyright (c) 2005-2008 David Jarvie <djarvie@kde.org> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Library General Public 0007 License as published by the Free Software Foundation; either 0008 version 2 of the License, or (at your option) any later version. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "ktzfiletimezone.h" 0022 0023 #include <config-date.h> 0024 0025 #if HAVE_SYS_TIME_H 0026 #include <sys/time.h> 0027 #endif 0028 #if HAVE_TIME_H 0029 #include <time.h> 0030 #endif 0031 0032 #include <QDebug> 0033 #include <QFile> 0034 #include <QDataStream> 0035 #include <QVector> 0036 0037 // Use this replacement for QDateTime::setTime_t(uint) since our time 0038 // values are signed. 0039 static QDateTime fromTime_t(qint32 seconds) 0040 { 0041 static const QDate epochDate(1970, 1, 1); 0042 static const QTime epochTime(0, 0, 0); 0043 int days = seconds / 86400; 0044 seconds -= days * 86400; 0045 if (seconds < 0) { 0046 --days; 0047 seconds += 86400; 0048 } 0049 return QDateTime(epochDate.addDays(days), epochTime.addSecs(seconds), Qt::UTC); 0050 } 0051 0052 /******************************************************************************/ 0053 0054 KTzfileTimeZoneBackend::KTzfileTimeZoneBackend(KTzfileTimeZoneSource *source, const QString &name, 0055 const QString &countryCode, float latitude, float longitude, const QString &comment) 0056 : KTimeZoneBackend(source, name, countryCode, latitude, longitude, comment) 0057 {} 0058 0059 KTzfileTimeZoneBackend::~KTzfileTimeZoneBackend() 0060 {} 0061 0062 KTimeZoneBackend *KTzfileTimeZoneBackend::clone() const 0063 { 0064 return new KTzfileTimeZoneBackend(*this); 0065 } 0066 0067 QByteArray KTzfileTimeZoneBackend::type() const 0068 { 0069 return "KTzfileTimeZone"; 0070 } 0071 0072 bool KTzfileTimeZoneBackend::hasTransitions(const KTimeZone *caller) const 0073 { 0074 Q_UNUSED(caller) 0075 return true; 0076 } 0077 0078 /******************************************************************************/ 0079 0080 KTzfileTimeZone::KTzfileTimeZone(KTzfileTimeZoneSource *source, const QString &name, 0081 const QString &countryCode, float latitude, float longitude, 0082 const QString &comment) 0083 : KTimeZone(new KTzfileTimeZoneBackend(source, name, countryCode, latitude, longitude, comment)) 0084 {} 0085 0086 KTzfileTimeZone::~KTzfileTimeZone() 0087 {} 0088 0089 /******************************************************************************/ 0090 0091 class KTzfileTimeZoneDataPrivate 0092 { 0093 public: 0094 }; 0095 0096 KTzfileTimeZoneData::KTzfileTimeZoneData() 0097 // : d(new KTzfileTimeZoneDataPrivate) 0098 { } 0099 0100 KTzfileTimeZoneData::KTzfileTimeZoneData(const KTzfileTimeZoneData &rhs) 0101 : KTimeZoneData(rhs) 0102 // d(new KTzfileTimeZoneDataPrivate) 0103 { 0104 } 0105 0106 KTzfileTimeZoneData::~KTzfileTimeZoneData() 0107 { 0108 // delete d; 0109 } 0110 0111 KTzfileTimeZoneData &KTzfileTimeZoneData::operator=(const KTzfileTimeZoneData &rhs) 0112 { 0113 KTimeZoneData::operator=(rhs); 0114 return *this; 0115 } 0116 0117 KTimeZoneData *KTzfileTimeZoneData::clone() const 0118 { 0119 return new KTzfileTimeZoneData(*this); 0120 } 0121 0122 bool KTzfileTimeZoneData::hasTransitions() const 0123 { 0124 return true; 0125 } 0126 0127 /******************************************************************************/ 0128 0129 class KTzfileTimeZoneSourcePrivate 0130 { 0131 public: 0132 KTzfileTimeZoneSourcePrivate(const QString &loc) 0133 : location(loc) {} 0134 ~KTzfileTimeZoneSourcePrivate() {} 0135 0136 QString location; 0137 }; 0138 0139 KTzfileTimeZoneSource::KTzfileTimeZoneSource(const QString &location) 0140 : d(new KTzfileTimeZoneSourcePrivate(location)) 0141 { 0142 if (location.length() > 1 && location.endsWith(QLatin1Char('/'))) { 0143 d->location.chop(1); 0144 } 0145 } 0146 0147 KTzfileTimeZoneSource::~KTzfileTimeZoneSource() 0148 { 0149 delete d; 0150 } 0151 0152 QString KTzfileTimeZoneSource::location() const 0153 { 0154 return d->location; 0155 } 0156 0157 KTimeZoneData *KTzfileTimeZoneSource::parse(const KTimeZone &zone) const 0158 { 0159 quint32 abbrCharCount; // the number of characters of time zone abbreviation strings 0160 quint32 ttisgmtcnt; 0161 quint8 is; 0162 quint8 T_, Z_, i_, f_; // tzfile identifier prefix 0163 0164 QString path = zone.name(); 0165 if (!path.startsWith(QLatin1Char('/'))) { 0166 if (d->location == QLatin1String("/")) { 0167 path.prepend(d->location); 0168 } else { 0169 path = d->location + QLatin1Char('/') + path; 0170 } 0171 } 0172 QFile f(path); 0173 if (!f.open(QIODevice::ReadOnly)) { 0174 qCritical() << "Cannot open " << f.fileName() << endl; 0175 return nullptr; 0176 } 0177 QDataStream str(&f); 0178 0179 // Read the file type identifier 0180 str >> T_ >> Z_ >> i_ >> f_; 0181 if (T_ != 'T' || Z_ != 'Z' || i_ != 'i' || f_ != 'f') { 0182 qCritical() << "Not a TZFILE: " << f.fileName() << endl; 0183 return nullptr; 0184 } 0185 // Discard 16 bytes reserved for future use 0186 unsigned i; 0187 for (i = 0; i < 4; ++i) { 0188 str >> ttisgmtcnt; 0189 } 0190 0191 KTzfileTimeZoneData *data = new KTzfileTimeZoneData; 0192 0193 // Read the sizes of arrays held in the file 0194 quint32 nTransitionTimes; 0195 quint32 nLocalTimeTypes; 0196 quint32 nLeapSecondAdjusts; 0197 quint32 nIsStandard; 0198 quint32 nIsUtc; 0199 str >> nIsUtc 0200 >> nIsStandard 0201 >> nLeapSecondAdjusts 0202 >> nTransitionTimes 0203 >> nLocalTimeTypes 0204 >> abbrCharCount; 0205 // qDebug() << "header: " << nIsUtc << ", " << nIsStandard << ", " << nLeapSecondAdjusts << ", " << 0206 // nTransitionTimes << ", " << nLocalTimeTypes << ", " << abbrCharCount << endl; 0207 0208 // Read the transition times, at which the rules for computing local time change 0209 struct TransitionTime { 0210 qint32 time; // time (as returned by time(2)) at which the rules for computing local time change 0211 quint8 localTimeIndex; // index into the LocalTimeType array 0212 }; 0213 //qDebug()<<"Reading zone "<<zone.name(); 0214 TransitionTime *transitionTimes = new TransitionTime[nTransitionTimes]; 0215 for (i = 0; i < nTransitionTimes; ++i) { 0216 str >> transitionTimes[i].time; 0217 } 0218 for (i = 0; i < nTransitionTimes; ++i) { 0219 str >> transitionTimes[i].localTimeIndex; 0220 //qDebug() << "Transition time "<<i<<": "<<transitionTimes[i].time<<" lt index="<<(int)transitionTimes[i].localTimeIndex; 0221 } 0222 0223 // Read the local time types 0224 struct LocalTimeType { 0225 qint32 gmtoff; // number of seconds to be added to UTC 0226 bool isdst; // whether tm_isdst should be set by localtime(3) 0227 quint8 abbrIndex; // index into the list of time zone abbreviations 0228 bool isutc; // transition times are in UTC. If UTC, isstd is ignored. 0229 bool isstd; // if true, transition times are in standard time; 0230 // if false, transition times are in wall clock time, 0231 // i.e. standard time or daylight savings time 0232 // whichever is current before the transition 0233 }; 0234 LocalTimeType *localTimeTypes = new LocalTimeType[nLocalTimeTypes]; 0235 LocalTimeType *ltt = localTimeTypes; 0236 for (i = 0; i < nLocalTimeTypes; ++ltt, ++i) { 0237 str >> ltt->gmtoff; 0238 str >> is; 0239 ltt->isdst = (is != 0); 0240 str >> ltt->abbrIndex; 0241 // qDebug() << "local type: " << ltt->gmtoff << ", " << is << ", " << ltt->abbrIndex; 0242 ltt->isstd = false; // default if no data 0243 ltt->isutc = false; // default if no data 0244 } 0245 0246 // Read the timezone abbreviations. They are stored as null terminated strings in 0247 // a character array. 0248 // Make sure we don't fall foul of maliciously coded time zone abbreviations. 0249 if (abbrCharCount > 64) { 0250 qCritical() << "excessive length for timezone abbreviations: " << abbrCharCount << endl; 0251 delete data; 0252 delete[] transitionTimes; 0253 delete[] localTimeTypes; 0254 return nullptr; 0255 } 0256 QByteArray array(abbrCharCount, 0); 0257 str.readRawData(array.data(), array.size()); 0258 const char *abbrs = array.constData(); 0259 if (abbrs[abbrCharCount - 1] != 0) { 0260 // These abbreviations are corrupt! 0261 qCritical() << "timezone abbreviations not null terminated: " << abbrs[abbrCharCount - 1] << endl; 0262 delete data; 0263 delete[] transitionTimes; 0264 delete[] localTimeTypes; 0265 return nullptr; 0266 } 0267 quint8 n = 0; 0268 QList<QByteArray> abbreviations; 0269 for (i = 0; i < abbrCharCount; ++n, i += strlen(abbrs + i) + 1) { 0270 abbreviations += QByteArray(abbrs + i); 0271 // Convert the LocalTimeTypes pointer to a sequential index 0272 ltt = localTimeTypes; 0273 for (unsigned j = 0; j < nLocalTimeTypes; ++ltt, ++j) { 0274 if (ltt->abbrIndex == i) { 0275 ltt->abbrIndex = n; 0276 } 0277 } 0278 } 0279 0280 // Read the leap second adjustments 0281 qint32 t; 0282 quint32 s; 0283 QList<KTimeZone::LeapSeconds> leapChanges; 0284 for (i = 0; i < nLeapSecondAdjusts; ++i) { 0285 str >> t >> s; 0286 // qDebug() << "leap entry: " << t << ", " << s; 0287 // Don't use QDateTime::setTime_t() because it takes an unsigned argument 0288 leapChanges += KTimeZone::LeapSeconds(fromTime_t(t), static_cast<int>(s)); 0289 } 0290 data->setLeapSecondChanges(leapChanges); 0291 0292 // Read the standard/wall time indicators. 0293 // These are true if the transition times associated with local time types 0294 // are specified as standard time, false if wall clock time. 0295 for (i = 0; i < nIsStandard; ++i) { 0296 str >> is; 0297 localTimeTypes[i].isstd = (is != 0); 0298 // qDebug() << "standard: " << is; 0299 } 0300 0301 // Read the UTC/local time indicators. 0302 // These are true if the transition times associated with local time types 0303 // are specified as UTC, false if local time. 0304 for (i = 0; i < nIsUtc; ++i) { 0305 str >> is; 0306 localTimeTypes[i].isutc = (is != 0); 0307 // qDebug() << "UTC: " << is; 0308 } 0309 0310 // Find the starting offset from UTC to use before the first transition time. 0311 // This is first non-daylight savings local time type, or if there is none, 0312 // the first local time type. 0313 LocalTimeType *firstLtt = nullptr; 0314 ltt = localTimeTypes; 0315 for (i = 0; i < nLocalTimeTypes; ++ltt, ++i) { 0316 if (!ltt->isdst) { 0317 firstLtt = ltt; 0318 break; 0319 } 0320 } 0321 0322 // Compile the time type data into a list of KTimeZone::Phase instances. 0323 // Also check for local time types which are identical (this does happen) 0324 // and use the same Phase instance for each. 0325 QByteArray abbrev; 0326 QList<KTimeZone::Phase> phases; 0327 QList<QByteArray> phaseAbbrevs; 0328 QVector<int> lttLookup(nLocalTimeTypes); 0329 ltt = localTimeTypes; 0330 for (i = 0; i < nLocalTimeTypes; ++ltt, ++i) { 0331 if (ltt->abbrIndex >= abbreviations.count()) { 0332 qCritical() << "KTzfileTimeZoneSource::parse(): abbreviation index out of range" << endl; 0333 abbrev = "???"; 0334 } else { 0335 abbrev = abbreviations[ltt->abbrIndex]; 0336 } 0337 // Check for an identical Phase 0338 int phindex = 0; 0339 for (int j = 0, jend = phases.count(); j < jend; ++j, ++phindex) { 0340 if (ltt->gmtoff == phases[j].utcOffset() 0341 && (bool)ltt->isdst == phases[j].isDst() 0342 && abbrev == phaseAbbrevs[j]) { 0343 break; 0344 } 0345 } 0346 lttLookup[i] = phindex; 0347 if (phindex == phases.count()) { 0348 phases += KTimeZone::Phase(ltt->gmtoff, abbrev, ltt->isdst); 0349 phaseAbbrevs += abbrev; 0350 } 0351 } 0352 KTimeZone::Phase prePhase(firstLtt->gmtoff, 0353 (firstLtt->abbrIndex < abbreviations.count() ? abbreviations[firstLtt->abbrIndex] : ""), 0354 false); 0355 data->setPhases(phases, prePhase); 0356 0357 // Compile the transition list 0358 QList<KTimeZone::Transition> transitions; 0359 TransitionTime *tt = transitionTimes; 0360 for (i = 0; i < nTransitionTimes; ++tt, ++i) { 0361 if (tt->localTimeIndex >= nLocalTimeTypes) { 0362 qCritical() << "KTzfileTimeZoneSource::parse(): transition ignored: local time type out of range: " << (int)tt->localTimeIndex << " > " << nLocalTimeTypes << endl; 0363 continue; 0364 } 0365 0366 // Convert local transition times to UTC 0367 ltt = &localTimeTypes[tt->localTimeIndex]; 0368 const KTimeZone::Phase phase = phases[lttLookup[tt->localTimeIndex]]; 0369 //qDebug(161) << "Transition time "<<i<<": "<<fromTime_t(tt->time)<<", offset="<<phase.utcOffset()/60; 0370 transitions += KTimeZone::Transition(fromTime_t(tt->time), phase); 0371 } 0372 data->setTransitions(transitions); 0373 //for(int xxx=1;xxx<data->transitions().count();xxx++) 0374 //qDebug(161) << "Transition time "<<xxx<<": "<<data->transitions()[xxx].time()<<", offset="<<data->transitions()[xxx].phase().utcOffset()/60; 0375 delete[] localTimeTypes; 0376 delete[] transitionTimes; 0377 0378 return data; 0379 }