Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qtimezoneprivate_tz.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// Copyright (C) 2019 Crimson AS <info@crimson.no>
3// Copyright (C) 2013 John Layt <jlayt@kde.org>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include "qtimezone.h"
8#include "private/qlocale_tools_p.h"
9#include "private/qlocking_p.h"
10
11#include <QtCore/QDataStream>
12#include <QtCore/QDateTime>
13#include <QtCore/QDirIterator>
14#include <QtCore/QFile>
15#include <QtCore/QCache>
16#include <QtCore/QMap>
17#include <QtCore/QMutex>
18
19#include <qdebug.h>
20#include <qplatformdefs.h>
21
22#include <algorithm>
23#include <errno.h>
24#include <limits.h>
25#ifndef Q_OS_INTEGRITY
26#include <sys/param.h> // to use MAXSYMLINKS constant
27#endif
28#include <unistd.h> // to use _SC_SYMLOOP_MAX constant
29
31
32using namespace Qt::StringLiterals;
33
34#if QT_CONFIG(icu)
35Q_CONSTINIT static QBasicMutex s_icu_mutex;
36#endif
37
38/*
39 Private
40
41 tz file implementation
42*/
43
47};
48
49// Define as a type as Q_GLOBAL_STATIC doesn't like it
51
52static bool isTzFile(const QString &name);
53
54// Parse zone.tab table for territory information, read directories to ensure we
55// find all installed zones (many are omitted from zone.tab; even more from
56// zone1970.tab).
58{
59 QString path = QStringLiteral("/usr/share/zoneinfo/zone.tab");
60 if (!QFile::exists(path))
61 path = QStringLiteral("/usr/lib/zoneinfo/zone.tab");
62
63 QFile tzif(path);
64 if (!tzif.open(QIODevice::ReadOnly))
65 return QTzTimeZoneHash();
66
67 QTzTimeZoneHash zonesHash;
68 while (!tzif.atEnd()) {
69 const QByteArray line = tzif.readLine().trimmed();
70 if (line.isEmpty() || line.at(0) == '#') // Ignore empty or comment
71 continue;
72 // Data rows are tab-separated columns Region, Coordinates, ID, Optional Comments
74 int cut = text.indexOf('\t');
75 if (Q_LIKELY(cut > 0)) {
76 QTzTimeZone zone;
77 // TODO: QLocale & friends could do this look-up without UTF8-conversion:
79 text = text.sliced(cut + 1);
80 cut = text.indexOf('\t');
81 if (Q_LIKELY(cut >= 0)) { // Skip over Coordinates, read ID and comment
82 text = text.sliced(cut + 1);
83 cut = text.indexOf('\t'); // < 0 if line has no comment
84 if (Q_LIKELY(cut)) {
85 const QByteArray id = (cut > 0 ? text.first(cut) : text).toByteArray();
86 if (cut > 0)
87 zone.comment = text.sliced(cut + 1).toByteArray();
88 zonesHash.insert(id, zone);
89 }
90 }
91 }
92 }
93
94 const qsizetype cut = path.lastIndexOf(u'/');
95 Q_ASSERT(cut > 0);
96 const QDir zoneDir = QDir(path.first(cut));
98 while (zoneFiles.hasNext()) {
99 const QFileInfo info = zoneFiles.nextFileInfo();
100 if (!(info.isFile() || info.isSymLink()))
101 continue;
102 const QString name = zoneDir.relativeFilePath(info.filePath());
103 // Two sub-directories containing (more or less) copies of the zoneinfo tree.
104 if (info.isDir() ? name == "posix"_L1 || name == "right"_L1
105 : name.startsWith("posix/"_L1) || name.startsWith("right/"_L1)) {
106 continue;
107 }
108 // We could filter out *.* and leapseconds instead of doing the
109 // isTzFile() check; in practice current (2023) zoneinfo/ contains only
110 // actual zone files and matches to that filter.
112 if (!zonesHash.contains(id) && isTzFile(zoneDir.absoluteFilePath(name)))
113 zonesHash.insert(id, QTzTimeZone());
114 }
115 return zonesHash;
116}
117
118// Hash of available system tz files as loaded by loadTzTimeZones()
120
121/*
122 The following is copied and modified from tzfile.h which is in the public domain.
123 Copied as no compatibility guarantee and is never system installed.
124 See https://github.com/eggert/tz/blob/master/tzfile.h
125*/
126
127#define TZ_MAGIC "TZif"
128#define TZ_MAX_TIMES 1200
129#define TZ_MAX_TYPES 256 // Limited by what (unsigned char)'s can hold
130#define TZ_MAX_CHARS 50 // Maximum number of abbreviation characters
131#define TZ_MAX_LEAPS 50 // Maximum number of leap second corrections
132
133struct QTzHeader {
134 char tzh_magic[4]; // TZ_MAGIC
135 char tzh_version; // '\0' or '2' as of 2005
136 char tzh_reserved[15]; // reserved--must be zero
137 quint32 tzh_ttisgmtcnt; // number of trans. time flags
138 quint32 tzh_ttisstdcnt; // number of trans. time flags
139 quint32 tzh_leapcnt; // number of leap seconds
140 quint32 tzh_timecnt; // number of transition times
141 quint32 tzh_typecnt; // number of local time types
142 quint32 tzh_charcnt; // number of abbr. chars
143};
144
146 qint64 tz_time; // Transition time
147 quint8 tz_typeind; // Type Index
148};
150
151struct QTzType {
152 int tz_gmtoff; // UTC offset in seconds
153 bool tz_isdst; // Is DST
154 quint8 tz_abbrind; // abbreviation list index
155};
157
158static bool isTzFile(const QString &name)
159{
160 QFile file(name);
161 return file.open(QFile::ReadOnly) && file.read(strlen(TZ_MAGIC)) == TZ_MAGIC;
162}
163
164// TZ File parsing
165
167{
168 QTzHeader hdr;
169 quint8 ch;
170 *ok = false;
171
172 // Parse Magic, 4 bytes
173 ds.readRawData(hdr.tzh_magic, 4);
174
175 if (memcmp(hdr.tzh_magic, TZ_MAGIC, 4) != 0 || ds.status() != QDataStream::Ok)
176 return hdr;
177
178 // Parse Version, 1 byte, before 2005 was '\0', since 2005 a '2', since 2013 a '3'
179 ds >> ch;
180 hdr.tzh_version = ch;
181 if (ds.status() != QDataStream::Ok
182 || (hdr.tzh_version != '2' && hdr.tzh_version != '\0' && hdr.tzh_version != '3')) {
183 return hdr;
184 }
185
186 // Parse reserved space, 15 bytes
187 ds.readRawData(hdr.tzh_reserved, 15);
188 if (ds.status() != QDataStream::Ok)
189 return hdr;
190
191 // Parse rest of header, 6 x 4-byte transition counts
192 ds >> hdr.tzh_ttisgmtcnt >> hdr.tzh_ttisstdcnt >> hdr.tzh_leapcnt >> hdr.tzh_timecnt
193 >> hdr.tzh_typecnt >> hdr.tzh_charcnt;
194
195 // Check defined maximums
196 if (ds.status() != QDataStream::Ok
201 || hdr.tzh_ttisgmtcnt > hdr.tzh_typecnt
202 || hdr.tzh_ttisstdcnt > hdr.tzh_typecnt) {
203 return hdr;
204 }
205
206 *ok = true;
207 return hdr;
208}
209
210static QList<QTzTransition> parseTzTransitions(QDataStream &ds, int tzh_timecnt, bool longTran)
211{
212 QList<QTzTransition> transitions(tzh_timecnt);
213
214 if (longTran) {
215 // Parse tzh_timecnt x 8-byte transition times
216 for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
217 ds >> transitions[i].tz_time;
218 if (ds.status() != QDataStream::Ok)
219 transitions.resize(i);
220 }
221 } else {
222 // Parse tzh_timecnt x 4-byte transition times
223 qint32 val;
224 for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
225 ds >> val;
226 transitions[i].tz_time = val;
227 if (ds.status() != QDataStream::Ok)
228 transitions.resize(i);
229 }
230 }
231
232 // Parse tzh_timecnt x 1-byte transition type index
233 for (int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
234 quint8 typeind;
235 ds >> typeind;
236 if (ds.status() == QDataStream::Ok)
237 transitions[i].tz_typeind = typeind;
238 }
239
240 return transitions;
241}
242
243static QList<QTzType> parseTzTypes(QDataStream &ds, int tzh_typecnt)
244{
245 QList<QTzType> types(tzh_typecnt);
246
247 // Parse tzh_typecnt x transition types
248 for (int i = 0; i < tzh_typecnt && ds.status() == QDataStream::Ok; ++i) {
249 QTzType &type = types[i];
250 // Parse UTC Offset, 4 bytes
251 ds >> type.tz_gmtoff;
252 // Parse Is DST flag, 1 byte
253 if (ds.status() == QDataStream::Ok)
254 ds >> type.tz_isdst;
255 // Parse Abbreviation Array Index, 1 byte
256 if (ds.status() == QDataStream::Ok)
257 ds >> type.tz_abbrind;
258 if (ds.status() != QDataStream::Ok)
259 types.resize(i);
260 }
261
262 return types;
263}
264
266{
267 // Parse the abbreviation list which is tzh_charcnt long with '\0' separated strings. The
268 // QTzType.tz_abbrind index points to the first char of the abbreviation in the array, not the
269 // occurrence in the list. It can also point to a partial string so we need to use the actual typeList
270 // index values when parsing. By using a map with tz_abbrind as ordered key we get both index
271 // methods in one data structure and can convert the types afterwards.
273 quint8 ch;
275 // First parse the full abbrev string
276 for (int i = 0; i < tzh_charcnt && ds.status() == QDataStream::Ok; ++i) {
277 ds >> ch;
278 if (ds.status() == QDataStream::Ok)
279 input.append(char(ch));
280 else
281 return map;
282 }
283 // Then extract all the substrings pointed to by types
284 for (const QTzType &type : types) {
285 QByteArray abbrev;
286 for (int i = type.tz_abbrind; input.at(i) != '\0'; ++i)
287 abbrev.append(input.at(i));
288 // Have reached end of an abbreviation, so add to map
289 map[type.tz_abbrind] = abbrev;
290 }
291 return map;
292}
293
294static void parseTzLeapSeconds(QDataStream &ds, int tzh_leapcnt, bool longTran)
295{
296 // Parse tzh_leapcnt x pairs of leap seconds
297 // We don't use leap seconds, so only read and don't store
298 qint32 val;
299 if (longTran) {
300 // v2 file format, each entry is 12 bytes long
301 qint64 time;
302 for (int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
303 // Parse Leap Occurrence Time, 8 bytes
304 ds >> time;
305 // Parse Leap Seconds To Apply, 4 bytes
306 if (ds.status() == QDataStream::Ok)
307 ds >> val;
308 }
309 } else {
310 // v0 file format, each entry is 8 bytes long
311 for (int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
312 // Parse Leap Occurrence Time, 4 bytes
313 ds >> val;
314 // Parse Leap Seconds To Apply, 4 bytes
315 if (ds.status() == QDataStream::Ok)
316 ds >> val;
317 }
318 }
319}
320
321static QList<QTzType> parseTzIndicators(QDataStream &ds, const QList<QTzType> &types, int tzh_ttisstdcnt,
322 int tzh_ttisgmtcnt)
323{
325 bool temp;
326 /*
327 Scan and discard indicators.
328
329 These indicators are only of use (by the date program) when "handling
330 POSIX-style time zone environment variables". The flags here say whether
331 the *specification* of the zone gave the time in UTC, local standard time
332 or local wall time; but whatever was specified has been digested for us,
333 already, by the zone-info compiler (zic), so that the tz_time values read
334 from the file (by parseTzTransitions) are all in UTC.
335 */
336
337 // Scan tzh_ttisstdcnt x 1-byte standard/wall indicators
338 for (int i = 0; i < tzh_ttisstdcnt && ds.status() == QDataStream::Ok; ++i)
339 ds >> temp;
340
341 // Scan tzh_ttisgmtcnt x 1-byte UTC/local indicators
342 for (int i = 0; i < tzh_ttisgmtcnt && ds.status() == QDataStream::Ok; ++i)
343 ds >> temp;
344
345 return result;
346}
347
349{
350 // Parse POSIX rule, variable length '\n' enclosed
352
353 quint8 ch;
354 ds >> ch;
355 if (ch != '\n' || ds.status() != QDataStream::Ok)
356 return rule;
357 ds >> ch;
358 while (ch != '\n' && ds.status() == QDataStream::Ok) {
359 rule.append((char)ch);
360 ds >> ch;
361 }
362
363 return rule;
364}
365
366static QDate calculateDowDate(int year, int month, int dayOfWeek, int week)
367{
368 if (dayOfWeek == 0) // Sunday; we represent it as 7, POSIX uses 0
369 dayOfWeek = 7;
370 else if (dayOfWeek & ~7 || month < 1 || month > 12 || week < 1 || week > 5)
371 return QDate();
372
373 QDate date(year, month, 1);
374 int startDow = date.dayOfWeek();
375 if (startDow <= dayOfWeek)
376 date = date.addDays(dayOfWeek - startDow - 7);
377 else
378 date = date.addDays(dayOfWeek - startDow);
379 date = date.addDays(week * 7);
380 while (date.month() != month)
381 date = date.addDays(-7);
382 return date;
383}
384
385static QDate calculatePosixDate(const QByteArray &dateRule, int year)
386{
387 Q_ASSERT(!dateRule.isEmpty());
388 bool ok;
389 // Can start with M, J, or a digit
390 if (dateRule.at(0) == 'M') {
391 // nth week in month format "Mmonth.week.dow"
392 QList<QByteArray> dateParts = dateRule.split('.');
393 if (dateParts.size() > 2) {
394 Q_ASSERT(!dateParts.at(0).isEmpty()); // the 'M' is its [0].
395 int month = QByteArrayView{ dateParts.at(0) }.sliced(1).toInt(&ok);
396 int week = ok ? dateParts.at(1).toInt(&ok) : 0;
397 int dow = ok ? dateParts.at(2).toInt(&ok) : 0;
398 if (ok)
399 return calculateDowDate(year, month, dow, week);
400 }
401 } else if (dateRule.at(0) == 'J') {
402 // Day of Year 1...365, ignores Feb 29.
403 // So March always starts on day 60.
404 int doy = QByteArrayView{ dateRule }.sliced(1).toInt(&ok);
405 if (ok && doy > 0 && doy < 366) {
406 // Subtract 1 because we're adding days *after* the first of
407 // January, unless it's after February in a leap year, when the leap
408 // day cancels that out:
409 if (!QDate::isLeapYear(year) || doy < 60)
410 --doy;
411 return QDate(year, 1, 1).addDays(doy);
412 }
413 } else {
414 // Day of Year 0...365, includes Feb 29
415 int doy = dateRule.toInt(&ok);
416 if (ok && doy >= 0 && doy < 366)
417 return QDate(year, 1, 1).addDays(doy);
418 }
419 return QDate();
420}
421
422// returns the time in seconds, INT_MIN if we failed to parse
423static int parsePosixTime(const char *begin, const char *end)
424{
425 // Format "hh[:mm[:ss]]"
426 int hour, min = 0, sec = 0;
427
428 const int maxHour = 137; // POSIX's extended range.
429 auto r = qstrntoll(begin, end - begin, 10);
430 hour = r.result;
431 if (!r.ok() || hour < -maxHour || hour > maxHour || r.used > 2)
432 return INT_MIN;
433 begin += r.used;
434 if (begin < end && *begin == ':') {
435 // minutes
436 ++begin;
437 r = qstrntoll(begin, end - begin, 10);
438 min = r.result;
439 if (!r.ok() || min < 0 || min > 59 || r.used > 2)
440 return INT_MIN;
441
442 begin += r.used;
443 if (begin < end && *begin == ':') {
444 // seconds
445 ++begin;
446 r = qstrntoll(begin, end - begin, 10);
447 sec = r.result;
448 if (!r.ok() || sec < 0 || sec > 59 || r.used > 2)
449 return INT_MIN;
450 begin += r.used;
451 }
452 }
453
454 // we must have consumed everything
455 if (begin != end)
456 return INT_MIN;
457
458 return (hour * 60 + min) * 60 + sec;
459}
460
461static int parsePosixTransitionTime(const QByteArray &timeRule)
462{
463 return parsePosixTime(timeRule.constBegin(), timeRule.constEnd());
464}
465
466static int parsePosixOffset(const char *begin, const char *end)
467{
468 // Format "[+|-]hh[:mm[:ss]]"
469 // note that the sign is inverted because POSIX counts in hours West of GMT
470 bool negate = true;
471 if (*begin == '+') {
472 ++begin;
473 } else if (*begin == '-') {
474 negate = false;
475 ++begin;
476 }
477
479 if (value == INT_MIN)
480 return value;
481 return negate ? -value : value;
482}
483
484static inline bool asciiIsLetter(char ch)
485{
486 ch |= 0x20; // lowercases if it is a letter, otherwise just corrupts ch
487 return ch >= 'a' && ch <= 'z';
488}
489
490namespace {
491
492struct PosixZone
493{
494 enum {
495 InvalidOffset = INT_MIN,
496 };
497
499 int offset;
500
501 static PosixZone invalid() { return {QString(), InvalidOffset}; }
502 static PosixZone parse(const char *&pos, const char *end);
503
504 bool hasValidOffset() const noexcept { return offset != InvalidOffset; }
505};
506
507} // unnamed namespace
508
509// Returns the zone name, the offset (in seconds) and advances \a begin to
510// where the parsing ended. Returns a zone of INT_MIN in case an offset
511// couldn't be read.
512PosixZone PosixZone::parse(const char *&pos, const char *end)
513{
514 static const char offsetChars[] = "0123456789:";
515
516 const char *nameBegin = pos;
517 const char *nameEnd;
518 Q_ASSERT(pos < end);
519
520 if (*pos == '<') {
521 ++nameBegin; // skip the '<'
522 nameEnd = nameBegin;
523 while (nameEnd < end && *nameEnd != '>') {
524 // POSIX says only alphanumeric, but we allow anything
525 ++nameEnd;
526 }
527 pos = nameEnd + 1; // skip the '>'
528 } else {
529 nameEnd = nameBegin;
530 while (nameEnd < end && asciiIsLetter(*nameEnd))
531 ++nameEnd;
532 pos = nameEnd;
533 }
534 if (nameEnd - nameBegin < 3)
535 return invalid(); // name must be at least 3 characters long
536
537 // zone offset, form [+-]hh:mm:ss
538 const char *zoneBegin = pos;
539 const char *zoneEnd = pos;
540 if (zoneEnd < end && (zoneEnd[0] == '+' || zoneEnd[0] == '-'))
541 ++zoneEnd;
542 while (zoneEnd < end) {
543 if (strchr(offsetChars, char(*zoneEnd)) == nullptr)
544 break;
545 ++zoneEnd;
546 }
547
548 QString name = QString::fromUtf8(nameBegin, nameEnd - nameBegin);
549 const int offset = zoneEnd > zoneBegin ? parsePosixOffset(zoneBegin, zoneEnd) : InvalidOffset;
550 pos = zoneEnd;
551 // UTC+hh:mm:ss or GMT+hh:mm:ss should be read as offsets from UTC, not as a
552 // POSIX rule naming a zone as UTC or GMT and specifying a non-zero offset.
553 if (offset != 0 && (name =="UTC"_L1 || name == "GMT"_L1))
554 return invalid();
555 return {std::move(name), offset};
556}
557
558/* Parse and check a POSIX rule.
559
560 By default a simple zone abbreviation with no offset information is accepted.
561 Set \a requireOffset to \c true to require that there be offset data present.
562*/
563static auto validatePosixRule(const QByteArray &posixRule, bool requireOffset = false)
564{
565 // Format is described here:
566 // http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
567 // See also calculatePosixTransition()'s reference.
568 const auto parts = posixRule.split(',');
569 const struct { bool isValid, hasDst; } fail{false, false}, good{true, parts.size() > 1};
570 const QByteArray &zoneinfo = parts.at(0);
571 if (zoneinfo.isEmpty())
572 return fail;
573
574 const char *begin = zoneinfo.begin();
575 {
576 // Updates begin to point after the name and offset it parses:
577 const auto posix = PosixZone::parse(begin, zoneinfo.end());
578 if (posix.name.isEmpty())
579 return fail;
580 if (requireOffset && !posix.hasValidOffset())
581 return fail;
582 }
583
584 if (good.hasDst) {
585 if (begin >= zoneinfo.end())
586 return fail;
587 // Expect a second name (and optional offset) after the first:
588 if (PosixZone::parse(begin, zoneinfo.end()).name.isEmpty())
589 return fail;
590 }
591 if (begin < zoneinfo.end())
592 return fail;
593
594 if (good.hasDst) {
595 if (parts.size() != 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty())
596 return fail;
597 for (int i = 1; i < 3; ++i) {
598 const auto tran = parts.at(i).split('/');
599 if (!calculatePosixDate(tran.at(0), 1972).isValid())
600 return fail;
601 if (tran.size() > 1) {
602 const auto time = tran.at(1);
603 if (parsePosixTime(time.begin(), time.end()) == INT_MIN)
604 return fail;
605 }
606 }
607 }
608 return good;
609}
610
612 int startYear, int endYear,
613 qint64 lastTranMSecs)
614{
616
617 // POSIX Format is like "TZ=CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00"
618 // i.e. "std offset dst [offset],start[/time],end[/time]"
619 // See the section about TZ at
620 // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
621 // and the link in validatePosixRule(), above.
622 QList<QByteArray> parts = posixRule.split(',');
623
624 PosixZone stdZone, dstZone = PosixZone::invalid();
625 {
626 const QByteArray &zoneinfo = parts.at(0);
627 const char *begin = zoneinfo.constBegin();
628
629 stdZone = PosixZone::parse(begin, zoneinfo.constEnd());
630 if (!stdZone.hasValidOffset()) {
631 stdZone.offset = 0; // reset to UTC if we failed to parse
632 } else if (begin < zoneinfo.constEnd()) {
633 dstZone = PosixZone::parse(begin, zoneinfo.constEnd());
634 if (!dstZone.hasValidOffset()) {
635 // if the dst offset isn't provided, it is 1 hour ahead of the standard offset
636 dstZone.offset = stdZone.offset + (60 * 60);
637 }
638 }
639 }
640
641 // If only the name part, or no DST specified, then no transitions
642 if (parts.size() == 1 || !dstZone.hasValidOffset()) {
644 data.atMSecsSinceEpoch = lastTranMSecs;
645 data.offsetFromUtc = stdZone.offset;
646 data.standardTimeOffset = stdZone.offset;
647 data.daylightTimeOffset = 0;
648 data.abbreviation = stdZone.name.isEmpty() ? QString::fromUtf8(parts.at(0)) : stdZone.name;
649 result << data;
650 return result;
651 }
652 if (parts.size() < 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty())
653 return result; // Malformed.
654
655 // Get the std to dst transition details
656 const int twoOClock = 7200; // Default transition time, when none specified
657 const auto dstParts = parts.at(1).split('/');
658 const QByteArray dstDateRule = dstParts.at(0);
659 const int dstTime = dstParts.size() < 2 ? twoOClock : parsePosixTransitionTime(dstParts.at(1));
660
661 // Get the dst to std transition details
662 const auto stdParts = parts.at(2).split('/');
663 const QByteArray stdDateRule = stdParts.at(0);
664 const int stdTime = stdParts.size() < 2 ? twoOClock : parsePosixTransitionTime(stdParts.at(1));
665
666 if (dstDateRule.isEmpty() || stdDateRule.isEmpty() || dstTime == INT_MIN || stdTime == INT_MIN)
667 return result; // Malformed.
668
669 // Limit year to the range QDateTime can represent:
670 const int minYear = int(QDateTime::YearRange::First);
671 const int maxYear = int(QDateTime::YearRange::Last);
672 startYear = qBound(minYear, startYear, maxYear);
673 endYear = qBound(minYear, endYear, maxYear);
674 Q_ASSERT(startYear <= endYear);
675
676 for (int year = startYear; year <= endYear; ++year) {
677 // Note: std and dst, despite being QDateTime(,, UTC), have the
678 // date() and time() of the *zone*'s description of the transition
679 // moments; the atMSecsSinceEpoch values computed from them are
680 // correctly offse to be UTC-based.
681
682 QTimeZonePrivate::Data dstData; // Transition to DST
683 QDateTime dst(calculatePosixDate(dstDateRule, year)
684 .startOfDay(QTimeZone::UTC).addSecs(dstTime));
685 dstData.atMSecsSinceEpoch = dst.toMSecsSinceEpoch() - stdZone.offset * 1000;
686 dstData.offsetFromUtc = dstZone.offset;
687 dstData.standardTimeOffset = stdZone.offset;
688 dstData.daylightTimeOffset = dstZone.offset - stdZone.offset;
689 dstData.abbreviation = dstZone.name;
690 QTimeZonePrivate::Data stdData; // Transition to standard time
691 QDateTime std(calculatePosixDate(stdDateRule, year)
692 .startOfDay(QTimeZone::UTC).addSecs(stdTime));
693 stdData.atMSecsSinceEpoch = std.toMSecsSinceEpoch() - dstZone.offset * 1000;
694 stdData.offsetFromUtc = stdZone.offset;
695 stdData.standardTimeOffset = stdZone.offset;
696 stdData.daylightTimeOffset = 0;
697 stdData.abbreviation = stdZone.name;
698
699 if (year == startYear) {
700 // Handle the special case of fixed state, which may be represented
701 // by fake transitions at start and end of each year:
702 if (dstData.atMSecsSinceEpoch < stdData.atMSecsSinceEpoch) {
703 if (dst <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
704 && std >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
705 // Permanent DST:
706 dstData.atMSecsSinceEpoch = lastTranMSecs;
707 result << dstData;
708 return result;
709 }
710 } else {
711 if (std <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
712 && dst >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
713 // Permanent Standard time, perversely described:
714 stdData.atMSecsSinceEpoch = lastTranMSecs;
715 result << stdData;
716 return result;
717 }
718 }
719 }
720
721 const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty();
722 const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty();
723 if (useStd && useDst) {
724 if (dst < std)
725 result << dstData << stdData;
726 else
727 result << stdData << dstData;
728 } else if (useStd) {
729 result << stdData;
730 } else if (useDst) {
731 result << dstData;
732 }
733 }
734 return result;
735}
736
737// Create the system default time zone
738QTzTimeZonePrivate::QTzTimeZonePrivate()
739 : QTzTimeZonePrivate(staticSystemTimeZoneId())
740{
741}
742
743QTzTimeZonePrivate::~QTzTimeZonePrivate()
744{
745}
746
747QTzTimeZonePrivate *QTzTimeZonePrivate::clone() const
748{
749#if QT_CONFIG(icu)
750 const auto lock = qt_scoped_lock(s_icu_mutex);
751#endif
752 return new QTzTimeZonePrivate(*this);
753}
754
756{
757public:
758 QTzTimeZoneCacheEntry fetchEntry(const QByteArray &ianaId);
759
760private:
761 QTzTimeZoneCacheEntry findEntry(const QByteArray &ianaId);
763 QMutex m_mutex;
764};
765
766QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId)
767{
768 QTzTimeZoneCacheEntry ret;
769 QFile tzif;
770 if (ianaId.isEmpty()) {
771 // Open system tz
772 tzif.setFileName(QStringLiteral("/etc/localtime"));
773 if (!tzif.open(QIODevice::ReadOnly))
774 return ret;
775 } else {
776 // Open named tz, try modern path first, if fails try legacy path
777 tzif.setFileName("/usr/share/zoneinfo/"_L1 + QString::fromLocal8Bit(ianaId));
778 if (!tzif.open(QIODevice::ReadOnly)) {
779 tzif.setFileName("/usr/lib/zoneinfo/"_L1 + QString::fromLocal8Bit(ianaId));
780 if (!tzif.open(QIODevice::ReadOnly)) {
781 // ianaId may be a POSIX rule, taken from $TZ or /etc/TZ
782 auto check = validatePosixRule(ianaId);
783 if (check.isValid) {
784 ret.m_hasDst = check.hasDst;
785 ret.m_posixRule = ianaId;
786 }
787 return ret;
788 }
789 }
790 }
791
792 QDataStream ds(&tzif);
793
794 // Parse the old version block of data
795 bool ok = false;
796 QByteArray posixRule;
797 QTzHeader hdr = parseTzHeader(ds, &ok);
798 if (!ok || ds.status() != QDataStream::Ok)
799 return ret;
800 QList<QTzTransition> tranList = parseTzTransitions(ds, hdr.tzh_timecnt, false);
801 if (ds.status() != QDataStream::Ok)
802 return ret;
804 if (ds.status() != QDataStream::Ok)
805 return ret;
807 if (ds.status() != QDataStream::Ok)
808 return ret;
809 parseTzLeapSeconds(ds, hdr.tzh_leapcnt, false);
810 if (ds.status() != QDataStream::Ok)
811 return ret;
813 if (ds.status() != QDataStream::Ok)
814 return ret;
815
816 // If version 2 then parse the second block of data
817 if (hdr.tzh_version == '2' || hdr.tzh_version == '3') {
818 ok = false;
819 QTzHeader hdr2 = parseTzHeader(ds, &ok);
820 if (!ok || ds.status() != QDataStream::Ok)
821 return ret;
822 tranList = parseTzTransitions(ds, hdr2.tzh_timecnt, true);
823 if (ds.status() != QDataStream::Ok)
824 return ret;
826 if (ds.status() != QDataStream::Ok)
827 return ret;
828 abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList);
829 if (ds.status() != QDataStream::Ok)
830 return ret;
831 parseTzLeapSeconds(ds, hdr2.tzh_leapcnt, true);
832 if (ds.status() != QDataStream::Ok)
833 return ret;
835 if (ds.status() != QDataStream::Ok)
836 return ret;
837 posixRule = parseTzPosixRule(ds);
838 if (ds.status() != QDataStream::Ok)
839 return ret;
840 }
841 // Translate the TZ file's raw data into our internal form:
842
843 if (!posixRule.isEmpty()) {
844 auto check = validatePosixRule(posixRule);
845 if (!check.isValid) // We got a POSIX rule, but it was malformed:
846 return ret;
847 ret.m_posixRule = posixRule;
848 ret.m_hasDst = check.hasDst;
849 }
850
851 // Translate the array-index-based tz_abbrind into list index
852 const int size = abbrevMap.size();
853 ret.m_abbreviations.clear();
854 ret.m_abbreviations.reserve(size);
855 QList<int> abbrindList;
856 abbrindList.reserve(size);
857 for (auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) {
858 ret.m_abbreviations.append(it.value());
859 abbrindList.append(it.key());
860 }
861 // Map tz_abbrind from map's keys (as initially read) to abbrindList's
862 // indices (used hereafter):
863 for (int i = 0; i < typeList.size(); ++i)
864 typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind);
865
866 // TODO: is typeList[0] always the "before zones" data ? It seems to be ...
867 if (typeList.size())
868 ret.m_preZoneRule = { typeList.at(0).tz_gmtoff, 0, typeList.at(0).tz_abbrind };
869 else
870 ret.m_preZoneRule = { 0, 0, 0 };
871
872 // Offsets are stored as total offset, want to know separate UTC and DST offsets
873 // so find the first non-dst transition to use as base UTC Offset
874 int utcOffset = ret.m_preZoneRule.stdOffset;
875 for (const QTzTransition &tran : std::as_const(tranList)) {
876 if (!typeList.at(tran.tz_typeind).tz_isdst) {
877 utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff;
878 break;
879 }
880 }
881
882 // Now for each transition time calculate and store our rule:
883 const int tranCount = tranList.size();;
884 ret.m_tranTimes.reserve(tranCount);
885 // The DST offset when in effect: usually stable, usually an hour:
886 int lastDstOff = 3600;
887 for (int i = 0; i < tranCount; i++) {
888 const QTzTransition &tz_tran = tranList.at(i);
889 QTzTransitionTime tran;
890 QTzTransitionRule rule;
891 const QTzType tz_type = typeList.at(tz_tran.tz_typeind);
892
893 // Calculate the associated Rule
894 if (!tz_type.tz_isdst) {
895 utcOffset = tz_type.tz_gmtoff;
896 } else if (Q_UNLIKELY(tz_type.tz_gmtoff != utcOffset + lastDstOff)) {
897 /*
898 This might be a genuine change in DST offset, but could also be
899 DST starting at the same time as the standard offset changed. See
900 if DST's end gives a more plausible utcOffset (i.e. one closer to
901 the last we saw, or a simple whole hour):
902 */
903 // Standard offset inferred from net offset and expected DST offset:
904 const int inferStd = tz_type.tz_gmtoff - lastDstOff; // != utcOffset
905 for (int j = i + 1; j < tranCount; j++) {
906 const QTzType new_type = typeList.at(tranList.at(j).tz_typeind);
907 if (!new_type.tz_isdst) {
908 const int newUtc = new_type.tz_gmtoff;
909 if (newUtc == utcOffset) {
910 // DST-end can't help us, avoid lots of messy checks.
911 // else: See if the end matches the familiar DST offset:
912 } else if (newUtc == inferStd) {
913 utcOffset = newUtc;
914 // else: let either end shift us to one hour as DST offset:
915 } else if (tz_type.tz_gmtoff - 3600 == utcOffset) {
916 // Start does it
917 } else if (tz_type.tz_gmtoff - 3600 == newUtc) {
918 utcOffset = newUtc; // End does it
919 // else: prefer whichever end gives DST offset closer to
920 // last, but consider any offset > 0 "closer" than any <= 0:
921 } else if (newUtc < tz_type.tz_gmtoff
922 ? (utcOffset >= tz_type.tz_gmtoff
923 || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))
924 : (utcOffset >= tz_type.tz_gmtoff
925 && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) {
926 utcOffset = newUtc;
927 }
928 break;
929 }
930 }
931 lastDstOff = tz_type.tz_gmtoff - utcOffset;
932 }
933 rule.stdOffset = utcOffset;
934 rule.dstOffset = tz_type.tz_gmtoff - utcOffset;
935 rule.abbreviationIndex = tz_type.tz_abbrind;
936
937 // If the rule already exist then use that, otherwise add it
938 int ruleIndex = ret.m_tranRules.indexOf(rule);
939 if (ruleIndex == -1) {
940 if (rule.dstOffset != 0)
941 ret.m_hasDst = true;
942 tran.ruleIndex = ret.m_tranRules.size();
943 ret.m_tranRules.append(rule);
944 } else {
945 tran.ruleIndex = ruleIndex;
946 }
947
948 tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000;
949 ret.m_tranTimes.append(tran);
950 }
951
952 return ret;
953}
954
955QTzTimeZoneCacheEntry QTzTimeZoneCache::fetchEntry(const QByteArray &ianaId)
956{
957 QMutexLocker locker(&m_mutex);
958
959 // search the cache...
960 QTzTimeZoneCacheEntry *obj = m_cache.object(ianaId);
961 if (obj)
962 return *obj;
963
964 // ... or build a new entry from scratch
965 QTzTimeZoneCacheEntry ret = findEntry(ianaId);
966 m_cache.insert(ianaId, new QTzTimeZoneCacheEntry(ret));
967 return ret;
968}
969
970// Create a named time zone
971QTzTimeZonePrivate::QTzTimeZonePrivate(const QByteArray &ianaId)
972{
973 static QTzTimeZoneCache tzCache;
974 auto entry = tzCache.fetchEntry(ianaId);
975 if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty())
976 return; // Invalid after all !
977
978 cached_data = std::move(entry);
979 m_id = ianaId;
980 // Avoid empty ID, if we have an abbreviation to use instead
981 if (m_id.isEmpty()) {
982 // This can only happen for the system zone, when we've read the
983 // contents of /etc/localtime because it wasn't a symlink.
984#if QT_CONFIG(icu)
985 // Use ICU's system zone, if only to avoid using the abbreviation as ID
986 // (ICU might mis-recognize it) in displayName().
987 m_icu = new QIcuTimeZonePrivate();
988 // Use its ID, as an alternate source of data:
989 m_id = m_icu->id();
990 if (!m_id.isEmpty())
991 return;
992#endif
993 m_id = abbreviation(QDateTime::currentMSecsSinceEpoch()).toUtf8();
994 }
995}
996
997QLocale::Territory QTzTimeZonePrivate::territory() const
998{
999 return tzZones->value(m_id).territory;
1000}
1001
1002QString QTzTimeZonePrivate::comment() const
1003{
1004 return QString::fromUtf8(tzZones->value(m_id).comment);
1005}
1006
1007QString QTzTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch,
1008 QTimeZone::NameType nameType,
1009 const QLocale &locale) const
1010{
1011#if QT_CONFIG(icu)
1012 auto lock = qt_unique_lock(s_icu_mutex);
1013 if (!m_icu)
1014 m_icu = new QIcuTimeZonePrivate(m_id);
1015 // TODO small risk may not match if tran times differ due to outdated files
1016 // TODO Some valid TZ names are not valid ICU names, use translation table?
1017 if (m_icu->isValid())
1018 return m_icu->displayName(atMSecsSinceEpoch, nameType, locale);
1019 lock.unlock();
1020#else
1021 Q_UNUSED(nameType);
1022 Q_UNUSED(locale);
1023#endif
1024 // Fall back to base-class:
1025 return QTimeZonePrivate::displayName(atMSecsSinceEpoch, nameType, locale);
1026}
1027
1028QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1029 QTimeZone::NameType nameType,
1030 const QLocale &locale) const
1031{
1032#if QT_CONFIG(icu)
1033 auto lock = qt_unique_lock(s_icu_mutex);
1034 if (!m_icu)
1035 m_icu = new QIcuTimeZonePrivate(m_id);
1036 // TODO small risk may not match if tran times differ due to outdated files
1037 // TODO Some valid TZ names are not valid ICU names, use translation table?
1038 if (m_icu->isValid())
1039 return m_icu->displayName(timeType, nameType, locale);
1040 lock.unlock();
1041#else
1042 Q_UNUSED(timeType);
1043 Q_UNUSED(nameType);
1044 Q_UNUSED(locale);
1045#endif
1046 // If no ICU available then have to use abbreviations instead
1047 // Abbreviations don't have GenericTime
1048 if (timeType == QTimeZone::GenericTime)
1049 timeType = QTimeZone::StandardTime;
1050
1051 // Get current tran, if valid and is what we want, then use it
1052 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
1053 QTimeZonePrivate::Data tran = data(currentMSecs);
1054 if (tran.atMSecsSinceEpoch != invalidMSecs()
1055 && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0)
1056 || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) {
1057 return tran.abbreviation;
1058 }
1059
1060 // Otherwise get next tran and if valid and is what we want, then use it
1061 tran = nextTransition(currentMSecs);
1062 if (tran.atMSecsSinceEpoch != invalidMSecs()
1063 && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0)
1064 || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) {
1065 return tran.abbreviation;
1066 }
1067
1068 // Otherwise get prev tran and if valid and is what we want, then use it
1069 tran = previousTransition(currentMSecs);
1070 if (tran.atMSecsSinceEpoch != invalidMSecs())
1071 tran = previousTransition(tran.atMSecsSinceEpoch);
1072 if (tran.atMSecsSinceEpoch != invalidMSecs()
1073 && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0)
1074 || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) {
1075 return tran.abbreviation;
1076 }
1077
1078 // Otherwise is strange sequence, so work backwards through trans looking for first match, if any
1079 auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1080 [currentMSecs](const QTzTransitionTime &at) {
1081 return at.atMSecsSinceEpoch <= currentMSecs;
1082 });
1083
1084 while (it != tranCache().cbegin()) {
1085 --it;
1086 tran = dataForTzTransition(*it);
1087 int offset = tran.daylightTimeOffset;
1088 if ((timeType == QTimeZone::DaylightTime) != (offset == 0))
1089 return tran.abbreviation;
1090 }
1091
1092 // Otherwise if no match use current data
1093 return data(currentMSecs).abbreviation;
1094}
1095
1096QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
1097{
1098 return data(atMSecsSinceEpoch).abbreviation;
1099}
1100
1101int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
1102{
1103 const QTimeZonePrivate::Data tran = data(atMSecsSinceEpoch);
1104 return tran.offsetFromUtc; // == tran.standardTimeOffset + tran.daylightTimeOffset
1105}
1106
1107int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
1108{
1109 return data(atMSecsSinceEpoch).standardTimeOffset;
1110}
1111
1112int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
1113{
1114 return data(atMSecsSinceEpoch).daylightTimeOffset;
1115}
1116
1117bool QTzTimeZonePrivate::hasDaylightTime() const
1118{
1119 return cached_data.m_hasDst;
1120}
1121
1122bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
1123{
1124 return (daylightTimeOffset(atMSecsSinceEpoch) != 0);
1125}
1126
1127QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran) const
1128{
1129 return dataFromRule(cached_data.m_tranRules.at(tran.ruleIndex), tran.atMSecsSinceEpoch);
1130}
1131
1132QTimeZonePrivate::Data QTzTimeZonePrivate::dataFromRule(QTzTransitionRule rule,
1133 qint64 msecsSinceEpoch) const
1134{
1135 return { QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)),
1136 msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset, rule.dstOffset };
1137}
1138
1139QList<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear) const
1140{
1141 const int year = QDateTime::fromMSecsSinceEpoch(msNear, QTimeZone::UTC).date().year();
1142 // The Data::atMSecsSinceEpoch of the single entry if zone is constant:
1143 qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch;
1144 return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime);
1145}
1146
1147QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
1148{
1149 // If the required time is after the last transition (or there were none)
1150 // and we have a POSIX rule, then use it:
1151 if (!cached_data.m_posixRule.isEmpty()
1152 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
1153 QList<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
1154 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1155 [forMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
1156 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1157 });
1158 // Use most recent, if any in the past; or the first if we have no other rules:
1159 if (it > posixTrans.cbegin() || (tranCache().isEmpty() && it < posixTrans.cend())) {
1160 QTimeZonePrivate::Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
1161 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1162 return data;
1163 }
1164 }
1165 if (tranCache().isEmpty()) // Only possible if !isValid()
1166 return invalidData();
1167
1168 // Otherwise, use the rule for the most recent or first transition:
1169 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1170 [forMSecsSinceEpoch] (const QTzTransitionTime &at) {
1171 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1172 });
1173 if (last == tranCache().cbegin())
1174 return dataFromRule(cached_data.m_preZoneRule, forMSecsSinceEpoch);
1175
1176 --last;
1177 return dataFromRule(cached_data.m_tranRules.at(last->ruleIndex), forMSecsSinceEpoch);
1178}
1179
1180bool QTzTimeZonePrivate::hasTransitions() const
1181{
1182 return true;
1183}
1184
1185QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
1186{
1187 // If the required time is after the last transition (or there were none)
1188 // and we have a POSIX rule, then use it:
1189 if (!cached_data.m_posixRule.isEmpty()
1190 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
1191 QList<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
1192 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1193 [afterMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
1194 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1195 });
1196
1197 return it == posixTrans.cend() ? invalidData() : *it;
1198 }
1199
1200 // Otherwise, if we can find a valid tran, use its rule:
1201 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1202 [afterMSecsSinceEpoch] (const QTzTransitionTime &at) {
1203 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1204 });
1205 return last != tranCache().cend() ? dataForTzTransition(*last) : invalidData();
1206}
1207
1208QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
1209{
1210 // If the required time is after the last transition (or there were none)
1211 // and we have a POSIX rule, then use it:
1212 if (!cached_data.m_posixRule.isEmpty()
1213 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
1214 QList<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
1215 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1216 [beforeMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) {
1217 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1218 });
1219 if (it > posixTrans.cbegin())
1220 return *--it;
1221 // It fell between the last transition (if any) and the first of the POSIX rule:
1222 return tranCache().isEmpty() ? invalidData() : dataForTzTransition(tranCache().last());
1223 }
1224
1225 // Otherwise if we can find a valid tran then use its rule
1226 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1227 [beforeMSecsSinceEpoch] (const QTzTransitionTime &at) {
1228 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1229 });
1230 return last > tranCache().cbegin() ? dataForTzTransition(*--last) : invalidData();
1231}
1232
1233bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const
1234{
1235 // Allow a POSIX rule as long as it has offset data. (This needs to reject a
1236 // plain abbreviation, without offset, since claiming to support such zones
1237 // would prevent the custom QTimeZone constructor from accepting such a
1238 // name, as it doesn't want a custom zone to over-ride a "real" one.)
1239 return tzZones->contains(ianaId) || validatePosixRule(ianaId, true).isValid;
1240}
1241
1242QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds() const
1243{
1244 QList<QByteArray> result = tzZones->keys();
1245 std::sort(result.begin(), result.end());
1246 return result;
1247}
1248
1249QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory) const
1250{
1251 // TODO AnyTerritory
1253 for (auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) {
1254 if (it.value().territory == territory)
1255 result << it.key();
1256 }
1257 std::sort(result.begin(), result.end());
1258 return result;
1259}
1260
1261// Getting the system zone's ID:
1262
1263namespace {
1264class ZoneNameReader
1265{
1266public:
1268 {
1269 /* Assumptions:
1270 a) Systems don't change which of localtime and TZ they use without a
1271 reboot.
1272 b) When they change, they use atomic renames, hence a new device and
1273 inode for the new file.
1274 c) If we change which *name* is used for a zone, while referencing
1275 the same final zoneinfo file, we don't care about the change of
1276 name (e.g. if Europe/Oslo and Europe/Berlin are both symlinks to
1277 the same CET file, continuing to use the old name, after
1278 /etc/localtime changes which of the two it points to, is
1279 harmless).
1280
1281 The alternative would be to use a file-system watcher, but they are a
1282 scarce resource.
1283 */
1284 const StatIdent local = identify("/etc/localtime");
1285 const StatIdent tz = identify("/etc/TZ");
1286 const StatIdent timezone = identify("/etc/timezone");
1287 if (!m_name.isEmpty() && m_last.isValid()
1288 && (m_last == local || m_last == tz || m_last == timezone)) {
1289 return m_name;
1290 }
1291
1292 m_name = etcLocalTime();
1293 if (!m_name.isEmpty()) {
1294 m_last = local;
1295 return m_name;
1296 }
1297
1298 // Some systems (e.g. uClibc) have a default value for $TZ in /etc/TZ:
1299 m_name = etcContent(QStringLiteral("/etc/TZ"));
1300 if (!m_name.isEmpty()) {
1301 m_last = tz;
1302 return m_name;
1303 }
1304
1305 // Gentoo still (2020, QTBUG-87326) uses this:
1306 m_name = etcContent(QStringLiteral("/etc/timezone"));
1307 m_last = m_name.isEmpty() ? StatIdent() : timezone;
1308 return m_name;
1309 }
1310
1311private:
1312 QByteArray m_name;
1313 struct StatIdent
1314 {
1315 static constexpr unsigned long bad = ~0ul;
1316 unsigned long m_dev, m_ino;
1317 constexpr StatIdent() : m_dev(bad), m_ino(bad) {}
1318 StatIdent(const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {}
1319 bool isValid() { return m_dev != bad || m_ino != bad; }
1320 bool operator==(const StatIdent &other)
1321 { return other.m_dev == m_dev && other.m_ino == m_ino; }
1322 };
1323 StatIdent m_last;
1324
1325 static StatIdent identify(const char *path)
1326 {
1327 QT_STATBUF data;
1328 return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data);
1329 }
1330
1331 static QByteArray etcLocalTime()
1332 {
1333 // On most distros /etc/localtime is a symlink to a real file so extract
1334 // name from the path
1335 const auto zoneinfo = "/zoneinfo/"_L1;
1336 QString path = QStringLiteral("/etc/localtime");
1337 long iteration = getSymloopMax();
1338 // Symlink may point to another symlink etc. before being under zoneinfo/
1339 // We stop on the first path under /zoneinfo/, even if it is itself a
1340 // symlink, like America/Montreal pointing to America/Toronto
1341 do {
1343 int index = path.indexOf(zoneinfo);
1344 if (index >= 0) // Found zoneinfo file; extract zone name from path:
1345 return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8();
1346 } while (!path.isEmpty() && --iteration > 0);
1347
1348 return QByteArray();
1349 }
1350
1351 static QByteArray etcContent(const QString &path)
1352 {
1353 QFile zone(path);
1354 if (zone.open(QIODevice::ReadOnly))
1355 return zone.readAll().trimmed();
1356
1357 return QByteArray();
1358 }
1359
1360 // Any chain of symlinks longer than this is assumed to be a loop:
1361 static long getSymloopMax()
1362 {
1363#ifdef SYMLOOP_MAX
1364 // If defined, at runtime it can only be greater than this, so this is a safe bet:
1365 return SYMLOOP_MAX;
1366#else
1367 errno = 0;
1368 long result = sysconf(_SC_SYMLOOP_MAX);
1369 if (result >= 0)
1370 return result;
1371 // result is -1, meaning either error or no limit
1372 Q_ASSERT(!errno); // ... but it can't be an error, POSIX mandates _SC_SYMLOOP_MAX
1373
1374 // therefore we can make up our own limit
1375# ifdef MAXSYMLINKS
1376 return MAXSYMLINKS;
1377# else
1378 return 8;
1379# endif
1380#endif
1381 }
1382};
1383}
1384
1385QByteArray QTzTimeZonePrivate::systemTimeZoneId() const
1386{
1387 return staticSystemTimeZoneId();
1388}
1389
1390QByteArray QTzTimeZonePrivate::staticSystemTimeZoneId()
1391{
1392 // Check TZ env var first, if not populated try find it
1393 QByteArray ianaId = qgetenv("TZ");
1394
1395 // The TZ value can be ":/etc/localtime" which libc considers
1396 // to be a "default timezone", in which case it will be read
1397 // by one of the blocks below, so unset it here so it is not
1398 // considered as a valid/found ianaId
1399 if (ianaId == ":/etc/localtime")
1400 ianaId.clear();
1401 else if (ianaId.startsWith(':'))
1402 ianaId = ianaId.sliced(1);
1403
1404 if (ianaId.isEmpty()) {
1405 Q_CONSTINIT thread_local static ZoneNameReader reader;
1406 ianaId = reader.name();
1407 }
1408
1409 return ianaId;
1410}
1411
\inmodule QtCore
Definition qbytearray.h:57
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing just after the last byte in the ...
Definition qbytearray.h:435
const_iterator constBegin() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first byte in the byte-ar...
Definition qbytearray.h:431
int toInt(bool *ok=nullptr, int base=10) const
Returns the byte array converted to an int using base base, which is ten by default.
QList< QByteArray > split(char sep) const
Splits the byte array into subarrays wherever sep occurs, and returns the list of those arrays.
iterator end()
Returns an \l{STL-style iterators}{STL-style iterator} pointing just after the last byte in the byte-...
Definition qbytearray.h:432
bool startsWith(QByteArrayView bv) const
Definition qbytearray.h:170
char at(qsizetype i) const
Returns the byte at index position i in the byte array.
Definition qbytearray.h:523
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
QByteArray sliced(qsizetype pos) const
Definition qbytearray.h:163
void clear()
Clears the contents of the byte array and makes it null.
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first byte in the byte-array.
Definition qbytearray.h:428
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
T * object(const Key &key) const noexcept
Definition qcache.h:209
bool insert(const Key &key, T *object, qsizetype cost=1)
Definition qcache.h:184
\inmodule QtCore\reentrant
Definition qdatastream.h:30
int readRawData(char *, int len)
Reads at most len bytes from the stream into s and returns the number of bytes read.
Status status() const
Returns the status of the data stream.
\inmodule QtCore\reentrant
Definition qdatetime.h:257
static QDateTime fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone)
static qint64 currentMSecsSinceEpoch() noexcept
QDate date() const
Returns the date part of the datetime.
\inmodule QtCore \reentrant
Definition qdatetime.h:27
constexpr bool isValid() const
Returns true if this date is valid; otherwise returns false.
Definition qdatetime.h:86
int month() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
QDate addDays(qint64 days) const
Returns a QDate object containing a date ndays later than the date of this object (or earlier if nday...
int year() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
int dayOfWeek() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
static bool isLeapYear(int year)
Returns true if the specified year is a leap year in the Gregorian calendar; otherwise returns false.
The QDirIterator class provides an iterator for directory entrylists.
bool hasNext() const
Returns true if there is at least one more entry in the directory; otherwise, false is returned.
QFileInfo nextFileInfo()
\inmodule QtCore
Definition qdir.h:19
QString relativeFilePath(const QString &fileName) const
Returns the path to fileName relative to the directory.
Definition qdir.cpp:843
QString absoluteFilePath(const QString &fileName) const
Returns the absolute path name of a file in the directory.
Definition qdir.cpp:809
bool atEnd() const override
Returns true if the end of the file has been reached; otherwise returns false.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
bool isSymLink() const
Returns true if this object points to a symbolic link, shortcut, or alias; otherwise returns false.
bool isFile() const
Returns true if this object points to a file or to a symbolic link to a file.
bool isDir() const
Returns true if this object points to a directory or to a symbolic link to a directory.
QString filePath() const
Returns the file name, including the path (which may be absolute or relative).
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
QString symLinkTarget() const
Definition qfile.cpp:387
static QByteArray encodeName(const QString &fileName)
Converts fileName to an 8-bit encoding that you can use in native APIs.
Definition qfile.h:158
void setFileName(const QString &name)
Sets the name of the file.
Definition qfile.cpp:302
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
\inmodule QtCore
Definition qhash.h:818
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:991
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
qint64 readLine(char *data, qint64 maxlen)
This function reads a line of ASCII characters from the device, up to a maximum of maxSize - 1 bytes,...
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
void reserve(qsizetype size)
Definition qlist.h:746
void resize(qsizetype size)
Definition qlist.h:392
const_iterator cend() const noexcept
Definition qlist.h:614
void append(parameter_type t)
Definition qlist.h:441
const_iterator cbegin() const noexcept
Definition qlist.h:613
static QLocale::Territory codeToTerritory(QStringView code) noexcept
Definition qlocale.cpp:185
@ AnyTerritory
Definition qlocale.h:558
Definition qmap.h:186
const_iterator cend() const
Definition qmap.h:604
const_iterator cbegin() const
Definition qmap.h:600
size_type size() const
Definition qmap.h:266
\inmodule QtCore
Definition qmutex.h:317
\inmodule QtCore
Definition qmutex.h:285
bool isEmpty() const
Definition qset.h:52
const_iterator cend() const noexcept
Definition qset.h:142
const_iterator cbegin() const noexcept
Definition qset.h:138
\inmodule QtCore
Definition qstringview.h:76
QByteArray toUtf8() const
Returns a UTF-8 representation of the string view as a QByteArray.
constexpr QStringView sliced(qsizetype pos) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5788
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
QString first(qsizetype n) const
Definition qstring.h:337
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1079
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
QString sliced(qsizetype pos) const
Definition qstring.h:341
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4420
virtual QString displayName(qint64 atMSecsSinceEpoch, QTimeZone::NameType nameType, const QLocale &locale) const
QTzTimeZoneCacheEntry fetchEntry(const QByteArray &ianaId)
QMap< QString, QString > map
[6]
QString text
QDate date
[1]
QSet< QString >::iterator it
const PluginKeyMapConstIterator cend
Combined button and popup list for selecting options.
Lock qt_scoped_lock(Mutex &mutex)
Definition qlocking_p.h:58
Lock qt_unique_lock(Mutex &mutex)
Definition qlocking_p.h:64
QImageReader reader("image.png")
[1]
static jboolean cut(JNIEnv *, jobject)
#define Q_UNLIKELY(x)
#define Q_LIKELY(x)
DBusConnection const char * rule
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
static const qint64 invalidData
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
QSimpleParsedNumber< qlonglong > qstrntoll(const char *begin, qsizetype size, int base)
return ret
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLboolean r
[2]
GLuint GLuint end
GLsizei GLenum GLenum * types
GLenum type
GLenum GLenum dst
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLintptr offset
GLuint name
GLbyte GLbyte tz
GLhandleARB obj
[2]
GLuint GLfloat * val
GLuint entry
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLenum GLenum GLenum input
static QT_BEGIN_NAMESPACE const char * typeList[]
bool operator==(const QRandomGenerator &rng1, const QRandomGenerator &rng2)
Definition qrandom.cpp:1219
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QtPrivate::QRegularExpressionMatchIteratorRangeBasedForIterator begin(const QRegularExpressionMatchIterator &iterator)
#define QStringLiteral(str)
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
static int parsePosixTime(const char *begin, const char *end)
#define TZ_MAGIC
static QMap< int, QByteArray > parseTzAbbreviations(QDataStream &ds, int tzh_charcnt, const QList< QTzType > &types)
static QTzTimeZoneHash loadTzTimeZones()
static int parsePosixTransitionTime(const QByteArray &timeRule)
#define TZ_MAX_CHARS
static bool isTzFile(const QString &name)
static void parseTzLeapSeconds(QDataStream &ds, int tzh_leapcnt, bool longTran)
static QTzHeader parseTzHeader(QDataStream &ds, bool *ok)
static bool asciiIsLetter(char ch)
static QDate calculateDowDate(int year, int month, int dayOfWeek, int week)
#define TZ_MAX_TYPES
#define TZ_MAX_TIMES
static auto validatePosixRule(const QByteArray &posixRule, bool requireOffset=false)
static QList< QTzType > parseTzIndicators(QDataStream &ds, const QList< QTzType > &types, int tzh_ttisstdcnt, int tzh_ttisgmtcnt)
static QDate calculatePosixDate(const QByteArray &dateRule, int year)
#define TZ_MAX_LEAPS
static QList< QTzType > parseTzTypes(QDataStream &ds, int tzh_typecnt)
static QList< QTimeZonePrivate::Data > calculatePosixTransitions(const QByteArray &posixRule, int startYear, int endYear, qint64 lastTranMSecs)
QHash< QByteArray, QTzTimeZone > QTzTimeZoneHash
static int parsePosixOffset(const char *begin, const char *end)
static QList< QTzTransition > parseTzTransitions(QDataStream &ds, int tzh_timecnt, bool longTran)
static QByteArray parseTzPosixRule(QDataStream &ds)
#define Q_UNUSED(x)
@ Q_PRIMITIVE_TYPE
Definition qtypeinfo.h:144
#define Q_DECLARE_TYPEINFO(TYPE, FLAGS)
Definition qtypeinfo.h:163
unsigned int quint32
Definition qtypes.h:45
int qint32
Definition qtypes.h:44
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
unsigned char quint8
Definition qtypes.h:41
QFile file
[0]
QFileInfo info(fileName)
[8]
QReadWriteLock lock
[0]
QSharedPointer< T > other(t)
[5]
QAction * at
qsizetype indexOf(const AT &t, qsizetype from=0) const noexcept
Definition qlist.h:955
QLocale::Territory territory