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);
814 static QTzTimeZoneCacheEntry findEntry(
const QByteArray &ianaId);
815 QCache<QByteArray, QTzTimeZoneCacheEntry> m_cache;
821 QTzTimeZoneCacheEntry ret;
823 if (ianaId.isEmpty()) {
825 tzif.setFileName(QStringLiteral(
"/etc/localtime"));
826 if (!tzif.open(QIODevice::ReadOnly))
828 }
else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) {
830 auto check = validatePosixRule(ianaId);
832 ret.m_hasDst = check.hasDst;
833 ret.m_posixRule = ianaId;
842 QByteArray posixRule;
844 if (!ok || ds.status() != QDataStream::Ok)
847 if (ds.status() != QDataStream::Ok)
849 QList<
QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt);
850 if (ds.status() != QDataStream::Ok)
852 QMap<
int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList);
853 if (ds.status() != QDataStream::Ok)
856 if (ds.status() != QDataStream::Ok)
858 typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt);
859 if (ds.status() != QDataStream::Ok)
866 if (!ok || ds.status() != QDataStream::Ok)
868 tranList = parseTzTransitions(ds, hdr2.tzh_timecnt,
true);
869 if (ds.status() != QDataStream::Ok)
871 typeList = parseTzTypes(ds, hdr2.tzh_typecnt);
872 if (ds.status() != QDataStream::Ok)
874 abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList);
875 if (ds.status() != QDataStream::Ok)
878 if (ds.status() != QDataStream::Ok)
880 typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt);
881 if (ds.status() != QDataStream::Ok)
883 posixRule = parseTzPosixRule(ds);
884 if (ds.status() != QDataStream::Ok)
889 if (!posixRule.isEmpty()) {
890 auto check = validatePosixRule(posixRule);
893 ret.m_posixRule = posixRule;
894 ret.m_hasDst = check.hasDst;
898 const int size = abbrevMap.size();
899 ret.m_abbreviations.clear();
900 ret.m_abbreviations.reserve(size);
901 QList<
int> abbrindList;
902 abbrindList.reserve(size);
903 for (
auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) {
904 ret.m_abbreviations.append(it.value());
905 abbrindList.append(it.key());
909 for (
int i = 0; i < typeList.size(); ++i)
910 typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind);
914 ret.m_preZoneRule = { typeList.at(0).tz_gmtoff, 0, typeList.at(0).tz_abbrind };
918 int utcOffset = ret.m_preZoneRule.stdOffset;
919 for (
const QTzTransition &tran : std::as_const(tranList)) {
920 if (!typeList.at(tran.tz_typeind).tz_isdst) {
921 utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff;
927 const int tranCount = tranList.size();
928 ret.m_tranTimes.reserve(tranCount);
930 int lastDstOff = 3600;
931 for (
int i = 0; i < tranCount; i++) {
933 QTzTransitionTime tran;
934 QTzTransitionRule rule;
935 const QTzType tz_type = typeList.at(tz_tran.tz_typeind);
940 }
else if (Q_UNLIKELY(tz_type
.tz_gmtoff != utcOffset + lastDstOff)) {
942
943
944
945
946
948 const int inferStd = tz_type
.tz_gmtoff - lastDstOff;
949 for (
int j = i + 1; j < tranCount; j++) {
950 const QTzType new_type = typeList.at(tranList.at(j).tz_typeind);
953 if (newUtc == utcOffset) {
956 }
else if (newUtc == inferStd) {
959 }
else if (tz_type
.tz_gmtoff - 3600 == utcOffset) {
961 }
else if (tz_type
.tz_gmtoff - 3600 == newUtc) {
967 || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))
969 && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) {
977 rule.stdOffset = utcOffset;
978 rule.dstOffset = tz_type
.tz_gmtoff - utcOffset;
979 rule.abbreviationIndex = tz_type.tz_abbrind;
982 int ruleIndex = ret.m_tranRules.indexOf(rule);
983 if (ruleIndex == -1) {
984 if (rule.dstOffset != 0)
986 tran.ruleIndex = ret.m_tranRules.size();
987 ret.m_tranRules.append(rule);
989 tran.ruleIndex = ruleIndex;
992 tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000;
993 ret.m_tranTimes.append(tran);
1001 QMutexLocker locker(&m_mutex);
1004 QTzTimeZoneCacheEntry *obj = m_cache.object(ianaId);
1012 QTzTimeZoneCacheEntry ret = findEntry(ianaId);
1013 if (ret.m_tranTimes.isEmpty() && ret.m_posixRule.isEmpty())
1016 auto ptr = std::make_unique<QTzTimeZoneCacheEntry>(ret);
1019 m_cache.insert(ianaId, ptr.release());
1026QTzTimeZonePrivate::QTzTimeZonePrivate(
const QByteArray &ianaId)
1028 if (!isTimeZoneIdAvailable(ianaId))
1030 static QTzTimeZoneCache tzCache;
1031 auto entry = tzCache.fetchEntry(ianaId);
1032 if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty())
1035 cached_data = std::move(entry);
1038 if (m_id.isEmpty()) {
1042 m_id = abbreviation(QDateTime::currentMSecsSinceEpoch()).toUtf8();
1046QLocale::Territory QTzTimeZonePrivate::territory()
const
1048 return tzZones->value(m_id).territory;
1051QString QTzTimeZonePrivate::comment()
const
1053 return QString::fromUtf8(tzZones->value(m_id).comment);
1056QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1057 QTimeZone::NameType nameType,
1058 const QLocale &locale)
const
1061 if (nameType != QTimeZone::LongName && isDataLocale(locale)) {
1062 Data tran = data(timeType);
1063 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
1064 if (nameType == QTimeZone::ShortName)
1065 return tran.abbreviation;
1067 if (isAnglicLocale(locale))
1068 return isoOffsetFormat(tran.offsetFromUtc);
1072 return QTimeZonePrivate::displayName(timeType, nameType, locale);
1075QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
1077 return data(atMSecsSinceEpoch).abbreviation;
1080int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
1082 const Data tran = data(atMSecsSinceEpoch);
1083 return tran.offsetFromUtc;
1086int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
1088 return data(atMSecsSinceEpoch).standardTimeOffset;
1091int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
1093 return data(atMSecsSinceEpoch).daylightTimeOffset;
1096bool QTzTimeZonePrivate::hasDaylightTime()
const
1098 return cached_data.m_hasDst;
1101bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
1103 return (daylightTimeOffset(atMSecsSinceEpoch) != 0);
1106QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran)
const
1108 return dataFromRule(cached_data.m_tranRules.at(tran.ruleIndex), tran.atMSecsSinceEpoch);
1111QTimeZonePrivate::Data QTzTimeZonePrivate::dataFromRule(QTzTransitionRule rule,
1112 qint64 msecsSinceEpoch)
const
1114 return Data(QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)),
1115 msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset);
1118QList<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear)
const
1120 const int year = QDateTime::fromMSecsSinceEpoch(msNear, QTimeZone::UTC).date().year();
1122 qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch;
1123 return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime);
1126QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
1130 if (!cached_data.m_posixRule.isEmpty()
1131 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
1132 QList<Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
1133 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1134 [forMSecsSinceEpoch] (
const Data &at) {
1135 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1138 if (it > posixTrans.cbegin() || (tranCache().isEmpty() && it < posixTrans.cend())) {
1139 Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
1140 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1144 if (tranCache().isEmpty())
1148 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1149 [forMSecsSinceEpoch] (QTzTransitionTime at) {
1150 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1152 if (last == tranCache().cbegin())
1153 return dataFromRule(cached_data.m_preZoneRule, forMSecsSinceEpoch);
1156 return dataFromRule(cached_data.m_tranRules.at(last->ruleIndex), forMSecsSinceEpoch);
1164QTimeZonePrivate::Data QTzTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
1167 const auto validMatch = [timeType](
const Data &tran) {
1168 return tran.atMSecsSinceEpoch != invalidMSecs()
1169 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
1173 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
1174 Data tran = data(currentMSecs);
1175 if (validMatch(tran))
1179 tran = nextTransition(currentMSecs);
1180 if (validMatch(tran))
1186 tran = previousTransition(currentMSecs + 1);
1187 if (tran.atMSecsSinceEpoch != invalidMSecs())
1188 tran = previousTransition(tran.atMSecsSinceEpoch);
1189 if (validMatch(tran))
1195 const auto untilNow = [currentMSecs](QTzTransitionTime at) {
1196 return at.atMSecsSinceEpoch <= currentMSecs;
1198 auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), untilNow);
1201 while (it != tranCache().cbegin()) {
1203 tran = dataForTzTransition(*it);
1204 if ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0))
1211bool QTzTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
1214 return isAnglicLocale(locale);
1217bool QTzTimeZonePrivate::hasTransitions()
const
1222QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
1226 if (!cached_data.m_posixRule.isEmpty()
1227 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
1228 QList<Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
1229 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1230 [afterMSecsSinceEpoch] (
const Data &at) {
1231 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1234 return it == posixTrans.cend() ? Data{} : *it;
1238 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1239 [afterMSecsSinceEpoch] (QTzTransitionTime at) {
1240 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1242 return last != tranCache().cend() ? dataForTzTransition(*last) : Data{};
1245QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
1249 if (!cached_data.m_posixRule.isEmpty()
1250 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
1251 QList<Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
1252 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1253 [beforeMSecsSinceEpoch] (
const Data &at) {
1254 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1256 if (it > posixTrans.cbegin())
1259 return tranCache().isEmpty() ? Data{} : dataForTzTransition(tranCache().last());
1263 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1264 [beforeMSecsSinceEpoch] (QTzTransitionTime at) {
1265 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1267 return last > tranCache().cbegin() ? dataForTzTransition(*--last) : Data{};
1270bool QTzTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId)
const
1276 return tzZones->contains(ianaId) || validatePosixRule(ianaId,
true).isValid;
1279QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds()
const
1281 return uniqueSortedAliasPadded(tzZones->keys());
1284QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
1286 QList<QByteArray> result;
1287 for (
auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) {
1288 if (it.value().territory == territory)
1292 std::sort(result.begin(), result.end());
1297 const auto unWantedZone = [territory](QByteArrayView id) {
1299 auto it = tzZones->constFind(id);
1300 return it == tzZones->end() || it->territory == territory;
1302 QList<QByteArrayView> cldrViews = matchingTimeZoneIds(territory);
1303 std::sort(cldrViews.begin(), cldrViews.end());
1304 const auto uniqueEnd = std::unique(cldrViews.begin(), cldrViews.end());
1305 const auto prunedEnd = std::remove_if(cldrViews.begin(), uniqueEnd, unWantedZone);
1306 const auto cldrSize = std::distance(cldrViews.begin(), prunedEnd);
1308 QList<QByteArray> cldrList;
1309 cldrList.reserve(cldrSize);
1310 for (
auto it = cldrViews.begin(); it != prunedEnd; ++it)
1311 cldrList.emplace_back(it->toByteArray());
1312 QList<QByteArray> joined;
1313 joined.reserve(result.size() + cldrSize);
1314 std::set_union(result.begin(), result.end(), cldrList.begin(), cldrList.end(),
1315 std::back_inserter(joined));
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345 const StatIdent local = identify(
"/etc/localtime");
1346 const StatIdent tz = identify(
"/etc/TZ");
1347 const StatIdent timezone = identify(
"/etc/timezone");
1348 if (!m_name.isEmpty() && m_last.isValid()
1349 && (m_last == local || m_last == tz || m_last == timezone)) {
1353 m_name = etcLocalTime();
1354 if (!m_name.isEmpty()) {
1360 m_name = etcContent(QStringLiteral(
"/etc/TZ"));
1361 if (!m_name.isEmpty()) {
1367 m_name = etcContent(QStringLiteral(
"/etc/timezone"));
1368 m_last = m_name.isEmpty() ? StatIdent() : timezone;
1376 static constexpr unsigned long bad = ~0ul;
1377 unsigned long m_dev, m_ino;
1378 constexpr StatIdent() : m_dev(bad), m_ino(bad) {}
1379 StatIdent(
const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {}
1380 bool isValid() {
return m_dev != bad || m_ino != bad; }
1381 friend constexpr bool operator==(StatIdent lhs, StatIdent rhs)
1382 {
return lhs.m_dev == rhs.m_dev && lhs.m_ino == rhs.m_ino; }
1386 static StatIdent identify(
const char *path)
1389 return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data);
1392 static QByteArray etcLocalTime()
1396 const QString tzdir = qEnvironmentVariable(
"TZDIR");
1397 constexpr auto zoneinfo =
"/zoneinfo/"_L1;
1398 QString path = QStringLiteral(
"/etc/localtime");
1399 long iteration = getSymloopMax();
1404 path = QFile::symLinkTarget(path);
1406 int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir);
1408 const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8();
1409 return tail.startsWith(u'/') ? tail.sliced(1) : tail;
1411 index = path.indexOf(zoneinfo);
1413 return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8();
1414 }
while (!path.isEmpty() && --iteration > 0);
1416 return QByteArray();
1419 static QByteArray etcContent(
const QString &path)
1422 if (zone.open(QIODevice::ReadOnly))
1423 return zone.readAll().trimmed();
1425 return QByteArray();
1429 static long getSymloopMax()
1436 long result = sysconf(_SC_SYMLOOP_MAX);
1453QByteArray QTzTimeZonePrivate::systemTimeZoneId()
const
1455 return staticSystemTimeZoneId();
1458QByteArray QTzTimeZonePrivate::staticSystemTimeZoneId()
1461 QByteArray ianaId = qgetenv(
"TZ");
1467 if (ianaId ==
":/etc/localtime")
1469 else if (ianaId.startsWith(
':'))
1470 ianaId = ianaId.sliced(1);
1472 if (ianaId.isEmpty()) {
1473 Q_CONSTINIT
thread_local static ZoneNameReader reader;
1474 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