1 /****************************************************************************
2 **
3 ** Copyright (C) 2013 John Layt <jlayt@kde.org>
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qtimezone.h"
41 #include "qtimezoneprivate_p.h"
42 
43 #include "qdatetime.h"
44 
45 #include "qdebug.h"
46 
47 #include <algorithm>
48 
49 #ifndef Q_OS_WINRT
50 #include <private/qwinregistry_p.h>
51 // The registry-based timezone backend is not available on WinRT, which falls back to equivalent APIs.
52 #define QT_USE_REGISTRY_TIMEZONE 1
53 #endif
54 
55 QT_BEGIN_NAMESPACE
56 
57 /*
58     Private
59 
60     Windows system implementation
61 */
62 
63 #define MAX_KEY_LENGTH 255
64 #define FILETIME_UNIX_EPOCH Q_UINT64_C(116444736000000000)
65 
66 // MSDN home page for Time support
67 // http://msdn.microsoft.com/en-us/library/windows/desktop/ms724962%28v=vs.85%29.aspx
68 
69 // For Windows XP and later refer to MSDN docs on TIME_ZONE_INFORMATION structure
70 // http://msdn.microsoft.com/en-gb/library/windows/desktop/ms725481%28v=vs.85%29.aspx
71 
72 // Vista introduced support for historic data, see MSDN docs on DYNAMIC_TIME_ZONE_INFORMATION
73 // http://msdn.microsoft.com/en-gb/library/windows/desktop/ms724253%28v=vs.85%29.aspx
74 #ifdef QT_USE_REGISTRY_TIMEZONE
75 static const wchar_t tzRegPath[] = LR"(SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones)";
76 static const wchar_t currTzRegPath[] = LR"(SYSTEM\CurrentControlSet\Control\TimeZoneInformation)";
77 #endif
78 
79 enum {
80     MIN_YEAR = -292275056,
81     MAX_YEAR = 292278994,
82     MSECS_PER_DAY = 86400000,
83     TIME_T_MAX = 2145916799,  // int maximum 2037-12-31T23:59:59 UTC
84     JULIAN_DAY_FOR_EPOCH = 2440588 // result of julianDayFromDate(1970, 1, 1)
85 };
86 
87 // Copied from MSDN, see above for link
88 typedef struct _REG_TZI_FORMAT
89 {
90     LONG Bias;
91     LONG StandardBias;
92     LONG DaylightBias;
93     SYSTEMTIME StandardDate;
94     SYSTEMTIME DaylightDate;
95 } REG_TZI_FORMAT;
96 
97 namespace {
98 
99 // Fast and reliable conversion from msecs to date for all values
100 // Adapted from QDateTime msecsToDate
msecsToDate(qint64 msecs)101 QDate msecsToDate(qint64 msecs)
102 {
103     qint64 jd = JULIAN_DAY_FOR_EPOCH;
104 
105     if (qAbs(msecs) >= MSECS_PER_DAY) {
106         jd += (msecs / MSECS_PER_DAY);
107         msecs %= MSECS_PER_DAY;
108     }
109 
110     if (msecs < 0) {
111         qint64 ds = MSECS_PER_DAY - msecs - 1;
112         jd -= ds / MSECS_PER_DAY;
113     }
114 
115     return QDate::fromJulianDay(jd);
116 }
117 
equalSystemtime(const SYSTEMTIME & t1,const SYSTEMTIME & t2)118 bool equalSystemtime(const SYSTEMTIME &t1, const SYSTEMTIME &t2)
119 {
120     return (t1.wYear == t2.wYear
121             && t1.wMonth == t2.wMonth
122             && t1.wDay == t2.wDay
123             && t1.wDayOfWeek == t2.wDayOfWeek
124             && t1.wHour == t2.wHour
125             && t1.wMinute == t2.wMinute
126             && t1.wSecond == t2.wSecond
127             && t1.wMilliseconds == t2.wMilliseconds);
128 }
129 
equalTzi(const TIME_ZONE_INFORMATION & tzi1,const TIME_ZONE_INFORMATION & tzi2)130 bool equalTzi(const TIME_ZONE_INFORMATION &tzi1, const TIME_ZONE_INFORMATION &tzi2)
131 {
132     return(tzi1.Bias == tzi2.Bias
133            && tzi1.StandardBias == tzi2.StandardBias
134            && equalSystemtime(tzi1.StandardDate, tzi2.StandardDate)
135            && wcscmp(tzi1.StandardName, tzi2.StandardName) == 0
136            && tzi1.DaylightBias == tzi2.DaylightBias
137            && equalSystemtime(tzi1.DaylightDate, tzi2.DaylightDate)
138            && wcscmp(tzi1.DaylightName, tzi2.DaylightName) == 0);
139 }
140 
141 #ifdef QT_USE_REGISTRY_TIMEZONE
142 
readRegistryRule(const HKEY & key,const wchar_t * value,bool * ok)143 QWinTimeZonePrivate::QWinTransitionRule readRegistryRule(const HKEY &key,
144                                                          const wchar_t *value, bool *ok)
145 {
146     *ok = false;
147     QWinTimeZonePrivate::QWinTransitionRule rule;
148     REG_TZI_FORMAT tzi;
149     DWORD tziSize = sizeof(tzi);
150     if (RegQueryValueEx(key, value, nullptr, nullptr, reinterpret_cast<BYTE *>(&tzi), &tziSize)
151         == ERROR_SUCCESS) {
152         rule.startYear = 0;
153         rule.standardTimeBias = tzi.Bias + tzi.StandardBias;
154         rule.daylightTimeBias = tzi.Bias + tzi.DaylightBias - rule.standardTimeBias;
155         rule.standardTimeRule = tzi.StandardDate;
156         rule.daylightTimeRule = tzi.DaylightDate;
157         *ok = true;
158     }
159     return rule;
160 }
161 
getRegistryTzi(const QByteArray & windowsId,bool * ok)162 TIME_ZONE_INFORMATION getRegistryTzi(const QByteArray &windowsId, bool *ok)
163 {
164     *ok = false;
165     TIME_ZONE_INFORMATION tzi;
166     REG_TZI_FORMAT regTzi;
167     DWORD regTziSize = sizeof(regTzi);
168     const QString tziKeyPath = QString::fromWCharArray(tzRegPath) + QLatin1Char('\\')
169                                + QString::fromUtf8(windowsId);
170 
171     QWinRegistryKey key(HKEY_LOCAL_MACHINE, tziKeyPath);
172     if (key.isValid()) {
173         DWORD size = sizeof(tzi.DaylightName);
174         RegQueryValueEx(key, L"Dlt", nullptr, nullptr, reinterpret_cast<LPBYTE>(tzi.DaylightName), &size);
175 
176         size = sizeof(tzi.StandardName);
177         RegQueryValueEx(key, L"Std", nullptr, nullptr, reinterpret_cast<LPBYTE>(tzi.StandardName), &size);
178 
179         if (RegQueryValueEx(key, L"TZI", nullptr, nullptr, reinterpret_cast<BYTE *>(&regTzi), &regTziSize)
180             == ERROR_SUCCESS) {
181             tzi.Bias = regTzi.Bias;
182             tzi.StandardBias = regTzi.StandardBias;
183             tzi.DaylightBias = regTzi.DaylightBias;
184             tzi.StandardDate = regTzi.StandardDate;
185             tzi.DaylightDate = regTzi.DaylightDate;
186             *ok = true;
187         }
188     }
189 
190     return tzi;
191 }
192 #else // QT_USE_REGISTRY_TIMEZONE
193 struct QWinDynamicTimeZone
194 {
195     QString standardName;
196     QString daylightName;
197     QString timezoneName;
198     qint32 bias;
199     bool daylightTime;
200 };
201 
202 typedef QHash<QByteArray, QWinDynamicTimeZone> QWinRTTimeZoneHash;
203 
Q_GLOBAL_STATIC(QWinRTTimeZoneHash,gTimeZones)204 Q_GLOBAL_STATIC(QWinRTTimeZoneHash, gTimeZones)
205 
206 void enumerateTimeZones()
207 {
208     DYNAMIC_TIME_ZONE_INFORMATION dtzInfo;
209     quint32 index = 0;
210     QString prevTimeZoneKeyName;
211     while (SUCCEEDED(EnumDynamicTimeZoneInformation(index++, &dtzInfo))) {
212         QWinDynamicTimeZone item;
213         item.timezoneName = QString::fromWCharArray(dtzInfo.TimeZoneKeyName);
214         // As soon as key name repeats, break. Some systems continue to always
215         // return the last item independent of index being out of range
216         if (item.timezoneName == prevTimeZoneKeyName)
217             break;
218         item.standardName = QString::fromWCharArray(dtzInfo.StandardName);
219         item.daylightName = QString::fromWCharArray(dtzInfo.DaylightName);
220         item.daylightTime = !dtzInfo.DynamicDaylightTimeDisabled;
221         item.bias = dtzInfo.Bias;
222         gTimeZones->insert(item.timezoneName.toUtf8(), item);
223         prevTimeZoneKeyName = item.timezoneName;
224     }
225 }
226 
dynamicInfoForId(const QByteArray & windowsId)227 DYNAMIC_TIME_ZONE_INFORMATION dynamicInfoForId(const QByteArray &windowsId)
228 {
229     DYNAMIC_TIME_ZONE_INFORMATION dtzInfo;
230     quint32 index = 0;
231     QString prevTimeZoneKeyName;
232     while (SUCCEEDED(EnumDynamicTimeZoneInformation(index++, &dtzInfo))) {
233         const QString timeZoneName = QString::fromWCharArray(dtzInfo.TimeZoneKeyName);
234         if (timeZoneName == QLatin1String(windowsId))
235             break;
236         if (timeZoneName == prevTimeZoneKeyName)
237             break;
238         prevTimeZoneKeyName = timeZoneName;
239     }
240     return dtzInfo;
241 }
242 
243 QWinTimeZonePrivate::QWinTransitionRule
readDynamicRule(DYNAMIC_TIME_ZONE_INFORMATION & dtzi,int year,bool * ok)244 readDynamicRule(DYNAMIC_TIME_ZONE_INFORMATION &dtzi, int year, bool *ok)
245 {
246     TIME_ZONE_INFORMATION tzi;
247     QWinTimeZonePrivate::QWinTransitionRule rule;
248     *ok = GetTimeZoneInformationForYear(year, &dtzi, &tzi);
249     if (*ok) {
250         rule.startYear = 0;
251         rule.standardTimeBias = tzi.Bias + tzi.StandardBias;
252         rule.daylightTimeBias = tzi.Bias + tzi.DaylightBias - rule.standardTimeBias;
253         rule.standardTimeRule = tzi.StandardDate;
254         rule.daylightTimeRule = tzi.DaylightDate;
255     }
256     return rule;
257 }
258 #endif // QT_USE_REGISTRY_TIMEZONE
259 
isSameRule(const QWinTimeZonePrivate::QWinTransitionRule & last,const QWinTimeZonePrivate::QWinTransitionRule & rule)260 bool isSameRule(const QWinTimeZonePrivate::QWinTransitionRule &last,
261                        const QWinTimeZonePrivate::QWinTransitionRule &rule)
262 {
263     // In particular, when this is true and either wYear is 0, so is the other;
264     // so if one rule is recurrent and they're equal, so is the other.  If
265     // either rule *isn't* recurrent, it has non-0 wYear which shall be
266     // different from the other's.  Note that we don't compare .startYear, since
267     // that will always be different.
268     return equalSystemtime(last.standardTimeRule, rule.standardTimeRule)
269         && equalSystemtime(last.daylightTimeRule, rule.daylightTimeRule)
270         && last.standardTimeBias == rule.standardTimeBias
271         && last.daylightTimeBias == rule.daylightTimeBias;
272 }
273 
availableWindowsIds()274 QList<QByteArray> availableWindowsIds()
275 {
276 #ifdef QT_USE_REGISTRY_TIMEZONE
277     // TODO Consider caching results in a global static, very unlikely to change.
278     QList<QByteArray> list;
279     QWinRegistryKey key(HKEY_LOCAL_MACHINE, tzRegPath);
280     if (key.isValid()) {
281         DWORD idCount = 0;
282         if (RegQueryInfoKey(key, 0, 0, 0, &idCount, 0, 0, 0, 0, 0, 0, 0) == ERROR_SUCCESS
283             && idCount > 0) {
284             for (DWORD i = 0; i < idCount; ++i) {
285                 DWORD maxLen = MAX_KEY_LENGTH;
286                 TCHAR buffer[MAX_KEY_LENGTH];
287                 if (RegEnumKeyEx(key, i, buffer, &maxLen, 0, 0, 0, 0) == ERROR_SUCCESS)
288                     list.append(QString::fromWCharArray(buffer).toUtf8());
289             }
290         }
291     }
292     return list;
293 #else // QT_USE_REGISTRY_TIMEZONE
294     if (gTimeZones->isEmpty())
295         enumerateTimeZones();
296     return gTimeZones->keys();
297 #endif // QT_USE_REGISTRY_TIMEZONE
298 }
299 
windowsSystemZoneId()300 QByteArray windowsSystemZoneId()
301 {
302 #ifdef QT_USE_REGISTRY_TIMEZONE
303     // On Vista and later is held in the value TimeZoneKeyName in key currTzRegPath
304     const QString id = QWinRegistryKey(HKEY_LOCAL_MACHINE, currTzRegPath)
305                        .stringValue(L"TimeZoneKeyName");
306     if (!id.isEmpty())
307         return id.toUtf8();
308 
309     // On XP we have to iterate over the zones until we find a match on
310     // names/offsets with the current data
311     TIME_ZONE_INFORMATION sysTzi;
312     GetTimeZoneInformation(&sysTzi);
313     bool ok = false;
314     const auto winIds = availableWindowsIds();
315     for (const QByteArray &winId : winIds) {
316         if (equalTzi(getRegistryTzi(winId, &ok), sysTzi))
317             return winId;
318     }
319 #else // QT_USE_REGISTRY_TIMEZONE
320     DYNAMIC_TIME_ZONE_INFORMATION dtzi;
321     if (SUCCEEDED(GetDynamicTimeZoneInformation(&dtzi)))
322         return QString::fromWCharArray(dtzi.TimeZoneKeyName).toLocal8Bit();
323 #endif // QT_USE_REGISTRY_TIMEZONE
324 
325     // If we can't determine the current ID use UTC
326     return QTimeZonePrivate::utcQByteArray();
327 }
328 
calculateTransitionLocalDate(const SYSTEMTIME & rule,int year)329 QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year)
330 {
331     // If month is 0 then there is no date
332     if (rule.wMonth == 0)
333         return QDate();
334 
335     // Interpret SYSTEMTIME according to the slightly quirky rules in:
336     // https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
337 
338     // If the year is set, the rule gives an absolute date:
339     if (rule.wYear)
340         return QDate(rule.wYear, rule.wMonth, rule.wDay);
341 
342     // Otherwise, the rule date is annual and relative:
343     const int dayOfWeek = rule.wDayOfWeek == 0 ? 7 : rule.wDayOfWeek;
344     QDate date(year, rule.wMonth, 1);
345     Q_ASSERT(date.isValid());
346     // How many days before was last dayOfWeek before target month ?
347     int adjust = dayOfWeek - date.dayOfWeek(); // -6 <= adjust < 7
348     if (adjust >= 0) // Ensure -7 <= adjust < 0:
349         adjust -= 7;
350     // Normally, wDay is day-within-month; but here it is 1 for the first
351     // of the given dayOfWeek in the month, through 4 for the fourth or ...
352     adjust += (rule.wDay < 1 ? 1 : rule.wDay > 4 ? 5 : rule.wDay) * 7;
353     date = date.addDays(adjust);
354     // ... 5 for the last; so back up by weeks to get within the month:
355     if (date.month() != rule.wMonth) {
356         Q_ASSERT(rule.wDay > 4);
357         // (Note that, with adjust < 0, date <= 28th of our target month
358         // is guaranteed when wDay <= 4, or after our first -7 here.)
359         date = date.addDays(-7);
360         Q_ASSERT(date.month() == rule.wMonth);
361     }
362     return date;
363 }
364 
365 // Converts a date/time value into msecs
timeToMSecs(QDate date,QTime time)366 inline qint64 timeToMSecs(QDate date, QTime time)
367 {
368     return ((date.toJulianDay() - JULIAN_DAY_FOR_EPOCH) * MSECS_PER_DAY)
369            + time.msecsSinceStartOfDay();
370 }
371 
calculateTransitionForYear(const SYSTEMTIME & rule,int year,int bias)372 qint64 calculateTransitionForYear(const SYSTEMTIME &rule, int year, int bias)
373 {
374     // TODO Consider caching the calculated values - i.e. replace SYSTEMTIME in
375     // WinTransitionRule; do this in init() once and store the results.
376     Q_ASSERT(year);
377     const QDate date = calculateTransitionLocalDate(rule, year);
378     const QTime time = QTime(rule.wHour, rule.wMinute, rule.wSecond);
379     if (date.isValid() && time.isValid())
380         return timeToMSecs(date, time) + bias * 60000;
381     return QTimeZonePrivate::invalidMSecs();
382 }
383 
384 struct TransitionTimePair
385 {
386     // Transition times after the epoch, in ms:
387     qint64 std, dst;
388     // If either is invalidMSecs(), which shall then be < the other, there is no
389     // DST and the other describes a change in actual standard offset.
390 
TransitionTimePair__anon2a5ba62c0211::TransitionTimePair391     TransitionTimePair(const QWinTimeZonePrivate::QWinTransitionRule &rule,
392                        int year, int oldYearOffset)
393         // The local time in Daylight Time of the switch to Standard Time
394         : std(calculateTransitionForYear(rule.standardTimeRule, year,
395                                          rule.standardTimeBias + rule.daylightTimeBias)),
396           // The local time in Standard Time of the switch to Daylight Time
397           dst(calculateTransitionForYear(rule.daylightTimeRule, year, rule.standardTimeBias))
398     {
399         /*
400           Check for potential "fake DST", used by MS's APIs because the
401           TIME_ZONE_INFORMATION spec either expresses no transitions in the
402           year, or expresses a transition of each kind, even if standard time
403           did change in a year with no DST.  We've seen year-start fake-DST
404           (whose offset matches prior standard offset, in which the previous
405           year ended); and conjecture that similar might be used at a year-end.
406           (This might be used for a southern-hemisphere zone, where the start of
407           the year usually is in DST, when applicable.)  Note that, here, wDay
408           identifies an instance of a given day-of-week in the month, with 5
409           meaning last.
410 
411           Either the alleged standardTimeRule or the alleged daylightTimeRule
412           may be faked; either way, the transition is actually a change to the
413           current standard offset; but the unfaked half of the rule contains the
414           useful bias data, so we have to go along with its lies.
415 
416           Example: Russia/Moscow
417           Format: -bias +( -stdBias, stdDate | -dstBias, dstDate ) notes
418           Last year of DST, 2010: 180 +( 0, 0-10-5 3:0 | 60, 0-3-5 2:0 ) normal DST
419           Zone change in 2011: 180 +( 0, 0-1-1 0:0 | 60 0-3-5 2:0 ) fake DST at transition
420           Fixed standard in 2012: 240 +( 0, 0-0-0 0:0 | 60, 0-0-0 0:0 ) standard time years
421           Zone change in 2014: 180 +( 0, 0-10-5 2:0 | 60, 0-1-1 0:0 ) fake DST at year-start
422           The last of these is missing on Win7 VMs (too old to know about it).
423         */
424         if (rule.daylightTimeRule.wMonth == 1 && rule.daylightTimeRule.wDay == 1) {
425             // Fake "DST transition" at start of year producing the same offset as
426             // previous year ended in.
427             if (rule.standardTimeBias + rule.daylightTimeBias == oldYearOffset)
428                 dst = QTimeZonePrivate::invalidMSecs();
429         } else if (rule.daylightTimeRule.wMonth == 12 && rule.daylightTimeRule.wDay > 3) {
430             // Similar, conjectured, for end of year, not changing offset.
431             if (rule.daylightTimeBias == 0)
432                 dst = QTimeZonePrivate::invalidMSecs();
433         }
434         if (rule.standardTimeRule.wMonth == 1 && rule.standardTimeRule.wDay == 1) {
435             // Fake "transition out of DST" at start of year producing the same
436             // offset as previous year ended in.
437             if (rule.standardTimeBias == oldYearOffset)
438                 std = QTimeZonePrivate::invalidMSecs();
439         } else if (rule.standardTimeRule.wMonth == 12 && rule.standardTimeRule.wDay > 3) {
440             // Similar, conjectured, for end of year, not changing offset.
441             if (rule.daylightTimeBias == 0)
442                 std = QTimeZonePrivate::invalidMSecs();
443         }
444     }
445 
fakesDst__anon2a5ba62c0211::TransitionTimePair446     bool fakesDst() const
447     {
448         return std == QTimeZonePrivate::invalidMSecs()
449             || dst == QTimeZonePrivate::invalidMSecs();
450     }
451 };
452 
yearEndOffset(const QWinTimeZonePrivate::QWinTransitionRule & rule,int year)453 int yearEndOffset(const QWinTimeZonePrivate::QWinTransitionRule &rule, int year)
454 {
455     Q_ASSERT(year);
456     int offset = rule.standardTimeBias;
457     // Only needed to help another TransitionTimePair work out year + 1's start
458     // offset; and the oldYearOffset we use only affects an alleged transition
459     // at the *start* of this year, so it doesn't matter if we guess wrong here:
460     TransitionTimePair pair(rule, year, offset);
461     if (pair.dst > pair.std)
462         offset += rule.daylightTimeBias;
463     return offset;
464 }
465 
userCountry()466 QLocale::Country userCountry()
467 {
468     const GEOID id = GetUserGeoID(GEOCLASS_NATION);
469     wchar_t code[3];
470     const int size = GetGeoInfo(id, GEO_ISO2, code, 3, 0);
471     return (size == 3) ? QLocalePrivate::codeToCountry(QStringView(code, size))
472                        : QLocale::AnyCountry;
473 }
474 
475 // Index of last rule in rules with .startYear <= year:
ruleIndexForYear(const QList<QWinTimeZonePrivate::QWinTransitionRule> & rules,int year)476 int ruleIndexForYear(const QList<QWinTimeZonePrivate::QWinTransitionRule> &rules, int year)
477 {
478     if (rules.last().startYear <= year)
479         return rules.count() - 1;
480     // We don't have a rule for before the first, but the first is the best we can offer:
481     if (rules.first().startYear > year)
482         return 0;
483 
484     // Otherwise, use binary chop:
485     int lo = 0, hi = rules.count();
486     // invariant: rules[i].startYear <= year < rules[hi].startYear
487     // subject to treating rules[rules.count()] as "off the end of time"
488     while (lo + 1 < hi) {
489         const int mid = (lo + hi) / 2;
490         // lo + 2 <= hi, so lo + 1 <= mid <= hi - 1, so lo < mid < hi
491         // In particular, mid < rules.count()
492         const int midYear = rules.at(mid).startYear;
493         if (midYear > year)
494             hi = mid;
495         else if (midYear < year)
496             lo = mid;
497         else // No two rules have the same startYear:
498             return mid;
499     }
500     return lo;
501 }
502 
503 } // anonymous namespace
504 
505 // Create the system default time zone
QWinTimeZonePrivate()506 QWinTimeZonePrivate::QWinTimeZonePrivate()
507                    : QTimeZonePrivate()
508 {
509     init(QByteArray());
510 }
511 
512 // Create a named time zone
QWinTimeZonePrivate(const QByteArray & ianaId)513 QWinTimeZonePrivate::QWinTimeZonePrivate(const QByteArray &ianaId)
514                    : QTimeZonePrivate()
515 {
516     init(ianaId);
517 }
518 
QWinTimeZonePrivate(const QWinTimeZonePrivate & other)519 QWinTimeZonePrivate::QWinTimeZonePrivate(const QWinTimeZonePrivate &other)
520                    : QTimeZonePrivate(other), m_windowsId(other.m_windowsId),
521                      m_displayName(other.m_displayName), m_standardName(other.m_standardName),
522                      m_daylightName(other.m_daylightName), m_tranRules(other.m_tranRules)
523 {
524 }
525 
~QWinTimeZonePrivate()526 QWinTimeZonePrivate::~QWinTimeZonePrivate()
527 {
528 }
529 
clone() const530 QWinTimeZonePrivate *QWinTimeZonePrivate::clone() const
531 {
532     return new QWinTimeZonePrivate(*this);
533 }
534 
init(const QByteArray & ianaId)535 void QWinTimeZonePrivate::init(const QByteArray &ianaId)
536 {
537     if (ianaId.isEmpty()) {
538         m_windowsId = windowsSystemZoneId();
539         m_id = systemTimeZoneId();
540     } else {
541         m_windowsId = ianaIdToWindowsId(ianaId);
542         m_id = ianaId;
543     }
544 
545     bool badMonth = false; // Only warn once per zone, if at all.
546     if (!m_windowsId.isEmpty()) {
547 #ifdef QT_USE_REGISTRY_TIMEZONE
548         // Open the base TZI for the time zone
549         const QString baseKeyPath = QString::fromWCharArray(tzRegPath) + QLatin1Char('\\')
550                                    + QString::fromUtf8(m_windowsId);
551         QWinRegistryKey baseKey(HKEY_LOCAL_MACHINE, baseKeyPath);
552         if (baseKey.isValid()) {
553             //  Load the localized names
554             m_displayName = baseKey.stringValue(L"Display");
555             m_standardName = baseKey.stringValue(L"Std");
556             m_daylightName = baseKey.stringValue(L"Dlt");
557             // On Vista and later the optional dynamic key holds historic data
558             const QString dynamicKeyPath = baseKeyPath + QLatin1String("\\Dynamic DST");
559             QWinRegistryKey dynamicKey(HKEY_LOCAL_MACHINE, dynamicKeyPath);
560             if (dynamicKey.isValid()) {
561                 // Find out the start and end years stored, then iterate over them
562                 const auto startYear = dynamicKey.dwordValue(L"FirstEntry");
563                 const auto endYear = dynamicKey.dwordValue(L"LastEntry");
564                 for (int year = int(startYear.first); year <= int(endYear.first); ++year) {
565                     bool ruleOk;
566                     QWinTransitionRule rule = readRegistryRule(dynamicKey,
567                                                                reinterpret_cast<LPCWSTR>(QString::number(year).utf16()),
568                                                                &ruleOk);
569                     if (ruleOk
570                         // Don't repeat a recurrent rule:
571                         && (m_tranRules.isEmpty()
572                             || !isSameRule(m_tranRules.last(), rule))) {
573                         if (!badMonth
574                             && (rule.standardTimeRule.wMonth == 0)
575                             != (rule.daylightTimeRule.wMonth == 0)) {
576                             badMonth = true;
577                             qWarning("MS registry TZ API violated its wMonth constraint;"
578                                      "this may cause mistakes for %s from %d",
579                                      ianaId.constData(), year);
580                         }
581                         rule.startYear = m_tranRules.isEmpty() ? MIN_YEAR : year;
582                         m_tranRules.append(rule);
583                     }
584                 }
585             } else {
586                 // No dynamic data so use the base data
587                 bool ruleOk;
588                 QWinTransitionRule rule = readRegistryRule(baseKey, L"TZI", &ruleOk);
589                 rule.startYear = MIN_YEAR;
590                 if (ruleOk)
591                     m_tranRules.append(rule);
592             }
593         }
594 #else // QT_USE_REGISTRY_TIMEZONE
595         if (gTimeZones->isEmpty())
596             enumerateTimeZones();
597         QWinRTTimeZoneHash::const_iterator it = gTimeZones->find(m_windowsId);
598         if (it != gTimeZones->constEnd()) {
599             m_displayName = it->timezoneName;
600             m_standardName = it->standardName;
601             m_daylightName = it->daylightName;
602             DWORD firstYear = 0;
603             DWORD lastYear = 0;
604             DYNAMIC_TIME_ZONE_INFORMATION dtzi = dynamicInfoForId(m_windowsId);
605             if (GetDynamicTimeZoneInformationEffectiveYears(&dtzi, &firstYear, &lastYear)
606                 == ERROR_SUCCESS && firstYear < lastYear) {
607                 for (DWORD year = firstYear; year <= lastYear; ++year) {
608                     bool ok = false;
609                     QWinTransitionRule rule = readDynamicRule(dtzi, year, &ok);
610                     if (ok
611                         // Don't repeat a recurrent rule
612                         && (m_tranRules.isEmpty()
613                             || !isSameRule(m_tranRules.last(), rule))) {
614                         if (!badMonth
615                             && (rule.standardTimeRule.wMonth == 0)
616                             != (rule.daylightTimeRule.wMonth == 0)) {
617                             badMonth = true;
618                             qWarning("MS dynamic TZ API violated its wMonth constraint;"
619                                      "this may cause mistakes for %s from %d",
620                                      ianaId.constData(), year);
621                         }
622                         rule.startYear = m_tranRules.isEmpty() ? MIN_YEAR : year;
623                         m_tranRules.append(rule);
624                     }
625                 }
626             } else {
627                 // At least try to get the non-dynamic data:
628                 dtzi.DynamicDaylightTimeDisabled = false;
629                 bool ok = false;
630                 QWinTransitionRule rule = readDynamicRule(dtzi, 1970, &ok);
631                 if (ok) {
632                     rule.startYear = MIN_YEAR;
633                     m_tranRules.append(rule);
634                 }
635             }
636         }
637 #endif // QT_USE_REGISTRY_TIMEZONE
638     }
639 
640     // If there are no rules then we failed to find a windowsId or any tzi info
641     if (m_tranRules.size() == 0) {
642         m_id.clear();
643         m_windowsId.clear();
644         m_displayName.clear();
645     } else if (m_id.isEmpty()) {
646         m_id = m_standardName.toUtf8();
647     }
648 }
649 
comment() const650 QString QWinTimeZonePrivate::comment() const
651 {
652     return m_displayName;
653 }
654 
displayName(QTimeZone::TimeType timeType,QTimeZone::NameType nameType,const QLocale & locale) const655 QString QWinTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
656                                          QTimeZone::NameType nameType,
657                                          const QLocale &locale) const
658 {
659     // TODO Registry holds MUI keys, should be able to look up translations?
660     Q_UNUSED(locale);
661 
662     if (nameType == QTimeZone::OffsetName) {
663         const QWinTransitionRule &rule =
664             m_tranRules.at(ruleIndexForYear(m_tranRules, QDate::currentDate().year()));
665         int offset = rule.standardTimeBias;
666         if (timeType == QTimeZone::DaylightTime)
667             offset += rule.daylightTimeBias;
668         return isoOffsetFormat(offset * -60);
669     }
670 
671     switch (timeType) {
672     case  QTimeZone::DaylightTime :
673         return m_daylightName;
674     case  QTimeZone::GenericTime :
675         return m_displayName;
676     case  QTimeZone::StandardTime :
677         return m_standardName;
678     }
679     return m_standardName;
680 }
681 
abbreviation(qint64 atMSecsSinceEpoch) const682 QString QWinTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const
683 {
684     return data(atMSecsSinceEpoch).abbreviation;
685 }
686 
offsetFromUtc(qint64 atMSecsSinceEpoch) const687 int QWinTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const
688 {
689     return data(atMSecsSinceEpoch).offsetFromUtc;
690 }
691 
standardTimeOffset(qint64 atMSecsSinceEpoch) const692 int QWinTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const
693 {
694     return data(atMSecsSinceEpoch).standardTimeOffset;
695 }
696 
daylightTimeOffset(qint64 atMSecsSinceEpoch) const697 int QWinTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const
698 {
699     return data(atMSecsSinceEpoch).daylightTimeOffset;
700 }
701 
hasDaylightTime() const702 bool QWinTimeZonePrivate::hasDaylightTime() const
703 {
704     return hasTransitions();
705 }
706 
isDaylightTime(qint64 atMSecsSinceEpoch) const707 bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
708 {
709     return (data(atMSecsSinceEpoch).daylightTimeOffset != 0);
710 }
711 
data(qint64 forMSecsSinceEpoch) const712 QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
713 {
714     int year = msecsToDate(forMSecsSinceEpoch).year();
715     for (int ruleIndex = ruleIndexForYear(m_tranRules, year);
716          ruleIndex >= 0; --ruleIndex) {
717         const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
718         // Does this rule's period include any transition at all ?
719         if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
720             int prior = year == 1 ? -1 : year - 1; // No year 0.
721             const int endYear = qMax(rule.startYear, prior);
722             while (year >= endYear) {
723                 const int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
724                     ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
725                     : yearEndOffset(rule, prior);
726                 const TransitionTimePair pair(rule, year, newYearOffset);
727                 bool isDst = false;
728                 if (pair.std != invalidMSecs() && pair.std <= forMSecsSinceEpoch) {
729                     isDst = pair.std < pair.dst && pair.dst <= forMSecsSinceEpoch;
730                 } else if (pair.dst != invalidMSecs() && pair.dst <= forMSecsSinceEpoch) {
731                     isDst = true;
732                 } else {
733                     year = prior; // Try an earlier year for this rule (once).
734                     prior = year == 1 ? -1 : year - 1; // No year 0.
735                     continue;
736                 }
737                 return ruleToData(rule, forMSecsSinceEpoch,
738                                   isDst ? QTimeZone::DaylightTime : QTimeZone::StandardTime,
739                                   pair.fakesDst());
740             }
741             // Fell off start of rule, try previous rule.
742         } else {
743             // No transition, no DST, use the year's standard time.
744             return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime);
745         }
746         if (year >= rule.startYear) {
747             year = rule.startYear - 1; // Seek last transition in new rule.
748             if (!year)
749                 --year;
750         }
751     }
752     // We don't have relevant data :-(
753     return invalidData();
754 }
755 
hasTransitions() const756 bool QWinTimeZonePrivate::hasTransitions() const
757 {
758     for (const QWinTransitionRule &rule : m_tranRules) {
759         if (rule.standardTimeRule.wMonth > 0 && rule.daylightTimeRule.wMonth > 0)
760             return true;
761     }
762     return false;
763 }
764 
nextTransition(qint64 afterMSecsSinceEpoch) const765 QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const
766 {
767     int year = msecsToDate(afterMSecsSinceEpoch).year();
768     for (int ruleIndex = ruleIndexForYear(m_tranRules, year);
769          ruleIndex < m_tranRules.count(); ++ruleIndex) {
770         const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
771         // Does this rule's period include any transition at all ?
772         if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
773             if (year < rule.startYear)
774                 year = rule.startYear; // Seek first transition in this rule.
775             const int endYear = ruleIndex + 1 < m_tranRules.count()
776                 ? qMin(m_tranRules.at(ruleIndex + 1).startYear, year + 2) : (year + 2);
777             int prior = year == 1 ? -1 : year - 1; // No year 0.
778             int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
779                 ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
780                 : yearEndOffset(rule, prior);
781             while (year < endYear) {
782                 const TransitionTimePair pair(rule, year, newYearOffset);
783                 bool isDst = false;
784                 Q_ASSERT(invalidMSecs() <= afterMSecsSinceEpoch); // invalid is min qint64
785                 if (pair.std > afterMSecsSinceEpoch) {
786                     isDst = pair.std > pair.dst && pair.dst > afterMSecsSinceEpoch;
787                 } else if (pair.dst > afterMSecsSinceEpoch) {
788                     isDst = true;
789                 } else {
790                     newYearOffset = rule.standardTimeBias;
791                     if (pair.dst > pair.std)
792                         newYearOffset += rule.daylightTimeBias;
793                     // Try a later year for this rule (once).
794                     prior = year;
795                     year = year == -1 ? 1 : year + 1; // No year 0
796                     continue;
797                 }
798 
799                 if (isDst)
800                     return ruleToData(rule, pair.dst, QTimeZone::DaylightTime, pair.fakesDst());
801                 return ruleToData(rule, pair.std, QTimeZone::StandardTime, pair.fakesDst());
802             }
803             // Fell off end of rule, try next rule.
804         } // else: no transition during rule's period
805     }
806     // Apparently no transition after the given time:
807     return invalidData();
808 }
809 
previousTransition(qint64 beforeMSecsSinceEpoch) const810 QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const
811 {
812     const qint64 startOfTime = invalidMSecs() + 1;
813     if (beforeMSecsSinceEpoch <= startOfTime)
814         return invalidData();
815 
816     int year = msecsToDate(beforeMSecsSinceEpoch).year();
817     for (int ruleIndex = ruleIndexForYear(m_tranRules, year);
818          ruleIndex >= 0; --ruleIndex) {
819         const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
820         // Does this rule's period include any transition at all ?
821         if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
822             int prior = year == 1 ? -1 : year - 1; // No year 0.
823             const int endYear = qMax(rule.startYear, prior);
824             while (year >= endYear) {
825                 const int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
826                     ? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
827                     : yearEndOffset(rule, prior);
828                 const TransitionTimePair pair(rule, year, newYearOffset);
829                 bool isDst = false;
830                 if (pair.std != invalidMSecs() && pair.std < beforeMSecsSinceEpoch) {
831                     isDst = pair.std < pair.dst && pair.dst < beforeMSecsSinceEpoch;
832                 } else if (pair.dst != invalidMSecs() && pair.dst < beforeMSecsSinceEpoch) {
833                     isDst = true;
834                 } else {
835                     year = prior; // Try an earlier year for this rule (once).
836                     prior = year == 1 ? -1 : year - 1; // No year 0.
837                     continue;
838                 }
839                 if (isDst)
840                     return ruleToData(rule, pair.dst, QTimeZone::DaylightTime, pair.fakesDst());
841                 return ruleToData(rule, pair.std, QTimeZone::StandardTime, pair.fakesDst());
842             }
843             // Fell off start of rule, try previous rule.
844         } else if (ruleIndex == 0) {
845             // Treat a no-transition first rule as a transition at the start of
846             // time, so that a scan through all rules *does* see it as the first
847             // rule:
848             return ruleToData(rule, startOfTime, QTimeZone::StandardTime, false);
849         } // else: no transition during rule's period
850         if (year >= rule.startYear) {
851             year = rule.startYear - 1; // Seek last transition in new rule
852             if (!year)
853                 --year;
854         }
855     }
856     // Apparently no transition before the given time:
857     return invalidData();
858 }
859 
systemTimeZoneId() const860 QByteArray QWinTimeZonePrivate::systemTimeZoneId() const
861 {
862     const QLocale::Country country = userCountry();
863     const QByteArray windowsId = windowsSystemZoneId();
864     QByteArray ianaId;
865     // If we have a real country, then try get a specific match for that country
866     if (country != QLocale::AnyCountry)
867         ianaId = windowsIdToDefaultIanaId(windowsId, country);
868     // If we don't have a real country, or there wasn't a specific match, try the global default
869     if (ianaId.isEmpty())
870         ianaId = windowsIdToDefaultIanaId(windowsId);
871     return ianaId;
872 }
873 
availableTimeZoneIds() const874 QList<QByteArray> QWinTimeZonePrivate::availableTimeZoneIds() const
875 {
876     QList<QByteArray> result;
877     const auto winIds = availableWindowsIds();
878     for (const QByteArray &winId : winIds)
879         result += windowsIdToIanaIds(winId);
880     std::sort(result.begin(), result.end());
881     result.erase(std::unique(result.begin(), result.end()), result.end());
882     return result;
883 }
884 
ruleToData(const QWinTransitionRule & rule,qint64 atMSecsSinceEpoch,QTimeZone::TimeType type,bool fakeDst) const885 QTimeZonePrivate::Data QWinTimeZonePrivate::ruleToData(const QWinTransitionRule &rule,
886                                                        qint64 atMSecsSinceEpoch,
887                                                        QTimeZone::TimeType type,
888                                                        bool fakeDst) const
889 {
890     Data tran = invalidData();
891     tran.atMSecsSinceEpoch = atMSecsSinceEpoch;
892     tran.standardTimeOffset = rule.standardTimeBias * -60;
893     if (fakeDst) {
894         tran.daylightTimeOffset = 0;
895         tran.abbreviation = m_standardName;
896         // Rule may claim we're in DST when it's actually a standard time change:
897         if (type == QTimeZone::DaylightTime)
898             tran.standardTimeOffset += rule.daylightTimeBias * -60;
899     } else if (type == QTimeZone::DaylightTime) {
900         tran.daylightTimeOffset = rule.daylightTimeBias * -60;
901         tran.abbreviation = m_daylightName;
902     } else {
903         tran.daylightTimeOffset = 0;
904         tran.abbreviation = m_standardName;
905     }
906     tran.offsetFromUtc = tran.standardTimeOffset + tran.daylightTimeOffset;
907     return tran;
908 }
909 
910 QT_END_NAMESPACE
911