1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Copyright (C) 2019 Crimson AS <info@crimson.no>
5 ** Copyright (C) 2013 John Layt <jlayt@kde.org>
6 ** Contact: https://www.qt.io/licensing/
7 **
8 ** This file is part of the QtCore module of the Qt Toolkit.
9 **
10 ** $QT_BEGIN_LICENSE:LGPL$
11 ** Commercial License Usage
12 ** Licensees holding valid commercial Qt licenses may use this file in
13 ** accordance with the commercial license agreement provided with the
14 ** Software or, alternatively, in accordance with the terms contained in
15 ** a written agreement between you and The Qt Company. For licensing terms
16 ** and conditions see https://www.qt.io/terms-conditions. For further
17 ** information use the contact form at https://www.qt.io/contact-us.
18 **
19 ** GNU Lesser General Public License Usage
20 ** Alternatively, this file may be used under the terms of the GNU Lesser
21 ** General Public License version 3 as published by the Free Software
22 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
23 ** packaging of this file. Please review the following information to
24 ** ensure the GNU Lesser General Public License version 3 requirements
25 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26 **
27 ** GNU General Public License Usage
28 ** Alternatively, this file may be used under the terms of the GNU
29 ** General Public License version 2.0 or (at your option) the GNU General
30 ** Public license version 3 or any later version approved by the KDE Free
31 ** Qt Foundation. The licenses are as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33 ** included in the packaging of this file. Please review the following
34 ** information to ensure the GNU General Public License requirements will
35 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36 ** https://www.gnu.org/licenses/gpl-3.0.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qtimezone.h"
43 #include "qtimezoneprivate_p.h"
44 #include "private/qlocale_tools_p.h"
45
46 #include <QtCore/QDataStream>
47 #include <QtCore/QDateTime>
48 #include <QtCore/QFile>
49 #include <QtCore/QHash>
50 #include <QtCore/QMutex>
51
52 #include <qdebug.h>
53 #include <qplatformdefs.h>
54
55 #include <algorithm>
56 #include <errno.h>
57 #include <limits.h>
58 #ifndef Q_OS_INTEGRITY
59 #include <sys/param.h> // to use MAXSYMLINKS constant
60 #endif
61 #include <unistd.h> // to use _SC_SYMLOOP_MAX constant
62
63 QT_BEGIN_NAMESPACE
64
65 /*
66 Private
67
68 tz file implementation
69 */
70
71 struct QTzTimeZone {
72 QLocale::Country country;
73 QByteArray comment;
74 };
75
76 // Define as a type as Q_GLOBAL_STATIC doesn't like it
77 typedef QHash<QByteArray, QTzTimeZone> QTzTimeZoneHash;
78
79 // Parse zone.tab table, assume lists all installed zones, if not will need to read directories
loadTzTimeZones()80 static QTzTimeZoneHash loadTzTimeZones()
81 {
82 QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab");
83 if (!QFile::exists(path))
84 path = QStringLiteral("/usr/lib/zoneinfo/zone.tab");
85
86 QFile tzif(path);
87 if (!tzif.open(QIODevice::ReadOnly))
88 return QTzTimeZoneHash();
89
90 QTzTimeZoneHash zonesHash;
91 // TODO QTextStream inefficient, replace later
92 QTextStream ts(&tzif);
93 while (!ts.atEnd()) {
94 const QString line = ts.readLine();
95 // Comment lines are prefixed with a #
96 if (!line.isEmpty() && line.at(0) != '#') {
97 // Data rows are tab-separated columns Region, Coordinates, ID, Optional Comments
98 const auto parts = line.splitRef(QLatin1Char('\t'));
99 QTzTimeZone zone;
100 zone.country = QLocalePrivate::codeToCountry(parts.at(0));
101 if (parts.size() > 3)
102 zone.comment = parts.at(3).toUtf8();
103 zonesHash.insert(parts.at(2).toUtf8(), zone);
104 }
105 }
106 return zonesHash;
107 }
108
109 // Hash of available system tz files as loaded by loadTzTimeZones()
110 Q_GLOBAL_STATIC_WITH_ARGS(const QTzTimeZoneHash, tzZones, (loadTzTimeZones()));
111
112 /*
113 The following is copied and modified from tzfile.h which is in the public domain.
114 Copied as no compatibility guarantee and is never system installed.
115 See https://github.com/eggert/tz/blob/master/tzfile.h
116 */
117
118 #define TZ_MAGIC "TZif"
119 #define TZ_MAX_TIMES 1200
120 #define TZ_MAX_TYPES 256 // Limited by what (unsigned char)'s can hold
121 #define TZ_MAX_CHARS 50 // Maximum number of abbreviation characters
122 #define TZ_MAX_LEAPS 50 // Maximum number of leap second corrections
123
124 struct QTzHeader {
125 char tzh_magic[4]; // TZ_MAGIC
126 char tzh_version; // '\0' or '2' as of 2005
127 char tzh_reserved[15]; // reserved--must be zero
128 quint32 tzh_ttisgmtcnt; // number of trans. time flags
129 quint32 tzh_ttisstdcnt; // number of trans. time flags
130 quint32 tzh_leapcnt; // number of leap seconds
131 quint32 tzh_timecnt; // number of transition times
132 quint32 tzh_typecnt; // number of local time types
133 quint32 tzh_charcnt; // number of abbr. chars
134 };
135
136 struct QTzTransition {
137 qint64 tz_time; // Transition time
138 quint8 tz_typeind; // Type Index
139 };
140 Q_DECLARE_TYPEINFO(QTzTransition, Q_PRIMITIVE_TYPE);
141
142 struct QTzType {
143 int tz_gmtoff; // UTC offset in seconds
144 bool tz_isdst; // Is DST
145 quint8 tz_abbrind; // abbreviation list index
146 };
147 Q_DECLARE_TYPEINFO(QTzType, Q_PRIMITIVE_TYPE);
148
149
150 // TZ File parsing
151
parseTzHeader(QDataStream & ds,bool * ok)152 static QTzHeader parseTzHeader(QDataStream &ds, bool *ok)
153 {
154 QTzHeader hdr;
155 quint8 ch;
156 *ok = false;
157
158 // Parse Magic, 4 bytes
159 ds.readRawData(hdr.tzh_magic, 4);
160
161 if (memcmp(hdr.tzh_magic, TZ_MAGIC, 4) != 0 || ds.status() != QDataStream::Ok)
162 return hdr;
163
164 // Parse Version, 1 byte, before 2005 was '\0', since 2005 a '2', since 2013 a '3'
165 ds >> ch;
166 hdr.tzh_version = ch;
167 if (ds.status() != QDataStream::Ok
168 || (hdr.tzh_version != '2' && hdr.tzh_version != '\0' && hdr.tzh_version != '3')) {
169 return hdr;
170 }
171
172 // Parse reserved space, 15 bytes
173 ds.readRawData(hdr.tzh_reserved, 15);
174 if (ds.status() != QDataStream::Ok)
175 return hdr;
176
177 // Parse rest of header, 6 x 4-byte transition counts
178 ds >> hdr.tzh_ttisgmtcnt >> hdr.tzh_ttisstdcnt >> hdr.tzh_leapcnt >> hdr.tzh_timecnt
179 >> hdr.tzh_typecnt >> hdr.tzh_charcnt;
180
181 // Check defined maximums
182 if (ds.status() != QDataStream::Ok
183 || hdr.tzh_timecnt > TZ_MAX_TIMES
184 || hdr.tzh_typecnt > TZ_MAX_TYPES
185 || hdr.tzh_charcnt > TZ_MAX_CHARS
186 || hdr.tzh_leapcnt > TZ_MAX_LEAPS
187 || hdr.tzh_ttisgmtcnt > hdr.tzh_typecnt
188 || hdr.tzh_ttisstdcnt > hdr.tzh_typecnt) {
189 return hdr;
190 }
191
192 *ok = true;
193 return hdr;
194 }
195
parseTzTransitions(QDataStream & ds,int tzh_timecnt,bool longTran)196 static QVector<QTzTransition> parseTzTransitions(QDataStream &ds, int tzh_timecnt, bool longTran)
197 {
198 QVector<QTzTransition> transitions(tzh_timecnt);
199
200 if (longTran) {
201 // Parse tzh_timecnt x 8-byte transition times
202 for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
203 ds >> transitions[i].tz_time;
204 if (ds.status() != QDataStream::Ok)
205 transitions.resize(i);
206 }
207 } else {
208 // Parse tzh_timecnt x 4-byte transition times
209 qint32 val;
210 for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
211 ds >> val;
212 transitions[i].tz_time = val;
213 if (ds.status() != QDataStream::Ok)
214 transitions.resize(i);
215 }
216 }
217
218 // Parse tzh_timecnt x 1-byte transition type index
219 for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
220 quint8 typeind;
221 ds >> typeind;
222 if (ds.status() == QDataStream::Ok)
223 transitions[i].tz_typeind = typeind;
224 }
225
226 return transitions;
227 }
228
parseTzTypes(QDataStream & ds,int tzh_typecnt)229 static QVector<QTzType> parseTzTypes(QDataStream &ds, int tzh_typecnt)
230 {
231 QVector<QTzType> types(tzh_typecnt);
232
233 // Parse tzh_typecnt x transition types
234 for (int i = 0; i < tzh_typecnt && ds.status() == QDataStream::Ok; ++i) {
235 QTzType &type = types[i];
236 // Parse UTC Offset, 4 bytes
237 ds >> type.tz_gmtoff;
238 // Parse Is DST flag, 1 byte
239 if (ds.status() == QDataStream::Ok)
240 ds >> type.tz_isdst;
241 // Parse Abbreviation Array Index, 1 byte
242 if (ds.status() == QDataStream::Ok)
243 ds >> type.tz_abbrind;
244 if (ds.status() != QDataStream::Ok)
245 types.resize(i);
246 }
247
248 return types;
249 }
250
parseTzAbbreviations(QDataStream & ds,int tzh_charcnt,const QVector<QTzType> & types)251 static QMap<int, QByteArray> parseTzAbbreviations(QDataStream &ds, int tzh_charcnt, const QVector<QTzType> &types)
252 {
253 // Parse the abbreviation list which is tzh_charcnt long with '\0' separated strings. The
254 // QTzType.tz_abbrind index points to the first char of the abbreviation in the array, not the
255 // occurrence in the list. It can also point to a partial string so we need to use the actual typeList
256 // index values when parsing. By using a map with tz_abbrind as ordered key we get both index
257 // methods in one data structure and can convert the types afterwards.
258 QMap<int, QByteArray> map;
259 quint8 ch;
260 QByteArray input;
261 // First parse the full abbrev string
262 for (int i = 0; i < tzh_charcnt && ds.status() == QDataStream::Ok; ++i) {
263 ds >> ch;
264 if (ds.status() == QDataStream::Ok)
265 input.append(char(ch));
266 else
267 return map;
268 }
269 // Then extract all the substrings pointed to by types
270 for (const QTzType &type : types) {
271 QByteArray abbrev;
272 for (int i = type.tz_abbrind; input.at(i) != '\0'; ++i)
273 abbrev.append(input.at(i));
274 // Have reached end of an abbreviation, so add to map
275 map[type.tz_abbrind] = abbrev;
276 }
277 return map;
278 }
279
parseTzLeapSeconds(QDataStream & ds,int tzh_leapcnt,bool longTran)280 static void parseTzLeapSeconds(QDataStream &ds, int tzh_leapcnt, bool longTran)
281 {
282 // Parse tzh_leapcnt x pairs of leap seconds
283 // We don't use leap seconds, so only read and don't store
284 qint32 val;
285 if (longTran) {
286 // v2 file format, each entry is 12 bytes long
287 qint64 time;
288 for (int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
289 // Parse Leap Occurrence Time, 8 bytes
290 ds >> time;
291 // Parse Leap Seconds To Apply, 4 bytes
292 if (ds.status() == QDataStream::Ok)
293 ds >> val;
294 }
295 } else {
296 // v0 file format, each entry is 8 bytes long
297 for (int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
298 // Parse Leap Occurrence Time, 4 bytes
299 ds >> val;
300 // Parse Leap Seconds To Apply, 4 bytes
301 if (ds.status() == QDataStream::Ok)
302 ds >> val;
303 }
304 }
305 }
306
parseTzIndicators(QDataStream & ds,const QVector<QTzType> & types,int tzh_ttisstdcnt,int tzh_ttisgmtcnt)307 static QVector<QTzType> parseTzIndicators(QDataStream &ds, const QVector<QTzType> &types, int tzh_ttisstdcnt, int tzh_ttisgmtcnt)
308 {
309 QVector<QTzType> result = types;
310 bool temp;
311 /*
312 Scan and discard indicators.
313
314 These indicators are only of use (by the date program) when "handling
315 POSIX-style time zone environment variables". The flags here say whether
316 the *specification* of the zone gave the time in UTC, local standard time
317 or local wall time; but whatever was specified has been digested for us,
318 already, by the zone-info compiler (zic), so that the tz_time values read
319 from the file (by parseTzTransitions) are all in UTC.
320 */
321
322 // Scan tzh_ttisstdcnt x 1-byte standard/wall indicators
323 for (int i = 0; i < tzh_ttisstdcnt && ds.status() == QDataStream::Ok; ++i)
324 ds >> temp;
325
326 // Scan tzh_ttisgmtcnt x 1-byte UTC/local indicators
327 for (int i = 0; i < tzh_ttisgmtcnt && ds.status() == QDataStream::Ok; ++i)
328 ds >> temp;
329
330 return result;
331 }
332
parseTzPosixRule(QDataStream & ds)333 static QByteArray parseTzPosixRule(QDataStream &ds)
334 {
335 // Parse POSIX rule, variable length '\n' enclosed
336 QByteArray rule;
337
338 quint8 ch;
339 ds >> ch;
340 if (ch != '\n' || ds.status() != QDataStream::Ok)
341 return rule;
342 ds >> ch;
343 while (ch != '\n' && ds.status() == QDataStream::Ok) {
344 rule.append((char)ch);
345 ds >> ch;
346 }
347
348 return rule;
349 }
350
calculateDowDate(int year,int month,int dayOfWeek,int week)351 static QDate calculateDowDate(int year, int month, int dayOfWeek, int week)
352 {
353 QDate date(year, month, 1);
354 int startDow = date.dayOfWeek();
355 if (startDow <= dayOfWeek)
356 date = date.addDays(dayOfWeek - startDow - 7);
357 else
358 date = date.addDays(dayOfWeek - startDow);
359 date = date.addDays(week * 7);
360 while (date.month() != month)
361 date = date.addDays(-7);
362 return date;
363 }
364
calculatePosixDate(const QByteArray & dateRule,int year)365 static QDate calculatePosixDate(const QByteArray &dateRule, int year)
366 {
367 // Can start with M, J, or a digit
368 if (dateRule.at(0) == 'M') {
369 // nth week in month format "Mmonth.week.dow"
370 QList<QByteArray> dateParts = dateRule.split('.');
371 int month = dateParts.at(0).mid(1).toInt();
372 int week = dateParts.at(1).toInt();
373 int dow = dateParts.at(2).toInt();
374 if (dow == 0) // Sunday; we represent it as 7
375 dow = 7;
376 return calculateDowDate(year, month, dow, week);
377 } else if (dateRule.at(0) == 'J') {
378 // Day of Year ignores Feb 29
379 int doy = dateRule.mid(1).toInt();
380 QDate date = QDate(year, 1, 1).addDays(doy - 1);
381 if (QDate::isLeapYear(date.year()))
382 date = date.addDays(-1);
383 return date;
384 } else {
385 // Day of Year includes Feb 29
386 int doy = dateRule.toInt();
387 return QDate(year, 1, 1).addDays(doy - 1);
388 }
389 }
390
391 // returns the time in seconds, INT_MIN if we failed to parse
parsePosixTime(const char * begin,const char * end)392 static int parsePosixTime(const char *begin, const char *end)
393 {
394 // Format "hh[:mm[:ss]]"
395 int hour, min = 0, sec = 0;
396
397 // Note that the calls to qstrtoll do *not* check against the end pointer,
398 // which means they proceed until they find a non-digit. We check that we're
399 // still in range at the end, but we may have read past end. It's the
400 // caller's responsibility to ensure that begin is part of a null-terminated
401 // string.
402
403 const int maxHour = QTimeZone::MaxUtcOffsetSecs / 3600;
404 bool ok = false;
405 const char *cut = begin;
406 hour = qstrtoll(begin, &cut, 10, &ok);
407 if (!ok || hour < 0 || hour > maxHour || cut > begin + 2)
408 return INT_MIN;
409 begin = cut;
410 if (begin < end && *begin == ':') {
411 // minutes
412 ++begin;
413 min = qstrtoll(begin, &cut, 10, &ok);
414 if (!ok || min < 0 || min > 59 || cut > begin + 2)
415 return INT_MIN;
416
417 begin = cut;
418 if (begin < end && *begin == ':') {
419 // seconds
420 ++begin;
421 sec = qstrtoll(begin, &cut, 10, &ok);
422 if (!ok || sec < 0 || sec > 59 || cut > begin + 2)
423 return INT_MIN;
424 begin = cut;
425 }
426 }
427
428 // we must have consumed everything
429 if (begin != end)
430 return INT_MIN;
431
432 return (hour * 60 + min) * 60 + sec;
433 }
434
parsePosixTransitionTime(const QByteArray & timeRule)435 static QTime parsePosixTransitionTime(const QByteArray &timeRule)
436 {
437 // Format "hh[:mm[:ss]]"
438 int value = parsePosixTime(timeRule.constBegin(), timeRule.constEnd());
439 if (value == INT_MIN) {
440 // if we failed to parse, return 02:00
441 return QTime(2, 0, 0);
442 }
443 return QTime::fromMSecsSinceStartOfDay(value * 1000);
444 }
445
parsePosixOffset(const char * begin,const char * end)446 static int parsePosixOffset(const char *begin, const char *end)
447 {
448 // Format "[+|-]hh[:mm[:ss]]"
449 // note that the sign is inverted because POSIX counts in hours West of GMT
450 bool negate = true;
451 if (*begin == '+') {
452 ++begin;
453 } else if (*begin == '-') {
454 negate = false;
455 ++begin;
456 }
457
458 int value = parsePosixTime(begin, end);
459 if (value == INT_MIN)
460 return value;
461 return negate ? -value : value;
462 }
463
asciiIsLetter(char ch)464 static inline bool asciiIsLetter(char ch)
465 {
466 ch |= 0x20; // lowercases if it is a letter, otherwise just corrupts ch
467 return ch >= 'a' && ch <= 'z';
468 }
469
470 namespace {
471
472 struct PosixZone
473 {
474 enum {
475 InvalidOffset = INT_MIN,
476 };
477
478 QString name;
479 int offset;
480
invalid__anone963e9c20111::PosixZone481 static PosixZone invalid() { return {QString(), InvalidOffset}; }
482 static PosixZone parse(const char *&pos, const char *end);
483
hasValidOffset__anone963e9c20111::PosixZone484 bool hasValidOffset() const noexcept { return offset != InvalidOffset; }
485 };
486
487 } // unnamed namespace
488
489 // Returns the zone name, the offset (in seconds) and advances \a begin to
490 // where the parsing ended. Returns a zone of INT_MIN in case an offset
491 // couldn't be read.
parse(const char * & pos,const char * end)492 PosixZone PosixZone::parse(const char *&pos, const char *end)
493 {
494 static const char offsetChars[] = "0123456789:";
495
496 const char *nameBegin = pos;
497 const char *nameEnd;
498 Q_ASSERT(pos < end);
499
500 if (*pos == '<') {
501 nameBegin = pos + 1; // skip the '<'
502 nameEnd = nameBegin;
503 while (nameEnd < end && *nameEnd != '>') {
504 // POSIX says only alphanumeric, but we allow anything
505 ++nameEnd;
506 }
507 pos = nameEnd + 1; // skip the '>'
508 } else {
509 nameBegin = pos;
510 nameEnd = nameBegin;
511 while (nameEnd < end && asciiIsLetter(*nameEnd))
512 ++nameEnd;
513 pos = nameEnd;
514 }
515 if (nameEnd - nameBegin < 3)
516 return invalid(); // name must be at least 3 characters long
517
518 // zone offset, form [+-]hh:mm:ss
519 const char *zoneBegin = pos;
520 const char *zoneEnd = pos;
521 if (zoneEnd < end && (zoneEnd[0] == '+' || zoneEnd[0] == '-'))
522 ++zoneEnd;
523 while (zoneEnd < end) {
524 if (strchr(offsetChars, char(*zoneEnd)) == nullptr)
525 break;
526 ++zoneEnd;
527 }
528
529 QString name = QString::fromUtf8(nameBegin, nameEnd - nameBegin);
530 const int offset = zoneEnd > zoneBegin ? parsePosixOffset(zoneBegin, zoneEnd) : InvalidOffset;
531 pos = zoneEnd;
532 // UTC+hh:mm:ss or GMT+hh:mm:ss should be read as offsets from UTC, not as a
533 // POSIX rule naming a zone as UTC or GMT and specifying a non-zero offset.
534 if (offset != 0 && (name == QLatin1String("UTC") || name == QLatin1String("GMT")))
535 return invalid();
536 return {std::move(name), offset};
537 }
538
calculatePosixTransitions(const QByteArray & posixRule,int startYear,int endYear,qint64 lastTranMSecs)539 static QVector<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray &posixRule,
540 int startYear, int endYear,
541 qint64 lastTranMSecs)
542 {
543 QVector<QTimeZonePrivate::Data> result;
544
545 // POSIX Format is like "TZ=CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00"
546 // i.e. "std offset dst [offset],start[/time],end[/time]"
547 // See the section about TZ at
548 // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
549 QList<QByteArray> parts = posixRule.split(',');
550
551 PosixZone stdZone, dstZone = PosixZone::invalid();
552 {
553 const QByteArray &zoneinfo = parts.at(0);
554 const char *begin = zoneinfo.constBegin();
555
556 stdZone = PosixZone::parse(begin, zoneinfo.constEnd());
557 if (!stdZone.hasValidOffset()) {
558 stdZone.offset = 0; // reset to UTC if we failed to parse
559 } else if (begin < zoneinfo.constEnd()) {
560 dstZone = PosixZone::parse(begin, zoneinfo.constEnd());
561 if (!dstZone.hasValidOffset()) {
562 // if the dst offset isn't provided, it is 1 hour ahead of the standard offset
563 dstZone.offset = stdZone.offset + (60 * 60);
564 }
565 }
566 }
567
568 // If only the name part then no transitions
569 if (parts.count() == 1) {
570 QTimeZonePrivate::Data data;
571 data.atMSecsSinceEpoch = lastTranMSecs;
572 data.offsetFromUtc = stdZone.offset;
573 data.standardTimeOffset = stdZone.offset;
574 data.daylightTimeOffset = 0;
575 data.abbreviation = stdZone.name;
576 result << data;
577 return result;
578 }
579
580
581 // Get the std to dst transtion details
582 QList<QByteArray> dstParts = parts.at(1).split('/');
583 QByteArray dstDateRule = dstParts.at(0);
584 QTime dstTime;
585 if (dstParts.count() > 1)
586 dstTime = parsePosixTransitionTime(dstParts.at(1));
587 else
588 dstTime = QTime(2, 0, 0);
589
590 // Get the dst to std transtion details
591 QList<QByteArray> stdParts = parts.at(2).split('/');
592 QByteArray stdDateRule = stdParts.at(0);
593 QTime stdTime;
594 if (stdParts.count() > 1)
595 stdTime = parsePosixTransitionTime(stdParts.at(1));
596 else
597 stdTime = QTime(2, 0, 0);
598
599 // Limit year to the range QDateTime can represent:
600 const int minYear = int(QDateTime::YearRange::First);
601 const int maxYear = int(QDateTime::YearRange::Last);
602 startYear = qBound(minYear, startYear, maxYear);
603 endYear = qBound(minYear, endYear, maxYear);
604 Q_ASSERT(startYear <= endYear);
605
606 for (int year = startYear; year <= endYear; ++year) {
607 QTimeZonePrivate::Data dstData;
608 QDateTime dst(calculatePosixDate(dstDateRule, year), dstTime, Qt::UTC);
609 dstData.atMSecsSinceEpoch = dst.toMSecsSinceEpoch() - (stdZone.offset * 1000);
610 dstData.offsetFromUtc = dstZone.offset;
611 dstData.standardTimeOffset = stdZone.offset;
612 dstData.daylightTimeOffset = dstZone.offset - stdZone.offset;
613 dstData.abbreviation = dstZone.name;
614 QTimeZonePrivate::Data stdData;
615 QDateTime std(calculatePosixDate(stdDateRule, year), stdTime, Qt::UTC);
616 stdData.atMSecsSinceEpoch = std.toMSecsSinceEpoch() - (dstZone.offset * 1000);
617 stdData.offsetFromUtc = stdZone.offset;
618 stdData.standardTimeOffset = stdZone.offset;
619 stdData.daylightTimeOffset = 0;
620 stdData.abbreviation = stdZone.name;
621 // Part of maxYear will overflow (likewise for minYear, below):
622 if (year == maxYear && (dstData.atMSecsSinceEpoch < 0 || stdData.atMSecsSinceEpoch < 0)) {
623 if (dstData.atMSecsSinceEpoch > 0) {
624 result << dstData;
625 } else if (stdData.atMSecsSinceEpoch > 0) {
626 result << stdData;
627 }
628 } else if (year < 1970) { // We ignore DST before the epoch.
629 if (year > minYear || stdData.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs())
630 result << stdData;
631 } else if (dst < std) {
632 result << dstData << stdData;
633 } else {
634 result << stdData << dstData;
635 }
636 }
637 return result;
638 }
639
640 // Create the system default time zone
QTzTimeZonePrivate()641 QTzTimeZonePrivate::QTzTimeZonePrivate()
642 {
643 init(systemTimeZoneId());
644 }
645
646 // Create a named time zone
QTzTimeZonePrivate(const QByteArray & ianaId)647 QTzTimeZonePrivate::QTzTimeZonePrivate(const QByteArray &ianaId)
648 {
649 init(ianaId);
650 }
651
~QTzTimeZonePrivate()652 QTzTimeZonePrivate::~QTzTimeZonePrivate()
653 {
654 }
655
clone() const656 QTzTimeZonePrivate *QTzTimeZonePrivate::clone() const
657 {
658 return new QTzTimeZonePrivate(*this);
659 }
660
661 class QTzTimeZoneCache
662 {
663 public:
664 QTzTimeZoneCacheEntry fetchEntry(const QByteArray &ianaId);
665
666 private:
667 QTzTimeZoneCacheEntry findEntry(const QByteArray &ianaId);
668 QHash<QByteArray, QTzTimeZoneCacheEntry> m_cache;
669 QMutex m_mutex;
670 };
671
findEntry(const QByteArray & ianaId)672 QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId)
673 {
674 QTzTimeZoneCacheEntry ret;
675 QFile tzif;
676 if (ianaId.isEmpty()) {
677 // Open system tz
678 tzif.setFileName(QStringLiteral("/etc/localtime"));
679 if (!tzif.open(QIODevice::ReadOnly))
680 return ret;
681 } else {
682 // Open named tz, try modern path first, if fails try legacy path
683 tzif.setFileName(QLatin1String("/usr/share/zoneinfo/") + QString::fromLocal8Bit(ianaId));
684 if (!tzif.open(QIODevice::ReadOnly)) {
685 tzif.setFileName(QLatin1String("/usr/lib/zoneinfo/") + QString::fromLocal8Bit(ianaId));
686 if (!tzif.open(QIODevice::ReadOnly)) {
687 // ianaId may be a POSIX rule, taken from $TZ or /etc/TZ
688 const QByteArray zoneInfo = ianaId.split(',').at(0);
689 const char *begin = zoneInfo.constBegin();
690 if (PosixZone::parse(begin, zoneInfo.constEnd()).hasValidOffset()
691 && (begin == zoneInfo.constEnd()
692 || PosixZone::parse(begin, zoneInfo.constEnd()).hasValidOffset())) {
693 ret.m_posixRule = ianaId;
694 }
695 return ret;
696 }
697 }
698 }
699
700 QDataStream ds(&tzif);
701
702 // Parse the old version block of data
703 bool ok = false;
704 QTzHeader hdr = parseTzHeader(ds, &ok);
705 if (!ok || ds.status() != QDataStream::Ok)
706 return ret;
707 QVector<QTzTransition> tranList = parseTzTransitions(ds, hdr.tzh_timecnt, false);
708 if (ds.status() != QDataStream::Ok)
709 return ret;
710 QVector<QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt);
711 if (ds.status() != QDataStream::Ok)
712 return ret;
713 QMap<int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList);
714 if (ds.status() != QDataStream::Ok)
715 return ret;
716 parseTzLeapSeconds(ds, hdr.tzh_leapcnt, false);
717 if (ds.status() != QDataStream::Ok)
718 return ret;
719 typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt);
720 if (ds.status() != QDataStream::Ok)
721 return ret;
722
723 // If version 2 then parse the second block of data
724 if (hdr.tzh_version == '2' || hdr.tzh_version == '3') {
725 ok = false;
726 QTzHeader hdr2 = parseTzHeader(ds, &ok);
727 if (!ok || ds.status() != QDataStream::Ok)
728 return ret;
729 tranList = parseTzTransitions(ds, hdr2.tzh_timecnt, true);
730 if (ds.status() != QDataStream::Ok)
731 return ret;
732 typeList = parseTzTypes(ds, hdr2.tzh_typecnt);
733 if (ds.status() != QDataStream::Ok)
734 return ret;
735 abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList);
736 if (ds.status() != QDataStream::Ok)
737 return ret;
738 parseTzLeapSeconds(ds, hdr2.tzh_leapcnt, true);
739 if (ds.status() != QDataStream::Ok)
740 return ret;
741 typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt);
742 if (ds.status() != QDataStream::Ok)
743 return ret;
744 ret.m_posixRule = parseTzPosixRule(ds);
745 if (ds.status() != QDataStream::Ok)
746 return ret;
747 }
748
749 // Translate the TZ file into internal format
750
751 // Translate the array index based tz_abbrind into list index
752 const int size = abbrevMap.size();
753 ret.m_abbreviations.clear();
754 ret.m_abbreviations.reserve(size);
755 QVector<int> abbrindList;
756 abbrindList.reserve(size);
757 for (auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) {
758 ret.m_abbreviations.append(it.value());
759 abbrindList.append(it.key());
760 }
761 for (int i = 0; i < typeList.size(); ++i)
762 typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind);
763
764 // Offsets are stored as total offset, want to know separate UTC and DST offsets
765 // so find the first non-dst transition to use as base UTC Offset
766 int utcOffset = 0;
767 for (const QTzTransition &tran : qAsConst(tranList)) {
768 if (!typeList.at(tran.tz_typeind).tz_isdst) {
769 utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff;
770 break;
771 }
772 }
773
774 // Now for each transition time calculate and store our rule:
775 const int tranCount = tranList.count();;
776 ret.m_tranTimes.reserve(tranCount);
777 // The DST offset when in effect: usually stable, usually an hour:
778 int lastDstOff = 3600;
779 for (int i = 0; i < tranCount; i++) {
780 const QTzTransition &tz_tran = tranList.at(i);
781 QTzTransitionTime tran;
782 QTzTransitionRule rule;
783 const QTzType tz_type = typeList.at(tz_tran.tz_typeind);
784
785 // Calculate the associated Rule
786 if (!tz_type.tz_isdst) {
787 utcOffset = tz_type.tz_gmtoff;
788 } else if (Q_UNLIKELY(tz_type.tz_gmtoff != utcOffset + lastDstOff)) {
789 /*
790 This might be a genuine change in DST offset, but could also be
791 DST starting at the same time as the standard offset changed. See
792 if DST's end gives a more plausible utcOffset (i.e. one closer to
793 the last we saw, or a simple whole hour):
794 */
795 // Standard offset inferred from net offset and expected DST offset:
796 const int inferStd = tz_type.tz_gmtoff - lastDstOff; // != utcOffset
797 for (int j = i + 1; j < tranCount; j++) {
798 const QTzType new_type = typeList.at(tranList.at(j).tz_typeind);
799 if (!new_type.tz_isdst) {
800 const int newUtc = new_type.tz_gmtoff;
801 if (newUtc == utcOffset) {
802 // DST-end can't help us, avoid lots of messy checks.
803 // else: See if the end matches the familiar DST offset:
804 } else if (newUtc == inferStd) {
805 utcOffset = newUtc;
806 // else: let either end shift us to one hour as DST offset:
807 } else if (tz_type.tz_gmtoff - 3600 == utcOffset) {
808 // Start does it
809 } else if (tz_type.tz_gmtoff - 3600 == newUtc) {
810 utcOffset = newUtc; // End does it
811 // else: prefer whichever end gives DST offset closer to
812 // last, but consider any offset > 0 "closer" than any <= 0:
813 } else if (newUtc < tz_type.tz_gmtoff
814 ? (utcOffset >= tz_type.tz_gmtoff
815 || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))
816 : (utcOffset >= tz_type.tz_gmtoff
817 && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) {
818 utcOffset = newUtc;
819 }
820 break;
821 }
822 }
823 lastDstOff = tz_type.tz_gmtoff - utcOffset;
824 }
825 rule.stdOffset = utcOffset;
826 rule.dstOffset = tz_type.tz_gmtoff - utcOffset;
827 rule.abbreviationIndex = tz_type.tz_abbrind;
828
829 // If the rule already exist then use that, otherwise add it
830 int ruleIndex = ret.m_tranRules.indexOf(rule);
831 if (ruleIndex == -1) {
832 ret.m_tranRules.append(rule);
833 tran.ruleIndex = ret.m_tranRules.size() - 1;
834 } else {
835 tran.ruleIndex = ruleIndex;
836 }
837
838 tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000;
839 ret.m_tranTimes.append(tran);
840 }
841
842 return ret;
843 }
844
fetchEntry(const QByteArray & ianaId)845 QTzTimeZoneCacheEntry QTzTimeZoneCache::fetchEntry(const QByteArray &ianaId)
846 {
847 QMutexLocker locker(&m_mutex);
848
849 // search the cache...
850 const auto& it = m_cache.find(ianaId);
851 if (it != m_cache.constEnd())
852 return *it;
853
854 // ... or build a new entry from scratch
855 QTzTimeZoneCacheEntry ret = findEntry(ianaId);
856 m_cache[ianaId] = ret;
857 return ret;
858 }
859
init(const QByteArray & ianaId)860 void QTzTimeZonePrivate::init(const QByteArray &ianaId)
861 {
862 static QTzTimeZoneCache tzCache;
863 const auto &entry = tzCache.fetchEntry(ianaId);
864 if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty())
865 return; // Invalid after all !
866
867 cached_data = std::move(entry);
868 m_id = ianaId;
869 // Avoid empty ID, if we have an abbreviation to use instead
870 if (m_id.isEmpty()) { // We've read /etc/localtime's contents
871 for (const auto &abbr : cached_data.m_abbreviations) {
872 if (!abbr.isEmpty()) {
873 m_id = abbr;
874 break;
875 }
876 }
877 }
878 }
879
country() const880 QLocale::Country QTzTimeZonePrivate::country() const
881 {
882 return tzZones->value(m_id).country;
883 }
884
comment() const885 QString QTzTimeZonePrivate::comment() const
886 {
887 return QString::fromUtf8(tzZones->value(m_id).comment);
888 }
889
displayName(qint64 atMSecsSinceEpoch,QTimeZone::NameType nameType,const QLocale & locale) const890 QString QTzTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
891 QTimeZone::NameType nameType,
892 const QLocale &locale) const
893 {
894 #if QT_CONFIG(icu)
895 if (!m_icu)
896 m_icu = new QIcuTimeZonePrivate(m_id);
897 // TODO small risk may not match if tran times differ due to outdated files
898 // TODO Some valid TZ names are not valid ICU names, use translation table?
899 if (m_icu->isValid())
900 return m_icu->displayName(atMSecsSinceEpoch, nameType, locale);
901 #else
902 Q_UNUSED(nameType)
903 Q_UNUSED(locale)
904 #endif
905 return abbreviation(atMSecsSinceEpoch);
906 }
907
displayName(QTimeZone::TimeType timeType,QTimeZone::NameType nameType,const QLocale & locale) const908 QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
909 QTimeZone::NameType nameType,
910 const QLocale &locale) const
911 {
912 #if QT_CONFIG(icu)
913 if (!m_icu)
914 m_icu = new QIcuTimeZonePrivate(m_id);
915 // TODO small risk may not match if tran times differ due to outdated files
916 // TODO Some valid TZ names are not valid ICU names, use translation table?
917 if (m_icu->isValid())
918 return m_icu->displayName(timeType, nameType, locale);
919 #else
920 Q_UNUSED(timeType)
921 Q_UNUSED(nameType)
922 Q_UNUSED(locale)
923 #endif
924 // If no ICU available then have to use abbreviations instead
925 // Abbreviations don't have GenericTime
926 if (timeType == QTimeZone::GenericTime)
927 timeType = QTimeZone::StandardTime;
928
929 // Get current tran, if valid and is what we want, then use it
930 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
931 QTimeZonePrivate::Data tran = data(currentMSecs);
932 if (tran.atMSecsSinceEpoch != invalidMSecs()
933 && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0)
934 || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) {
935 return tran.abbreviation;
936 }
937
938 // Otherwise get next tran and if valid and is what we want, then use it
939 tran = nextTransition(currentMSecs);
940 if (tran.atMSecsSinceEpoch != invalidMSecs()
941 && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0)
942 || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) {
943 return tran.abbreviation;
944 }
945
946 // Otherwise get prev tran and if valid and is what we want, then use it
947 tran = previousTransition(currentMSecs);
948 if (tran.atMSecsSinceEpoch != invalidMSecs())
949 tran = previousTransition(tran.atMSecsSinceEpoch);
950 if (tran.atMSecsSinceEpoch != invalidMSecs()
951 && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0)
952 || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) {
953 return tran.abbreviation;
954 }
955
956 // Otherwise is strange sequence, so work backwards through trans looking for first match, if any
957 auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(),
958 [currentMSecs](const QTzTransitionTime &at) {
959 return at.atMSecsSinceEpoch <= currentMSecs;
960 });
961
962 while (it != tranCache().cbegin()) {
963 --it;
964 tran = dataForTzTransition(*it);
965 int offset = tran.daylightTimeOffset;
966 if ((timeType == QTimeZone::DaylightTime) != (offset == 0))
967 return tran.abbreviation;
968 }
969
970 // Otherwise if no match use current data
971 return data(currentMSecs).abbreviation;
972 }
973
abbreviation(qint64 atMSecsSinceEpoch) const974 QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
975 {
976 return data(atMSecsSinceEpoch).abbreviation;
977 }
978
offsetFromUtc(qint64 atMSecsSinceEpoch) const979 int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
980 {
981 const QTimeZonePrivate::Data tran = data(atMSecsSinceEpoch);
982 return tran.offsetFromUtc; // == tran.standardTimeOffset + tran.daylightTimeOffset
983 }
984
standardTimeOffset(qint64 atMSecsSinceEpoch) const985 int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
986 {
987 return data(atMSecsSinceEpoch).standardTimeOffset;
988 }
989
daylightTimeOffset(qint64 atMSecsSinceEpoch) const990 int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
991 {
992 return data(atMSecsSinceEpoch).daylightTimeOffset;
993 }
994
hasDaylightTime() const995 bool QTzTimeZonePrivate::hasDaylightTime() const
996 {
997 // TODO Perhaps cache as frequently accessed?
998 for (const QTzTransitionRule &rule : cached_data.m_tranRules) {
999 if (rule.dstOffset != 0)
1000 return true;
1001 }
1002 return false;
1003 }
1004
isDaylightTime(qint64 atMSecsSinceEpoch) const1005 bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
1006 {
1007 return (daylightTimeOffset(atMSecsSinceEpoch) != 0);
1008 }
1009
dataForTzTransition(QTzTransitionTime tran) const1010 QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran) const
1011 {
1012 QTimeZonePrivate::Data data;
1013 data.atMSecsSinceEpoch = tran.atMSecsSinceEpoch;
1014 QTzTransitionRule rule = cached_data.m_tranRules.at(tran.ruleIndex);
1015 data.standardTimeOffset = rule.stdOffset;
1016 data.daylightTimeOffset = rule.dstOffset;
1017 data.offsetFromUtc = rule.stdOffset + rule.dstOffset;
1018 data.abbreviation = QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex));
1019 return data;
1020 }
1021
getPosixTransitions(qint64 msNear) const1022 QVector<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear) const
1023 {
1024 const int year = QDateTime::fromMSecsSinceEpoch(msNear, Qt::UTC).date().year();
1025 // The Data::atMSecsSinceEpoch of the single entry if zone is constant:
1026 qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch;
1027 return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime);
1028 }
1029
data(qint64 forMSecsSinceEpoch) const1030 QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
1031 {
1032 // If the required time is after the last transition (or there were none)
1033 // and we have a POSIX rule, then use it:
1034 if (!cached_data.m_posixRule.isEmpty()
1035 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
1036 QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
1037 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1038 [forMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
1039 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1040 });
1041 // Use most recent, if any in the past; or the first if we have no other rules:
1042 if (it > posixTrans.cbegin() || (tranCache().isEmpty() && it < posixTrans.cend())) {
1043 QTimeZonePrivate::Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
1044 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1045 return data;
1046 }
1047 }
1048 if (tranCache().isEmpty()) // Only possible if !isValid()
1049 return invalidData();
1050
1051 // Otherwise, use the rule for the most recent or first transition:
1052 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1053 [forMSecsSinceEpoch] (const QTzTransitionTime &at) {
1054 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1055 });
1056 if (last > tranCache().cbegin())
1057 --last;
1058 Data data = dataForTzTransition(*last);
1059 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1060 return data;
1061 }
1062
hasTransitions() const1063 bool QTzTimeZonePrivate::hasTransitions() const
1064 {
1065 return true;
1066 }
1067
nextTransition(qint64 afterMSecsSinceEpoch) const1068 QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
1069 {
1070 // If the required time is after the last transition (or there were none)
1071 // and we have a POSIX rule, then use it:
1072 if (!cached_data.m_posixRule.isEmpty()
1073 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
1074 QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
1075 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1076 [afterMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
1077 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1078 });
1079
1080 return it == posixTrans.cend() ? invalidData() : *it;
1081 }
1082
1083 // Otherwise, if we can find a valid tran, use its rule:
1084 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1085 [afterMSecsSinceEpoch] (const QTzTransitionTime &at) {
1086 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1087 });
1088 return last != tranCache().cend() ? dataForTzTransition(*last) : invalidData();
1089 }
1090
previousTransition(qint64 beforeMSecsSinceEpoch) const1091 QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
1092 {
1093 // If the required time is after the last transition (or there were none)
1094 // and we have a POSIX rule, then use it:
1095 if (!cached_data.m_posixRule.isEmpty()
1096 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
1097 QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
1098 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1099 [beforeMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
1100 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1101 });
1102 if (it > posixTrans.cbegin())
1103 return *--it;
1104 // It fell between the last transition (if any) and the first of the POSIX rule:
1105 return tranCache().isEmpty() ? invalidData() : dataForTzTransition(tranCache().last());
1106 }
1107
1108 // Otherwise if we can find a valid tran then use its rule
1109 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1110 [beforeMSecsSinceEpoch] (const QTzTransitionTime &at) {
1111 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1112 });
1113 return last > tranCache().cbegin() ? dataForTzTransition(*--last) : invalidData();
1114 }
1115
isTimeZoneIdAvailable(const QByteArray & ianaId) const1116 bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
1117 {
1118 return tzZones->contains(ianaId);
1119 }
1120
availableTimeZoneIds() const1121 QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds() const
1122 {
1123 QList<QByteArray> result = tzZones->keys();
1124 std::sort(result.begin(), result.end());
1125 return result;
1126 }
1127
availableTimeZoneIds(QLocale::Country country) const1128 QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const
1129 {
1130 // TODO AnyCountry
1131 QList<QByteArray> result;
1132 for (auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) {
1133 if (it.value().country == country)
1134 result << it.key();
1135 }
1136 std::sort(result.begin(), result.end());
1137 return result;
1138 }
1139
1140 // Getting the system zone's ID:
1141
1142 namespace {
1143 class ZoneNameReader : public QObject
1144 {
1145 public:
name()1146 QByteArray name()
1147 {
1148 /* Assumptions:
1149 a) Systems don't change which of localtime and TZ they use without a
1150 reboot.
1151 b) When they change, they use atomic renames, hence a new device and
1152 inode for the new file.
1153 c) If we change which *name* is used for a zone, while referencing
1154 the same final zoneinfo file, we don't care about the change of
1155 name (e.g. if Europe/Oslo and Europe/Berlin are both symlinks to
1156 the same CET file, continuing to use the old name, after
1157 /etc/localtime changes which of the two it points to, is
1158 harmless).
1159
1160 The alternative would be to use a file-system watcher, but they are a
1161 scarce resource.
1162 */
1163 const StatIdent local = identify("/etc/localtime");
1164 const StatIdent tz = identify("/etc/TZ");
1165 if (!m_name.isEmpty() && m_last.isValid() && (m_last == local || m_last == tz))
1166 return m_name;
1167
1168 m_name = etcLocalTime();
1169 if (!m_name.isEmpty()) {
1170 m_last = local;
1171 return m_name;
1172 }
1173
1174 m_name = etcTZ();
1175 m_last = m_name.isEmpty() ? StatIdent() : tz;
1176 return m_name;
1177 }
1178
1179
1180 private:
1181 QByteArray m_name;
1182 struct StatIdent
1183 {
1184 static constexpr unsigned long bad = ~0ul;
1185 unsigned long m_dev, m_ino;
StatIdent__anone963e9c20a11::ZoneNameReader::StatIdent1186 StatIdent() : m_dev(bad), m_ino(bad) {}
StatIdent__anone963e9c20a11::ZoneNameReader::StatIdent1187 StatIdent(const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {}
isValid__anone963e9c20a11::ZoneNameReader::StatIdent1188 bool isValid() { return m_dev != bad || m_ino != bad; }
operator ==__anone963e9c20a11::ZoneNameReader::StatIdent1189 bool operator==(const StatIdent &other)
1190 { return other.m_dev == m_dev && other.m_ino == m_ino; }
1191 };
1192 StatIdent m_last;
1193
identify(const char * path)1194 static StatIdent identify(const char *path)
1195 {
1196 QT_STATBUF data;
1197 return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data);
1198 }
1199
etcLocalTime()1200 static QByteArray etcLocalTime()
1201 {
1202 // On most distros /etc/localtime is a symlink to a real file so extract
1203 // name from the path
1204 const QLatin1String zoneinfo("/zoneinfo/");
1205 QString path = QStringLiteral("/etc/localtime");
1206 long iteration = getSymloopMax();
1207 // Symlink may point to another symlink etc. before being under zoneinfo/
1208 // We stop on the first path under /zoneinfo/, even if it is itself a
1209 // symlink, like America/Montreal pointing to America/Toronto
1210 do {
1211 path = QFile::symLinkTarget(path);
1212 int index = path.indexOf(zoneinfo);
1213 if (index >= 0) // Found zoneinfo file; extract zone name from path:
1214 return path.midRef(index + zoneinfo.size()).toUtf8();
1215 } while (!path.isEmpty() && --iteration > 0);
1216
1217 return QByteArray();
1218 }
1219
etcTZ()1220 static QByteArray etcTZ()
1221 {
1222 // Some systems (e.g. uClibc) have a default value for $TZ in /etc/TZ:
1223 const QString path = QStringLiteral("/etc/TZ");
1224 QFile zone(path);
1225 if (zone.open(QIODevice::ReadOnly))
1226 return zone.readAll().trimmed();
1227
1228 return QByteArray();
1229 }
1230
1231 // Any chain of symlinks longer than this is assumed to be a loop:
getSymloopMax()1232 static long getSymloopMax()
1233 {
1234 #ifdef SYMLOOP_MAX
1235 // If defined, at runtime it can only be greater than this, so this is a safe bet:
1236 return SYMLOOP_MAX;
1237 #else
1238 errno = 0;
1239 long result = sysconf(_SC_SYMLOOP_MAX);
1240 if (result >= 0)
1241 return result;
1242 // result is -1, meaning either error or no limit
1243 Q_ASSERT(!errno); // ... but it can't be an error, POSIX mandates _SC_SYMLOOP_MAX
1244
1245 // therefore we can make up our own limit
1246 # ifdef MAXSYMLINKS
1247 return MAXSYMLINKS;
1248 # else
1249 return 8;
1250 # endif
1251 #endif
1252 }
1253 };
1254 }
1255
systemTimeZoneId() const1256 QByteArray QTzTimeZonePrivate::systemTimeZoneId() const
1257 {
1258 // Check TZ env var first, if not populated try find it
1259 QByteArray ianaId = qgetenv("TZ");
1260
1261 // The TZ value can be ":/etc/localtime" which libc considers
1262 // to be a "default timezone", in which case it will be read
1263 // by one of the blocks below, so unset it here so it is not
1264 // considered as a valid/found ianaId
1265 if (ianaId == ":/etc/localtime")
1266 ianaId.clear();
1267 else if (ianaId.startsWith(':'))
1268 ianaId = ianaId.mid(1);
1269
1270 if (ianaId.isEmpty()) {
1271 thread_local static ZoneNameReader reader;
1272 ianaId = reader.name();
1273 }
1274
1275 return ianaId;
1276 }
1277
1278 QT_END_NAMESPACE
1279