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 QList<QByteArray> dateParts = dateRule.split(
'.');
419 if (dateParts.size() > 2) {
420 Q_ASSERT(!dateParts.at(0).isEmpty());
421 int month = QByteArrayView{ dateParts.at(0) }.sliced(1).toInt(&ok);
422 int week = ok ? dateParts.at(1).toInt(&ok) : 0;
423 int dow = ok ? dateParts.at(2).toInt(&ok) : 0;
425 return calculateDowDate(year, month, dow, week);
427 }
else if (dateRule.at(0) ==
'J') {
430 int doy = QByteArrayView{ dateRule }.sliced(1).toInt(&ok);
431 if (ok && doy > 0 && doy < 366) {
435 if (!QDate::isLeapYear(year) || doy < 60)
437 return QDate(year, 1, 1).addDays(doy);
441 int doy = dateRule.toInt(&ok);
442 if (ok && doy >= 0 && doy < 366)
443 return QDate(year, 1, 1).addDays(doy);
452 int hour, min = 0, sec = 0;
454 const int maxHour = 137;
455 auto r = qstrntoll(begin, end - begin, 10);
457 if (!r.ok() || hour < -maxHour || hour > maxHour || r.used > 2)
460 if (begin < end && *begin ==
':') {
463 r = qstrntoll(begin, end - begin, 10);
465 if (!r.ok() || min < 0 || min > 59 || r.used > 2)
469 if (begin < end && *begin ==
':') {
472 r = qstrntoll(begin, end - begin, 10);
474 if (!r.ok() || sec < 0 || sec > 59 || r.used > 2)
484 return (hour * 60 + min) * 60 + sec;
489 return parsePosixTime(timeRule.constBegin(), timeRule.constEnd());
499 }
else if (*begin ==
'-') {
505 if (value == INT_MIN)
507 return negate ? -value : value;
513 return ch >=
'a' && ch <=
'z';
521 InvalidOffset = INT_MIN,
525 int offset = InvalidOffset;
526 bool hasValidOffset()
const noexcept {
return offset != InvalidOffset; }
527 QTimeZonePrivate::Data dataAt(qint64 when)
529 Q_ASSERT(hasValidOffset());
530 return QTimeZonePrivate::Data(name, when, offset, offset);
532 QTimeZonePrivate::Data dataAtOffset(qint64 when,
int standard)
534 Q_ASSERT(hasValidOffset());
535 return QTimeZonePrivate::Data(name, when, offset, standard);
538 static PosixZone parse(
const char *&pos,
const char *end);
546PosixZone PosixZone::parse(
const char *&pos,
const char *end)
548 static const char offsetChars[] =
"0123456789:";
550 const char *nameBegin = pos;
557 while (nameEnd < end && *nameEnd !=
'>') {
568 if (nameEnd - nameBegin < 3)
572 const char *zoneBegin = pos;
573 const char *zoneEnd = pos;
574 if (zoneEnd < end && (zoneEnd[0] ==
'+' || zoneEnd[0] ==
'-'))
576 while (zoneEnd < end) {
577 if (strchr(offsetChars,
char(*zoneEnd)) ==
nullptr)
582 QString name = QString::fromUtf8(nameBegin, nameEnd - nameBegin);
583 const int offset = zoneEnd > zoneBegin ?
parsePosixOffset(zoneBegin
, zoneEnd
) : InvalidOffset;
587 if (offset != 0 && (name ==
"UTC"_L1 || name ==
"GMT"_L1))
589 return {
std::move(name), offset};
593
594
595
596
602 const auto parts = posixRule.split(
',');
603 const struct {
bool isValid, hasDst; } fail{
false,
false}, good{
true, parts.size() > 1};
604 const QByteArray &zoneinfo = parts.at(0).trimmed();
605 if (zoneinfo.isEmpty())
608 const char *begin = zoneinfo.begin();
611 const auto posix = PosixZone::parse(begin, zoneinfo.end());
612 if (posix.name.isEmpty())
614 if (requireOffset && !posix.hasValidOffset())
619 if (begin >= zoneinfo.end())
622 if (PosixZone::parse(begin, zoneinfo.end()).name.isEmpty())
625 if (begin < zoneinfo.end())
629 if (parts.size() != 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty())
631 for (
int i = 1; i < 3; ++i) {
632 const auto tran = parts.at(i).split(
'/');
633 if (!calculatePosixDate(tran.at(0), 1972).isValid())
635 if (tran.size() > 1) {
636 const auto time = tran.at(1);
637 if (parsePosixTime(time.begin(), time.end()) == INT_MIN)
646 int startYear,
int endYear,
647 qint64 lastTranMSecs)
649 QList<QTimeZonePrivate::Data> result;
656 QList<QByteArray> parts = posixRule.split(
',');
658 PosixZone stdZone, dstZone;
660 const QByteArray &zoneinfo = parts.at(0).trimmed();
661 const char *begin = zoneinfo.constBegin();
663 stdZone = PosixZone::parse(begin, zoneinfo.constEnd());
664 if (!stdZone.hasValidOffset()) {
666 }
else if (begin < zoneinfo.constEnd()) {
667 dstZone = PosixZone::parse(begin, zoneinfo.constEnd());
668 if (!dstZone.hasValidOffset()) {
670 dstZone.offset = stdZone.offset + (60 * 60);
676 if (parts.size() == 1 || !dstZone.hasValidOffset()) {
678 stdZone.name.isEmpty() ? QString::fromUtf8(parts.at(0)) : stdZone.name,
679 lastTranMSecs, stdZone.offset, stdZone.offset);
682 if (parts.size() < 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty())
686 const int twoOClock = 7200;
687 const auto dstParts = parts.at(1).split(
'/');
688 const QByteArray dstDateRule = dstParts.at(0);
689 const int dstTime = dstParts.size() < 2 ? twoOClock : parsePosixTransitionTime(dstParts.at(1));
692 const auto stdParts = parts.at(2).split(
'/');
693 const QByteArray stdDateRule = stdParts.at(0);
694 const int stdTime = stdParts.size() < 2 ? twoOClock : parsePosixTransitionTime(stdParts.at(1));
696 if (dstDateRule.isEmpty() || stdDateRule.isEmpty() || dstTime == INT_MIN || stdTime == INT_MIN)
700 const int minYear =
int(QDateTime::YearRange::First);
701 const int maxYear =
int(QDateTime::YearRange::Last);
702 startYear = qBound(minYear, startYear, maxYear);
703 endYear = qBound(minYear, endYear, maxYear);
704 Q_ASSERT(startYear <= endYear);
706 for (
int year = startYear; year <= endYear; ++year) {
713 QDateTime dst(calculatePosixDate(dstDateRule, year)
714 .startOfDay(QTimeZone::UTC).addSecs(dstTime));
715 auto saving = dstZone.dataAtOffset(dst.toMSecsSinceEpoch() - stdZone.offset * 1000,
718 QDateTime std(calculatePosixDate(stdDateRule, year)
719 .startOfDay(QTimeZone::UTC).addSecs(stdTime));
720 auto standard = stdZone.dataAt(std.toMSecsSinceEpoch() - dstZone.offset * 1000);
722 if (year == startYear) {
725 if (saving.atMSecsSinceEpoch < standard.atMSecsSinceEpoch) {
726 if (dst <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
727 && std >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
729 saving.atMSecsSinceEpoch = lastTranMSecs;
730 result.emplaceBack(
std::move(saving));
734 if (std <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC)
735 && dst >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) {
737 standard.atMSecsSinceEpoch = lastTranMSecs;
738 result.emplaceBack(
std::move(standard));
744 const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty();
745 const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty();
746 if (useStd && useDst) {
748 result.emplaceBack(
std::move(saving));
749 result.emplaceBack(
std::move(standard));
751 result.emplaceBack(
std::move(standard));
752 result.emplaceBack(
std::move(saving));
755 result.emplaceBack(
std::move(standard));
757 result.emplaceBack(
std::move(saving));
764QTzTimeZonePrivate::QTzTimeZonePrivate()
765 : QTzTimeZonePrivate(staticSystemTimeZoneId())
769QTzTimeZonePrivate::~QTzTimeZonePrivate()
773QTzTimeZonePrivate *QTzTimeZonePrivate::clone()
const
775 return new QTzTimeZonePrivate(*
this);
784 static QTzTimeZoneCacheEntry findEntry(
const QByteArray &ianaId);
785 QCache<QByteArray, QTzTimeZoneCacheEntry> m_cache;
791 QTzTimeZoneCacheEntry ret;
793 if (ianaId.isEmpty()) {
795 tzif.setFileName(QStringLiteral(
"/etc/localtime"));
796 if (!tzif.open(QIODevice::ReadOnly))
798 }
else if (!openZoneInfo(QString::fromLocal8Bit(ianaId), &tzif)) {
800 auto check = validatePosixRule(ianaId);
802 ret.m_hasDst = check.hasDst;
803 ret.m_posixRule = ianaId;
812 QByteArray posixRule;
814 if (!ok || ds.status() != QDataStream::Ok)
817 if (ds.status() != QDataStream::Ok)
819 QList<
QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt);
820 if (ds.status() != QDataStream::Ok)
822 QMap<
int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList);
823 if (ds.status() != QDataStream::Ok)
825 parseTzLeapSeconds(ds, hdr.tzh_leapcnt,
false);
826 if (ds.status() != QDataStream::Ok)
828 typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt);
829 if (ds.status() != QDataStream::Ok)
836 if (!ok || ds.status() != QDataStream::Ok)
838 tranList = parseTzTransitions(ds, hdr2.tzh_timecnt,
true);
839 if (ds.status() != QDataStream::Ok)
841 typeList = parseTzTypes(ds, hdr2.tzh_typecnt);
842 if (ds.status() != QDataStream::Ok)
844 abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList);
845 if (ds.status() != QDataStream::Ok)
847 parseTzLeapSeconds(ds, hdr2.tzh_leapcnt,
true);
848 if (ds.status() != QDataStream::Ok)
850 typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt);
851 if (ds.status() != QDataStream::Ok)
853 posixRule = parseTzPosixRule(ds);
854 if (ds.status() != QDataStream::Ok)
859 if (!posixRule.isEmpty()) {
860 auto check = validatePosixRule(posixRule);
863 ret.m_posixRule = posixRule;
864 ret.m_hasDst = check.hasDst;
868 const int size = abbrevMap.size();
869 ret.m_abbreviations.clear();
870 ret.m_abbreviations.reserve(size);
871 QList<
int> abbrindList;
872 abbrindList.reserve(size);
873 for (
auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) {
874 ret.m_abbreviations.append(it.value());
875 abbrindList.append(it.key());
879 for (
int i = 0; i < typeList.size(); ++i)
880 typeList[i].tz_abbrind = abbrindList.indexOf(typeList.at(i).tz_abbrind);
884 ret.m_preZoneRule = { typeList.at(0).tz_gmtoff, 0, typeList.at(0).tz_abbrind };
888 int utcOffset = ret.m_preZoneRule.stdOffset;
889 for (
const QTzTransition &tran : std::as_const(tranList)) {
890 if (!typeList.at(tran.tz_typeind).tz_isdst) {
891 utcOffset = typeList.at(tran.tz_typeind).tz_gmtoff;
897 const int tranCount = tranList.size();
898 ret.m_tranTimes.reserve(tranCount);
900 int lastDstOff = 3600;
901 for (
int i = 0; i < tranCount; i++) {
903 QTzTransitionTime tran;
904 QTzTransitionRule rule;
905 const QTzType tz_type = typeList.at(tz_tran.tz_typeind);
910 }
else if (Q_UNLIKELY(tz_type
.tz_gmtoff != utcOffset + lastDstOff)) {
912
913
914
915
916
918 const int inferStd = tz_type
.tz_gmtoff - lastDstOff;
919 for (
int j = i + 1; j < tranCount; j++) {
920 const QTzType new_type = typeList.at(tranList.at(j).tz_typeind);
923 if (newUtc == utcOffset) {
926 }
else if (newUtc == inferStd) {
929 }
else if (tz_type
.tz_gmtoff - 3600 == utcOffset) {
931 }
else if (tz_type
.tz_gmtoff - 3600 == newUtc) {
937 || qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))
939 && qAbs(newUtc - inferStd) < qAbs(utcOffset - inferStd))) {
947 rule.stdOffset = utcOffset;
948 rule.dstOffset = tz_type
.tz_gmtoff - utcOffset;
949 rule.abbreviationIndex = tz_type.tz_abbrind;
952 int ruleIndex = ret.m_tranRules.indexOf(rule);
953 if (ruleIndex == -1) {
954 if (rule.dstOffset != 0)
956 tran.ruleIndex = ret.m_tranRules.size();
957 ret.m_tranRules.append(rule);
959 tran.ruleIndex = ruleIndex;
962 tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000;
963 ret.m_tranTimes.append(tran);
971 QMutexLocker locker(&m_mutex);
974 QTzTimeZoneCacheEntry *obj = m_cache.object(ianaId);
982 QTzTimeZoneCacheEntry ret = findEntry(ianaId);
983 auto ptr = std::make_unique<QTzTimeZoneCacheEntry>(ret);
986 m_cache.insert(ianaId, ptr.release());
993QTzTimeZonePrivate::QTzTimeZonePrivate(
const QByteArray &ianaId)
995 if (!isTimeZoneIdAvailable(ianaId))
997 static QTzTimeZoneCache tzCache;
998 auto entry = tzCache.fetchEntry(ianaId);
999 if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty())
1002 cached_data = std::move(entry);
1005 if (m_id.isEmpty()) {
1009 m_id = abbreviation(QDateTime::currentMSecsSinceEpoch()).toUtf8();
1013QLocale::Territory QTzTimeZonePrivate::territory()
const
1015 return tzZones->value(m_id).territory;
1018QString QTzTimeZonePrivate::comment()
const
1020 return QString::fromUtf8(tzZones->value(m_id).comment);
1023QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
1024 QTimeZone::NameType nameType,
1025 const QLocale &locale)
const
1028 if (nameType != QTimeZone::LongName && isDataLocale(locale)) {
1029 Data tran = data(timeType);
1030 if (tran.atMSecsSinceEpoch != invalidMSecs()) {
1031 if (nameType == QTimeZone::ShortName)
1032 return tran.abbreviation;
1034 if (isAnglicLocale(locale))
1035 return isoOffsetFormat(tran.offsetFromUtc);
1039 return QTimeZonePrivate::displayName(timeType, nameType, locale);
1042QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
1044 return data(atMSecsSinceEpoch).abbreviation;
1047int QTzTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
1049 const Data tran = data(atMSecsSinceEpoch);
1050 return tran.offsetFromUtc;
1053int QTzTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
1055 return data(atMSecsSinceEpoch).standardTimeOffset;
1058int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
1060 return data(atMSecsSinceEpoch).daylightTimeOffset;
1063bool QTzTimeZonePrivate::hasDaylightTime()
const
1065 return cached_data.m_hasDst;
1068bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
1070 return (daylightTimeOffset(atMSecsSinceEpoch) != 0);
1073QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran)
const
1075 return dataFromRule(cached_data.m_tranRules.at(tran.ruleIndex), tran.atMSecsSinceEpoch);
1078QTimeZonePrivate::Data QTzTimeZonePrivate::dataFromRule(QTzTransitionRule rule,
1079 qint64 msecsSinceEpoch)
const
1081 return Data(QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)),
1082 msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset);
1085QList<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear)
const
1087 const int year = QDateTime::fromMSecsSinceEpoch(msNear, QTimeZone::UTC).date().year();
1089 qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch;
1090 return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime);
1093QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
1097 if (!cached_data.m_posixRule.isEmpty()
1098 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) {
1099 QList<Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch);
1100 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1101 [forMSecsSinceEpoch] (
const Data &at) {
1102 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1105 if (it > posixTrans.cbegin() || (tranCache().isEmpty() && it < posixTrans.cend())) {
1106 Data data = *(it > posixTrans.cbegin() ? it - 1 : it);
1107 data.atMSecsSinceEpoch = forMSecsSinceEpoch;
1111 if (tranCache().isEmpty())
1115 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1116 [forMSecsSinceEpoch] (QTzTransitionTime at) {
1117 return at.atMSecsSinceEpoch <= forMSecsSinceEpoch;
1119 if (last == tranCache().cbegin())
1120 return dataFromRule(cached_data.m_preZoneRule, forMSecsSinceEpoch);
1123 return dataFromRule(cached_data.m_tranRules.at(last->ruleIndex), forMSecsSinceEpoch);
1131QTimeZonePrivate::Data QTzTimeZonePrivate::data(QTimeZone::TimeType timeType)
const
1134 const auto validMatch = [timeType](
const Data &tran) {
1135 return tran.atMSecsSinceEpoch != invalidMSecs()
1136 && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0));
1140 const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch();
1141 Data tran = data(currentMSecs);
1142 if (validMatch(tran))
1146 tran = nextTransition(currentMSecs);
1147 if (validMatch(tran))
1153 tran = previousTransition(currentMSecs + 1);
1154 if (tran.atMSecsSinceEpoch != invalidMSecs())
1155 tran = previousTransition(tran.atMSecsSinceEpoch);
1156 if (validMatch(tran))
1162 const auto untilNow = [currentMSecs](QTzTransitionTime at) {
1163 return at.atMSecsSinceEpoch <= currentMSecs;
1165 auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), untilNow);
1168 while (it != tranCache().cbegin()) {
1170 tran = dataForTzTransition(*it);
1171 if ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0))
1178bool QTzTimeZonePrivate::isDataLocale(
const QLocale &locale)
const
1181 return isAnglicLocale(locale);
1184bool QTzTimeZonePrivate::hasTransitions()
const
1189QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
1193 if (!cached_data.m_posixRule.isEmpty()
1194 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) {
1195 QList<Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch);
1196 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1197 [afterMSecsSinceEpoch] (
const Data &at) {
1198 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1201 return it == posixTrans.cend() ? Data{} : *it;
1205 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1206 [afterMSecsSinceEpoch] (QTzTransitionTime at) {
1207 return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch;
1209 return last != tranCache().cend() ? dataForTzTransition(*last) : Data{};
1212QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
1216 if (!cached_data.m_posixRule.isEmpty()
1217 && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) {
1218 QList<Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch);
1219 auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(),
1220 [beforeMSecsSinceEpoch] (
const Data &at) {
1221 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1223 if (it > posixTrans.cbegin())
1226 return tranCache().isEmpty() ? Data{} : dataForTzTransition(tranCache().last());
1230 auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(),
1231 [beforeMSecsSinceEpoch] (QTzTransitionTime at) {
1232 return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch;
1234 return last > tranCache().cbegin() ? dataForTzTransition(*--last) : Data{};
1237bool QTzTimeZonePrivate::isTimeZoneIdAvailable(
const QByteArray &ianaId)
const
1243 return tzZones->contains(ianaId) || validatePosixRule(ianaId,
true).isValid;
1246QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds()
const
1248 QList<QByteArray> result = tzZones->keys();
1249 std::sort(result.begin(), result.end());
1253QList<QByteArray> QTzTimeZonePrivate::availableTimeZoneIds(QLocale::Territory territory)
const
1255 QList<QByteArray> result;
1256 for (
auto it = tzZones->cbegin(), end = tzZones->cend(); it != end; ++it) {
1257 if (it.value().territory == territory)
1260 std::sort(result.begin(), result.end());
1265 const auto unWantedZone = [territory](QByteArrayView id) {
1267 auto it = tzZones->constFind(id);
1268 return it == tzZones->end() || it->territory == territory;
1270 QList<QByteArrayView> cldrViews = matchingTimeZoneIds(territory);
1271 std::sort(cldrViews.begin(), cldrViews.end());
1272 const auto uniqueEnd = std::unique(cldrViews.begin(), cldrViews.end());
1273 const auto prunedEnd = std::remove_if(cldrViews.begin(), uniqueEnd, unWantedZone);
1274 const auto cldrSize = std::distance(cldrViews.begin(), prunedEnd);
1276 QList<QByteArray> cldrList;
1277 cldrList.reserve(cldrSize);
1278 for (
auto it = cldrViews.begin(); it != prunedEnd; ++it)
1279 cldrList.emplace_back(it->toByteArray());
1280 QList<QByteArray> joined;
1281 joined.reserve(result.size() + cldrSize);
1282 std::set_union(result.begin(), result.end(), cldrList.begin(), cldrList.end(),
1283 std::back_inserter(joined));
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313 const StatIdent local = identify(
"/etc/localtime");
1314 const StatIdent tz = identify(
"/etc/TZ");
1315 const StatIdent timezone = identify(
"/etc/timezone");
1316 if (!m_name.isEmpty() && m_last.isValid()
1317 && (m_last == local || m_last == tz || m_last == timezone)) {
1321 m_name = etcLocalTime();
1322 if (!m_name.isEmpty()) {
1328 m_name = etcContent(QStringLiteral(
"/etc/TZ"));
1329 if (!m_name.isEmpty()) {
1335 m_name = etcContent(QStringLiteral(
"/etc/timezone"));
1336 m_last = m_name.isEmpty() ? StatIdent() : timezone;
1344 static constexpr unsigned long bad = ~0ul;
1345 unsigned long m_dev, m_ino;
1346 constexpr StatIdent() : m_dev(bad), m_ino(bad) {}
1347 StatIdent(
const QT_STATBUF &data) : m_dev(data.st_dev), m_ino(data.st_ino) {}
1348 bool isValid() {
return m_dev != bad || m_ino != bad; }
1349 friend constexpr bool operator==(StatIdent lhs, StatIdent rhs)
1350 {
return lhs.m_dev == rhs.m_dev && lhs.m_ino == rhs.m_ino; }
1354 static StatIdent identify(
const char *path)
1357 return QT_STAT(path, &data) == -1 ? StatIdent() : StatIdent(data);
1360 static QByteArray etcLocalTime()
1364 const QString tzdir = qEnvironmentVariable(
"TZDIR");
1365 constexpr auto zoneinfo =
"/zoneinfo/"_L1;
1366 QString path = QStringLiteral(
"/etc/localtime");
1367 long iteration = getSymloopMax();
1372 path = QFile::symLinkTarget(path);
1374 int index = tzdir.isEmpty() ? -1 : path.indexOf(tzdir);
1376 const auto tail = QStringView{ path }.sliced(index + tzdir.size()).toUtf8();
1377 return tail.startsWith(u'/') ? tail.sliced(1) : tail;
1379 index = path.indexOf(zoneinfo);
1381 return QStringView{ path }.sliced(index + zoneinfo.size()).toUtf8();
1382 }
while (!path.isEmpty() && --iteration > 0);
1384 return QByteArray();
1387 static QByteArray etcContent(
const QString &path)
1390 if (zone.open(QIODevice::ReadOnly))
1391 return zone.readAll().trimmed();
1393 return QByteArray();
1397 static long getSymloopMax()
1404 long result = sysconf(_SC_SYMLOOP_MAX);
1421QByteArray QTzTimeZonePrivate::systemTimeZoneId()
const
1423 return staticSystemTimeZoneId();
1426QByteArray QTzTimeZonePrivate::staticSystemTimeZoneId()
1429 QByteArray ianaId = qgetenv(
"TZ");
1435 if (ianaId ==
":/etc/localtime")
1437 else if (ianaId.startsWith(
':'))
1438 ianaId = ianaId.sliced(1);
1440 if (ianaId.isEmpty()) {
1441 Q_CONSTINIT
thread_local static ZoneNameReader reader;
1442 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