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