Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtimezoneprivate_win.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// Copyright (C) 2013 John Layt <jlayt@kde.org>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qtimezone.h"
7
8#include "qdatetime.h"
9#include "qdebug.h"
10#include <private/qnumeric_p.h>
11#include <private/wcharhelpers_win_p.h>
12
13#include <algorithm>
14
15#include <private/qwinregistry_p.h>
16
18
19QT_BEGIN_NAMESPACE
20
21using namespace Qt::StringLiterals;
22
23/*
24 Private
25
26 Windows system implementation
27*/
28
29#define MAX_KEY_LENGTH 255
30
31// MSDN home page for Time support
32// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724962%28v=vs.85%29.aspx
33
34// For Windows XP and later refer to MSDN docs on TIME_ZONE_INFORMATION structure
35// http://msdn.microsoft.com/en-gb/library/windows/desktop/ms725481%28v=vs.85%29.aspx
36
37// Vista introduced support for historic data, see MSDN docs on DYNAMIC_TIME_ZONE_INFORMATION
38// http://msdn.microsoft.com/en-gb/library/windows/desktop/ms724253%28v=vs.85%29.aspx
39static const wchar_t tzRegPath[] = LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones)";
40static const wchar_t currTzRegPath[] = LR"(SYSTEM\CurrentControlSet\Control\TimeZoneInformation)";
41
42constexpr qint64 MSECS_PER_DAY = 86400000LL;
43constexpr qint64 JULIAN_DAY_FOR_EPOCH = 2440588LL; // result of julianDayFromDate(1970, 1, 1)
44
45/* Ignore any claims of DST before 1900.
46
47 Daylight-Saving time adjustments were first proposed in 1895 (George Vernon
48 Hudson in New Zealand) and 1905 (William Willett in the UK) and first adopted
49 in 1908 (one town in Ontario, Canada) and 1916 (Germany). Since MS's data
50 tends to pretend the rules in force in 1970ish (or later) had always been in
51 effect, which presents difficulties for the code that selects correct data
52 (for a time close to the earliest we can represent), always ignore any claim
53 a first rule may make of DST starting any time before 1900.
54
55 For southern-hemisphere zones, this implies that a rule claiming 1900 started
56 in DST is overruled to merely start DST later in 1900, having spent the whole
57 part of 1900 prior to that in standard time. This erases 1900's earlier
58 transition out of daylight-saving time so as to prevent a fake change of
59 offset at the start of the year, since 1899 shall be treated as observing
60 standard time throughout.
61
62 In the unlikely event of MS supplying a change in standard time before 1900,
63 however, that should be faithfully represented. If that ever happens, trust
64 that MS gets the start year of any subsequend DST right.
65
66 See:
67 * https://www.timeanddate.com/time/dst/history.html
68 * https://en.wikipedia.org/wiki/Daylight_saving_time#History
69*/
70constexpr int FIRST_DST_YEAR = 1900;
71
72// Copied from MSDN, see above for link
81
82namespace {
83
84// Fast and reliable conversion from msecs to date for all values
85// Adapted from QDateTime msecsToDate
86QDate msecsToDate(qint64 msecs)
87{
88 qint64 jd = JULIAN_DAY_FOR_EPOCH;
89 // Corner case: don't use qAbs() because msecs may be numeric_limits<qint64>::min()
90 if (msecs >= MSECS_PER_DAY || msecs <= -MSECS_PER_DAY) {
91 jd += msecs / MSECS_PER_DAY;
92 msecs %= MSECS_PER_DAY;
93 }
94
95 if (msecs < 0) {
96 Q_ASSERT(msecs > -MSECS_PER_DAY);
97 --jd;
98 }
99
100 return QDate::fromJulianDay(jd);
101}
102
103bool equalSystemtime(const SYSTEMTIME &t1, const SYSTEMTIME &t2)
104{
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);
113}
114
115bool equalTzi(const TIME_ZONE_INFORMATION &tzi1, const TIME_ZONE_INFORMATION &tzi2)
116{
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);
124}
125
126QWinTimeZonePrivate::QWinTransitionRule readRegistryRule(const HKEY &key,
127 const wchar_t *value, bool *ok)
128{
129 *ok = false;
130 QWinTimeZonePrivate::QWinTransitionRule rule;
131 REG_TZI_FORMAT tzi;
132 DWORD tziSize = sizeof(tzi);
133 if (RegQueryValueEx(key, value, nullptr, nullptr, reinterpret_cast<BYTE *>(&tzi), &tziSize)
134 == ERROR_SUCCESS) {
135 rule.startYear = 0;
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;
140 *ok = true;
141 }
142 return rule;
143}
144
145TIME_ZONE_INFORMATION getRegistryTzi(const QByteArray &windowsId, bool *ok)
146{
147 *ok = false;
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);
153
154 QWinRegistryKey key(HKEY_LOCAL_MACHINE, tziKeyPath);
155 if (key.isValid()) {
156 DWORD size = sizeof(tzi.DaylightName);
157 RegQueryValueEx(key, L"Dlt", nullptr, nullptr, reinterpret_cast<LPBYTE>(tzi.DaylightName), &size);
158
159 size = sizeof(tzi.StandardName);
160 RegQueryValueEx(key, L"Std", nullptr, nullptr, reinterpret_cast<LPBYTE>(tzi.StandardName), &size);
161
162 if (RegQueryValueEx(key, L"TZI", nullptr, nullptr, reinterpret_cast<BYTE *>(&regTzi), &regTziSize)
163 == ERROR_SUCCESS) {
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;
169 *ok = true;
170 }
171 }
172
173 return tzi;
174}
175
176bool isSameRule(const QWinTimeZonePrivate::QWinTransitionRule &last,
177 const QWinTimeZonePrivate::QWinTransitionRule &rule)
178{
179 // In particular, when this is true and either wYear is 0, so is the other;
180 // so if one rule is recurrent and they're equal, so is the other. If
181 // either rule *isn't* recurrent, it has non-0 wYear which shall be
182 // different from the other's. Note that we don't compare .startYear, since
183 // that will always be different.
184 return equalSystemtime(last.standardTimeRule, rule.standardTimeRule)
185 && equalSystemtime(last.daylightTimeRule, rule.daylightTimeRule)
186 && last.standardTimeBias == rule.standardTimeBias
187 && last.daylightTimeBias == rule.daylightTimeBias;
188}
189
190QList<QByteArray> availableWindowsIds()
191{
192 static const QList<QByteArray> cache = [] {
193 QList<QByteArray> list;
194 QWinRegistryKey key(HKEY_LOCAL_MACHINE, tzRegPath);
195 if (key.isValid()) {
196 DWORD idCount = 0;
197 if (RegQueryInfoKey(key, 0, 0, 0, &idCount, 0, 0, 0, 0, 0, 0, 0) == ERROR_SUCCESS
198 && idCount > 0) {
199 for (DWORD i = 0; i < idCount; ++i) {
200 DWORD maxLen = MAX_KEY_LENGTH;
201 TCHAR buffer[MAX_KEY_LENGTH];
202 if (RegEnumKeyEx(key, i, buffer, &maxLen, 0, 0, 0, 0) == ERROR_SUCCESS)
203 list.append(QString::fromWCharArray(buffer).toUtf8());
204 }
205 }
206 }
207 return list;
208 }();
209 return cache;
210}
211
212QByteArray windowsSystemZoneId()
213{
214 // On Vista and later is held in the value TimeZoneKeyName in key currTzRegPath
215 const QString id = QWinRegistryKey(HKEY_LOCAL_MACHINE, currTzRegPath)
216 .stringValue(L"TimeZoneKeyName");
217 if (!id.isEmpty())
218 return id.toUtf8();
219
220 // On XP we have to iterate over the zones until we find a match on
221 // names/offsets with the current data
222 TIME_ZONE_INFORMATION sysTzi;
223 GetTimeZoneInformation(&sysTzi);
224 bool ok = false;
225 const auto winIds = availableWindowsIds();
226 for (const QByteArray &winId : winIds) {
227 if (equalTzi(getRegistryTzi(winId, &ok), sysTzi))
228 return winId;
229 }
230
231 // If we can't determine the current ID use UTC
232 return QTimeZonePrivate::utcQByteArray();
233}
234
235QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year)
236{
237 // If month is 0 then there is no date
238 if (rule.wMonth == 0)
239 return QDate();
240
241 // Interpret SYSTEMTIME according to the slightly quirky rules in:
242 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
243
244 // If the year is set, the rule gives an absolute date:
245 if (rule.wYear)
246 return QDate(rule.wYear, rule.wMonth, rule.wDay);
247
248 // Otherwise, the rule date is annual and relative:
249 const int dayOfWeek = rule.wDayOfWeek == 0 ? 7 : rule.wDayOfWeek;
250 QDate date(year, rule.wMonth, 1);
251 Q_ASSERT(date.isValid());
252 // How many days before was last dayOfWeek before target month ?
253 int adjust = dayOfWeek - date.dayOfWeek(); // -6 <= adjust < 7
254 if (adjust >= 0) // Ensure -7 <= adjust < 0:
255 adjust -= 7;
256 // Normally, wDay is day-within-month; but here it is 1 for the first
257 // of the given dayOfWeek in the month, through 4 for the fourth or ...
258 adjust += (rule.wDay < 1 ? 1 : rule.wDay > 4 ? 5 : rule.wDay) * 7;
259 date = date.addDays(adjust);
260 // ... 5 for the last; so back up by weeks to get within the month:
261 if (date.month() != rule.wMonth) {
262 Q_ASSERT(rule.wDay > 4);
263 // (Note that, with adjust < 0, date <= 28th of our target month
264 // is guaranteed when wDay <= 4, or after our first -7 here.)
265 date = date.addDays(-7);
266 Q_ASSERT(date.month() == rule.wMonth);
267 }
268 return date;
269}
270
271// Converts a date/time value into msecs, returns true on overflow:
272inline bool timeToMSecs(QDate date, QTime time, qint64 *msecs)
273{
274 qint64 dayms = 0;
275 qint64 daySinceEpoch = date.toJulianDay() - JULIAN_DAY_FOR_EPOCH;
276 qint64 msInDay = time.msecsSinceStartOfDay();
277 if (daySinceEpoch < 0 && msInDay > 0) {
278 // In the earliest day with representable parts, take care to not
279 // underflow before an addition that would have fixed it.
280 ++daySinceEpoch;
281 msInDay -= MSECS_PER_DAY;
282 }
283 return qMulOverflow(daySinceEpoch, std::integral_constant<qint64, MSECS_PER_DAY>(), &dayms)
284 || qAddOverflow(dayms, msInDay, msecs);
285}
286
287qint64 calculateTransitionForYear(const SYSTEMTIME &rule, int year, int bias)
288{
289 // TODO Consider caching the calculated values - i.e. replace SYSTEMTIME in
290 // WinTransitionRule; do this in init() once and store the results.
291 Q_ASSERT(year);
292 const QDate date = calculateTransitionLocalDate(rule, year);
293 const QTime time = QTime(rule.wHour, rule.wMinute, rule.wSecond);
294 qint64 msecs = 0;
295 if (date.isValid() && time.isValid() && !timeToMSecs(date, time, &msecs)) {
296 // If bias pushes us outside the representable range, clip to range
297 // (overflow went past the end bias pushed us towards; and
298 // invalidMSecs() is a representable value less than minMSecs()):
299 return bias && qAddOverflow(msecs, qint64(bias) * 60000, &msecs)
300 ? (bias < 0 ? QTimeZonePrivate::minMSecs() : QTimeZonePrivate::maxMSecs())
301 : qMax(QTimeZonePrivate::minMSecs(), msecs);
302 }
303 return QTimeZonePrivate::invalidMSecs();
304}
305
306// True precisely if transition represents the start of the year.
307bool isAtStartOfYear(const SYSTEMTIME &transition, int year)
308{
309 /*
310 Note that, here, wDay identifies an instance of a given day-of-week in the
311 month, with 5 meaning last. (December 31st is, incidentally, always the
312 fifth instance of its day of the week in its month. But we aren't testing
313 that - see below.)
314
315 QDate represents Sunday by 7, SYSTEMTIME by 0; so compare day of the week
316 by taking difference mod 7.
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;
321}
322
323struct TransitionTimePair
324{
325 // Transition times, in ms:
326 qint64 std, dst;
327 // If either is invalidMSecs(), which shall then be < the other, there is no
328 // DST and the other describes a change in actual standard offset.
329 bool fakesDst = false;
330
331 TransitionTimePair(const QWinTimeZonePrivate::QWinTransitionRule &rule,
332 int year, int oldYearOffset)
333 // The local time in Daylight Time of the switch to Standard Time
334 : std(calculateTransitionForYear(rule.standardTimeRule, year,
335 rule.standardTimeBias + rule.daylightTimeBias)),
336 // The local time in Standard Time of the switch to Daylight Time
337 dst(calculateTransitionForYear(rule.daylightTimeRule, year, rule.standardTimeBias))
338 {
339 /*
340 Check for potential "fake DST", used by MS's APIs because the
341 TIME_ZONE_INFORMATION spec either expresses no transitions in the
342 year, or expresses a transition of each kind, even if standard time
343 did change in a year with no DST. We've seen year-start fake-DST
344 (whose offset matches prior standard offset, in which the previous
345 year ended).
346
347 It is possible there might also be year-end fake-DST but Bangladesh
348 toyed with DST from 2009-06-19 (a Friday) at 23:00 until, according to
349 the Olson database, 2009-12-32 24:00; however, MS represents that by
350 the last millisecond of the year, technically a millisecond early. (MS
351 falsely claims Bhutan did the same.) So we do not attempt to detect an
352 end-of-year fake transition; nor is there any reason to suppose MS
353 would need to do that, as anything it could implement thereby could
354 equally be implemented by a start-of-year fake.
355
356 A fake transition at the start of the year tells us what the offset at
357 the start of the year is; if this doesn't match the offset in effect
358 at the end of the previous year, then it's a real transition. If it
359 does match, then we have a fake transition. (A fake transition of one
360 kind at the end of the year would be paired with a real transition,
361 allegedly of the other kind, part way through the year; that would be
362 a transition away from the offset that would nominally be restored by
363 the fake so, again, the year would have started with the post-fake
364 offset in effect.)
365
366 Either the alleged standardTimeRule or the alleged daylightTimeRule
367 may be faked; either way, the transition is actually a change to the
368 current standard offset; but the unfaked half of the rule contains the
369 useful bias data, so we have to go along with its lies. Clients of
370 this class should still use DaylightTime and StandardTime as if the
371 fake were not a lie, selecting which side of the real transition to
372 use the data for, and ruleToData() will take care of extracting the
373 right offset based on that, while tagging the resulting Data as
374 standard time.
375
376 Example: Russia/Moscow
377 Format: -bias +( -stdBias, stdDate | -dstBias, dstDate ) notes
378 Last year of DST, 2010: 180 +( 0, 0-10-5 3:0 | 60, 0-3-5 2:0 ) normal DST
379 Zone change in 2011: 180 +( 0, 0-1-1 0:0 | 60 0-3-5 2:0 ) fake DST at transition
380 Fixed standard in 2012: 240 +( 0, 0-0-0 0:0 | 60, 0-0-0 0:0 ) standard time years
381 Zone change in 2014: 180 +( 0, 0-10-5 2:0 | 60, 0-1-1 0:0 ) fake DST at year-start
382 The last of these is missing on Win7 VMs (too old to know about it).
383 */
384 if (rule.standardTimeBias + rule.daylightTimeBias == oldYearOffset
385 && isAtStartOfYear(rule.daylightTimeRule, year)) {
386 dst = QTimeZonePrivate::invalidMSecs();
387 fakesDst = true;
388 }
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();
394 fakesDst = true;
395 }
396 }
397
398 bool startsInDst() const
399 {
400 // Year starts in daylightTimeRule iff it has a valid transition out of
401 // DST with no earlier valid transition into it.
402 return std != QTimeZonePrivate::invalidMSecs()
403 && (std < dst || dst == QTimeZonePrivate::invalidMSecs());
404 }
405
406 // Returns true if (assuming this pair was derived from the first rule, and
407 // that has non-zero wMonth values, so is a DST-recurrence or faking it) the
408 // given millis, presumed to be in the given year, is before the first
409 // transition into DST.
410 bool beforeInitialDst(int year, qint64 millis) const
411 {
412 return !fakesDst && (year == FIRST_DST_YEAR ? millis < dst : year < FIRST_DST_YEAR);
413 }
414
415 QTimeZonePrivate::Data ruleToData(const QWinTimeZonePrivate::QWinTransitionRule &rule,
416 const QWinTimeZonePrivate *tzp, bool isDst) const
417 {
418 const auto type = isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
419 auto time = isDst ? dst : std;
420 // The isDst we're asked for may be set to the valid one of dst and
421 // std, when fake, but not always - so make sure:
422 if (fakesDst && time == QTimeZonePrivate::invalidMSecs())
423 time = isDst ? std : dst;
424 return tzp->ruleToData(rule, time, type, fakesDst);
425 }
426};
427
428int yearEndOffset(const QWinTimeZonePrivate::QWinTransitionRule &rule, int year)
429{
430 Q_ASSERT(year);
431 int offset = rule.standardTimeBias;
432 // Only needed to help another TransitionTimePair work out year + 1's start
433 // offset; and the oldYearOffset we use only affects an alleged transition
434 // at the *start* of this year, so it doesn't matter if we guess wrong here:
435 TransitionTimePair pair(rule, year, offset);
436 if (pair.dst > pair.std)
437 offset += rule.daylightTimeBias;
438 return offset;
439}
440
441QLocale::Territory userTerritory()
442{
443 const GEOID id = GetUserGeoID(GEOCLASS_NATION);
444 wchar_t code[3];
445 const int size = GetGeoInfo(id, GEO_ISO2, code, 3, 0);
446 return (size == 3) ? QLocalePrivate::codeToTerritory(QStringView(code, size))
447 : QLocale::AnyTerritory;
448}
449
450// Index of last rule in rules with .startYear <= year, or 0 if none satisfies that:
451int ruleIndexForYear(const QList<QWinTimeZonePrivate::QWinTransitionRule> &rules, int year)
452{
453 if (rules.last().startYear <= year)
454 return rules.count() - 1;
455 // We don't have a rule for before the first, but the first is the best we can offer:
456 if (rules.first().startYear > year)
457 return 0;
458
459 // Otherwise, use binary chop:
460 int lo = 0, hi = rules.count();
461 // invariant: rules[i].startYear <= year < rules[hi].startYear
462 // subject to treating rules[rules.count()] as "off the end of time"
463 while (lo + 1 < hi) {
464 const int mid = (lo + hi) / 2;
465 // lo + 2 <= hi, so lo + 1 <= mid <= hi - 1, so lo < mid < hi
466 // In particular, mid < rules.count()
467 const int midYear = rules.at(mid).startYear;
468 if (midYear > year)
469 hi = mid;
470 else if (midYear < year)
471 lo = mid;
472 else // No two rules have the same startYear:
473 return mid;
474 }
475 return lo;
476}
477
478} // anonymous namespace
479
480// Create the system default time zone
481QWinTimeZonePrivate::QWinTimeZonePrivate()
482 : QTimeZonePrivate()
483{
484 init(QByteArray());
485}
486
487// Create a named time zone
488QWinTimeZonePrivate::QWinTimeZonePrivate(const QByteArray &ianaId)
489 : QTimeZonePrivate()
490{
491 init(ianaId);
492}
493
494QWinTimeZonePrivate::~QWinTimeZonePrivate()
495{
496}
497
498QWinTimeZonePrivate *QWinTimeZonePrivate::clone() const
499{
500 return new QWinTimeZonePrivate(*this);
501}
502
503void QWinTimeZonePrivate::init(const QByteArray &ianaId)
504{
505 if (ianaId.isEmpty()) {
506 m_windowsId = windowsSystemZoneId();
507 m_id = systemTimeZoneId();
508 } else {
509 m_windowsId = ianaIdToWindowsId(ianaId);
510 m_id = ianaId;
511 }
512 const auto initialYear = [](const QWinTransitionRule &rule) {
513 // Only applicable to the first rule, and only if not faking DST.
514 // The rule starts in FIRST_DST_YEAR if it is a DST recurrence (with
515 // non-zero wMonth fields), otherwise read as a constant
516 // offset rule dating back to the start of time.
517 return (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0
518 ? FIRST_DST_YEAR : int(QDateTime::YearRange::First));
519 };
520
521 bool badMonth = false; // Only warn once per zone, if at all.
522 if (!m_windowsId.isEmpty()) {
523 // Open the base TZI for the time zone
524 const QString baseKeyPath = QString::fromWCharArray(tzRegPath) + u'\\'
525 + QString::fromUtf8(m_windowsId);
526 QWinRegistryKey baseKey(HKEY_LOCAL_MACHINE, baseKeyPath);
527 if (baseKey.isValid()) {
528 // Load the localized names
529 m_displayName = baseKey.stringValue(L"Display");
530 m_standardName = baseKey.stringValue(L"Std");
531 m_daylightName = baseKey.stringValue(L"Dlt");
532 // On Vista and later the optional dynamic key holds historic data
533 const QString dynamicKeyPath = baseKeyPath + "\\Dynamic DST"_L1;
534 QWinRegistryKey dynamicKey(HKEY_LOCAL_MACHINE, dynamicKeyPath);
535 if (dynamicKey.isValid()) {
536 // Find out the start and end years stored, then iterate over them
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) {
540 bool ruleOk;
541 QWinTransitionRule rule = readRegistryRule(dynamicKey,
542 qt_castToWchar(QString::number(year)),
543 &ruleOk);
544 if (ruleOk
545 // Don't repeat a recurrent rule:
546 && (m_tranRules.isEmpty()
547 || !isSameRule(m_tranRules.last(), rule))) {
548 if (!badMonth
549 && (rule.standardTimeRule.wMonth == 0)
550 != (rule.daylightTimeRule.wMonth == 0)) {
551 badMonth = true;
552 qWarning("MS registry TZ API violated its wMonth constraint;"
553 "this may cause mistakes for %s from %d",
554 ianaId.constData(), year);
555 }
556 const TransitionTimePair pair(rule, year, rule.standardTimeBias);
557 // First rule may be a standard offset change, for which fakesDst is true.
558 rule.startYear
559 = m_tranRules.size() || pair.fakesDst ? year : initialYear(rule);
560 m_tranRules.append(rule);
561 }
562 }
563 } else {
564 // No dynamic data so use the base data
565 bool ruleOk;
566 QWinTransitionRule rule = readRegistryRule(baseKey, L"TZI", &ruleOk);
567 if (ruleOk) {
568 rule.startYear = initialYear(rule);
569 m_tranRules.append(rule);
570 }
571 }
572 }
573 }
574
575 // If there are no rules then we failed to find a windowsId or any tzi info
576 if (m_tranRules.size() == 0) {
577 m_id.clear();
578 m_windowsId.clear();
579 m_displayName.clear();
580 } else if (m_id.isEmpty()) {
581 m_id = m_standardName.toUtf8();
582 }
583}
584
585QString QWinTimeZonePrivate::comment() const
586{
587 return m_displayName;
588}
589
590QString QWinTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
591 QTimeZone::NameType nameType,
592 const QLocale &locale) const
593{
594 // Registry gave us long names for the system locale:
595 if (nameType == QTimeZone::LongName && locale == QLocale::system()) {
596 switch (timeType) {
597 case QTimeZone::DaylightTime :
598 return m_daylightName;
599 case QTimeZone::GenericTime :
600 return m_displayName;
601 case QTimeZone::StandardTime :
602 return m_standardName;
603 }
604 }
605 // Fall back to base class for everything else.
606 return QTimeZonePrivate::displayName(timeType, nameType, locale);;
607}
608
609QString QWinTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
610{
611 return data(atMSecsSinceEpoch).abbreviation;
612}
613
614int QWinTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
615{
616 return data(atMSecsSinceEpoch).offsetFromUtc;
617}
618
619int QWinTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
620{
621 return data(atMSecsSinceEpoch).standardTimeOffset;
622}
623
624int QWinTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
625{
626 return data(atMSecsSinceEpoch).daylightTimeOffset;
627}
628
629bool QWinTimeZonePrivate::hasDaylightTime() const
630{
631 return hasTransitions();
632}
633
634bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
635{
636 return (data(atMSecsSinceEpoch).daylightTimeOffset != 0);
637}
638
639QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
640{
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)) {
648 // No transition (or before first rule), no DST, use the rule's standard time.
649 return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime);
650 }
651
652 int prior = year == 1 ? -1 : year - 1; // No year 0.
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);
659 bool isDst = false;
660 if (ruleIndex == 0 && pair.beforeInitialDst(year, forMSecsSinceEpoch)) {
661 // We're before DST first started and have no earlier rule that
662 // might give better data on this year, so just extrapolate
663 // standard time backwards.
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) {
667 isDst = true;
668 } else {
669 year = prior; // Try an earlier year for this rule (once).
670 prior = year == 1 ? -1 : year - 1; // No year 0.
671 continue;
672 }
673 return ruleToData(rule, forMSecsSinceEpoch,
674 isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime,
675 pair.fakesDst);
676 }
677 // We can only fall off the end of that loop if endYear is rule.startYear:
678 Q_ASSERT(year < rule.startYear);
679 // Fell off start of rule, try previous rule.
680 }
681 // We don't have relevant data :-(
682 return {};
683}
684
685bool QWinTimeZonePrivate::hasTransitions() const
686{
687 for (const QWinTransitionRule &rule : m_tranRules) {
688 if (rule.standardTimeRule.wMonth > 0 && rule.daylightTimeRule.wMonth > 0)
689 return true;
690 }
691 return false;
692}
693
694QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
695{
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);
701 // Does this rule's period include any transition at all ?
702 if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
703 int prior = year == 1 ? -1 : year - 1; // No year 0.
704 if (newYearOffset == invalidSeconds()) {
705 // First rule tried. (Will revise newYearOffset before any
706 // fall-back to a later rule.)
707 newYearOffset = (prior < rule.startYear && ruleIndex > 0)
708 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
709 : yearEndOffset(rule, prior);
710 }
711 if (year < rule.startYear) {
712 // Either before first rule's start, or we fell off the end of
713 // the rule for year because afterMSecsSinceEpoch is after any
714 // transitions in it. Find first transition in this rule.
715 TransitionTimePair pair(rule, rule.startYear, newYearOffset);
716 // First transition is to DST precisely if the year started in
717 // standard time. If the year is FIRST_DST_YEAR or earlier, it
718 // definitely started in standard time.
719 return pair.ruleToData(rule, this, !(year > FIRST_DST_YEAR && pair.startsInDst()));
720 }
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);
725 bool isDst = false;
726 Q_ASSERT(invalidMSecs() <= afterMSecsSinceEpoch); // invalid is min qint64
727 if (ruleIndex == 0 && pair.beforeInitialDst(year, afterMSecsSinceEpoch)) {
728 // This is an initial recurrence rule, whose startYear
729 // (which we know is <= year) is FIRST_DST_YEAR:
730 Q_ASSERT(year == FIRST_DST_YEAR);
731 // This year's DST transition is the first ever DST
732 // transition, and we're before it. The transition back to
733 // standard time is a lie unless the DST one comes before
734 // it; either way, the DST one is next.
735 isDst = true;
736 } else if (pair.std > afterMSecsSinceEpoch) {
737 isDst = pair.std > pair.dst && pair.dst > afterMSecsSinceEpoch;
738 } else if (pair.dst > afterMSecsSinceEpoch) {
739 isDst = true;
740 } else {
741 newYearOffset = rule.standardTimeBias;
742 if (pair.dst > pair.std)
743 newYearOffset += rule.daylightTimeBias;
744 // Try a later year for this rule (once).
745 prior = year;
746 year = year == -1 ? 1 : year + 1; // No year 0
747 continue;
748 }
749
750 return pair.ruleToData(rule, this, isDst);
751 }
752 // Fell off end of rule, try next rule.
753 } else {
754 // No transition during rule's period. If this is our first rule,
755 // record its standard time as newYearOffset for the next rule;
756 // otherwise, it should be consistent with what we have.
757 if (newYearOffset == invalidSeconds())
758 newYearOffset = rule.standardTimeBias;
759 else
760 Q_ASSERT(newYearOffset == rule.standardTimeBias);
761 }
762 }
763 // Apparently no transition after the given time:
764 return {};
765}
766
767QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
768{
769 if (beforeMSecsSinceEpoch <= minMSecs())
770 return {};
771
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);
777 // Does this rule's period include any transition at all ?
778 if (year >= rule.startYear
779 && (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0)) {
780 int prior = year == 1 ? -1 : year - 1; // No year 0.
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);
787 // A recurrent DST rule, before DST first started, is a lie:
788 // fake a first transition at the start of time, as for the
789 // other (ruleIndex == 0) case below. Same applies to first
790 // instant of DST; there is no prior (real) transition.
791 if (ruleIndex == 0 && pair.beforeInitialDst(year, beforeMSecsSinceEpoch - 1))
792 return ruleToData(rule, minMSecs(), QTimeZone::StandardTime, false);
793
794 bool isDst = 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) {
798 isDst = true;
799 } else {
800 year = prior; // Try an earlier year for this rule (once).
801 prior = year == 1 ? -1 : year - 1; // No year 0.
802 continue;
803 }
804 return pair.ruleToData(rule, this, isDst);
805 }
806 // Fell off start of rule, try previous rule.
807 } else if (ruleIndex == 0) {
808 // Describe time before the first transition in terms of a fictional
809 // transition at the start of time, so that a scan through all rules
810 // *does* see a first rule that supplies the offset for such times:
811 return ruleToData(rule, minMSecs(), QTimeZone::StandardTime, false);
812 } // else: no transition during rule's period
813 if (year >= rule.startYear) {
814 year = rule.startYear - 1; // Seek last transition in new rule
815 if (!year)
816 --year;
817 }
818 }
819 // Apparently no transition before the given time:
820 return {};
821}
822
823QByteArray QWinTimeZonePrivate::systemTimeZoneId() const
824{
825 const QLocale::Territory territory = userTerritory();
826 const QByteArray windowsId = windowsSystemZoneId();
827 QByteArray ianaId;
828 // If we have a real territory, then try get a specific match for that territory
829 if (territory != QLocale::AnyTerritory)
830 ianaId = windowsIdToDefaultIanaId(windowsId, territory);
831 // If we don't have a real territory, or there wasn't a specific match, try the global default
832 if (ianaId.isEmpty())
833 ianaId = windowsIdToDefaultIanaId(windowsId);
834 return ianaId;
835}
836
837QList<QByteArray> QWinTimeZonePrivate::availableTimeZoneIds() const
838{
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());
846 return result;
847 }();
848 return cache;
849}
850
851QTimeZonePrivate::Data QWinTimeZonePrivate::ruleToData(const QWinTransitionRule &rule,
852 qint64 atMSecsSinceEpoch,
853 QTimeZone::TimeType type,
854 bool fakeDst) const
855{
856 Data tran;
857 tran.atMSecsSinceEpoch = atMSecsSinceEpoch;
858 tran.standardTimeOffset = rule.standardTimeBias * -60;
859 if (fakeDst) {
860 tran.daylightTimeOffset = 0;
861 // Rule may claim we're in DST when it's actually a standard time change:
862 if (type == QTimeZone::DaylightTime)
863 tran.standardTimeOffset += rule.daylightTimeBias * -60;
864 } else if (type == QTimeZone::DaylightTime) {
865 tran.daylightTimeOffset = rule.daylightTimeBias * -60;
866 } else {
867 tran.daylightTimeOffset = 0;
868 }
869 tran.offsetFromUtc = tran.standardTimeOffset + tran.daylightTimeOffset;
870 tran.abbreviation = localeName(atMSecsSinceEpoch, tran.offsetFromUtc,
871 type, QTimeZone::ShortName, QLocale::system());
872 return tran;
873}
874
875QT_END_NAMESPACE
\inmodule QtCore \reentrant
Definition qdatetime.h:29
QT_REQUIRE_CONFIG(timezone_locale)
#define MAX_KEY_LENGTH
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