8#include "private/qlocale_tools_p.h"
9#include "private/qlocking_p.h"
11#include <QtCore/QDataStream>
12#include <QtCore/QDateTime>
13#include <QtCore/QDirListing>
15#include <QtCore/QFile>
16#include <QtCore/QCache>
18#include <QtCore/QMutex>
21#include <qplatformdefs.h>
35using namespace Qt::StringLiterals;
38
39
40
41
52static bool isTzFile(
const QString &name);
59 const QString tzdir = qEnvironmentVariable(
"TZDIR");
60 if (!tzdir.isEmpty()) {
61 file->setFileName(QDir(tzdir).filePath(name));
62 if (file->open(QIODevice::ReadOnly))
66 constexpr auto zoneShare =
"/usr/share/zoneinfo/"_L1;
67 if (tzdir != zoneShare && tzdir != zoneShare.chopped(1)) {
68 file->setFileName(zoneShare + name);
69 if (file->open(QIODevice::ReadOnly))
73 constexpr auto zoneLib =
"/usr/lib/zoneinfo/"_L1;
74 if (tzdir != zoneLib && tzdir != zoneLib.chopped(1)) {
75 file->setFileName(zoneShare + name);
76 if (file->open(QIODevice::ReadOnly))
88 if (!openZoneInfo(
"zone.tab"_L1, &tzif))
93 while (tzif.readLineInto(&line)) {
94 QByteArrayView text = QByteArrayView(line).trimmed();
95 if (text.isEmpty() || text.at(0) ==
'#')
98 int cut = text.indexOf(
'\t');
99 if (Q_LIKELY(cut > 0)) {
102 zone.territory = QLocalePrivate::codeToTerritory(QString::fromUtf8(text.first(cut)));
103 text = text.sliced(cut + 1);
104 cut = text.indexOf(
'\t');
105 if (Q_LIKELY(cut >= 0)) {
106 text = text.sliced(cut + 1);
107 cut = text.indexOf(
'\t');
109 const QByteArray id = (cut > 0 ? text.first(cut) : text).toByteArray();
111 zone.comment = text.sliced(cut + 1).toByteArray();
112 zonesHash.insert(id, zone);
118 QString path = tzif.fileName();
119 const qsizetype cut = path.lastIndexOf(u'/');
121 path.truncate(cut + 1);
122 const qsizetype prefixLen = path.size();
123 for (
const auto &info : QDirListing(path, QDirListing::IteratorFlag::Recursive)) {
124 if (!(info.isFile() || info.isSymLink()))
126 const QString infoAbsolutePath = info.absoluteFilePath();
127 const QString name = infoAbsolutePath.sliced(prefixLen);
129 if (info.isDir() ? name ==
"posix"_L1 || name ==
"right"_L1
130 : name.startsWith(
"posix/"_L1) || name.startsWith(
"right/"_L1)) {
136 const QByteArray id = QFile::encodeName(name);
137 if (!zonesHash.contains(id) && isTzFile(infoAbsolutePath))
138 zonesHash.insert(id, QTzTimeZone());
147
148
149
150
152#define TZ_MAGIC "TZif"
153#define TZ_MAX_TIMES 1200
154#define TZ_MAX_TYPES 256
155#define TZ_MAX_CHARS 50
156#define TZ_MAX_LEAPS 50
186 return file.open(QFile::ReadOnly) && file.read(strlen(
TZ_MAGIC)) ==
TZ_MAGIC;
198 ds.readRawData(hdr.tzh_magic, 4);
200 if (memcmp(hdr.tzh_magic,
TZ_MAGIC, 4) != 0 || ds.status() != QDataStream::Ok)
205 hdr.tzh_version = ch;
206 if (ds.status() != QDataStream::Ok
207 || (hdr.tzh_version !=
'2' && hdr.tzh_version !=
'\0' && hdr.tzh_version !=
'3')) {
212 ds.readRawData(hdr.tzh_reserved, 15);
213 if (ds.status() != QDataStream::Ok)
217 ds >> hdr.tzh_ttisgmtcnt >> hdr.tzh_ttisstdcnt >> hdr.tzh_leapcnt >> hdr.tzh_timecnt
218 >> hdr.tzh_typecnt >> hdr.tzh_charcnt;
221 if (ds.status() != QDataStream::Ok
226 || hdr.tzh_ttisgmtcnt > hdr.tzh_typecnt
227 || hdr.tzh_ttisstdcnt > hdr.tzh_typecnt) {
237 QList<QTzTransition> transitions(tzh_timecnt);
241 for (
int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
242 ds >> transitions[i].tz_time;
243 if (ds.status() != QDataStream::Ok)
244 transitions.resize(i);
249 for (
int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
251 transitions[i].tz_time = val;
252 if (ds.status() != QDataStream::Ok)
253 transitions.resize(i);
258 for (
int i = 0; i < tzh_timecnt && ds.status() == QDataStream::Ok; ++i) {
261 if (ds.status() == QDataStream::Ok)
262 transitions[i].tz_typeind = typeind;
270 QList<QTzType> types(tzh_typecnt);
273 for (
int i = 0; i < tzh_typecnt && ds.status() == QDataStream::Ok; ++i) {
274 QTzType &type = types[i];
276 ds >> type.tz_gmtoff;
278 if (ds.status() == QDataStream::Ok)
281 if (ds.status() == QDataStream::Ok)
282 ds >> type.tz_abbrind;
283 if (ds.status() != QDataStream::Ok)
297 QMap<
int, QByteArray> map;
301 for (
int i = 0; i < tzh_charcnt && ds.status() == QDataStream::Ok; ++i) {
303 if (ds.status() == QDataStream::Ok)
304 input.append(
char(ch));
309 for (
const QTzType &type : types) {
311 for (
int i = type.tz_abbrind; input.at(i) !=
'\0'; ++i)
312 abbrev.append(input.at(i));
314 map[type.tz_abbrind] = abbrev;
327 for (
int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
331 if (ds.status() == QDataStream::Ok)
336 for (
int i = 0; i < tzh_leapcnt && ds.status() == QDataStream::Ok; ++i) {
340 if (ds.status() == QDataStream::Ok)
349 QList<QTzType> result = types;
352
353
354
355
356
357
358
359
360
363 for (
int i = 0; i < tzh_ttisstdcnt && ds.status() == QDataStream::Ok; ++i)
367 for (
int i = 0; i < tzh_ttisgmtcnt && ds.status() == QDataStream::Ok; ++i)
380 if (ch !=
'\n' || ds.status() != QDataStream::Ok)
383 while (ch !=
'\n' && ds.status() == QDataStream::Ok) {
384 rule.append((
char)ch);
395 else if (dayOfWeek & ~7 || month < 1 || month > 12 || week < 1 || week > 5)
398 QDate date(year, month, 1);
399 int startDow = date.dayOfWeek();
400 if (startDow <= dayOfWeek)
401 date = date.addDays(dayOfWeek - startDow - 7);
403 date = date.addDays(dayOfWeek - startDow);
404 date = date.addDays(week * 7);
405 while (date.month() != month)
406 date = date.addDays(-7);
412 Q_ASSERT(!dateRule.isEmpty());
415 if (dateRule.at(0) ==
'M') {
417 QList<QByteArray> dateParts = dateRule.split(
'.');
418 if (dateParts.size() > 2) {
419 Q_ASSERT(!dateParts.at(0).isEmpty());
420 int month = QByteArrayView{ dateParts.at(0) }.sliced(1).toInt(&ok);
421 int week = ok ? dateParts.at(1).toInt(&ok) : 0;
422 int dow = ok ? dateParts.at(2).toInt(&ok) : 0;
424 return calculateDowDate(year, month, dow, week);
426 }
else if (dateRule.at(0) ==
'J') {
429 int doy = QByteArrayView{ dateRule }.sliced(1).toInt(&ok);
430 if (ok && doy > 0 && doy < 366) {
434 if (!QDate::isLeapYear(year) || doy < 60)
436 return QDate(year, 1, 1).addDays(doy);
440 int doy = dateRule.toInt(&ok);
441 if (ok && doy >= 0 && doy < 366)
442 return QDate(year, 1, 1).addDays(doy);
451 int hour, min = 0, sec = 0;
453 const int maxHour = 137;
454 auto r = qstrntoll(begin, end - begin, 10);
456 if (!r.ok() || hour < -maxHour || hour > maxHour || r.used > 2)
459 if (begin < end && *begin ==
':') {
462 r = qstrntoll(begin, end - begin, 10);
464 if (!r.ok() || min < 0 || min > 59 || r.used > 2)
468 if (begin < end && *begin ==
':') {
471 r = qstrntoll(begin, end - begin, 10);
473 if (!r.ok() || sec < 0 || sec > 59 || r.used > 2)
483 return (hour * 60 + min) * 60 + sec;
488 return parsePosixTime(timeRule.constBegin(), timeRule.constEnd());
498 }
else if (*begin ==
'-') {
504 if (value == INT_MIN)
506 return negate ? -value : value;
512 return ch >=
'a' && ch <=
'z';
520 InvalidOffset = INT_MIN,
524 int offset = InvalidOffset;
525 bool hasValidOffset()
const noexcept {
return offset != InvalidOffset; }
526 QTimeZonePrivate::Data dataAt(qint64 when)
528 Q_ASSERT(hasValidOffset());
529 return QTimeZonePrivate::Data(name, when, offset, offset);
531 QTimeZonePrivate::Data dataAtOffset(qint64 when,
int standard)
533 Q_ASSERT(hasValidOffset());
534 return QTimeZonePrivate::Data(name, when, offset, standard);
537 static PosixZone parse(
const char *&pos,
const char *end);
545PosixZone PosixZone::parse(
const char *&pos,
const char *end)
547 static const char offsetChars[] =
"0123456789:";
549 const char *nameBegin = pos;
556 while (nameEnd < end && *nameEnd !=
'>') {
567 if (nameEnd - nameBegin < 3)
571 const char *zoneBegin = pos;
572 const char *zoneEnd = pos;
573 if (zoneEnd < end && (zoneEnd[0] ==
'+' || zoneEnd[0] ==
'-'))
575 while (zoneEnd < end) {
576 if (strchr(offsetChars,
char(*zoneEnd)) ==
nullptr)
581 QString name = QString::fromUtf8(nameBegin, nameEnd - nameBegin);
582 const int offset = zoneEnd > zoneBegin ?
parsePosixOffset(zoneBegin
, zoneEnd
) : InvalidOffset;
586 if (offset != 0 && (name ==
"UTC"_L1 || name ==
"GMT"_L1))
588 return {
std::move(name), offset};
592
593
594
595
601 const auto parts = posixRule.split(
',');
602 const struct {
bool isValid, hasDst; } fail{
false,
false}, good{
true, parts.size() > 1};
603 const QByteArray &zoneinfo = parts.at(0);
604 if (zoneinfo.isEmpty())
607 const char *begin = zoneinfo.begin();
610 const auto posix = PosixZone::parse(begin, zoneinfo.end());
611 if (posix.name.isEmpty())
613 if (requireOffset && !posix.hasValidOffset())
618 if (begin >= zoneinfo.end())
621 if (PosixZone::parse(begin, zoneinfo.end()).name.isEmpty())
624 if (begin < zoneinfo.end())
628 if (parts.size() != 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty())
630 for (
int i = 1; i < 3; ++i) {
631 const auto tran = parts.at(i).split(
'/');
632 if (!calculatePosixDate(tran.at(0), 1972).isValid())
634 if (tran.size() > 1) {
635 const auto time = tran.at(1);
636 if (parsePosixTime(time.begin(), time.end()) == INT_MIN)
645 int startYear,
int endYear,
646 qint64 lastTranMSecs)
648 QList<QTimeZonePrivate::Data> result;
655 QList<QByteArray> parts = posixRule.split(
',');
657 PosixZone stdZone, dstZone;
659 const QByteArray &zoneinfo = parts.at(0);
660 const char *begin = zoneinfo.constBegin();
662 stdZone = PosixZone::parse(begin, zoneinfo.constEnd());
663 if (!stdZone.hasValidOffset()) {
665 }
else if (begin < zoneinfo.constEnd()) {
666 dstZone = PosixZone::parse(begin, zoneinfo.constEnd());
667 if (!dstZone.hasValidOffset()) {
669 dstZone.offset = stdZone.offset + (60 * 60);
675 if (parts.size() == 1 || !dstZone.hasValidOffset()) {
677 stdZone.name.isEmpty() ? QString::fromUtf8(parts.at(0)) : stdZone.name,
678 lastTranMSecs, stdZone.offset, stdZone.offset);
681 if (parts.size() < 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty())
685 const int twoOClock = 7200;
686 const auto dstParts = parts.at(1).split(
'/');
687 const QByteArray dstDateRule = dstParts.at(0);
688 const int dstTime = dstParts.size() < 2 ? twoOClock : parsePosixTransitionTime(dstParts.at(1));
691 const auto stdParts = parts.at(2).split(
'/');
692 const QByteArray stdDateRule = stdParts.at(0);
693 const int stdTime = stdParts.size() < 2 ? twoOClock : parsePosixTransitionTime(stdParts.at(1));
695 if (dstDateRule.isEmpty() || stdDateRule.isEmpty() || dstTime == INT_MIN || stdTime == INT_MIN)
699 const int minYear =
int(QDateTime::YearRange::First);
700 const int maxYear =
int(QDateTime::YearRange::Last);
701 startYear = qBound(minYear, startYear, maxYear);
702 endYear = qBound(minYear, endYear, maxYear);
703 Q_ASSERT(startYear <= endYear);
705 for (
int year = startYear; year <= endYear; ++year) {
712 QDateTime dst(calculatePosixDate(dstDateRule, year)
713 .startOfDay(QTimeZone::UTC).addSecs(dstTime));
714 auto saving = dstZone.dataAtOffset(dst.toMSecsSinceEpoch() - stdZone.offset * 1000,
717 QDateTime std(calculatePosixDate(stdDateRule, year)
718 .startOfDay(QTimeZone::UTC).addSecs(stdTime));
719 auto standard = stdZone.dataAt(std.toMSecsSinceEpoch() - dstZone.offset * 1000);
721 if (year == startYear) {
724 if (saving.atMSecsSinceEpoch < standard.atMSecsSinceEpoch) {
725 if (dst <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
726 && std >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
728 saving.atMSecsSinceEpoch = lastTranMSecs;
729 result.emplaceBack(
std::move(saving));
733 if (std <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
734 && dst >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
736 standard.atMSecsSinceEpoch = lastTranMSecs;
737 result.emplaceBack(
std::move(standard));
743 const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty();
744 const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty();
745 if (useStd && useDst) {
747 result.emplaceBack(
std::move(saving));
748 result.emplaceBack(
std::move(standard));
750 result.emplaceBack(
std::move(standard));
751 result.emplaceBack(
std::move(saving));
754 result.emplaceBack(
std::move(standard));
756 result.emplaceBack(
std::move(saving));
763QTzTimeZonePrivate::QTzTimeZonePrivate()
764 : QTzTimeZonePrivate(staticSystemTimeZoneId())
768QTzTimeZonePrivate::~QTzTimeZonePrivate()
772QTzTimeZonePrivate *QTzTimeZonePrivate::clone()
const
774 return new QTzTimeZonePrivate(*
this);
783 static QTzTimeZoneCacheEntry findEntry(
const QByteArray &ianaId);
784 QCache<QByteArray, QTzTimeZoneCacheEntry> m_cache;
790 QTzTimeZoneCacheEntry ret;
792 if (ianaId.isEmpty()) {
794 tzif.setFileName(QStringLiteral(
"/etc/localtime"));
795 if (!tzif.open(QIODevice::ReadOnly))
797 }
else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) {
799 auto check = validatePosixRule(ianaId);
801 ret.m_hasDst = check.hasDst;
802 ret.m_posixRule = ianaId;
811 QByteArray posixRule;
813 if (!ok || ds.status() != QDataStream::Ok)
816 if (ds.status() != QDataStream::Ok)
818 QList<
QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt);
819 if (ds.status() != QDataStream::Ok)
821 QMap<
int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList);
822 if (ds.status() != QDataStream::Ok)
824 parseTzLeapSeconds(ds, hdr.tzh_leapcnt,
false);
825 if (ds.status() != QDataStream::Ok)
827 typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt);
828 if (ds.status() != QDataStream::Ok)
835 if (!ok || ds.status() != QDataStream::Ok)
837 tranList = parseTzTransitions(ds, hdr2.tzh_timecnt,
true);
838 if (ds.status() != QDataStream::Ok)
840 typeList = parseTzTypes(ds, hdr2.tzh_typecnt);
841 if (ds.status() != QDataStream::Ok)
843 abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList);
844 if (ds.status() != QDataStream::Ok)
846 parseTzLeapSeconds(ds, hdr2.tzh_leapcnt,
true);
847 if (ds.status() != QDataStream::Ok)
849 typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt);
850 if (ds.status() != QDataStream::Ok)
852 posixRule = parseTzPosixRule(ds);
853 if (ds.status() != QDataStream::Ok)
858 if (!posixRule.isEmpty()) {
859 auto check = validatePosixRule(posixRule);
862 ret.m_posixRule = posixRule;
863 ret.m_hasDst = check.hasDst;
867 const int size = abbrevMap.size();
868 ret.m_abbreviations.clear();
869 ret.m_abbreviations.reserve(size);
870 QList<
int> abbrindList;
871 abbrindList.reserve(size);
872 for (
auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) {
873 ret.m_abbreviations.append(it.value());
874 abbrindList.append(it.key());
878 for (
int i = 0; i < typeList.size(); ++i)
879 typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind);
883 ret.m_preZoneRule = { typeList.at(0).tz_gmtoff, 0, typeList.at(0).tz_abbrind };
887 int utcOffset = ret.m_preZoneRule.stdOffset;
888 for (
const QTzTransition &tran : std::as_const(tranList)) {
889 if (!typeList.at(tran.tz_typeind).tz_isdst) {
890 utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff;
896 const int tranCount = tranList.size();
897 ret.m_tranTimes.reserve(tranCount);
899 int lastDstOff = 3600;
900 for (
int i = 0; i < tranCount; i++) {
902 QTzTransitionTime tran;
903 QTzTransitionRule rule;
904 const QTzType tz_type = typeList.at(tz_tran.tz_typeind);
909 }
else if (Q_UNLIKELY(tz_type
.tz_gmtoff != utcOffset + lastDstOff)) {
911
912
913
914
915
917 const int inferStd = tz_type
.tz_gmtoff - lastDstOff;
918 for (
int j = i + 1; j < tranCount; j++) {
919 const QTzType new_type = typeList.at(tranList.at(j).tz_typeind);
922 if (newUtc == utcOffset) {
925 }
else if (newUtc == inferStd) {
928 }
else if (tz_type
.tz_gmtoff - 3600 == utcOffset) {
930 }
else if (tz_type
.tz_gmtoff - 3600 == newUtc) {
936 || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))
938 && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) {
946 rule.stdOffset = utcOffset;
947 rule.dstOffset = tz_type
.tz_gmtoff - utcOffset;
948 rule.abbreviationIndex = tz_type.tz_abbrind;
951 int ruleIndex = ret.m_tranRules.indexOf(rule);
952 if (ruleIndex == -1) {
953 if (rule.dstOffset != 0)
955 tran.ruleIndex = ret.m_tranRules.size();
956 ret.m_tranRules.append(rule);
958 tran.ruleIndex = ruleIndex;
961 tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000;
962 ret.m_tranTimes.append(tran);
970 QMutexLocker locker(&m_mutex);
973 QTzTimeZoneCacheEntry *obj = m_cache.object(ianaId);
981 QTzTimeZoneCacheEntry ret = findEntry(ianaId);
982 auto ptr = std::make_unique<QTzTimeZoneCacheEntry>(ret);
985 m_cache.insert(ianaId, ptr.release());
992QTzTimeZonePrivate::QTzTimeZonePrivate(
const QByteArray &ianaId)
994 if (!isTimeZoneIdAvailable(ianaId))
996 static QTzTimeZoneCache tzCache;
997 auto entry = tzCache.fetchEntry(ianaId);
998 if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty())
1001 cached_data = std::move(entry);
1004 if (m_id.isEmpty()) {
1008 m_id = abbreviation(QDateTime::currentMSecsSinceEpoch()).toUtf8();
1012QLocale::Territory QTzTimeZonePrivate::territory()
const
1014 return tzZones->value(m_id).territory;
1017QString QTzTimeZonePrivate::comment()
const
1019 return QString::fromUtf8(tzZones->value(m_id).comment);
1022QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1023 QTimeZone::NameType nameType,
1024 const QLocale &locale)
const
1027 if (nameType != QTimeZone::LongName && isDataLocale(locale)) {
1028 Data tran = data(timeType);
1029 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
1030 if (nameType == QTimeZone::ShortName)
1031 return tran.abbreviation;
1033 if (locale.language() == QLocale::C)
1034 return isoOffsetFormat(tran.offsetFromUtc);
1038 return QTimeZonePrivate::displayName(timeType, nameType, locale);
1041QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
1043 return data(atMSecsSinceEpoch).abbreviation;
1046int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
1048 const Data tran = data(atMSecsSinceEpoch);
1049 return tran.offsetFromUtc;
1052int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
1054 return data(atMSecsSinceEpoch).standardTimeOffset;
1057int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
1059 return data(atMSecsSinceEpoch).daylightTimeOffset;
1062bool QTzTimeZonePrivate::hasDaylightTime()
const
1064 return cached_data.m_hasDst;
1067bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
1069 return (daylightTimeOffset(atMSecsSinceEpoch) != 0);
1072QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran)
const
1074 return dataFromRule(cached_data.m_tranRules.at(tran.ruleIndex), tran.atMSecsSinceEpoch);
1077QTimeZonePrivate::Data QTzTimeZonePrivate::dataFromRule(QTzTransitionRule rule,
1078 qint64 msecsSinceEpoch)
const
1080 return Data(QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)),
1081 msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset);
1084QList<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear)
const
1086 const int year = QDateTime::fromMSecsSinceEpoch(msNear, QTimeZone::UTC).date().year();
1088 qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch;
1089 return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime);
1092QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
1096 if (!cached_data.m_posixRule.isEmpty()
1097 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
1098 QList<Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
1099 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1100 [forMSecsSinceEpoch] (
const Data &at) {
1101 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1104 if (it > posixTrans.cbegin() || (tranCache().isEmpty() && it < posixTrans.cend())) {
1105 Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
1106 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1110 if (tranCache().isEmpty())
1114 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1115 [forMSecsSinceEpoch] (QTzTransitionTime at) {
1116 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1118 if (last == tranCache().cbegin())
1119 return dataFromRule(cached_data.m_preZoneRule, forMSecsSinceEpoch);
1122 return dataFromRule(cached_data.m_tranRules.at(last->ruleIndex), forMSecsSinceEpoch);
1130QTimeZonePrivate::Data QTzTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
1133 const auto validMatch = [timeType](
const Data &tran) {
1134 return tran.atMSecsSinceEpoch != invalidMSecs()
1135 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
1139 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
1140 Data tran = data(currentMSecs);
1141 if (validMatch(tran))
1145 tran = nextTransition(currentMSecs);
1146 if (validMatch(tran))
1152 tran = previousTransition(currentMSecs + 1);
1153 if (tran.atMSecsSinceEpoch != invalidMSecs())
1154 tran = previousTransition(tran.atMSecsSinceEpoch);
1155 if (validMatch(tran))
1161 const auto untilNow = [currentMSecs](QTzTransitionTime at) {
1162 return at.atMSecsSinceEpoch <= currentMSecs;
1164 auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), untilNow);
1167 while (it != tranCache().cbegin()) {
1169 tran = dataForTzTransition(*it);
1170 if ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0))
1177bool QTzTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
1180 return locale.language() == QLocale::C || locale.language() == QLocale::English;
1183bool QTzTimeZonePrivate::hasTransitions()
const
1188QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
1192 if (!cached_data.m_posixRule.isEmpty()
1193 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
1194 QList<Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
1195 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1196 [afterMSecsSinceEpoch] (
const Data &at) {
1197 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1200 return it == posixTrans.cend() ? Data{} : *it;
1204 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1205 [afterMSecsSinceEpoch] (QTzTransitionTime at) {
1206 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1208 return last != tranCache().cend() ? dataForTzTransition(*last) : Data{};
1211QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
1215 if (!cached_data.m_posixRule.isEmpty()
1216 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
1217 QList<Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
1218 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1219 [beforeMSecsSinceEpoch] (
const Data &at) {
1220 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1222 if (it > posixTrans.cbegin())
1225 return tranCache().isEmpty() ? Data{} : dataForTzTransition(tranCache().last());
1229 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1230 [beforeMSecsSinceEpoch] (QTzTransitionTime at) {
1231 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1233 return last > tranCache().cbegin() ? dataForTzTransition(*--last) : Data{};
1236bool QTzTimeZonePrivate::isTimeZoneIdAvailable(
const QByteArray &ianaId)
const
1242 return tzZones->contains(ianaId) || validatePosixRule(ianaId,
true).isValid;
1245QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds()
const
1247 QList<QByteArray> result = tzZones->keys();
1248 std::sort(result.begin(), result.end());
1252QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
1254 QList<QByteArray> result;
1255 for (
auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) {
1256 if (it.value().territory == territory)
1259 std::sort(result.begin(), result.end());
1264 const auto unWantedZone = [territory](QByteArrayView id) {
1266 auto it = tzZones->constFind(id);
1267 return it == tzZones->end() || it->territory == territory;
1269 QList<QByteArrayView> cldrViews = matchingTimeZoneIds(territory);
1270 std::sort(cldrViews.begin(), cldrViews.end());
1271 const auto uniqueEnd = std::unique(cldrViews.begin(), cldrViews.end());
1272 const auto prunedEnd = std::remove_if(cldrViews.begin(), uniqueEnd, unWantedZone);
1273 const auto cldrSize = std::distance(cldrViews.begin(), prunedEnd);
1275 QList<QByteArray> cldrList;
1276 cldrList.reserve(cldrSize);
1277 for (
auto it = cldrViews.begin(); it != prunedEnd; ++it)
1278 cldrList.emplace_back(it->toByteArray());
1279 QList<QByteArray> joined;
1280 joined.reserve(result.size() + cldrSize);
1281 std::set_union(result.begin(), result.end(), cldrList.begin(), cldrList.end(),
1282 std::back_inserter(joined));
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312 const StatIdent local = identify(
"/etc/localtime");
1313 const StatIdent tz = identify(
"/etc/TZ");
1314 const StatIdent timezone = identify(
"/etc/timezone");
1315 if (!m_name.isEmpty() && m_last.isValid()
1316 && (m_last == local || m_last == tz || m_last == timezone)) {
1320 m_name = etcLocalTime();
1321 if (!m_name.isEmpty()) {
1327 m_name = etcContent(QStringLiteral(
"/etc/TZ"));
1328 if (!m_name.isEmpty()) {
1334 m_name = etcContent(QStringLiteral(
"/etc/timezone"));
1335 m_last = m_name.isEmpty() ? StatIdent() : timezone;
1343 static constexpr unsigned long bad = ~0ul;
1344 unsigned long m_dev, m_ino;
1345 constexpr StatIdent() : m_dev(bad), m_ino(bad) {}
1346 StatIdent(
const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {}
1347 bool isValid() {
return m_dev != bad || m_ino != bad; }
1348 friend constexpr bool operator==(StatIdent lhs, StatIdent rhs)
1349 {
return lhs.m_dev == rhs.m_dev && lhs.m_ino == rhs.m_ino; }
1353 static StatIdent identify(
const char *path)
1356 return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data);
1359 static QByteArray etcLocalTime()
1363 const QString tzdir = qEnvironmentVariable(
"TZDIR");
1364 constexpr auto zoneinfo =
"/zoneinfo/"_L1;
1365 QString path = QStringLiteral(
"/etc/localtime");
1366 long iteration = getSymloopMax();
1371 path = QFile::symLinkTarget(path);
1373 int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir);
1375 const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8();
1376 return tail.startsWith(u'/') ? tail.sliced(1) : tail;
1378 index = path.indexOf(zoneinfo);
1380 return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8();
1381 }
while (!path.isEmpty() && --iteration > 0);
1383 return QByteArray();
1386 static QByteArray etcContent(
const QString &path)
1389 if (zone.open(QIODevice::ReadOnly))
1390 return zone.readAll().trimmed();
1392 return QByteArray();
1396 static long getSymloopMax()
1403 long result = sysconf(_SC_SYMLOOP_MAX);
1420QByteArray QTzTimeZonePrivate::systemTimeZoneId()
const
1422 return staticSystemTimeZoneId();
1425QByteArray QTzTimeZonePrivate::staticSystemTimeZoneId()
1428 QByteArray ianaId = qgetenv(
"TZ");
1434 if (ianaId ==
":/etc/localtime")
1436 else if (ianaId.startsWith(
':'))
1437 ianaId = ianaId.sliced(1);
1439 if (ianaId.isEmpty()) {
1440 Q_CONSTINIT
thread_local static ZoneNameReader reader;
1441 ianaId = reader.name();
\inmodule QtCore\reentrant
QTzTimeZoneCacheEntry fetchEntry(const QByteArray &ianaId)
static int parsePosixTime(const char *begin, const char *end)
static QMap< int, QByteArray > parseTzAbbreviations(QDataStream &ds, int tzh_charcnt, const QList< QTzType > &types)
static QTzTimeZoneHash loadTzTimeZones()
Q_GLOBAL_STATIC(const QTzTimeZoneHash, tzZones, loadTzTimeZones())
static int parsePosixTransitionTime(const QByteArray &timeRule)
QHash< QByteArray, QTzTimeZone > QTzTimeZoneHash
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 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)
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