From a99c773972d845e453a86974653df19f60d8e7ca Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Fri, 27 Oct 2017 16:40:46 +0200 Subject: Refine handling of wMonth checks in QWinTimeZonePrivate The MS API documents that the two TIME_ZONE_INFORMATION date fields either both have wMonth clear (when there is no DST) or both have it set (for each part of a DST pair). This rule is followed even when there's a standard time change without DST, with perverse results I'll deal with in a later commit. Add code in init() to verify the rule is followed and qWarning() if not. A year with no transitions doesn't imply no earlier or later year has transitions, so don't give up on searches for transitions because of it. Also fix a potential uninitialized variable bug, related to data() breaking out of a loop on such a year. Change-Id: I1ad86c07e54b2eb835a2e02d18dc64022f52a0d9 Reviewed-by: Thiago Macieira --- src/corelib/tools/qtimezoneprivate_win.cpp | 34 ++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/corelib/tools/qtimezoneprivate_win.cpp b/src/corelib/tools/qtimezoneprivate_win.cpp index 3bce55925f..e9930f4fe6 100644 --- a/src/corelib/tools/qtimezoneprivate_win.cpp +++ b/src/corelib/tools/qtimezoneprivate_win.cpp @@ -491,6 +491,7 @@ void QWinTimeZonePrivate::init(const QByteArray &ianaId) m_id = ianaId; } + bool badMonth = false; // Only warn once per zone, if at all. if (!m_windowsId.isEmpty()) { #ifdef QT_USE_REGISTRY_TIMEZONE // Open the base TZI for the time zone @@ -518,6 +519,14 @@ void QWinTimeZonePrivate::init(const QByteArray &ianaId) // Don't repeat a recurrent rule: && (m_tranRules.isEmpty() || !isSameRule(m_tranRules.last(), rule))) { + if (!badMonth + && (rule.standardTimeRule.wMonth == 0) + != (rule.daylightTimeRule.wMonth == 0)) { + badMonth = true; + qWarning("MS registry TZ API violated its wMonth constraint;" + "this may cause mistakes for %s from %d", + ianaId.constData(), year); + } rule.startYear = m_tranRules.isEmpty() ? MIN_YEAR : year; m_tranRules.append(rule); } @@ -553,6 +562,14 @@ void QWinTimeZonePrivate::init(const QByteArray &ianaId) // Don't repeat a recurrent rule && (m_tranRules.isEmpty() || !isSameRule(m_tranRules.last(), rule))) { + if (!badMonth + && (rule.standardTimeRule.wMonth == 0) + != (rule.daylightTimeRule.wMonth == 0)) { + badMonth = true; + qWarning("MS dynamic TZ API violated its wMonth constraint;" + "this may cause mistakes for %s from %d", + ianaId.constData(), year); + } rule.startYear = m_tranRules.isEmpty() ? MIN_YEAR : year; m_tranRules.append(rule); } @@ -650,6 +667,7 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) cons qint64 second; qint64 next = maxMSecs(); TransitionTimePair pair; + Q_ASSERT(next != pair.dst); // so break on first iteration gets standard time QWinTransitionRule rule; do { // Convert the transition rules into msecs for the year we want to try @@ -712,9 +730,11 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinc do { // Convert the transition rules into msecs for the year we want to try rule = m_tranRules.at(ruleIndexForYear(m_tranRules, year)); - // If no transition rules to calculate then no next transition - if (rule.standardTimeRule.wMonth == 0 && rule.daylightTimeRule.wMonth == 0) - return invalidData(); + // If there's no transition this year, check for a later year: + if (rule.standardTimeRule.wMonth == 0 && rule.daylightTimeRule.wMonth == 0) { + ++year; + continue; + } pair = TransitionTimePair(rule, year); // Find the first and second transition for the year if (pair.std < pair.dst) { @@ -762,9 +782,11 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSec do { // Convert the transition rules into msecs for the year we want to try rule = m_tranRules.at(ruleIndexForYear(m_tranRules, year)); - // If no transition rules to calculate then no previous transition - if (rule.standardTimeRule.wMonth == 0 && rule.daylightTimeRule.wMonth == 0) - return invalidData(); + // If there's no transition this year, check for an earlier year: + if (rule.standardTimeRule.wMonth == 0 && rule.daylightTimeRule.wMonth == 0) { + --year; + continue; + } pair = TransitionTimePair(rule, year); if (pair.std < pair.dst) { first = pair.std; -- cgit v1.2.3