11#include <private/qnumeric_p.h>
12#include <private/wcharhelpers_win_p.h>
16#include <private/qwinregistry_p.h>
22using namespace Qt::StringLiterals;
25
26
27
28
30#define MAX_KEY_LENGTH 255
40static const wchar_t tzRegPath[] = LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones)";
41static const wchar_t currTzRegPath[] = LR"(SYSTEM\CurrentControlSet\Control\TimeZoneInformation)";
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
87QDate msecsToDate(qint64 msecs)
89 qint64 jd = JULIAN_DAY_FOR_EPOCH;
91 if (msecs >= MSECS_PER_DAY || msecs <= -MSECS_PER_DAY) {
92 jd += msecs / MSECS_PER_DAY;
93 msecs %= MSECS_PER_DAY;
97 Q_ASSERT(msecs > -MSECS_PER_DAY);
101 return QDate::fromJulianDay(jd);
104bool equalSystemtime(
const SYSTEMTIME &t1,
const SYSTEMTIME &t2)
106 return (t1.wYear == t2.wYear
107 && t1.wMonth == t2.wMonth
108 && t1.wDay == t2.wDay
109 && t1.wDayOfWeek == t2.wDayOfWeek
110 && t1.wHour == t2.wHour
111 && t1.wMinute == t2.wMinute
112 && t1.wSecond == t2.wSecond
113 && t1.wMilliseconds == t2.wMilliseconds);
116bool equalTzi(
const TIME_ZONE_INFORMATION &tzi1,
const TIME_ZONE_INFORMATION &tzi2)
118 return(tzi1.Bias == tzi2.Bias
119 && tzi1.StandardBias == tzi2.StandardBias
120 && equalSystemtime(tzi1.StandardDate, tzi2.StandardDate)
121 && wcscmp(tzi1.StandardName, tzi2.StandardName) == 0
122 && tzi1.DaylightBias == tzi2.DaylightBias
123 && equalSystemtime(tzi1.DaylightDate, tzi2.DaylightDate)
124 && wcscmp(tzi1.DaylightName, tzi2.DaylightName) == 0);
127QWinTimeZonePrivate::QWinTransitionRule readRegistryRule(
const HKEY &key,
128 const wchar_t *value,
bool *ok)
131 QWinTimeZonePrivate::QWinTransitionRule rule;
133 DWORD tziSize =
sizeof(tzi);
134 if (RegQueryValueEx(key, value,
nullptr,
nullptr,
reinterpret_cast<BYTE *>(&tzi), &tziSize)
137 rule.standardTimeBias = tzi.Bias + tzi.StandardBias;
138 rule.daylightTimeBias = tzi.Bias + tzi.DaylightBias - rule.standardTimeBias;
139 rule.standardTimeRule = tzi.StandardDate;
140 rule.daylightTimeRule = tzi.DaylightDate;
146TIME_ZONE_INFORMATION getRegistryTzi(
const QByteArray &windowsId,
bool *ok)
149 TIME_ZONE_INFORMATION tzi;
150 REG_TZI_FORMAT regTzi;
151 DWORD regTziSize =
sizeof(regTzi);
152 const QString tziKeyPath = QString::fromWCharArray(tzRegPath) + u'\\'
153 + QString::fromUtf8(windowsId);
155 QWinRegistryKey key(HKEY_LOCAL_MACHINE, tziKeyPath);
157 DWORD size =
sizeof(tzi.DaylightName);
158 RegQueryValueEx(key, L"Dlt",
nullptr,
nullptr,
reinterpret_cast<LPBYTE>(tzi.DaylightName), &size);
160 size =
sizeof(tzi.StandardName);
161 RegQueryValueEx(key, L"Std",
nullptr,
nullptr,
reinterpret_cast<LPBYTE>(tzi.StandardName), &size);
163 if (RegQueryValueEx(key, L"TZI",
nullptr,
nullptr,
reinterpret_cast<BYTE *>(®Tzi), ®TziSize)
165 tzi.Bias = regTzi.Bias;
166 tzi.StandardBias = regTzi.StandardBias;
167 tzi.DaylightBias = regTzi.DaylightBias;
168 tzi.StandardDate = regTzi.StandardDate;
169 tzi.DaylightDate = regTzi.DaylightDate;
177bool isSameRule(
const QWinTimeZonePrivate::QWinTransitionRule &last,
178 const QWinTimeZonePrivate::QWinTransitionRule &rule)
185 return equalSystemtime(last.standardTimeRule, rule.standardTimeRule)
186 && equalSystemtime(last.daylightTimeRule, rule.daylightTimeRule)
187 && last.standardTimeBias == rule.standardTimeBias
188 && last.daylightTimeBias == rule.daylightTimeBias;
191QList<QByteArray> availableWindowsIds()
193 static const QList<QByteArray> cache = [] {
194 QList<QByteArray> list;
195 QWinRegistryKey key(HKEY_LOCAL_MACHINE, tzRegPath);
198 if (RegQueryInfoKey(key, 0, 0, 0, &idCount, 0, 0, 0, 0, 0, 0, 0) == ERROR_SUCCESS
200 for (DWORD i = 0; i < idCount; ++i) {
203 if (RegEnumKeyEx(key, i, buffer, &maxLen, 0, 0, 0, 0) == ERROR_SUCCESS)
204 list.append(QString::fromWCharArray(buffer).toUtf8());
213QByteArray windowsSystemZoneId()
216 const QString id = QWinRegistryKey(HKEY_LOCAL_MACHINE,
currTzRegPath)
217 .stringValue(L"TimeZoneKeyName");
223 TIME_ZONE_INFORMATION sysTzi;
224 GetTimeZoneInformation(&sysTzi);
226 const auto winIds = availableWindowsIds();
227 for (
const QByteArray &winId : winIds) {
228 if (equalTzi(getRegistryTzi(winId, &ok), sysTzi))
233 return QTimeZonePrivate::utcQByteArray();
236QDate calculateTransitionLocalDate(
const SYSTEMTIME &rule,
int year)
239 if (rule.wMonth == 0)
247 return QDate(rule.wYear, rule.wMonth, rule.wDay);
250 const int dayOfWeek = rule.wDayOfWeek == 0 ? 7 : rule.wDayOfWeek;
251 QDate date(year, rule.wMonth, 1);
252 Q_ASSERT(date.isValid());
254 int adjust = dayOfWeek - date.dayOfWeek();
259 adjust += (rule.wDay < 1 ? 1 : rule.wDay > 4 ? 5 : rule.wDay) * 7;
260 date = date.addDays(adjust);
262 if (date.month() != rule.wMonth) {
263 Q_ASSERT(rule.wDay > 4);
266 date = date.addDays(-7);
267 Q_ASSERT(date.month() == rule.wMonth);
273inline bool timeToMSecs(QDate date, QTime time, qint64 *msecs)
276 qint64 daySinceEpoch = date.toJulianDay() - JULIAN_DAY_FOR_EPOCH;
277 qint64 msInDay = time.msecsSinceStartOfDay();
278 if (daySinceEpoch < 0 && msInDay > 0) {
282 msInDay -= MSECS_PER_DAY;
284 return qMulOverflow(daySinceEpoch, std::integral_constant<qint64, MSECS_PER_DAY>(), &dayms)
285 || qAddOverflow(dayms, msInDay, msecs);
288qint64 calculateTransitionForYear(
const SYSTEMTIME &rule,
int year,
int bias)
293 const QDate date = calculateTransitionLocalDate(rule, year);
294 const QTime time = QTime(rule.wHour, rule.wMinute, rule.wSecond);
296 if (date.isValid() && time.isValid() && !timeToMSecs(date, time, &msecs)) {
300 return bias && qAddOverflow(msecs, qint64(bias) * 60000, &msecs)
301 ? (bias < 0 ? QTimeZonePrivate::minMSecs() : QTimeZonePrivate::maxMSecs())
302 : qMax(QTimeZonePrivate::minMSecs(), msecs);
304 return QTimeZonePrivate::invalidMSecs();
308bool isAtStartOfYear(
const SYSTEMTIME &transition,
int year)
311
312
313
314
315
316
317
318
319 return transition.wMonth == 1 && transition.wDay == 1
320 && (
QDate(year, 1, 1).dayOfWeek() - transition.wDayOfWeek) % 7 == 0
321 && transition.wHour == 0 && transition.wMinute == 0 && transition.wSecond == 0;
324struct TransitionTimePair
330 bool fakesDst =
false;
332 TransitionTimePair(
const QWinTimeZonePrivate::QWinTransitionRule &rule,
333 int year,
int oldYearOffset)
335 : std(calculateTransitionForYear(rule.standardTimeRule, year,
336 rule.standardTimeBias + rule.daylightTimeBias)),
338 dst(calculateTransitionForYear(rule.daylightTimeRule, year, rule.standardTimeBias))
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
385 if (rule.standardTimeBias + rule.daylightTimeBias == oldYearOffset
386 && isAtStartOfYear(rule.daylightTimeRule, year)) {
387 dst = QTimeZonePrivate::invalidMSecs();
390 if (rule.standardTimeBias == oldYearOffset
391 && isAtStartOfYear(rule.standardTimeRule, year)) {
392 Q_ASSERT_X(!fakesDst,
"TransitionTimePair",
393 "Year with (DST bias zero and) both transitions fake !");
394 std = QTimeZonePrivate::invalidMSecs();
399 bool startsInDst()
const
403 return std != QTimeZonePrivate::invalidMSecs()
404 && (std < dst || dst == QTimeZonePrivate::invalidMSecs());
411 bool beforeInitialDst(
int year, qint64 millis)
const
413 return !fakesDst && (year == FIRST_DST_YEAR ? millis < dst : year < FIRST_DST_YEAR);
416 QTimeZonePrivate::Data ruleToData(
const QWinTimeZonePrivate::QWinTransitionRule &rule,
417 const QWinTimeZonePrivate *tzp,
bool isDst)
const
419 const auto type = isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
420 auto time = isDst ? dst : std;
423 if (fakesDst && time == QTimeZonePrivate::invalidMSecs())
424 time = isDst ? std : dst;
425 return tzp->ruleToData(rule, time, type, fakesDst);
429int yearEndOffset(
const QWinTimeZonePrivate::QWinTransitionRule &rule,
int year)
432 int offset = rule.standardTimeBias;
436 TransitionTimePair pair(rule, year, offset);
437 if (pair.dst > pair.std)
438 offset += rule.daylightTimeBias;
442QLocale::Territory userTerritory()
444 const GEOID id = GetUserGeoID(GEOCLASS_NATION);
446 const int size = GetGeoInfo(id, GEO_ISO2, code, 3, 0);
447 return (size == 3) ? QLocalePrivate::codeToTerritory(QStringView(code, size))
448 : QLocale::AnyTerritory;
452int ruleIndexForYear(
const QList<QWinTimeZonePrivate::QWinTransitionRule> &rules,
int year)
454 if (rules.last().startYear <= year)
455 return rules.count() - 1;
457 if (rules.first().startYear > year)
461 int lo = 0, hi = rules.count();
464 while (lo + 1 < hi) {
465 const int mid = (lo + hi) / 2;
468 const int midYear = rules.at(mid).startYear;
471 else if (midYear < year)
482QWinTimeZonePrivate::QWinTimeZonePrivate()
489QWinTimeZonePrivate::QWinTimeZonePrivate(
const QByteArray &ianaId)
495QWinTimeZonePrivate::~QWinTimeZonePrivate()
499QWinTimeZonePrivate *QWinTimeZonePrivate::clone()
const
501 return new QWinTimeZonePrivate(*
this);
504void QWinTimeZonePrivate::init(
const QByteArray &ianaId)
506 if (ianaId.isEmpty()) {
507 m_windowsId = windowsSystemZoneId();
508 m_id = systemTimeZoneId();
510 m_windowsId = ianaIdToWindowsId(ianaId);
513 const auto initialYear = [](
const QWinTransitionRule &rule) {
518 return (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0
519 ? FIRST_DST_YEAR :
int(QDateTime::YearRange::First));
522 bool badMonth =
false;
523 if (!m_windowsId.isEmpty()) {
525 const QString baseKeyPath = QString::fromWCharArray(tzRegPath) + u'\\'
526 + QString::fromUtf8(m_windowsId);
527 QWinRegistryKey baseKey(HKEY_LOCAL_MACHINE, baseKeyPath);
528 if (baseKey.isValid()) {
530 m_displayName = baseKey.stringValue(L"Display");
531 m_standardName = baseKey.stringValue(L"Std");
532 m_daylightName = baseKey.stringValue(L"Dlt");
534 const QString dynamicKeyPath = baseKeyPath +
"\\Dynamic DST"_L1;
535 QWinRegistryKey dynamicKey(HKEY_LOCAL_MACHINE, dynamicKeyPath);
536 if (dynamicKey.isValid()) {
538 const int startYear = dynamicKey.value<
int>(L"FirstEntry").value_or(0);
539 const int endYear = dynamicKey.value<
int>(L"LastEntry").value_or(0);
540 for (
int year = startYear; year <= endYear; ++year) {
542 QWinTransitionRule rule = readRegistryRule(dynamicKey,
543 qt_castToWchar(QString::number(year)),
547 && (m_tranRules.isEmpty()
548 || !isSameRule(m_tranRules.last(), rule))) {
550 && (rule.standardTimeRule.wMonth == 0)
551 != (rule.daylightTimeRule.wMonth == 0)) {
553 qWarning(
"MS registry TZ API violated its wMonth constraint;"
554 "this may cause mistakes for %s from %d",
555 ianaId.constData(), year);
557 const TransitionTimePair pair(rule, year, rule.standardTimeBias);
560 = m_tranRules.size() || pair.fakesDst ? year : initialYear(rule);
561 m_tranRules.append(rule);
567 QWinTransitionRule rule = readRegistryRule(baseKey, L"TZI", &ruleOk);
569 rule.startYear = initialYear(rule);
570 m_tranRules.append(rule);
577 if (m_tranRules.size() == 0) {
580 m_displayName.clear();
581 }
else if (m_id.isEmpty()) {
582 m_id = m_standardName.toUtf8();
586QString QWinTimeZonePrivate::comment()
const
588 return m_displayName;
591QString QWinTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
592 QTimeZone::NameType nameType,
593 const QLocale &locale)
const
596 if (nameType == QTimeZone::LongName && locale == QLocale::system()) {
598 case QTimeZone::DaylightTime :
599 return m_daylightName;
600 case QTimeZone::GenericTime :
601 return m_displayName;
602 case QTimeZone::StandardTime :
603 return m_standardName;
607 return QTimeZonePrivate::displayName(timeType, nameType, locale);;
610QString QWinTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch)
const
612 return data(atMSecsSinceEpoch).abbreviation;
615int QWinTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch)
const
617 return data(atMSecsSinceEpoch).offsetFromUtc;
620int QWinTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch)
const
622 return data(atMSecsSinceEpoch).standardTimeOffset;
625int QWinTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch)
const
627 return data(atMSecsSinceEpoch).daylightTimeOffset;
630bool QWinTimeZonePrivate::hasDaylightTime()
const
632 return hasTransitions();
635bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch)
const
637 return (data(atMSecsSinceEpoch).daylightTimeOffset != 0);
640QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch)
const
642 int year = msecsToDate(forMSecsSinceEpoch).year();
643 for (
int ruleIndex = ruleIndexForYear(m_tranRules, year);
644 ruleIndex >= 0; --ruleIndex) {
645 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
646 Q_ASSERT(ruleIndex == 0 || year >= rule.startYear);
647 if (year < rule.startYear
648 || !(rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0)) {
650 return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime);
653 int prior = year == 1 ? -1 : year - 1;
654 const int endYear = qMax(rule.startYear, prior);
655 while (year >= endYear) {
656 const int newYearOffset = (prior < rule.startYear && ruleIndex > 0)
657 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
658 : yearEndOffset(rule, prior);
659 const TransitionTimePair pair(rule, year, newYearOffset);
661 if (ruleIndex == 0 && pair.beforeInitialDst(year, forMSecsSinceEpoch)) {
665 }
else if (pair.std != invalidMSecs() && pair.std <= forMSecsSinceEpoch) {
666 isDst = pair.std < pair.dst && pair.dst <= forMSecsSinceEpoch;
667 }
else if (pair.dst != invalidMSecs() && pair.dst <= forMSecsSinceEpoch) {
671 prior = year == 1 ? -1 : year - 1;
674 return ruleToData(rule, forMSecsSinceEpoch,
675 isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime,
679 Q_ASSERT(year < rule.startYear);
686bool QWinTimeZonePrivate::hasTransitions()
const
688 for (
const QWinTransitionRule &rule : m_tranRules) {
689 if (rule.standardTimeRule.wMonth > 0 && rule.daylightTimeRule.wMonth > 0)
695QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch)
const
697 int year = msecsToDate(afterMSecsSinceEpoch).year();
698 int newYearOffset = invalidSeconds();
699 for (
int ruleIndex = ruleIndexForYear(m_tranRules, year);
700 ruleIndex < m_tranRules.count(); ++ruleIndex) {
701 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
703 if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
704 int prior = year == 1 ? -1 : year - 1;
705 if (newYearOffset == invalidSeconds()) {
708 newYearOffset = (prior < rule.startYear && ruleIndex > 0)
709 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
710 : yearEndOffset(rule, prior);
712 if (year < rule.startYear) {
716 TransitionTimePair pair(rule, rule.startYear, newYearOffset);
720 return pair.ruleToData(rule,
this, !(year > FIRST_DST_YEAR && pair.startsInDst()));
722 const int endYear = ruleIndex + 1 < m_tranRules.count()
723 ? qMin(m_tranRules.at(ruleIndex + 1).startYear, year + 2) : (year + 2);
724 while (year < endYear) {
725 const TransitionTimePair pair(rule, year, newYearOffset);
727 Q_ASSERT(invalidMSecs() <= afterMSecsSinceEpoch);
728 if (ruleIndex == 0 && pair.beforeInitialDst(year, afterMSecsSinceEpoch)) {
731 Q_ASSERT(year == FIRST_DST_YEAR);
737 }
else if (pair.std > afterMSecsSinceEpoch) {
738 isDst = pair.std > pair.dst && pair.dst > afterMSecsSinceEpoch;
739 }
else if (pair.dst > afterMSecsSinceEpoch) {
742 newYearOffset = rule.standardTimeBias;
743 if (pair.dst > pair.std)
744 newYearOffset += rule.daylightTimeBias;
747 year = year == -1 ? 1 : year + 1;
751 return pair.ruleToData(rule,
this, isDst);
758 if (newYearOffset == invalidSeconds())
759 newYearOffset = rule.standardTimeBias;
761 Q_ASSERT(newYearOffset == rule.standardTimeBias);
768QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch)
const
770 if (beforeMSecsSinceEpoch <= minMSecs())
773 int year = msecsToDate(beforeMSecsSinceEpoch).year();
774 for (
int ruleIndex = ruleIndexForYear(m_tranRules, year);
775 ruleIndex >= 0; --ruleIndex) {
776 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
777 Q_ASSERT(ruleIndex == 0 || year >= rule.startYear);
779 if (year >= rule.startYear
780 && (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0)) {
781 int prior = year == 1 ? -1 : year - 1;
782 const int endYear = qMax(rule.startYear, prior);
783 while (year >= endYear) {
784 const int newYearOffset = (prior < rule.startYear && ruleIndex > 0)
785 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
786 : yearEndOffset(rule, prior);
787 const TransitionTimePair pair(rule, year, newYearOffset);
792 if (ruleIndex == 0 && pair.beforeInitialDst(year, beforeMSecsSinceEpoch - 1))
793 return ruleToData(rule, minMSecs(), QTimeZone::StandardTime,
false);
796 if (pair.std != invalidMSecs() && pair.std < beforeMSecsSinceEpoch) {
797 isDst = pair.std < pair.dst && pair.dst < beforeMSecsSinceEpoch;
798 }
else if (pair.dst != invalidMSecs() && pair.dst < beforeMSecsSinceEpoch) {
802 prior = year == 1 ? -1 : year - 1;
805 return pair.ruleToData(rule,
this, isDst);
808 }
else if (ruleIndex == 0) {
812 return ruleToData(rule, minMSecs(), QTimeZone::StandardTime,
false);
814 if (year >= rule.startYear) {
815 year = rule.startYear - 1;
824QByteArray QWinTimeZonePrivate::systemTimeZoneId()
const
826 const QLocale::Territory territory = userTerritory();
827 const QByteArray windowsId = windowsSystemZoneId();
830 if (territory != QLocale::AnyTerritory)
831 ianaId = windowsIdToDefaultIanaId(windowsId, territory);
833 if (ianaId.isEmpty())
834 ianaId = windowsIdToDefaultIanaId(windowsId);
838QList<QByteArray> QWinTimeZonePrivate::availableTimeZoneIds()
const
840 static const QList<QByteArray> cache = [] {
841 QList<QByteArray> result;
842 const auto winIds = availableWindowsIds();
843 for (
const QByteArray &winId : winIds)
844 result += windowsIdToIanaIds(winId);
845 std::sort(result.begin(), result.end());
846 result.erase(std::unique(result.begin(), result.end()), result.end());
852QTimeZonePrivate::Data QWinTimeZonePrivate::ruleToData(
const QWinTransitionRule &rule,
853 qint64 atMSecsSinceEpoch,
854 QTimeZone::TimeType type,
858 tran.atMSecsSinceEpoch = atMSecsSinceEpoch;
859 tran.standardTimeOffset = rule.standardTimeBias * -60;
861 tran.daylightTimeOffset = 0;
863 if (type == QTimeZone::DaylightTime)
864 tran.standardTimeOffset += rule.daylightTimeBias * -60;
865 }
else if (type == QTimeZone::DaylightTime) {
866 tran.daylightTimeOffset = rule.daylightTimeBias * -60;
868 tran.daylightTimeOffset = 0;
870 tran.offsetFromUtc = tran.standardTimeOffset + tran.daylightTimeOffset;
871 tran.abbreviation = localeName(atMSecsSinceEpoch, tran.offsetFromUtc,
872 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