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(zoneLib + 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) {
239 QList<QTzTransition> transitions(tzh_timecnt);
243 for (
int i = 0; i < tzh_timecnt; ++i) {
244 ds >> transitions[i].tz_time;
245 if (ds.status() != QDataStream::Ok)
251 for (
int i = 0; i < tzh_timecnt; ++i) {
253 transitions[i].tz_time = val;
254 if (ds.status() != QDataStream::Ok)
260 for (
int i = 0; i < tzh_timecnt; ++i) {
263 if (ds.status() != QDataStream::Ok)
265 if (typeind >= tzh_typecnt)
267 transitions[i].tz_typeind = typeind;
275 QList<QTzType> types(tzh_typecnt);
278 for (
int i = 0; i < tzh_typecnt && ds.status() == QDataStream::Ok; ++i) {
279 QTzType &type = types[i];
281 ds >> type.tz_gmtoff;
283 if (ds.status() == QDataStream::Ok)
286 if (ds.status() == QDataStream::Ok)
287 ds >> type.tz_abbrind;
288 if (ds.status() != QDataStream::Ok)
302 QMap<
int, QByteArray> map;
306 for (
int i = 0; i < tzh_charcnt && ds.status() == QDataStream::Ok; ++i) {
308 if (ds.status() == QDataStream::Ok)
309 input.append(
char(ch));
314 for (
const QTzType &type : types) {
315 const qsizetype end = type.tz_abbrind < input.size()
316 ? input.indexOf(
'\0', type.tz_abbrind) : -1;
318 return QMap<
int, QByteArray>();
320 map[type.tz_abbrind] = input.sliced(type.tz_abbrind, end - type.tz_abbrind);
333 for (
int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
337 if (ds.status() == QDataStream::Ok)
342 for (
int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
346 if (ds.status() == QDataStream::Ok)
355 QList<QTzType> result = types;
358
359
360
361
362
363
364
365
366
369 for (
int i = 0; i < tzh_ttisstdcnt && ds.status() == QDataStream::Ok; ++i)
373 for (
int i = 0; i < tzh_ttisgmtcnt && ds.status() == QDataStream::Ok; ++i)
386 if (ch !=
'\n' || ds.status() != QDataStream::Ok)
389 while (ch !=
'\n' && ds.status() == QDataStream::Ok) {
390 rule.append((
char)ch);
401 else if (dayOfWeek & ~7 || month < 1 || month > 12 || week < 1 || week > 5)
404 QDate date(year, month, 1);
405 int startDow = date.dayOfWeek();
406 if (startDow <= dayOfWeek)
407 date = date.addDays(dayOfWeek - startDow - 7);
409 date = date.addDays(dayOfWeek - startDow);
410 date = date.addDays(week * 7);
411 while (date.month() != month)
412 date = date.addDays(-7);
418 if (dateRule.isEmpty())
422 if (dateRule.at(0) ==
'M') {
424 const auto dateParts = dateRule.tokenize(u'.');
425 auto token = dateParts.begin();
426 Q_ASSERT(token != dateParts.end());
427 Q_ASSERT(!token->isEmpty());
428 const int month = token->sliced(1).toInt(&ok);
429 if (ok && ++token != dateParts.end()) {
430 const int week = token->toInt(&ok);
431 if (ok && ++token != dateParts.end()) {
432 const int dow = token->toInt(&ok);
434 return calculateDowDate(year, month, dow, week);
437 }
else if (dateRule.at(0) ==
'J') {
440 int doy = dateRule.sliced(1).toInt(&ok);
441 if (ok && doy > 0 && doy < 366) {
445 if (!QDate::isLeapYear(year) || doy < 60)
447 return QDate(year, 1, 1).addDays(doy);
451 int doy = dateRule.toInt(&ok);
452 if (ok && doy >= 0 && doy < 366)
453 return QDate(year, 1, 1).addDays(doy);
462 int hour, min = 0, sec = 0;
464 const int maxHour = 137;
465 auto r = qstrntoll(begin, end - begin, 10);
467 if (!r.ok() || hour < -maxHour || hour > maxHour || r.used > 2)
470 if (begin < end && *begin ==
':') {
473 r = qstrntoll(begin, end - begin, 10);
475 if (!r.ok() || min < 0 || min > 59 || r.used > 2)
479 if (begin < end && *begin ==
':') {
482 r = qstrntoll(begin, end - begin, 10);
484 if (!r.ok() || sec < 0 || sec > 59 || r.used > 2)
494 return (hour * 60 + min) * 60 + sec;
499 return parsePosixTime(timeRule.begin(), timeRule.end());
509 }
else if (*begin ==
'-') {
518 using namespace QtPrivate::DateTimeConstants;
519 if (value < -SECS_PER_DAY || value > SECS_PER_DAY)
521 return negate ? -value : value;
527 return ch >=
'a' && ch <=
'z';
533 return asciiIsLetter(ch
) || (ch >=
'0' && ch <=
'9') || ch ==
'+' || ch ==
'-';
541 InvalidOffset = INT_MIN,
545 int offset = InvalidOffset;
546 bool hasValidOffset()
const noexcept {
return offset != InvalidOffset; }
547 QTimeZonePrivate::Data dataAt(qint64 when)
549 Q_ASSERT(hasValidOffset());
550 return QTimeZonePrivate::Data(name, when, offset, offset);
552 QTimeZonePrivate::Data dataAtOffset(qint64 when,
int standard)
554 Q_ASSERT(hasValidOffset());
555 return QTimeZonePrivate::Data(name, when, offset, standard);
558 static PosixZone parse(
const char *&pos,
const char *end);
566PosixZone PosixZone::parse(
const char *&pos,
const char *end)
568 static const char offsetChars[] =
"0123456789:";
570 const char *nameBegin = pos;
579 if (nameEnd == end || *nameEnd !=
'>') {
591 if (nameEnd - nameBegin < 3)
595 const char *zoneBegin = pos;
596 const char *zoneEnd = pos;
598 if (zoneEnd < end && (zoneEnd[0] ==
'+' || zoneEnd[0] ==
'-'))
600 while (zoneEnd < end) {
601 if (strchr(offsetChars,
char(*zoneEnd)) ==
nullptr)
606 QString name = QString::fromUtf8(nameBegin, nameEnd - nameBegin);
607 const int offset = zoneEnd > zoneBegin ?
parsePosixOffset(zoneBegin
, zoneEnd
) : InvalidOffset;
611 if (offset != 0 && (name ==
"UTC"_L1 || name ==
"GMT"_L1))
613 return {
std::move(name), offset};
617
618
619
620
626 QLatin1StringView zoneinfo, startDate, endDate;
629 const auto tokens = QLatin1StringView(posixRule).tokenize(u',');
630 auto token = tokens.begin();
631 Q_ASSERT(token != tokens.end());
632 zoneinfo = token->trimmed();
633 if (++token != tokens.end()) {
636 if (++token != tokens.end()) {
639 if (++token != tokens.end())
644 const struct {
bool isValid, hasDst; } fail{
false,
false}, good{
true, parts > 1};
645 if (zoneinfo.isEmpty())
648 const char *begin = zoneinfo.begin();
651 const auto posix = PosixZone::parse(begin, zoneinfo.end());
652 if (posix.name.isEmpty())
654 if (requireOffset && !posix.hasValidOffset())
659 if (begin >= zoneinfo.end())
662 if (PosixZone::parse(begin, zoneinfo.end()).name.isEmpty())
665 if (begin < zoneinfo.end())
669 if (parts != 3 || startDate.isEmpty() || endDate.isEmpty())
671 for (
int i = 0; i < 2; ++i) {
672 const auto tokens = (i ? endDate : startDate).tokenize(u'/');
673 auto token = tokens.begin();
674 Q_ASSERT(token != tokens.end());
675 if (!calculatePosixDate(*token, 1972).isValid())
677 if (++token != tokens.end() && parsePosixTime(token->begin(), token->end()) == INT_MIN)
685 int startYear,
int endYear,
686 qint64 lastTranMSecs)
688 QList<QTimeZonePrivate::Data> result;
695 const auto tokens = QLatin1String(posixRule).tokenize(u',');
696 auto token = tokens.begin();
697 Q_ASSERT(token != tokens.end());
699 PosixZone stdZone, dstZone;
700 QLatin1StringView zoneText = *token;
702 QLatin1StringView zoneinfo = zoneText.trimmed();
703 const char *begin = zoneinfo.begin();
705 stdZone = PosixZone::parse(begin, zoneinfo.end());
706 if (!stdZone.hasValidOffset()) {
708 }
else if (begin < zoneinfo.end()) {
709 dstZone = PosixZone::parse(begin, zoneinfo.end());
710 if (!dstZone.hasValidOffset()) {
712 dstZone.offset = stdZone.offset + (60 * 60);
718 if (++token == tokens.end() || !dstZone.hasValidOffset()) {
720 stdZone.name.isEmpty() ? QString(zoneText) : stdZone.name,
721 lastTranMSecs, stdZone.offset, stdZone.offset);
724 QLatin1StringView dstRule = *token;
725 if (++token == tokens.end() || dstRule.isEmpty() || token->isEmpty())
727 QLatin1StringView stdRule = *token;
730 const int twoOClock = 7200;
731 const auto dstParts = dstRule.tokenize(u'/');
732 auto subtok = dstParts.begin();
733 Q_ASSERT(subtok != dstParts.end());
734 QLatin1StringView dstDateRule = *subtok;
735 const int dstTime = ++subtok == dstParts.end() ? twoOClock : parsePosixTransitionTime(*subtok);
738 const auto stdParts = stdRule.tokenize(u'/');
739 subtok = stdParts.begin();
740 Q_ASSERT(subtok != stdParts.end());
741 QLatin1StringView stdDateRule = *subtok;
742 const int stdTime = ++subtok == stdParts.end() ? twoOClock : parsePosixTransitionTime(*subtok);
744 if (dstDateRule.isEmpty() || stdDateRule.isEmpty() || dstTime == INT_MIN || stdTime == INT_MIN)
748 const int minYear =
int(QDateTime::YearRange::First);
749 const int maxYear =
int(QDateTime::YearRange::Last);
750 startYear = qBound(minYear, startYear, maxYear);
751 endYear = qBound(minYear, endYear, maxYear);
752 Q_ASSERT(startYear <= endYear);
754 for (
int year = startYear; year <= endYear; ++year) {
761 QDateTime dst(calculatePosixDate(dstDateRule, year)
762 .startOfDay(QTimeZone::UTC).addSecs(dstTime));
763 auto saving = dstZone.dataAtOffset(dst.toMSecsSinceEpoch() - stdZone.offset * 1000,
766 QDateTime std(calculatePosixDate(stdDateRule, year)
767 .startOfDay(QTimeZone::UTC).addSecs(stdTime));
768 auto standard = stdZone.dataAt(std.toMSecsSinceEpoch() - dstZone.offset * 1000);
770 if (year == startYear) {
773 if (saving.atMSecsSinceEpoch < standard.atMSecsSinceEpoch) {
774 if (dst <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
775 && std >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
777 saving.atMSecsSinceEpoch = lastTranMSecs;
778 result.emplaceBack(
std::move(saving));
782 if (std <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
783 && dst >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
785 standard.atMSecsSinceEpoch = lastTranMSecs;
786 result.emplaceBack(
std::move(standard));
792 const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty();
793 const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty();
794 if (useStd && useDst) {
796 result.emplaceBack(
std::move(saving));
797 result.emplaceBack(
std::move(standard));
799 result.emplaceBack(
std::move(standard));
800 result.emplaceBack(
std::move(saving));
803 result.emplaceBack(
std::move(standard));
805 result.emplaceBack(
std::move(saving));
812QTzTimeZonePrivate::QTzTimeZonePrivate()
813 : QTzTimeZonePrivate(staticSystemTimeZoneId())
817QTzTimeZonePrivate::~QTzTimeZonePrivate()
821QTzTimeZonePrivate *QTzTimeZonePrivate::clone()
const
823 return new QTzTimeZonePrivate(*
this);
834 static QTzTimeZoneCacheEntry findEntry(
const QByteArray &ianaId);
835 QCache<QByteArray, QTzTimeZoneCacheEntry> m_cache;
841 QTzTimeZoneCacheEntry ret;
843 if (ianaId.isEmpty()) {
845 tzif.setFileName(QStringLiteral(
"/etc/localtime"));
846 if (!tzif.open(QIODevice::ReadOnly))
848 }
else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) {
850 auto check = validatePosixRule(ianaId);
852 ret.m_hasDst = check.hasDst;
853 ret.m_posixRule = ianaId;
862 QByteArray posixRule;
864 if (!ok || ds.status() != QDataStream::Ok)
867 auto trans = parseTzTransitions(ds, hdr.tzh_timecnt, hdr.tzh_typecnt,
false);
870 Q_ASSERT(ds.status() == QDataStream::Ok);
873 QList<
QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt);
874 if (ds.status() != QDataStream::Ok)
876 QMap<
int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList);
877 if (ds.status() != QDataStream::Ok)
880 if (ds.status() != QDataStream::Ok)
882 typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt);
883 if (ds.status() != QDataStream::Ok)
890 if (!ok || ds.status() != QDataStream::Ok)
893 trans = parseTzTransitions(ds, hdr2.tzh_timecnt, hdr2.tzh_typecnt,
true);
896 Q_ASSERT(ds.status() == QDataStream::Ok);
897 tranList =
std::move(*trans);
899 typeList = parseTzTypes(ds, hdr2.tzh_typecnt);
900 if (ds.status() != QDataStream::Ok)
902 abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList);
903 if (ds.status() != QDataStream::Ok)
906 if (ds.status() != QDataStream::Ok)
908 typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt);
909 if (ds.status() != QDataStream::Ok)
911 posixRule = parseTzPosixRule(ds);
912 if (ds.status() != QDataStream::Ok)
917 if (!posixRule.isEmpty()) {
918 auto check = validatePosixRule(posixRule);
921 ret.m_posixRule = posixRule;
922 ret.m_hasDst = check.hasDst;
926 const int size = abbrevMap.size();
927 ret.m_abbreviations.clear();
928 ret.m_abbreviations.reserve(size);
929 QList<
int> abbrindList;
930 abbrindList.reserve(size);
931 for (
auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) {
932 ret.m_abbreviations.append(it.value());
933 abbrindList.append(it.key());
937 for (
int i = 0; i < typeList.size(); ++i)
938 typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind);
942 ret.m_preZoneRule = { typeList.at(0).tz_gmtoff, 0, typeList.at(0).tz_abbrind };
946 int utcOffset = ret.m_preZoneRule.stdOffset;
947 for (
const QTzTransition &tran : std::as_const(tranList)) {
948 if (!typeList.at(tran.tz_typeind).tz_isdst) {
949 utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff;
955 const int tranCount = tranList.size();
956 ret.m_tranTimes.reserve(tranCount);
958 int lastDstOff = 3600;
959 for (
int i = 0; i < tranCount; i++) {
961 QTzTransitionTime tran;
962 QTzTransitionRule rule;
963 const QTzType tz_type = typeList.at(tz_tran.tz_typeind);
968 }
else if (Q_UNLIKELY(tz_type
.tz_gmtoff != utcOffset + lastDstOff)) {
970
971
972
973
974
976 const int inferStd = tz_type
.tz_gmtoff - lastDstOff;
977 for (
int j = i + 1; j < tranCount; j++) {
978 const QTzType new_type = typeList.at(tranList.at(j).tz_typeind);
981 if (newUtc == utcOffset) {
984 }
else if (newUtc == inferStd) {
987 }
else if (tz_type
.tz_gmtoff - 3600 == utcOffset) {
989 }
else if (tz_type
.tz_gmtoff - 3600 == newUtc) {
995 || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))
997 && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) {
1005 rule.stdOffset = utcOffset;
1006 rule.dstOffset = tz_type
.tz_gmtoff - utcOffset;
1007 rule.abbreviationIndex = tz_type.tz_abbrind;
1010 int ruleIndex = ret.m_tranRules.indexOf(rule);
1011 if (ruleIndex == -1) {
1012 if (rule.dstOffset != 0)
1013 ret.m_hasDst =
true;
1014 tran.ruleIndex = ret.m_tranRules.size();
1015 ret.m_tranRules.append(rule);
1017 tran.ruleIndex = ruleIndex;
1020 if (qMulOverflow<1000>(tz_tran.tz_time, &tran.atMSecsSinceEpoch)) {
1021 ret.m_tranTimes.clear();
1022 ret.m_posixRule.clear();
1025 ret.m_tranTimes.append(tran);
1033 QMutexLocker locker(&m_mutex);
1036 QTzTimeZoneCacheEntry *obj = m_cache.object(ianaId);
1044 QTzTimeZoneCacheEntry ret = findEntry(ianaId);
1045 if (ret.m_tranTimes.isEmpty() && ret.m_posixRule.isEmpty())
1048 auto ptr = std::make_unique<QTzTimeZoneCacheEntry>(ret);
1051 m_cache.insert(ianaId, ptr.release());
1058QTzTimeZonePrivate::QTzTimeZonePrivate(
const QByteArray &ianaId)
1060 if (!isTimeZoneIdAvailable(ianaId))
1062 static QTzTimeZoneCache tzCache;
1063 auto entry = tzCache.fetchEntry(ianaId);
1064 if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty())
1067 cached_data = std::move(entry);
1070 if (m_id.isEmpty()) {
1074 m_id = abbreviation(QDateTime::currentMSecsSinceEpoch()).toUtf8();
1078QLocale::Territory QTzTimeZonePrivate::territory()
const
1080 return tzZones->value(m_id).territory;
1083QString QTzTimeZonePrivate::comment()
const
1085 return QString::fromUtf8(tzZones->value(m_id).comment);
1088QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1089 QTimeZone::NameType nameType,
1090 const QLocale &locale)
const
1093 if (nameType != QTimeZone::LongName && isDataLocale(locale)) {
1094 Data tran = data(timeType);
1095 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
1096 if (nameType == QTimeZone::ShortName)
1097 return tran.abbreviation;
1099 if (isAnglicLocale(locale))
1100 return isoOffsetFormat(tran.offsetFromUtc);
1104 return QTimeZonePrivate::displayName(timeType, nameType, locale);
1107QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
1109 return data(atMSecsSinceEpoch).abbreviation;
1112int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
1114 const Data tran = data(atMSecsSinceEpoch);
1115 return tran.offsetFromUtc;
1118int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
1120 return data(atMSecsSinceEpoch).standardTimeOffset;
1123int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
1125 return data(atMSecsSinceEpoch).daylightTimeOffset;
1128bool QTzTimeZonePrivate::hasDaylightTime()
const
1130 return cached_data.m_hasDst;
1133bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
1135 return (daylightTimeOffset(atMSecsSinceEpoch) != 0);
1138QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran)
const
1140 return dataFromRule(cached_data.m_tranRules.at(tran.ruleIndex), tran.atMSecsSinceEpoch);
1143QTimeZonePrivate::Data QTzTimeZonePrivate::dataFromRule(QTzTransitionRule rule,
1144 qint64 msecsSinceEpoch)
const
1146 return Data(QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)),
1147 msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset);
1150QList<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear)
const
1152 const int year = QDateTime::fromMSecsSinceEpoch(msNear, QTimeZone::UTC).date().year();
1154 qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch;
1155 return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime);
1158QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
1162 if (!cached_data.m_posixRule.isEmpty()
1163 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
1164 QList<Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
1165 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1166 [forMSecsSinceEpoch] (
const Data &at) {
1167 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1170 if (it > posixTrans.cbegin() || (tranCache().isEmpty() && it < posixTrans.cend())) {
1171 Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
1172 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1176 if (tranCache().isEmpty())
1180 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1181 [forMSecsSinceEpoch] (QTzTransitionTime at) {
1182 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1184 if (last == tranCache().cbegin())
1185 return dataFromRule(cached_data.m_preZoneRule, forMSecsSinceEpoch);
1188 return dataFromRule(cached_data.m_tranRules.at(last->ruleIndex), forMSecsSinceEpoch);
1196QTimeZonePrivate::Data QTzTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
1199 const auto validMatch = [timeType](
const Data &tran) {
1200 return tran.atMSecsSinceEpoch != invalidMSecs()
1201 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
1205 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
1206 Data tran = data(currentMSecs);
1207 if (validMatch(tran))
1211 tran = nextTransition(currentMSecs);
1212 if (validMatch(tran))
1218 tran = previousTransition(currentMSecs + 1);
1219 if (tran.atMSecsSinceEpoch != invalidMSecs())
1220 tran = previousTransition(tran.atMSecsSinceEpoch);
1221 if (validMatch(tran))
1227 const auto untilNow = [currentMSecs](QTzTransitionTime at) {
1228 return at.atMSecsSinceEpoch <= currentMSecs;
1230 auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), untilNow);
1233 while (it != tranCache().cbegin()) {
1235 tran = dataForTzTransition(*it);
1236 if ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0))
1243bool QTzTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
1246 return isAnglicLocale(locale);
1249bool QTzTimeZonePrivate::hasTransitions()
const
1254QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
1258 if (!cached_data.m_posixRule.isEmpty()
1259 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
1260 QList<Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
1261 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1262 [afterMSecsSinceEpoch] (
const Data &at) {
1263 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1266 return it == posixTrans.cend() ? Data{} : *it;
1270 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1271 [afterMSecsSinceEpoch] (QTzTransitionTime at) {
1272 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1274 return last != tranCache().cend() ? dataForTzTransition(*last) : Data{};
1277QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
1281 if (!cached_data.m_posixRule.isEmpty()
1282 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
1283 QList<Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
1284 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1285 [beforeMSecsSinceEpoch] (
const Data &at) {
1286 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1288 if (it > posixTrans.cbegin())
1291 return tranCache().isEmpty() ? Data{} : dataForTzTransition(tranCache().last());
1295 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1296 [beforeMSecsSinceEpoch] (QTzTransitionTime at) {
1297 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1299 return last > tranCache().cbegin() ? dataForTzTransition(*--last) : Data{};
1302bool QTzTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
1308 return tzZones->contains(ianaId) || validatePosixRule(ianaId,
true).isValid;
1311QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds()
const
1313 return uniqueSortedAliasPadded(tzZones->keys());
1316QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
1318 QList<QByteArray> result;
1319 for (
auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) {
1320 if (it.value().territory == territory)
1324 std::sort(result.begin(), result.end());
1329 const auto unWantedZone = [territory](QByteArrayView id) {
1331 auto it = tzZones->constFind(id);
1332 return it == tzZones->end() || it->territory == territory;
1334 QList<QByteArrayView> cldrViews = matchingTimeZoneIds(territory);
1335 std::sort(cldrViews.begin(), cldrViews.end());
1336 const auto uniqueEnd = std::unique(cldrViews.begin(), cldrViews.end());
1337 const auto prunedEnd = std::remove_if(cldrViews.begin(), uniqueEnd, unWantedZone);
1338 const auto cldrSize = std::distance(cldrViews.begin(), prunedEnd);
1340 QList<QByteArray> cldrList;
1341 cldrList.reserve(cldrSize);
1342 for (
auto it = cldrViews.begin(); it != prunedEnd; ++it)
1343 cldrList.emplace_back(it->toByteArray());
1344 QList<QByteArray> joined;
1345 joined.reserve(result.size() + cldrSize);
1346 std::set_union(result.begin(), result.end(), cldrList.begin(), cldrList.end(),
1347 std::back_inserter(joined));
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377 const StatIdent local = identify(
"/etc/localtime");
1378 const StatIdent tz = identify(
"/etc/TZ");
1379 const StatIdent timezone = identify(
"/etc/timezone");
1380 if (!m_name.isEmpty() && m_last.isValid()
1381 && (m_last == local || m_last == tz || m_last == timezone)) {
1385 m_name = etcLocalTime();
1386 if (!m_name.isEmpty()) {
1392 m_name = etcContent(QStringLiteral(
"/etc/TZ"));
1393 if (!m_name.isEmpty()) {
1399 m_name = etcContent(QStringLiteral(
"/etc/timezone"));
1400 m_last = m_name.isEmpty() ? StatIdent() : timezone;
1408 static constexpr unsigned long bad = ~0ul;
1409 unsigned long m_dev, m_ino;
1410 constexpr StatIdent() : m_dev(bad), m_ino(bad) {}
1411 StatIdent(
const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {}
1412 bool isValid() {
return m_dev != bad || m_ino != bad; }
1413 friend constexpr bool operator==(StatIdent lhs, StatIdent rhs)
1414 {
return lhs.m_dev == rhs.m_dev && lhs.m_ino == rhs.m_ino; }
1418 static StatIdent identify(
const char *path)
1421 return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data);
1424 static QByteArray etcLocalTime()
1428 const QString tzdir = qEnvironmentVariable(
"TZDIR");
1429 constexpr auto zoneinfo =
"/zoneinfo/"_L1;
1430 QString path = QStringLiteral(
"/etc/localtime");
1431 long iteration = getSymloopMax();
1436 path = QFile::symLinkTarget(path);
1438 int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir);
1440 const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8();
1441 return tail.startsWith(u'/') ? tail.sliced(1) : tail;
1443 index = path.indexOf(zoneinfo);
1445 return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8();
1446 }
while (!path.isEmpty() && --iteration > 0);
1448 return QByteArray();
1451 static QByteArray etcContent(
const QString &path)
1454 if (zone.open(QIODevice::ReadOnly))
1455 return zone.readAll().trimmed();
1457 return QByteArray();
1461 static long getSymloopMax()
1468 long result = sysconf(_SC_SYMLOOP_MAX);
1485QByteArray QTzTimeZonePrivate::systemTimeZoneId()
const
1487 return staticSystemTimeZoneId();
1490QByteArray QTzTimeZonePrivate::staticSystemTimeZoneId()
1493 QByteArray ianaId = qgetenv(
"TZ");
1499 if (ianaId ==
":/etc/localtime")
1501 else if (ianaId.startsWith(
':'))
1502 ianaId = ianaId.sliced(1);
1504 if (ianaId.isEmpty()) {
1505 Q_CONSTINIT
thread_local static ZoneNameReader reader;
1506 ianaId = reader.name();
\inmodule QtCore\reentrant
QTzTimeZoneCacheEntry fetchEntry(const QByteArray &ianaId)
static int parsePosixTime(const char *begin, const char *end)
static bool asciiIsQuotedNameChar(char ch)
static auto validatePosixRule(QByteArrayView posixRule, bool requireOffset=false)
static QMap< int, QByteArray > parseTzAbbreviations(QDataStream &ds, int tzh_charcnt, const QList< QTzType > &types)
static std::optional< QList< QTzTransition > > parseTzTransitions(QDataStream &ds, int tzh_timecnt, int tzh_typecnt, bool longTran)
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 QByteArray parseTzPosixRule(QDataStream &ds)
QLocale::Territory territory