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