9#include "private/qlocale_tools_p.h"
10#include "private/qlocking_p.h"
12#include <QtCore/QDataStream>
13#include <QtCore/QDateTime>
14#include <QtCore/QDirListing>
16#include <QtCore/QFile>
17#include <QtCore/QCache>
19#include <QtCore/QMutex>
22#include <qplatformdefs.h>
36using namespace Qt::StringLiterals;
39
40
41
42
53static bool isTzFile(
const QString &name);
60 const QString tzdir = qEnvironmentVariable(
"TZDIR");
61 if (!tzdir.isEmpty()) {
62 file->setFileName(QDir(tzdir).filePath(name));
63 if (file->open(QIODevice::ReadOnly))
67 constexpr auto zoneShare =
"/usr/share/zoneinfo/"_L1;
68 if (tzdir != zoneShare && tzdir != zoneShare.chopped(1)) {
69 file->setFileName(zoneShare + name);
70 if (file->open(QIODevice::ReadOnly))
74 constexpr auto zoneLib =
"/usr/lib/zoneinfo/"_L1;
75 if (tzdir != zoneLib && tzdir != zoneLib.chopped(1)) {
76 file->setFileName(zoneShare + name);
77 if (file->open(QIODevice::ReadOnly))
89 if (!openZoneInfo(
"zone.tab"_L1, &tzif))
94 while (tzif.readLineInto(&line)) {
95 QByteArrayView text = QByteArrayView(line).trimmed();
96 if (text.isEmpty() || text.at(0) ==
'#')
99 int cut = text.indexOf(
'\t');
100 if (Q_LIKELY(cut > 0)) {
103 zone.territory = QLocalePrivate::codeToTerritory(QString::fromUtf8(text.first(cut)));
104 text = text.sliced(cut + 1);
105 cut = text.indexOf(
'\t');
106 if (Q_LIKELY(cut >= 0)) {
107 text = text.sliced(cut + 1);
108 cut = text.indexOf(
'\t');
110 const QByteArray id = (cut > 0 ? text.first(cut) : text).toByteArray();
112 zone.comment = text.sliced(cut + 1).toByteArray();
113 zonesHash.insert(id, zone);
119 QString path = tzif.fileName();
120 const qsizetype cut = path.lastIndexOf(u'/');
122 path.truncate(cut + 1);
123 const qsizetype prefixLen = path.size();
124 for (
const auto &info : QDirListing(path, QDirListing::IteratorFlag::Recursive)) {
125 if (!(info.isFile() || info.isSymLink()))
127 const QString infoAbsolutePath = info.absoluteFilePath();
128 const QString name = infoAbsolutePath.sliced(prefixLen);
130 if (info.isDir() ? name ==
"posix"_L1 || name ==
"right"_L1
131 : name.startsWith(
"posix/"_L1) || name.startsWith(
"right/"_L1)) {
137 const QByteArray id = QFile::encodeName(name);
138 if (!zonesHash.contains(id) && isTzFile(infoAbsolutePath))
139 zonesHash.insert(id, QTzTimeZone());
148
149
150
151
153#define TZ_MAGIC "TZif"
154#define TZ_MAX_TIMES 1200
155#define TZ_MAX_TYPES 256
156#define TZ_MAX_CHARS 50
157#define TZ_MAX_LEAPS 50
187 return file.open(QFile::ReadOnly) && file.read(strlen(
TZ_MAGIC)) ==
TZ_MAGIC;
199 ds.readRawData(hdr.tzh_magic, 4);
201 if (memcmp(hdr.tzh_magic,
TZ_MAGIC, 4) != 0 || ds.status() != QDataStream::Ok)
206 hdr.tzh_version = ch;
207 if (ds.status() != QDataStream::Ok
208 || (hdr.tzh_version !=
'2' && hdr.tzh_version !=
'\0' && hdr.tzh_version !=
'3')) {
213 ds.readRawData(hdr.tzh_reserved, 15);
214 if (ds.status() != QDataStream::Ok)
218 ds >> hdr.tzh_ttisgmtcnt >> hdr.tzh_ttisstdcnt >> hdr.tzh_leapcnt >> hdr.tzh_timecnt
219 >> hdr.tzh_typecnt >> hdr.tzh_charcnt;
222 if (ds.status() != QDataStream::Ok
227 || hdr.tzh_ttisgmtcnt > hdr.tzh_typecnt
228 || hdr.tzh_ttisstdcnt > hdr.tzh_typecnt) {
238 QList<QTzTransition> transitions(tzh_timecnt);
242 for (
int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
243 ds >> transitions[i].tz_time;
244 if (ds.status() != QDataStream::Ok)
245 transitions.resize(i);
250 for (
int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
252 transitions[i].tz_time = val;
253 if (ds.status() != QDataStream::Ok)
254 transitions.resize(i);
259 for (
int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
262 if (ds.status() == QDataStream::Ok)
263 transitions[i].tz_typeind = typeind;
271 QList<QTzType> types(tzh_typecnt);
274 for (
int i = 0; i < tzh_typecnt && ds.status() == QDataStream::Ok; ++i) {
275 QTzType &type = types[i];
277 ds >> type.tz_gmtoff;
279 if (ds.status() == QDataStream::Ok)
282 if (ds.status() == QDataStream::Ok)
283 ds >> type.tz_abbrind;
284 if (ds.status() != QDataStream::Ok)
298 QMap<
int, QByteArray> map;
302 for (
int i = 0; i < tzh_charcnt && ds.status() == QDataStream::Ok; ++i) {
304 if (ds.status() == QDataStream::Ok)
305 input.append(
char(ch));
310 for (
const QTzType &type : types) {
312 for (
int i = type.tz_abbrind; input.at(i) !=
'\0'; ++i)
313 abbrev.append(input.at(i));
315 map[type.tz_abbrind] = abbrev;
328 for (
int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
332 if (ds.status() == QDataStream::Ok)
337 for (
int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
341 if (ds.status() == QDataStream::Ok)
350 QList<QTzType> result = types;
353
354
355
356
357
358
359
360
361
364 for (
int i = 0; i < tzh_ttisstdcnt && ds.status() == QDataStream::Ok; ++i)
368 for (
int i = 0; i < tzh_ttisgmtcnt && ds.status() == QDataStream::Ok; ++i)
381 if (ch !=
'\n' || ds.status() != QDataStream::Ok)
384 while (ch !=
'\n' && ds.status() == QDataStream::Ok) {
385 rule.append((
char)ch);
396 else if (dayOfWeek & ~7 || month < 1 || month > 12 || week < 1 || week > 5)
399 QDate date(year, month, 1);
400 int startDow = date.dayOfWeek();
401 if (startDow <= dayOfWeek)
402 date = date.addDays(dayOfWeek - startDow - 7);
404 date = date.addDays(dayOfWeek - startDow);
405 date = date.addDays(week * 7);
406 while (date.month() != month)
407 date = date.addDays(-7);
413 Q_ASSERT(!dateRule.isEmpty());
416 if (dateRule.at(0) ==
'M') {
418 const auto dateParts = dateRule.tokenize(u'.');
419 auto token = dateParts.begin();
420 Q_ASSERT(token != dateParts.end());
421 Q_ASSERT(!token->isEmpty());
422 const int month = token->sliced(1).toInt(&ok);
423 if (ok && ++token != dateParts.end()) {
424 const int week = token->toInt(&ok);
425 if (ok && ++token != dateParts.end()) {
426 const int dow = token->toInt(&ok);
428 return calculateDowDate(year, month, dow, week);
431 }
else if (dateRule.at(0) ==
'J') {
434 int doy = dateRule.sliced(1).toInt(&ok);
435 if (ok && doy > 0 && doy < 366) {
439 if (!QDate::isLeapYear(year) || doy < 60)
441 return QDate(year, 1, 1).addDays(doy);
445 int doy = dateRule.toInt(&ok);
446 if (ok && doy >= 0 && doy < 366)
447 return QDate(year, 1, 1).addDays(doy);
456 int hour, min = 0, sec = 0;
458 const int maxHour = 137;
459 auto r = qstrntoll(begin, end - begin, 10);
461 if (!r.ok() || hour < -maxHour || hour > maxHour || r.used > 2)
464 if (begin < end && *begin ==
':') {
467 r = qstrntoll(begin, end - begin, 10);
469 if (!r.ok() || min < 0 || min > 59 || r.used > 2)
473 if (begin < end && *begin ==
':') {
476 r = qstrntoll(begin, end - begin, 10);
478 if (!r.ok() || sec < 0 || sec > 59 || r.used > 2)
488 return (hour * 60 + min) * 60 + sec;
493 return parsePosixTime(timeRule.begin(), timeRule.end());
503 }
else if (*begin ==
'-') {
509 if (value == INT_MIN)
511 return negate ? -value : value;
517 return ch >=
'a' && ch <=
'z';
525 InvalidOffset = INT_MIN,
529 int offset = InvalidOffset;
530 bool hasValidOffset()
const noexcept {
return offset != InvalidOffset; }
531 QTimeZonePrivate::Data dataAt(qint64 when)
533 Q_ASSERT(hasValidOffset());
534 return QTimeZonePrivate::Data(name, when, offset, offset);
536 QTimeZonePrivate::Data dataAtOffset(qint64 when,
int standard)
538 Q_ASSERT(hasValidOffset());
539 return QTimeZonePrivate::Data(name, when, offset, standard);
542 static PosixZone parse(
const char *&pos,
const char *end);
550PosixZone PosixZone::parse(
const char *&pos,
const char *end)
552 static const char offsetChars[] =
"0123456789:";
554 const char *nameBegin = pos;
561 while (nameEnd < end && *nameEnd !=
'>') {
572 if (nameEnd - nameBegin < 3)
576 const char *zoneBegin = pos;
577 const char *zoneEnd = pos;
578 if (zoneEnd < end && (zoneEnd[0] ==
'+' || zoneEnd[0] ==
'-'))
580 while (zoneEnd < end) {
581 if (strchr(offsetChars,
char(*zoneEnd)) ==
nullptr)
586 QString name = QString::fromUtf8(nameBegin, nameEnd - nameBegin);
587 const int offset = zoneEnd > zoneBegin ?
parsePosixOffset(zoneBegin
, zoneEnd
) : InvalidOffset;
591 if (offset != 0 && (name ==
"UTC"_L1 || name ==
"GMT"_L1))
593 return {
std::move(name), offset};
597
598
599
600
606 QLatin1StringView zoneinfo, startDate, endDate;
609 const auto tokens = QLatin1StringView(posixRule).tokenize(u',');
610 auto token = tokens.begin();
611 Q_ASSERT(token != tokens.end());
612 zoneinfo = token->trimmed();
613 if (++token != tokens.end()) {
616 if (++token != tokens.end()) {
619 if (++token != tokens.end())
624 const struct {
bool isValid, hasDst; } fail{
false,
false}, good{
true, parts > 1};
625 if (zoneinfo.isEmpty())
628 const char *begin = zoneinfo.begin();
631 const auto posix = PosixZone::parse(begin, zoneinfo.end());
632 if (posix.name.isEmpty())
634 if (requireOffset && !posix.hasValidOffset())
639 if (begin >= zoneinfo.end())
642 if (PosixZone::parse(begin, zoneinfo.end()).name.isEmpty())
645 if (begin < zoneinfo.end())
649 if (parts != 3 || startDate.isEmpty() || endDate.isEmpty())
651 for (
int i = 0; i < 2; ++i) {
652 const auto tokens = (i ? endDate : startDate).tokenize(u'/');
653 auto token = tokens.begin();
654 Q_ASSERT(token != tokens.end());
655 if (!calculatePosixDate(*token, 1972).isValid())
657 if (++token != tokens.end() && parsePosixTime(token->begin(), token->end()) == INT_MIN)
665 int startYear,
int endYear,
666 qint64 lastTranMSecs)
668 QList<QTimeZonePrivate::Data> result;
675 const auto tokens = QLatin1String(posixRule).tokenize(u',');
676 auto token = tokens.begin();
677 Q_ASSERT(token != tokens.end());
679 PosixZone stdZone, dstZone;
680 QLatin1StringView zoneText = *token;
682 QLatin1StringView zoneinfo = zoneText.trimmed();
683 const char *begin = zoneinfo.begin();
685 stdZone = PosixZone::parse(begin, zoneinfo.end());
686 if (!stdZone.hasValidOffset()) {
688 }
else if (begin < zoneinfo.end()) {
689 dstZone = PosixZone::parse(begin, zoneinfo.end());
690 if (!dstZone.hasValidOffset()) {
692 dstZone.offset = stdZone.offset + (60 * 60);
698 if (++token == tokens.end() || !dstZone.hasValidOffset()) {
700 stdZone.name.isEmpty() ? QString(zoneText) : stdZone.name,
701 lastTranMSecs, stdZone.offset, stdZone.offset);
704 QLatin1StringView dstRule = *token;
705 if (++token == tokens.end() || dstRule.isEmpty() || token->isEmpty())
707 QLatin1StringView stdRule = *token;
710 const int twoOClock = 7200;
711 const auto dstParts = dstRule.tokenize(u'/');
712 auto subtok = dstParts.begin();
713 Q_ASSERT(subtok != dstParts.end());
714 QLatin1StringView dstDateRule = *subtok;
715 const int dstTime = ++subtok == dstParts.end() ? twoOClock : parsePosixTransitionTime(*subtok);
718 const auto stdParts = stdRule.tokenize(u'/');
719 subtok = stdParts.begin();
720 Q_ASSERT(subtok != stdParts.end());
721 QLatin1StringView stdDateRule = *subtok;
722 const int stdTime = ++subtok == stdParts.end() ? twoOClock : parsePosixTransitionTime(*subtok);
724 if (dstDateRule.isEmpty() || stdDateRule.isEmpty() || dstTime == INT_MIN || stdTime == INT_MIN)
728 const int minYear =
int(QDateTime::YearRange::First);
729 const int maxYear =
int(QDateTime::YearRange::Last);
730 startYear = qBound(minYear, startYear, maxYear);
731 endYear = qBound(minYear, endYear, maxYear);
732 Q_ASSERT(startYear <= endYear);
734 for (
int year = startYear; year <= endYear; ++year) {
741 QDateTime dst(calculatePosixDate(dstDateRule, year)
742 .startOfDay(QTimeZone::UTC).addSecs(dstTime));
743 auto saving = dstZone.dataAtOffset(dst.toMSecsSinceEpoch() - stdZone.offset * 1000,
746 QDateTime std(calculatePosixDate(stdDateRule, year)
747 .startOfDay(QTimeZone::UTC).addSecs(stdTime));
748 auto standard = stdZone.dataAt(std.toMSecsSinceEpoch() - dstZone.offset * 1000);
750 if (year == startYear) {
753 if (saving.atMSecsSinceEpoch < standard.atMSecsSinceEpoch) {
754 if (dst <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
755 && std >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
757 saving.atMSecsSinceEpoch = lastTranMSecs;
758 result.emplaceBack(
std::move(saving));
762 if (std <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
763 && dst >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
765 standard.atMSecsSinceEpoch = lastTranMSecs;
766 result.emplaceBack(
std::move(standard));
772 const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty();
773 const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty();
774 if (useStd && useDst) {
776 result.emplaceBack(
std::move(saving));
777 result.emplaceBack(
std::move(standard));
779 result.emplaceBack(
std::move(standard));
780 result.emplaceBack(
std::move(saving));
783 result.emplaceBack(
std::move(standard));
785 result.emplaceBack(
std::move(saving));
792QTzTimeZonePrivate::QTzTimeZonePrivate()
793 : QTzTimeZonePrivate(staticSystemTimeZoneId())
797QTzTimeZonePrivate::~QTzTimeZonePrivate()
801QTzTimeZonePrivate *QTzTimeZonePrivate::clone()
const
803 return new QTzTimeZonePrivate(*
this);
812 static QTzTimeZoneCacheEntry findEntry(
const QByteArray &ianaId);
813 QCache<QByteArray, QTzTimeZoneCacheEntry> m_cache;
819 QTzTimeZoneCacheEntry ret;
821 if (ianaId.isEmpty()) {
823 tzif.setFileName(QStringLiteral(
"/etc/localtime"));
824 if (!tzif.open(QIODevice::ReadOnly))
826 }
else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) {
828 auto check = validatePosixRule(ianaId);
830 ret.m_hasDst = check.hasDst;
831 ret.m_posixRule = ianaId;
840 QByteArray posixRule;
842 if (!ok || ds.status() != QDataStream::Ok)
845 if (ds.status() != QDataStream::Ok)
847 QList<
QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt);
848 if (ds.status() != QDataStream::Ok)
850 QMap<
int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList);
851 if (ds.status() != QDataStream::Ok)
854 if (ds.status() != QDataStream::Ok)
856 typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt);
857 if (ds.status() != QDataStream::Ok)
864 if (!ok || ds.status() != QDataStream::Ok)
866 tranList = parseTzTransitions(ds, hdr2.tzh_timecnt,
true);
867 if (ds.status() != QDataStream::Ok)
869 typeList = parseTzTypes(ds, hdr2.tzh_typecnt);
870 if (ds.status() != QDataStream::Ok)
872 abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList);
873 if (ds.status() != QDataStream::Ok)
876 if (ds.status() != QDataStream::Ok)
878 typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt);
879 if (ds.status() != QDataStream::Ok)
881 posixRule = parseTzPosixRule(ds);
882 if (ds.status() != QDataStream::Ok)
887 if (!posixRule.isEmpty()) {
888 auto check = validatePosixRule(posixRule);
891 ret.m_posixRule = posixRule;
892 ret.m_hasDst = check.hasDst;
896 const int size = abbrevMap.size();
897 ret.m_abbreviations.clear();
898 ret.m_abbreviations.reserve(size);
899 QList<
int> abbrindList;
900 abbrindList.reserve(size);
901 for (
auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) {
902 ret.m_abbreviations.append(it.value());
903 abbrindList.append(it.key());
907 for (
int i = 0; i < typeList.size(); ++i)
908 typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind);
912 ret.m_preZoneRule = { typeList.at(0).tz_gmtoff, 0, typeList.at(0).tz_abbrind };
916 int utcOffset = ret.m_preZoneRule.stdOffset;
917 for (
const QTzTransition &tran : std::as_const(tranList)) {
918 if (!typeList.at(tran.tz_typeind).tz_isdst) {
919 utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff;
925 const int tranCount = tranList.size();
926 ret.m_tranTimes.reserve(tranCount);
928 int lastDstOff = 3600;
929 for (
int i = 0; i < tranCount; i++) {
931 QTzTransitionTime tran;
932 QTzTransitionRule rule;
933 const QTzType tz_type = typeList.at(tz_tran.tz_typeind);
938 }
else if (Q_UNLIKELY(tz_type
.tz_gmtoff != utcOffset + lastDstOff)) {
940
941
942
943
944
946 const int inferStd = tz_type
.tz_gmtoff - lastDstOff;
947 for (
int j = i + 1; j < tranCount; j++) {
948 const QTzType new_type = typeList.at(tranList.at(j).tz_typeind);
951 if (newUtc == utcOffset) {
954 }
else if (newUtc == inferStd) {
957 }
else if (tz_type
.tz_gmtoff - 3600 == utcOffset) {
959 }
else if (tz_type
.tz_gmtoff - 3600 == newUtc) {
965 || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))
967 && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) {
975 rule.stdOffset = utcOffset;
976 rule.dstOffset = tz_type
.tz_gmtoff - utcOffset;
977 rule.abbreviationIndex = tz_type.tz_abbrind;
980 int ruleIndex = ret.m_tranRules.indexOf(rule);
981 if (ruleIndex == -1) {
982 if (rule.dstOffset != 0)
984 tran.ruleIndex = ret.m_tranRules.size();
985 ret.m_tranRules.append(rule);
987 tran.ruleIndex = ruleIndex;
990 tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000;
991 ret.m_tranTimes.append(tran);
999 QMutexLocker locker(&m_mutex);
1002 QTzTimeZoneCacheEntry *obj = m_cache.object(ianaId);
1010 QTzTimeZoneCacheEntry ret = findEntry(ianaId);
1011 auto ptr = std::make_unique<QTzTimeZoneCacheEntry>(ret);
1014 m_cache.insert(ianaId, ptr.release());
1021QTzTimeZonePrivate::QTzTimeZonePrivate(
const QByteArray &ianaId)
1023 if (!isTimeZoneIdAvailable(ianaId))
1025 static QTzTimeZoneCache tzCache;
1026 auto entry = tzCache.fetchEntry(ianaId);
1027 if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty())
1030 cached_data = std::move(entry);
1033 if (m_id.isEmpty()) {
1037 m_id = abbreviation(QDateTime::currentMSecsSinceEpoch()).toUtf8();
1041QLocale::Territory QTzTimeZonePrivate::territory()
const
1043 return tzZones->value(m_id).territory;
1046QString QTzTimeZonePrivate::comment()
const
1048 return QString::fromUtf8(tzZones->value(m_id).comment);
1051QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1052 QTimeZone::NameType nameType,
1053 const QLocale &locale)
const
1056 if (nameType != QTimeZone::LongName && isDataLocale(locale)) {
1057 Data tran = data(timeType);
1058 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
1059 if (nameType == QTimeZone::ShortName)
1060 return tran.abbreviation;
1062 if (isAnglicLocale(locale))
1063 return isoOffsetFormat(tran.offsetFromUtc);
1067 return QTimeZonePrivate::displayName(timeType, nameType, locale);
1070QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
1072 return data(atMSecsSinceEpoch).abbreviation;
1075int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
1077 const Data tran = data(atMSecsSinceEpoch);
1078 return tran.offsetFromUtc;
1081int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
1083 return data(atMSecsSinceEpoch).standardTimeOffset;
1086int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
1088 return data(atMSecsSinceEpoch).daylightTimeOffset;
1091bool QTzTimeZonePrivate::hasDaylightTime()
const
1093 return cached_data.m_hasDst;
1096bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
1098 return (daylightTimeOffset(atMSecsSinceEpoch) != 0);
1101QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran)
const
1103 return dataFromRule(cached_data.m_tranRules.at(tran.ruleIndex), tran.atMSecsSinceEpoch);
1106QTimeZonePrivate::Data QTzTimeZonePrivate::dataFromRule(QTzTransitionRule rule,
1107 qint64 msecsSinceEpoch)
const
1109 return Data(QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)),
1110 msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset);
1113QList<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear)
const
1115 const int year = QDateTime::fromMSecsSinceEpoch(msNear, QTimeZone::UTC).date().year();
1117 qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch;
1118 return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime);
1121QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
1125 if (!cached_data.m_posixRule.isEmpty()
1126 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
1127 QList<Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
1128 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1129 [forMSecsSinceEpoch] (
const Data &at) {
1130 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1133 if (it > posixTrans.cbegin() || (tranCache().isEmpty() && it < posixTrans.cend())) {
1134 Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
1135 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1139 if (tranCache().isEmpty())
1143 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1144 [forMSecsSinceEpoch] (QTzTransitionTime at) {
1145 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1147 if (last == tranCache().cbegin())
1148 return dataFromRule(cached_data.m_preZoneRule, forMSecsSinceEpoch);
1151 return dataFromRule(cached_data.m_tranRules.at(last->ruleIndex), forMSecsSinceEpoch);
1159QTimeZonePrivate::Data QTzTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
1162 const auto validMatch = [timeType](
const Data &tran) {
1163 return tran.atMSecsSinceEpoch != invalidMSecs()
1164 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
1168 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
1169 Data tran = data(currentMSecs);
1170 if (validMatch(tran))
1174 tran = nextTransition(currentMSecs);
1175 if (validMatch(tran))
1181 tran = previousTransition(currentMSecs + 1);
1182 if (tran.atMSecsSinceEpoch != invalidMSecs())
1183 tran = previousTransition(tran.atMSecsSinceEpoch);
1184 if (validMatch(tran))
1190 const auto untilNow = [currentMSecs](QTzTransitionTime at) {
1191 return at.atMSecsSinceEpoch <= currentMSecs;
1193 auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), untilNow);
1196 while (it != tranCache().cbegin()) {
1198 tran = dataForTzTransition(*it);
1199 if ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0))
1206bool QTzTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
1209 return isAnglicLocale(locale);
1212bool QTzTimeZonePrivate::hasTransitions()
const
1217QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
1221 if (!cached_data.m_posixRule.isEmpty()
1222 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
1223 QList<Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
1224 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1225 [afterMSecsSinceEpoch] (
const Data &at) {
1226 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1229 return it == posixTrans.cend() ? Data{} : *it;
1233 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1234 [afterMSecsSinceEpoch] (QTzTransitionTime at) {
1235 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1237 return last != tranCache().cend() ? dataForTzTransition(*last) : Data{};
1240QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
1244 if (!cached_data.m_posixRule.isEmpty()
1245 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
1246 QList<Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
1247 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1248 [beforeMSecsSinceEpoch] (
const Data &at) {
1249 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1251 if (it > posixTrans.cbegin())
1254 return tranCache().isEmpty() ? Data{} : dataForTzTransition(tranCache().last());
1258 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1259 [beforeMSecsSinceEpoch] (QTzTransitionTime at) {
1260 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1262 return last > tranCache().cbegin() ? dataForTzTransition(*--last) : Data{};
1265bool QTzTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
1271 return tzZones->contains(ianaId) || validatePosixRule(ianaId,
true).isValid;
1274QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds()
const
1276 return uniqueSortedAliasPadded(tzZones->keys());
1279QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
1281 QList<QByteArray> result;
1282 for (
auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) {
1283 if (it.value().territory == territory)
1287 std::sort(result.begin(), result.end());
1292 const auto unWantedZone = [territory](QByteArrayView id) {
1294 auto it = tzZones->constFind(id);
1295 return it == tzZones->end() || it->territory == territory;
1297 QList<QByteArrayView> cldrViews = matchingTimeZoneIds(territory);
1298 std::sort(cldrViews.begin(), cldrViews.end());
1299 const auto uniqueEnd = std::unique(cldrViews.begin(), cldrViews.end());
1300 const auto prunedEnd = std::remove_if(cldrViews.begin(), uniqueEnd, unWantedZone);
1301 const auto cldrSize = std::distance(cldrViews.begin(), prunedEnd);
1303 QList<QByteArray> cldrList;
1304 cldrList.reserve(cldrSize);
1305 for (
auto it = cldrViews.begin(); it != prunedEnd; ++it)
1306 cldrList.emplace_back(it->toByteArray());
1307 QList<QByteArray> joined;
1308 joined.reserve(result.size() + cldrSize);
1309 std::set_union(result.begin(), result.end(), cldrList.begin(), cldrList.end(),
1310 std::back_inserter(joined));
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340 const StatIdent local = identify(
"/etc/localtime");
1341 const StatIdent tz = identify(
"/etc/TZ");
1342 const StatIdent timezone = identify(
"/etc/timezone");
1343 if (!m_name.isEmpty() && m_last.isValid()
1344 && (m_last == local || m_last == tz || m_last == timezone)) {
1348 m_name = etcLocalTime();
1349 if (!m_name.isEmpty()) {
1355 m_name = etcContent(QStringLiteral(
"/etc/TZ"));
1356 if (!m_name.isEmpty()) {
1362 m_name = etcContent(QStringLiteral(
"/etc/timezone"));
1363 m_last = m_name.isEmpty() ? StatIdent() : timezone;
1371 static constexpr unsigned long bad = ~0ul;
1372 unsigned long m_dev, m_ino;
1373 constexpr StatIdent() : m_dev(bad), m_ino(bad) {}
1374 StatIdent(
const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {}
1375 bool isValid() {
return m_dev != bad || m_ino != bad; }
1376 friend constexpr bool operator==(StatIdent lhs, StatIdent rhs)
1377 {
return lhs.m_dev == rhs.m_dev && lhs.m_ino == rhs.m_ino; }
1381 static StatIdent identify(
const char *path)
1384 return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data);
1387 static QByteArray etcLocalTime()
1391 const QString tzdir = qEnvironmentVariable(
"TZDIR");
1392 constexpr auto zoneinfo =
"/zoneinfo/"_L1;
1393 QString path = QStringLiteral(
"/etc/localtime");
1394 long iteration = getSymloopMax();
1399 path = QFile::symLinkTarget(path);
1401 int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir);
1403 const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8();
1404 return tail.startsWith(u'/') ? tail.sliced(1) : tail;
1406 index = path.indexOf(zoneinfo);
1408 return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8();
1409 }
while (!path.isEmpty() && --iteration > 0);
1411 return QByteArray();
1414 static QByteArray etcContent(
const QString &path)
1417 if (zone.open(QIODevice::ReadOnly))
1418 return zone.readAll().trimmed();
1420 return QByteArray();
1424 static long getSymloopMax()
1431 long result = sysconf(_SC_SYMLOOP_MAX);
1448QByteArray QTzTimeZonePrivate::systemTimeZoneId()
const
1450 return staticSystemTimeZoneId();
1453QByteArray QTzTimeZonePrivate::staticSystemTimeZoneId()
1456 QByteArray ianaId = qgetenv(
"TZ");
1462 if (ianaId ==
":/etc/localtime")
1464 else if (ianaId.startsWith(
':'))
1465 ianaId = ianaId.sliced(1);
1467 if (ianaId.isEmpty()) {
1468 Q_CONSTINIT
thread_local static ZoneNameReader reader;
1469 ianaId = reader.name();
\inmodule QtCore\reentrant
QTzTimeZoneCacheEntry fetchEntry(const QByteArray &ianaId)
static int parsePosixTime(const char *begin, const char *end)
static auto validatePosixRule(QByteArrayView posixRule, bool requireOffset=false)
static QMap< int, QByteArray > parseTzAbbreviations(QDataStream &ds, int tzh_charcnt, const QList< QTzType > &types)
static QTzTimeZoneHash loadTzTimeZones()
Q_GLOBAL_STATIC(const QTzTimeZoneHash, tzZones, loadTzTimeZones())
QHash< QByteArray, QTzTimeZone > QTzTimeZoneHash
static int parsePosixTransitionTime(QLatin1StringView timeRule)
static QDate calculatePosixDate(QLatin1StringView dateRule, int year)
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)
static bool openZoneInfo(const QString &name, QFile *file)
Q_DECLARE_TYPEINFO(QTzType, Q_PRIMITIVE_TYPE)
static QList< QTzType > parseTzIndicators(QDataStream &ds, const QList< QTzType > &types, int tzh_ttisstdcnt, int tzh_ttisgmtcnt)
Q_DECLARE_TYPEINFO(QTzTransition, Q_PRIMITIVE_TYPE)
static QList< QTzType > parseTzTypes(QDataStream &ds, int tzh_typecnt)
static QList< QTimeZonePrivate::Data > calculatePosixTransitions(const QByteArray &posixRule, int startYear, int endYear, qint64 lastTranMSecs)
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)
QLocale::Territory territory