Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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 static const QList<QByteArray> cache = [] {
190 QList<QByteArray> list;
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 return cache;
207}
208
209QByteArray windowsSystemZoneId()
210{
211 // On Vista and later is held in the value TimeZoneKeyName in key currTzRegPath
212 const QString id = QWinRegistryKey(HKEY_LOCAL_MACHINE, currTzRegPath)
213 .stringValue(L"TimeZoneKeyName");
214 if (!id.isEmpty())
215 return id.toUtf8();
216
217 // On XP we have to iterate over the zones until we find a match on
218 // names/offsets with the current data
219 TIME_ZONE_INFORMATION sysTzi;
220 GetTimeZoneInformation(&sysTzi);
221 bool ok = false;
222 const auto winIds = availableWindowsIds();
223 for (const QByteArray &winId : winIds) {
224 if (equalTzi(getRegistryTzi(winId, &ok), sysTzi))
225 return winId;
226 }
227
228 // If we can't determine the current ID use UTC
230}
231
232QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year)
233{
234 // If month is 0 then there is no date
235 if (rule.wMonth == 0)
236 return QDate();
237
238 // Interpret SYSTEMTIME according to the slightly quirky rules in:
239 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
240
241 // If the year is set, the rule gives an absolute date:
242 if (rule.wYear)
243 return QDate(rule.wYear, rule.wMonth, rule.wDay);
244
245 // Otherwise, the rule date is annual and relative:
246 const int dayOfWeek = rule.wDayOfWeek == 0 ? 7 : rule.wDayOfWeek;
247 QDate date(year, rule.wMonth, 1);
249 // How many days before was last dayOfWeek before target month ?
250 int adjust = dayOfWeek - date.dayOfWeek(); // -6 <= adjust < 7
251 if (adjust >= 0) // Ensure -7 <= adjust < 0:
252 adjust -= 7;
253 // Normally, wDay is day-within-month; but here it is 1 for the first
254 // of the given dayOfWeek in the month, through 4 for the fourth or ...
255 adjust += (rule.wDay < 1 ? 1 : rule.wDay > 4 ? 5 : rule.wDay) * 7;
256 date = date.addDays(adjust);
257 // ... 5 for the last; so back up by weeks to get within the month:
258 if (date.month() != rule.wMonth) {
259 Q_ASSERT(rule.wDay > 4);
260 // (Note that, with adjust < 0, date <= 28th of our target month
261 // is guaranteed when wDay <= 4, or after our first -7 here.)
262 date = date.addDays(-7);
263 Q_ASSERT(date.month() == rule.wMonth);
264 }
265 return date;
266}
267
268// Converts a date/time value into msecs, returns true on overflow:
269inline bool timeToMSecs(QDate date, QTime time, qint64 *msecs)
270{
271 qint64 dayms = 0;
272 qint64 daySinceEpoch = date.toJulianDay() - JULIAN_DAY_FOR_EPOCH;
273 qint64 msInDay = time.msecsSinceStartOfDay();
274 if (daySinceEpoch < 0 && msInDay > 0) {
275 // In the earliest day with representable parts, take care to not
276 // underflow before an addition that would have fixed it.
277 ++daySinceEpoch;
278 msInDay -= MSECS_PER_DAY;
279 }
280 return qMulOverflow(daySinceEpoch, std::integral_constant<qint64, MSECS_PER_DAY>(), &dayms)
281 || qAddOverflow(dayms, msInDay, msecs);
282}
283
284qint64 calculateTransitionForYear(const SYSTEMTIME &rule, int year, int bias)
285{
286 // TODO Consider caching the calculated values - i.e. replace SYSTEMTIME in
287 // WinTransitionRule; do this in init() once and store the results.
288 Q_ASSERT(year);
289 const QDate date = calculateTransitionLocalDate(rule, year);
290 const QTime time = QTime(rule.wHour, rule.wMinute, rule.wSecond);
291 qint64 msecs = 0;
292 if (date.isValid() && time.isValid() && !timeToMSecs(date, time, &msecs)) {
293 // If bias pushes us outside the representable range, clip to range
294 // (overflow went past the end bias pushed us towards; and
295 // invalidMSecs() is a representable value less than minMSecs()):
296 return bias && qAddOverflow(msecs, qint64(bias) * 60000, &msecs)
297 ? (bias < 0 ? QTimeZonePrivate::minMSecs() : QTimeZonePrivate::maxMSecs())
298 : qMax(QTimeZonePrivate::minMSecs(), msecs);
299 }
301}
302
303// True precisely if transition represents the start of the year.
304bool isAtStartOfYear(const SYSTEMTIME &transition, int year)
305{
306 /*
307 Note that, here, wDay identifies an instance of a given day-of-week in the
308 month, with 5 meaning last. (December 31st is, incidentally, always the
309 fifth instance of its day of the week in its month. But we aren't testing
310 that - see below.)
311
312 QDate represents Sunday by 7, SYSTEMTIME by 0; so compare day of the week
313 by taking difference mod 7.
314 */
315 return transition.wMonth == 1 && transition.wDay == 1
316 && (QDate(year, 1, 1).dayOfWeek() - transition.wDayOfWeek) % 7 == 0
317 && transition.wHour == 0 && transition.wMinute == 0 && transition.wSecond == 0;
318}
319
320struct TransitionTimePair
321{
322 // Transition times, in ms:
323 qint64 std, dst;
324 // If either is invalidMSecs(), which shall then be < the other, there is no
325 // DST and the other describes a change in actual standard offset.
326 bool fakesDst = false;
327
328 TransitionTimePair(const QWinTimeZonePrivate::QWinTransitionRule &rule,
329 int year, int oldYearOffset)
330 // The local time in Daylight Time of the switch to Standard Time
331 : std(calculateTransitionForYear(rule.standardTimeRule, year,
332 rule.standardTimeBias + rule.daylightTimeBias)),
333 // The local time in Standard Time of the switch to Daylight Time
334 dst(calculateTransitionForYear(rule.daylightTimeRule, year, rule.standardTimeBias))
335 {
336 /*
337 Check for potential "fake DST", used by MS's APIs because the
338 TIME_ZONE_INFORMATION spec either expresses no transitions in the
339 year, or expresses a transition of each kind, even if standard time
340 did change in a year with no DST. We've seen year-start fake-DST
341 (whose offset matches prior standard offset, in which the previous
342 year ended).
343
344 It is possible there might also be year-end fake-DST but Bangladesh
345 toyed with DST from 2009-06-19 (a Friday) at 23:00 until, according to
346 the Olson database, 2009-12-32 24:00; however, MS represents that by
347 the last millisecond of the year, technically a millisecond early. (MS
348 falsely claims Bhutan did the same.) So we do not attempt to detect an
349 end-of-year fake transition; nor is there any reason to suppose MS
350 would need to do that, as anything it could implement thereby could
351 equally be implemented by a start-of-year fake.
352
353 A fake transition at the start of the year tells us what the offset at
354 the start of the year is; if this doesn't match the offset in effect
355 at the end of the previous year, then it's a real transition. If it
356 does match, then we have a fake transition. (A fake transition of one
357 kind at the end of the year would be paired with a real transition,
358 allegedly of the other kind, part way through the year; that would be
359 a transition away from the offset that would nominally be restored by
360 the fake so, again, the year would have started with the post-fake
361 offset in effect.)
362
363 Either the alleged standardTimeRule or the alleged daylightTimeRule
364 may be faked; either way, the transition is actually a change to the
365 current standard offset; but the unfaked half of the rule contains the
366 useful bias data, so we have to go along with its lies. Clients of
367 this class should still use DaylightTime and StandardTime as if the
368 fake were not a lie, selecting which side of the real transition to
369 use the data for, and ruleToData() will take care of extracting the
370 right offset based on that, while tagging the resulting Data as
371 standard time.
372
373 Example: Russia/Moscow
374 Format: -bias +( -stdBias, stdDate | -dstBias, dstDate ) notes
375 Last year of DST, 2010: 180 +( 0, 0-10-5 3:0 | 60, 0-3-5 2:0 ) normal DST
376 Zone change in 2011: 180 +( 0, 0-1-1 0:0 | 60 0-3-5 2:0 ) fake DST at transition
377 Fixed standard in 2012: 240 +( 0, 0-0-0 0:0 | 60, 0-0-0 0:0 ) standard time years
378 Zone change in 2014: 180 +( 0, 0-10-5 2:0 | 60, 0-1-1 0:0 ) fake DST at year-start
379 The last of these is missing on Win7 VMs (too old to know about it).
380 */
381 if (rule.standardTimeBias + rule.daylightTimeBias == oldYearOffset
382 && isAtStartOfYear(rule.daylightTimeRule, year)) {
384 fakesDst = true;
385 }
386 if (rule.standardTimeBias == oldYearOffset
387 && isAtStartOfYear(rule.standardTimeRule, year)) {
388 Q_ASSERT_X(!fakesDst, "TransitionTimePair",
389 "Year with (DST bias zero and) both transitions fake !");
391 fakesDst = true;
392 }
393 }
394
395 bool startsInDst() const
396 {
397 // Year starts in daylightTimeRule iff it has a valid transition out of
398 // DST with no earlier valid transition into it.
401 }
402
403 // Returns true if (assuming this pair was derived from the first rule, and
404 // that has non-zero wMonth values, so is a DST-recurrence or faking it) the
405 // given millis, presumed to be in the given year, is before the first
406 // transition into DST.
407 bool beforeInitialDst(int year, qint64 millis) const
408 {
409 return !fakesDst && (year == FIRST_DST_YEAR ? millis < dst : year < FIRST_DST_YEAR);
410 }
411
412 QTimeZonePrivate::Data ruleToData(const QWinTimeZonePrivate::QWinTransitionRule &rule,
413 const QWinTimeZonePrivate *tzp, bool isDst) const
414 {
415 const auto type = isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime;
416 auto time = isDst ? dst : std;
417 // The isDst we're asked for may be set to the valid one of dst and
418 // std, when fake, but not always - so make sure:
419 if (fakesDst && time == QTimeZonePrivate::invalidMSecs())
420 time = isDst ? std : dst;
421 return tzp->ruleToData(rule, time, type, fakesDst);
422 }
423};
424
425int yearEndOffset(const QWinTimeZonePrivate::QWinTransitionRule &rule, int year)
426{
427 Q_ASSERT(year);
428 int offset = rule.standardTimeBias;
429 // Only needed to help another TransitionTimePair work out year + 1's start
430 // offset; and the oldYearOffset we use only affects an alleged transition
431 // at the *start* of this year, so it doesn't matter if we guess wrong here:
432 TransitionTimePair pair(rule, year, offset);
433 if (pair.dst > pair.std)
434 offset += rule.daylightTimeBias;
435 return offset;
436}
437
438QLocale::Territory userTerritory()
439{
440 const GEOID id = GetUserGeoID(GEOCLASS_NATION);
441 wchar_t code[3];
442 const int size = GetGeoInfo(id, GEO_ISO2, code, 3, 0);
444 : QLocale::AnyTerritory;
445}
446
447// Index of last rule in rules with .startYear <= year, or 0 if none satisfies that:
448int ruleIndexForYear(const QList<QWinTimeZonePrivate::QWinTransitionRule> &rules, int year)
449{
450 if (rules.last().startYear <= year)
451 return rules.count() - 1;
452 // We don't have a rule for before the first, but the first is the best we can offer:
453 if (rules.first().startYear > year)
454 return 0;
455
456 // Otherwise, use binary chop:
457 int lo = 0, hi = rules.count();
458 // invariant: rules[i].startYear <= year < rules[hi].startYear
459 // subject to treating rules[rules.count()] as "off the end of time"
460 while (lo + 1 < hi) {
461 const int mid = (lo + hi) / 2;
462 // lo + 2 <= hi, so lo + 1 <= mid <= hi - 1, so lo < mid < hi
463 // In particular, mid < rules.count()
464 const int midYear = rules.at(mid).startYear;
465 if (midYear > year)
466 hi = mid;
467 else if (midYear < year)
468 lo = mid;
469 else // No two rules have the same startYear:
470 return mid;
471 }
472 return lo;
473}
474
475} // anonymous namespace
476
477// Create the system default time zone
478QWinTimeZonePrivate::QWinTimeZonePrivate()
480{
481 init(QByteArray());
482}
483
484// Create a named time zone
485QWinTimeZonePrivate::QWinTimeZonePrivate(const QByteArray &ianaId)
487{
488 init(ianaId);
489}
490
491QWinTimeZonePrivate::QWinTimeZonePrivate(const QWinTimeZonePrivate &other)
492 : QTimeZonePrivate(other), m_windowsId(other.m_windowsId),
493 m_displayName(other.m_displayName), m_standardName(other.m_standardName),
494 m_daylightName(other.m_daylightName), m_tranRules(other.m_tranRules)
495{
496}
497
498QWinTimeZonePrivate::~QWinTimeZonePrivate()
499{
500}
501
502QWinTimeZonePrivate *QWinTimeZonePrivate::clone() const
503{
504 return new QWinTimeZonePrivate(*this);
505}
506
507void QWinTimeZonePrivate::init(const QByteArray &ianaId)
508{
509 if (ianaId.isEmpty()) {
510 m_windowsId = windowsSystemZoneId();
511 m_id = systemTimeZoneId();
512 } else {
513 m_windowsId = ianaIdToWindowsId(ianaId);
514 m_id = ianaId;
515 }
516 const auto initialYear = [](const QWinTransitionRule &rule) {
517 // Only applicable to the first rule, and only if not faking DST.
518 // The rule starts in FIRST_DST_YEAR if it is a DST recurrence (with
519 // non-zero wMonth fields), otherwise read as a constant
520 // offset rule dating back to the start of time.
521 return (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0
522 ? FIRST_DST_YEAR : int(QDateTime::YearRange::First));
523 };
524
525 bool badMonth = false; // Only warn once per zone, if at all.
526 if (!m_windowsId.isEmpty()) {
527 // Open the base TZI for the time zone
528 const QString baseKeyPath = QString::fromWCharArray(tzRegPath) + u'\\'
529 + QString::fromUtf8(m_windowsId);
530 QWinRegistryKey baseKey(HKEY_LOCAL_MACHINE, baseKeyPath);
531 if (baseKey.isValid()) {
532 // Load the localized names
533 m_displayName = baseKey.stringValue(L"Display");
534 m_standardName = baseKey.stringValue(L"Std");
535 m_daylightName = baseKey.stringValue(L"Dlt");
536 // On Vista and later the optional dynamic key holds historic data
537 const QString dynamicKeyPath = baseKeyPath + "\\Dynamic DST"_L1;
538 QWinRegistryKey dynamicKey(HKEY_LOCAL_MACHINE, dynamicKeyPath);
539 if (dynamicKey.isValid()) {
540 // Find out the start and end years stored, then iterate over them
541 const auto startYear = dynamicKey.dwordValue(L"FirstEntry");
542 const auto endYear = dynamicKey.dwordValue(L"LastEntry");
543 for (int year = int(startYear.first); year <= int(endYear.first); ++year) {
544 bool ruleOk;
545 QWinTransitionRule rule =
546 readRegistryRule(dynamicKey,
547 reinterpret_cast<LPCWSTR>(QString::number(year).utf16()),
548 &ruleOk);
549 if (ruleOk
550 // Don't repeat a recurrent rule:
551 && (m_tranRules.isEmpty()
552 || !isSameRule(m_tranRules.last(), rule))) {
553 if (!badMonth
554 && (rule.standardTimeRule.wMonth == 0)
555 != (rule.daylightTimeRule.wMonth == 0)) {
556 badMonth = true;
557 qWarning("MS registry TZ API violated its wMonth constraint;"
558 "this may cause mistakes for %s from %d",
559 ianaId.constData(), year);
560 }
561 const TransitionTimePair pair(rule, year, rule.standardTimeBias);
562 // First rule may be a standard offset change, for which fakesDst is true.
563 rule.startYear
564 = m_tranRules.size() || pair.fakesDst ? year : initialYear(rule);
565 m_tranRules.append(rule);
566 }
567 }
568 } else {
569 // No dynamic data so use the base data
570 bool ruleOk;
571 QWinTransitionRule rule = readRegistryRule(baseKey, L"TZI", &ruleOk);
572 if (ruleOk) {
573 rule.startYear = initialYear(rule);
574 m_tranRules.append(rule);
575 }
576 }
577 }
578 }
579
580 // If there are no rules then we failed to find a windowsId or any tzi info
581 if (m_tranRules.size() == 0) {
582 m_id.clear();
583 m_windowsId.clear();
584 m_displayName.clear();
585 } else if (m_id.isEmpty()) {
586 m_id = m_standardName.toUtf8();
587 }
588}
589
590QString QWinTimeZonePrivate::comment() const
591{
592 return m_displayName;
593}
594
595QString QWinTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
596 QTimeZone::NameType nameType,
597 const QLocale &locale) const
598{
599 // TODO Registry holds MUI keys, should be able to look up translations?
600 Q_UNUSED(locale);
601
602 if (nameType == QTimeZone::OffsetName) {
603 const QWinTransitionRule &rule =
604 m_tranRules.at(ruleIndexForYear(m_tranRules, QDate::currentDate().year()));
605 int offset = rule.standardTimeBias;
606 if (timeType == QTimeZone::DaylightTime)
607 offset += rule.daylightTimeBias;
608 return isoOffsetFormat(offset * -60);
609 }
610
611 switch (timeType) {
612 case QTimeZone::DaylightTime :
613 return m_daylightName;
614 case QTimeZone::GenericTime :
615 return m_displayName;
616 case QTimeZone::StandardTime :
617 return m_standardName;
618 }
619 return m_standardName;
620}
621
622QString QWinTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
623{
624 return data(atMSecsSinceEpoch).abbreviation;
625}
626
627int QWinTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
628{
629 return data(atMSecsSinceEpoch).offsetFromUtc;
630}
631
632int QWinTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
633{
634 return data(atMSecsSinceEpoch).standardTimeOffset;
635}
636
637int QWinTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
638{
639 return data(atMSecsSinceEpoch).daylightTimeOffset;
640}
641
642bool QWinTimeZonePrivate::hasDaylightTime() const
643{
644 return hasTransitions();
645}
646
647bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
648{
649 return (data(atMSecsSinceEpoch).daylightTimeOffset != 0);
650}
651
652QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
653{
654 int year = msecsToDate(forMSecsSinceEpoch).year();
655 for (int ruleIndex = ruleIndexForYear(m_tranRules, year);
656 ruleIndex >= 0; --ruleIndex) {
657 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
658 Q_ASSERT(ruleIndex == 0 || year >= rule.startYear);
659 if (year < rule.startYear
660 || !(rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0)) {
661 // No transition (or before first rule), no DST, use the rule's standard time.
662 return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime);
663 }
664
665 int prior = year == 1 ? -1 : year - 1; // No year 0.
666 const int endYear = qMax(rule.startYear, prior);
667 while (year >= endYear) {
668 const int newYearOffset = (prior < rule.startYear && ruleIndex > 0)
669 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
670 : yearEndOffset(rule, prior);
671 const TransitionTimePair pair(rule, year, newYearOffset);
672 bool isDst = false;
673 if (ruleIndex == 0 && pair.beforeInitialDst(year, forMSecsSinceEpoch)) {
674 // We're before DST first started and have no earlier rule that
675 // might give better data on this year, so just extrapolate
676 // standard time backwards.
677 } else if (pair.std != invalidMSecs() && pair.std <= forMSecsSinceEpoch) {
678 isDst = pair.std < pair.dst && pair.dst <= forMSecsSinceEpoch;
679 } else if (pair.dst != invalidMSecs() && pair.dst <= forMSecsSinceEpoch) {
680 isDst = true;
681 } else {
682 year = prior; // Try an earlier year for this rule (once).
683 prior = year == 1 ? -1 : year - 1; // No year 0.
684 continue;
685 }
686 return ruleToData(rule, forMSecsSinceEpoch,
687 isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime,
688 pair.fakesDst);
689 }
690 // We can only fall off the end of that loop if endYear is rule.startYear:
691 Q_ASSERT(year < rule.startYear);
692 // Fell off start of rule, try previous rule.
693 }
694 // We don't have relevant data :-(
695 return {};
696}
697
698bool QWinTimeZonePrivate::hasTransitions() const
699{
700 for (const QWinTransitionRule &rule : m_tranRules) {
701 if (rule.standardTimeRule.wMonth > 0 && rule.daylightTimeRule.wMonth > 0)
702 return true;
703 }
704 return false;
705}
706
707QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
708{
709 int year = msecsToDate(afterMSecsSinceEpoch).year();
710 int newYearOffset = invalidSeconds();
711 for (int ruleIndex = ruleIndexForYear(m_tranRules, year);
712 ruleIndex < m_tranRules.count(); ++ruleIndex) {
713 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
714 // Does this rule's period include any transition at all ?
715 if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
716 int prior = year == 1 ? -1 : year - 1; // No year 0.
717 if (newYearOffset == invalidSeconds()) {
718 // First rule tried. (Will revise newYearOffset before any
719 // fall-back to a later rule.)
720 newYearOffset = (prior < rule.startYear && ruleIndex > 0)
721 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
722 : yearEndOffset(rule, prior);
723 }
724 if (year < rule.startYear) {
725 // Either before first rule's start, or we fell off the end of
726 // the rule for year because afterMSecsSinceEpoch is after any
727 // transitions in it. Find first transition in this rule.
728 TransitionTimePair pair(rule, rule.startYear, newYearOffset);
729 // First transition is to DST precisely if the year started in
730 // standard time. If the year is FIRST_DST_YEAR or earlier, it
731 // definitely started in standard time.
732 return pair.ruleToData(rule, this, !(year > FIRST_DST_YEAR && pair.startsInDst()));
733 }
734 const int endYear = ruleIndex + 1 < m_tranRules.count()
735 ? qMin(m_tranRules.at(ruleIndex + 1).startYear, year + 2) : (year + 2);
736 while (year < endYear) {
737 const TransitionTimePair pair(rule, year, newYearOffset);
738 bool isDst = false;
739 Q_ASSERT(invalidMSecs() <= afterMSecsSinceEpoch); // invalid is min qint64
740 if (ruleIndex == 0 && pair.beforeInitialDst(year, afterMSecsSinceEpoch)) {
741 // This is an initial recurrence rule, whose startYear
742 // (which we know is <= year) is FIRST_DST_YEAR:
743 Q_ASSERT(year == FIRST_DST_YEAR);
744 // This year's DST transition is the first ever DST
745 // transition, and we're before it. The transition back to
746 // standard time is a lie unless the DST one comes before
747 // it; either way, the DST one is next.
748 isDst = true;
749 } else if (pair.std > afterMSecsSinceEpoch) {
750 isDst = pair.std > pair.dst && pair.dst > afterMSecsSinceEpoch;
751 } else if (pair.dst > afterMSecsSinceEpoch) {
752 isDst = true;
753 } else {
754 newYearOffset = rule.standardTimeBias;
755 if (pair.dst > pair.std)
756 newYearOffset += rule.daylightTimeBias;
757 // Try a later year for this rule (once).
758 prior = year;
759 year = year == -1 ? 1 : year + 1; // No year 0
760 continue;
761 }
762
763 return pair.ruleToData(rule, this, isDst);
764 }
765 // Fell off end of rule, try next rule.
766 } else {
767 // No transition during rule's period. If this is our first rule,
768 // record its standard time as newYearOffset for the next rule;
769 // otherwise, it should be consistent with what we have.
770 if (newYearOffset == invalidSeconds())
771 newYearOffset = rule.standardTimeBias;
772 else
773 Q_ASSERT(newYearOffset == rule.standardTimeBias);
774 }
775 }
776 // Apparently no transition after the given time:
777 return {};
778}
779
780QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
781{
782 if (beforeMSecsSinceEpoch <= minMSecs())
783 return {};
784
785 int year = msecsToDate(beforeMSecsSinceEpoch).year();
786 for (int ruleIndex = ruleIndexForYear(m_tranRules, year);
787 ruleIndex >= 0; --ruleIndex) {
788 const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
789 Q_ASSERT(ruleIndex == 0 || year >= rule.startYear);
790 // Does this rule's period include any transition at all ?
791 if (year >= rule.startYear
792 && (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0)) {
793 int prior = year == 1 ? -1 : year - 1; // No year 0.
794 const int endYear = qMax(rule.startYear, prior);
795 while (year >= endYear) {
796 const int newYearOffset = (prior < rule.startYear && ruleIndex > 0)
797 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
798 : yearEndOffset(rule, prior);
799 const TransitionTimePair pair(rule, year, newYearOffset);
800 // A recurrent DST rule, before DST first started, is a lie:
801 // fake a first transition at the start of time, as for the
802 // other (ruleIndex == 0) case below. Same applies to first
803 // instant of DST; there is no prior (real) transition.
804 if (ruleIndex == 0 && pair.beforeInitialDst(year, beforeMSecsSinceEpoch - 1))
805 return ruleToData(rule, minMSecs(), QTimeZone::StandardTime, false);
806
807 bool isDst = false;
808 if (pair.std != invalidMSecs() && pair.std < beforeMSecsSinceEpoch) {
809 isDst = pair.std < pair.dst && pair.dst < beforeMSecsSinceEpoch;
810 } else if (pair.dst != invalidMSecs() && pair.dst < beforeMSecsSinceEpoch) {
811 isDst = true;
812 } else {
813 year = prior; // Try an earlier year for this rule (once).
814 prior = year == 1 ? -1 : year - 1; // No year 0.
815 continue;
816 }
817 return pair.ruleToData(rule, this, isDst);
818 }
819 // Fell off start of rule, try previous rule.
820 } else if (ruleIndex == 0) {
821 // Describe time before the first transition in terms of a fictional
822 // transition at the start of time, so that a scan through all rules
823 // *does* see a first rule that supplies the offset for such times:
824 return ruleToData(rule, minMSecs(), QTimeZone::StandardTime, false);
825 } // else: no transition during rule's period
826 if (year >= rule.startYear) {
827 year = rule.startYear - 1; // Seek last transition in new rule
828 if (!year)
829 --year;
830 }
831 }
832 // Apparently no transition before the given time:
833 return {};
834}
835
836QByteArray QWinTimeZonePrivate::systemTimeZoneId() const
837{
838 const QLocale::Territory territory = userTerritory();
839 const QByteArray windowsId = windowsSystemZoneId();
840 QByteArray ianaId;
841 // If we have a real territory, then try get a specific match for that territory
842 if (territory != QLocale::AnyTerritory)
843 ianaId = windowsIdToDefaultIanaId(windowsId, territory);
844 // If we don't have a real territory, or there wasn't a specific match, try the global default
845 if (ianaId.isEmpty())
846 ianaId = windowsIdToDefaultIanaId(windowsId);
847 return ianaId;
848}
849
850QList<QByteArray> QWinTimeZonePrivate::availableTimeZoneIds() const
851{
852 static const QList<QByteArray> cache = [] {
853 QList<QByteArray> result;
854 const auto winIds = availableWindowsIds();
855 for (const QByteArray &winId : winIds)
856 result += windowsIdToIanaIds(winId);
857 std::sort(result.begin(), result.end());
858 result.erase(std::unique(result.begin(), result.end()), result.end());
859 return result;
860 }();
861 return cache;
862}
863
864QTimeZonePrivate::Data QWinTimeZonePrivate::ruleToData(const QWinTransitionRule &rule,
865 qint64 atMSecsSinceEpoch,
866 QTimeZone::TimeType type,
867 bool fakeDst) const
868{
869 Data tran;
870 tran.atMSecsSinceEpoch = atMSecsSinceEpoch;
871 tran.standardTimeOffset = rule.standardTimeBias * -60;
872 if (fakeDst) {
873 tran.daylightTimeOffset = 0;
874 tran.abbreviation = m_standardName;
875 // Rule may claim we're in DST when it's actually a standard time change:
876 if (type == QTimeZone::DaylightTime)
877 tran.standardTimeOffset += rule.daylightTimeBias * -60;
878 } else if (type == QTimeZone::DaylightTime) {
879 tran.daylightTimeOffset = rule.daylightTimeBias * -60;
880 tran.abbreviation = m_daylightName;
881 } else {
882 tran.daylightTimeOffset = 0;
883 tran.abbreviation = m_standardName;
884 }
885 tran.offsetFromUtc = tran.standardTimeOffset + tran.daylightTimeOffset;
886 return tran;
887}
888
\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:124
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
\inmodule QtCore\reentrant
Definition qdatetime.h:283
\inmodule QtCore \reentrant
Definition qdatetime.h:29
constexpr bool isValid() const
Returns true if this date is valid; otherwise returns false.
Definition qdatetime.h:71
constexpr qint64 toJulianDay() const
Converts the date to a Julian day.
Definition qdatetime.h:170
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:168
QDate addDays(qint64 days) const
Returns a QDate object containing a date ndays later than the date of this object (or earlier if nday...
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...
void append(parameter_type t)
Definition qlist.h:458
static QLocale::Territory codeToTerritory(QStringView code) noexcept
Definition qlocale.cpp:187
@ AnyTerritory
Definition qlocale.h:568
\inmodule QtCore
Definition qstringview.h:78
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1309
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:8084
QByteArray toUtf8() const &
Definition qstring.h:634
static QByteArray utcQByteArray()
static constexpr qint64 invalidMSecs()
static constexpr qint64 minMSecs()
\inmodule QtCore
Definition qtimezone.h:26
\inmodule QtCore \reentrant
Definition qdatetime.h:215
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:244
QString stringValue(QStringView subKey) const
QDate date
[1]
QCache< int, Employee > cache
[0]
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
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:166
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]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLenum GLuint buffer
GLenum type
GLenum GLenum dst
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:60
QList< int > list
[14]
QSharedPointer< T > other(t)
[5]