10#include <private/qnumeric_p.h>
11#include <private/wcharhelpers_win_p.h>
15#include <private/qwinregistry_p.h>
21using namespace Qt::StringLiterals;
24
25
26
27
29#define MAX_KEY_LENGTH 255
39static const wchar_t tzRegPath[] = LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones)";
40static const wchar_t currTzRegPath[] = LR"(SYSTEM\CurrentControlSet\Control\TimeZoneInformation)";
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
86QDate msecsToDate(qint64 msecs)
88 qint64 jd = JULIAN_DAY_FOR_EPOCH;
90 if (msecs >= MSECS_PER_DAY || msecs <= -MSECS_PER_DAY) {
91 jd += msecs / MSECS_PER_DAY;
92 msecs %= MSECS_PER_DAY;
96 Q_ASSERT(msecs > -MSECS_PER_DAY);
100 return QDate::fromJulianDay(jd);
103bool equalSystemtime(
const SYSTEMTIME &t1,
const SYSTEMTIME &t2)
105 return (t1.wYear == t2.wYear
106 && t1.wMonth == t2.wMonth
107 && t1.wDay == t2.wDay
108 && t1.wDayOfWeek == t2.wDayOfWeek
109 && t1.wHour == t2.wHour
110 && t1.wMinute == t2.wMinute
111 && t1.wSecond == t2.wSecond
112 && t1.wMilliseconds == t2.wMilliseconds);
115bool equalTzi(
const TIME_ZONE_INFORMATION &tzi1,
const TIME_ZONE_INFORMATION &tzi2)
117 return(tzi1.Bias == tzi2.Bias
118 && tzi1.StandardBias == tzi2.StandardBias
119 && equalSystemtime(tzi1.StandardDate, tzi2.StandardDate)
120 && wcscmp(tzi1.StandardName, tzi2.StandardName) == 0
121 && tzi1.DaylightBias == tzi2.DaylightBias
122 && equalSystemtime(tzi1.DaylightDate, tzi2.DaylightDate)
123 && wcscmp(tzi1.DaylightName, tzi2.DaylightName) == 0);
126QWinTimeZonePrivate::QWinTransitionRule readRegistryRule(
const HKEY &key,
127 const wchar_t *value,
bool *ok)
130 QWinTimeZonePrivate::QWinTransitionRule rule;
132 DWORD tziSize =
sizeof(tzi);
133 if (RegQueryValueEx(key, value,
nullptr,
nullptr,
reinterpret_cast<BYTE *>(&tzi), &tziSize)
136 rule.standardTimeBias = tzi.Bias + tzi.StandardBias;
137 rule.daylightTimeBias = tzi.Bias + tzi.DaylightBias - rule.standardTimeBias;
138 rule.standardTimeRule = tzi.StandardDate;
139 rule.daylightTimeRule = tzi.DaylightDate;
145TIME_ZONE_INFORMATION getRegistryTzi(
const QByteArray &windowsId,
bool *ok)
148 TIME_ZONE_INFORMATION tzi;
149 REG_TZI_FORMAT regTzi;
150 DWORD regTziSize =
sizeof(regTzi);
151 const QString tziKeyPath = QString::fromWCharArray(tzRegPath) + u'\\'
152 + QString::fromUtf8(windowsId);
154 QWinRegistryKey key(HKEY_LOCAL_MACHINE, tziKeyPath);
156 DWORD size =
sizeof(tzi.DaylightName);
157 RegQueryValueEx(key, L"Dlt",
nullptr,
nullptr,
reinterpret_cast<LPBYTE>(tzi.DaylightName), &size);
159 size =
sizeof(tzi.StandardName);
160 RegQueryValueEx(key, L"Std",
nullptr,
nullptr,
reinterpret_cast<LPBYTE>(tzi.StandardName), &size);
162 if (RegQueryValueEx(key, L"TZI",
nullptr,
nullptr,
reinterpret_cast<BYTE *>(®Tzi), ®TziSize)
164 tzi.Bias = regTzi.Bias;
165 tzi.StandardBias = regTzi.StandardBias;
166 tzi.DaylightBias = regTzi.DaylightBias;
167 tzi.StandardDate = regTzi.StandardDate;
168 tzi.DaylightDate = regTzi.DaylightDate;
176bool isSameRule(
const QWinTimeZonePrivate::QWinTransitionRule &last,
177 const QWinTimeZonePrivate::QWinTransitionRule &rule)
184 return equalSystemtime(last.standardTimeRule, rule.standardTimeRule)
185 && equalSystemtime(last.daylightTimeRule, rule.daylightTimeRule)
186 && last.standardTimeBias == rule.standardTimeBias
187 && last.daylightTimeBias == rule.daylightTimeBias;
190QList<QByteArray> availableWindowsIds()
192 static const QList<QByteArray> cache = [] {
193 QList<QByteArray> list;
194 QWinRegistryKey key(HKEY_LOCAL_MACHINE, tzRegPath);
197 if (RegQueryInfoKey(key, 0, 0, 0, &idCount, 0, 0, 0, 0, 0, 0, 0) == ERROR_SUCCESS
199 for (DWORD i = 0; i < idCount; ++i) {
202 if (RegEnumKeyEx(key, i, buffer, &maxLen, 0, 0, 0, 0) == ERROR_SUCCESS)
203 list.append(QString::fromWCharArray(buffer).toUtf8());
212QByteArray windowsSystemZoneId()
215 const QString id = QWinRegistryKey(HKEY_LOCAL_MACHINE, currTzRegPath)
216 .stringValue(L"TimeZoneKeyName");
222 TIME_ZONE_INFORMATION sysTzi;
223 GetTimeZoneInformation(&sysTzi);
225 const auto winIds = availableWindowsIds();
226 for (
const QByteArray &winId : winIds) {
227 if (equalTzi(getRegistryTzi(winId, &ok), sysTzi))
232 return QTimeZonePrivate::utcQByteArray();
235QDate calculateTransitionLocalDate(
const SYSTEMTIME &rule,
int year)
238 if (rule.wMonth == 0)
246 return QDate(rule.wYear, rule.wMonth, rule.wDay);
249 const int dayOfWeek = rule.wDayOfWeek == 0 ? 7 : rule.wDayOfWeek;
250 QDate date(year, rule.wMonth, 1);
251 Q_ASSERT(date.isValid());
253 int adjust = dayOfWeek - date.dayOfWeek();
258 adjust += (rule.wDay < 1 ? 1 : rule.wDay > 4 ? 5 : rule.wDay) * 7;
259 date = date.addDays(adjust);
261 if (date.month() != rule.wMonth) {
262 Q_ASSERT(rule.wDay > 4);
265 date = date.addDays(-7);
266 Q_ASSERT(date.month() == rule.wMonth);
272inline bool timeToMSecs(QDate date, QTime time, qint64 *msecs)
275 qint64 daySinceEpoch = date.toJulianDay() - JULIAN_DAY_FOR_EPOCH;
276 qint64 msInDay = time.msecsSinceStartOfDay();
277 if (daySinceEpoch < 0 && msInDay > 0) {
281 msInDay -= MSECS_PER_DAY;
283 return qMulOverflow(daySinceEpoch, std::integral_constant<qint64, MSECS_PER_DAY>(), &dayms)
284 || qAddOverflow(dayms, msInDay, msecs);
287qint64 calculateTransitionForYear(
const SYSTEMTIME &rule,
int year,
int bias)
292 const QDate date = calculateTransitionLocalDate(rule, year);
293 const QTime time = QTime(rule.wHour, rule.wMinute, rule.wSecond);
295 if (date.isValid() && time.isValid() && !timeToMSecs(date, time, &msecs)) {
299 return bias && qAddOverflow(msecs, qint64(bias) * 60000, &msecs)
300 ? (bias < 0 ? QTimeZonePrivate::minMSecs() : QTimeZonePrivate::maxMSecs())
301 : qMax(QTimeZonePrivate::minMSecs(), msecs);
303 return QTimeZonePrivate::invalidMSecs();
307bool isAtStartOfYear(
const SYSTEMTIME &transition,
int year)
310
311
312
313
314
315
316
317
318 return transition.wMonth == 1 && transition.wDay == 1
319 && (
QDate(year, 1, 1).dayOfWeek() - transition.wDayOfWeek) % 7 == 0
320 && transition.wHour == 0 && transition.wMinute == 0 && transition.wSecond == 0;
323struct TransitionTimePair
329 bool fakesDst =
false;
331 TransitionTimePair(
const QWinTimeZonePrivate::QWinTransitionRule &rule,
332 int year,
int oldYearOffset)
334 : std(calculateTransitionForYear(rule.standardTimeRule, year,
335 rule.standardTimeBias + rule.daylightTimeBias)),
337 dst(calculateTransitionForYear(rule.daylightTimeRule, year, rule.standardTimeBias))
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384 if (rule.standardTimeBias + rule.daylightTimeBias == oldYearOffset
385 && isAtStartOfYear(rule.daylightTimeRule, year)) {
386 dst = QTimeZonePrivate::invalidMSecs();
389 if (rule.standardTimeBias == oldYearOffset
390 && isAtStartOfYear(rule.standardTimeRule, year)) {
391 Q_ASSERT_X(!fakesDst,
"TransitionTimePair",
392 "Year with (DST bias zero and) both transitions fake !");
393 std = QTimeZonePrivate::invalidMSecs();
398 bool startsInDst()
const
402 return std != QTimeZonePrivate::invalidMSecs()
403 && (std < dst || dst == QTimeZonePrivate::invalidMSecs());
410 bool beforeInitialDst(
int year, qint64 millis)
const
412 return !fakesDst && (year == FIRST_DST_YEAR ? millis < dst : year < FIRST_DST_YEAR);
415 QTimeZonePrivate::Data ruleToData(
const QWinTimeZonePrivate::QWinTransitionRule &rule,
416 const QWinTimeZonePrivate *tzp,
bool isDst)
const
418 const auto type = isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
419 auto time = isDst ? dst : std;
422 if (fakesDst && time == QTimeZonePrivate::invalidMSecs())
423 time = isDst ? std : dst;
424 return tzp->ruleToData(rule, time, type, fakesDst);
428int yearEndOffset(
const QWinTimeZonePrivate::QWinTransitionRule &rule,
int year)
431 int offset = rule.standardTimeBias;
435 TransitionTimePair pair(rule, year, offset);
436 if (pair.dst > pair.std)
437 offset += rule.daylightTimeBias;
441QLocale::Territory userTerritory()
443 const GEOID id = GetUserGeoID(GEOCLASS_NATION);
445 const int size = GetGeoInfo(id, GEO_ISO2, code, 3, 0);
446 return (size == 3) ? QLocalePrivate::codeToTerritory(QStringView(code, size))
447 : QLocale::AnyTerritory;
451int ruleIndexForYear(
const QList<QWinTimeZonePrivate::QWinTransitionRule> &rules,
int year)
453 if (rules.last().startYear <= year)
454 return rules.count() - 1;
456 if (rules.first().startYear > year)
460 int lo = 0, hi = rules.count();
463 while (lo + 1 < hi) {
464 const int mid = (lo + hi) / 2;
467 const int midYear = rules.at(mid).startYear;
470 else if (midYear < year)
481QWinTimeZonePrivate::QWinTimeZonePrivate()
488QWinTimeZonePrivate::QWinTimeZonePrivate(
const QByteArray &ianaId)
494QWinTimeZonePrivate::~QWinTimeZonePrivate()
498QWinTimeZonePrivate *QWinTimeZonePrivate::clone()
const
500 return new QWinTimeZonePrivate(*
this);
503void QWinTimeZonePrivate::init(
const QByteArray &ianaId)
505 if (ianaId.isEmpty()) {
506 m_windowsId = windowsSystemZoneId();
507 m_id = systemTimeZoneId();
509 m_windowsId = ianaIdToWindowsId(ianaId);
512 const auto initialYear = [](
const QWinTransitionRule &rule) {
517 return (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0
518 ? FIRST_DST_YEAR :
int(QDateTime::YearRange::First));
521 bool badMonth =
false;
522 if (!m_windowsId.isEmpty()) {
524 const QString baseKeyPath = QString::fromWCharArray(tzRegPath) + u'\\'
525 + QString::fromUtf8(m_windowsId);
526 QWinRegistryKey baseKey(HKEY_LOCAL_MACHINE, baseKeyPath);
527 if (baseKey.isValid()) {
529 m_displayName = baseKey.stringValue(L"Display");
530 m_standardName = baseKey.stringValue(L"Std");
531 m_daylightName = baseKey.stringValue(L"Dlt");
533 const QString dynamicKeyPath = baseKeyPath +
"\\Dynamic DST"_L1;
534 QWinRegistryKey dynamicKey(HKEY_LOCAL_MACHINE, dynamicKeyPath);
535 if (dynamicKey.isValid()) {
537 const int startYear = dynamicKey.value<
int>(L"FirstEntry").value_or(0);
538 const int endYear = dynamicKey.value<
int>(L"LastEntry").value_or(0);
539 for (
int year = startYear; year <= endYear; ++year) {
541 QWinTransitionRule rule = readRegistryRule(dynamicKey,
542 qt_castToWchar(QString::number(year)),
546 && (m_tranRules.isEmpty()
547 || !isSameRule(m_tranRules.last(), rule))) {
549 && (rule.standardTimeRule.wMonth == 0)
550 != (rule.daylightTimeRule.wMonth == 0)) {
552 qWarning(
"MS registry TZ API violated its wMonth constraint;"
553 "this may cause mistakes for %s from %d",
554 ianaId.constData(), year);
556 const TransitionTimePair pair(rule, year, rule.standardTimeBias);
559 = m_tranRules.size() || pair.fakesDst ? year : initialYear(rule);
560 m_tranRules.append(rule);
566 QWinTransitionRule rule = readRegistryRule(baseKey, L"TZI", &ruleOk);
568 rule.startYear = initialYear(rule);
569 m_tranRules.append(rule);
576 if (m_tranRules.size() == 0) {
579 m_displayName.clear();
580 }
else if (m_id.isEmpty()) {
581 m_id = m_standardName.toUtf8();
585QString QWinTimeZonePrivate::comment()
const
587 return m_displayName;
590QString QWinTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
591 QTimeZone::NameType nameType,
592 const QLocale &locale)
const
595 if (nameType == QTimeZone::LongName && locale == QLocale::system()) {
597 case QTimeZone::DaylightTime :
598 return m_daylightName;
599 case QTimeZone::GenericTime :
600 return m_displayName;
601 case QTimeZone::StandardTime :
602 return m_standardName;
606 return QTimeZonePrivate::displayName(timeType, nameType, locale);;
609QString QWinTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
611 return data(atMSecsSinceEpoch).abbreviation;
614int QWinTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
616 return data(atMSecsSinceEpoch).offsetFromUtc;
619int QWinTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
621 return data(atMSecsSinceEpoch).standardTimeOffset;
624int QWinTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
626 return data(atMSecsSinceEpoch).daylightTimeOffset;
629bool QWinTimeZonePrivate::hasDaylightTime()
const
631 return hasTransitions();
634bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
636 return (data(atMSecsSinceEpoch).daylightTimeOffset != 0);
639QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
641 int year = msecsToDate(forMSecsSinceEpoch).year();
642 for (
int ruleIndex = ruleIndexForYear(m_tranRules, year);
643 ruleIndex >= 0; --ruleIndex) {
644 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
645 Q_ASSERT(ruleIndex == 0 || year >= rule.startYear);
646 if (year < rule.startYear
647 || !(rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0)) {
649 return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime);
652 int prior = year == 1 ? -1 : year - 1;
653 const int endYear = qMax(rule.startYear, prior);
654 while (year >= endYear) {
655 const int newYearOffset = (prior < rule.startYear && ruleIndex > 0)
656 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
657 : yearEndOffset(rule, prior);
658 const TransitionTimePair pair(rule, year, newYearOffset);
660 if (ruleIndex == 0 && pair.beforeInitialDst(year, forMSecsSinceEpoch)) {
664 }
else if (pair.std != invalidMSecs() && pair.std <= forMSecsSinceEpoch) {
665 isDst = pair.std < pair.dst && pair.dst <= forMSecsSinceEpoch;
666 }
else if (pair.dst != invalidMSecs() && pair.dst <= forMSecsSinceEpoch) {
670 prior = year == 1 ? -1 : year - 1;
673 return ruleToData(rule, forMSecsSinceEpoch,
674 isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime,
678 Q_ASSERT(year < rule.startYear);
685bool QWinTimeZonePrivate::hasTransitions()
const
687 for (
const QWinTransitionRule &rule : m_tranRules) {
688 if (rule.standardTimeRule.wMonth > 0 && rule.daylightTimeRule.wMonth > 0)
694QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
696 int year = msecsToDate(afterMSecsSinceEpoch).year();
697 int newYearOffset = invalidSeconds();
698 for (
int ruleIndex = ruleIndexForYear(m_tranRules, year);
699 ruleIndex < m_tranRules.count(); ++ruleIndex) {
700 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
702 if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
703 int prior = year == 1 ? -1 : year - 1;
704 if (newYearOffset == invalidSeconds()) {
707 newYearOffset = (prior < rule.startYear && ruleIndex > 0)
708 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
709 : yearEndOffset(rule, prior);
711 if (year < rule.startYear) {
715 TransitionTimePair pair(rule, rule.startYear, newYearOffset);
719 return pair.ruleToData(rule,
this, !(year > FIRST_DST_YEAR && pair.startsInDst()));
721 const int endYear = ruleIndex + 1 < m_tranRules.count()
722 ? qMin(m_tranRules.at(ruleIndex + 1).startYear, year + 2) : (year + 2);
723 while (year < endYear) {
724 const TransitionTimePair pair(rule, year, newYearOffset);
726 Q_ASSERT(invalidMSecs() <= afterMSecsSinceEpoch);
727 if (ruleIndex == 0 && pair.beforeInitialDst(year, afterMSecsSinceEpoch)) {
730 Q_ASSERT(year == FIRST_DST_YEAR);
736 }
else if (pair.std > afterMSecsSinceEpoch) {
737 isDst = pair.std > pair.dst && pair.dst > afterMSecsSinceEpoch;
738 }
else if (pair.dst > afterMSecsSinceEpoch) {
741 newYearOffset = rule.standardTimeBias;
742 if (pair.dst > pair.std)
743 newYearOffset += rule.daylightTimeBias;
746 year = year == -1 ? 1 : year + 1;
750 return pair.ruleToData(rule,
this, isDst);
757 if (newYearOffset == invalidSeconds())
758 newYearOffset = rule.standardTimeBias;
760 Q_ASSERT(newYearOffset == rule.standardTimeBias);
767QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
769 if (beforeMSecsSinceEpoch <= minMSecs())
772 int year = msecsToDate(beforeMSecsSinceEpoch).year();
773 for (
int ruleIndex = ruleIndexForYear(m_tranRules, year);
774 ruleIndex >= 0; --ruleIndex) {
775 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
776 Q_ASSERT(ruleIndex == 0 || year >= rule.startYear);
778 if (year >= rule.startYear
779 && (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0)) {
780 int prior = year == 1 ? -1 : year - 1;
781 const int endYear = qMax(rule.startYear, prior);
782 while (year >= endYear) {
783 const int newYearOffset = (prior < rule.startYear && ruleIndex > 0)
784 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
785 : yearEndOffset(rule, prior);
786 const TransitionTimePair pair(rule, year, newYearOffset);
791 if (ruleIndex == 0 && pair.beforeInitialDst(year, beforeMSecsSinceEpoch - 1))
792 return ruleToData(rule, minMSecs(), QTimeZone::StandardTime,
false);
795 if (pair.std != invalidMSecs() && pair.std < beforeMSecsSinceEpoch) {
796 isDst = pair.std < pair.dst && pair.dst < beforeMSecsSinceEpoch;
797 }
else if (pair.dst != invalidMSecs() && pair.dst < beforeMSecsSinceEpoch) {
801 prior = year == 1 ? -1 : year - 1;
804 return pair.ruleToData(rule,
this, isDst);
807 }
else if (ruleIndex == 0) {
811 return ruleToData(rule, minMSecs(), QTimeZone::StandardTime,
false);
813 if (year >= rule.startYear) {
814 year = rule.startYear - 1;
823QByteArray QWinTimeZonePrivate::systemTimeZoneId()
const
825 const QLocale::Territory territory = userTerritory();
826 const QByteArray windowsId = windowsSystemZoneId();
829 if (territory != QLocale::AnyTerritory)
830 ianaId = windowsIdToDefaultIanaId(windowsId, territory);
832 if (ianaId.isEmpty())
833 ianaId = windowsIdToDefaultIanaId(windowsId);
837QList<QByteArray> QWinTimeZonePrivate::availableTimeZoneIds()
const
839 static const QList<QByteArray> cache = [] {
840 QList<QByteArray> result;
841 const auto winIds = availableWindowsIds();
842 for (
const QByteArray &winId : winIds)
843 result += windowsIdToIanaIds(winId);
844 std::sort(result.begin(), result.end());
845 result.erase(std::unique(result.begin(), result.end()), result.end());
851QTimeZonePrivate::Data QWinTimeZonePrivate::ruleToData(
const QWinTransitionRule &rule,
852 qint64 atMSecsSinceEpoch,
853 QTimeZone::TimeType type,
857 tran.atMSecsSinceEpoch = atMSecsSinceEpoch;
858 tran.standardTimeOffset = rule.standardTimeBias * -60;
860 tran.daylightTimeOffset = 0;
862 if (type == QTimeZone::DaylightTime)
863 tran.standardTimeOffset += rule.daylightTimeBias * -60;
864 }
else if (type == QTimeZone::DaylightTime) {
865 tran.daylightTimeOffset = rule.daylightTimeBias * -60;
867 tran.daylightTimeOffset = 0;
869 tran.offsetFromUtc = tran.standardTimeOffset + tran.daylightTimeOffset;
870 tran.abbreviation = localeName(atMSecsSinceEpoch, tran.offsetFromUtc,
871 type, QTimeZone::ShortName, QLocale::system());
\inmodule QtCore \reentrant
QT_REQUIRE_CONFIG(timezone_locale)
constexpr qint64 JULIAN_DAY_FOR_EPOCH
constexpr int FIRST_DST_YEAR
static const wchar_t tzRegPath[]
static const wchar_t currTzRegPath[]
constexpr qint64 MSECS_PER_DAY