From f767d3a1b20b16c5e19456d3839651ffe14dd442 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 31 Oct 2013 11:07:47 +0100 Subject: QTimeZone - Fix Windows Transitions The Windows tz transition routines were not checking for a number of invalid scenarios, in particular where there are no next transitions able to be calcualted, leading to infinite loops. Change-Id: I262b4321a95be1df4228774ada3908f8d3ed6c1a Reviewed-by: Mitch Curtis Reviewed-by: Thiago Macieira --- src/corelib/tools/qtimezoneprivate_win.cpp | 152 +++++++++++++++++------------ 1 file changed, 89 insertions(+), 63 deletions(-) diff --git a/src/corelib/tools/qtimezoneprivate_win.cpp b/src/corelib/tools/qtimezoneprivate_win.cpp index efdd6676fb..f411d35b3d 100644 --- a/src/corelib/tools/qtimezoneprivate_win.cpp +++ b/src/corelib/tools/qtimezoneprivate_win.cpp @@ -254,6 +254,10 @@ static QByteArray windowsSystemZoneId() static QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year) { + // If month is 0 then there is no date + if (rule.wMonth == 0) + return QDate(); + SYSTEMTIME time = rule; // If the year isn't set, then the rule date is relative if (time.wYear == 0) { @@ -269,9 +273,10 @@ static QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year) while (date.month() != time.wMonth) date = date.addDays(-7); return date; - } else { - return QDate(time.wYear, time.wMonth, time.wDay); } + + // If the year is set then is an absolute date + return QDate(time.wYear, time.wMonth, time.wDay); } // Converts a date/time value into msecs @@ -285,19 +290,25 @@ static void calculateTransitionsForYear(const QWinTimeZonePrivate::QWinTransitio qint64 *stdMSecs, qint64 *dstMSecs) { // TODO Consider caching the calculated values - - // The local time in Daylight Time when switches to Standard TIme + // The local time in Daylight Time when switches to Standard Time QDate standardDate = calculateTransitionLocalDate(rule.standardTimeRule, year); QTime standardTime = QTime(rule.standardTimeRule.wHour, rule.standardTimeRule.wMinute, rule.standardTimeRule.wSecond); - // The local time in Standard Time when switches to Daylight TIme + if (standardDate.isValid() && standardTime.isValid()) { + *stdMSecs = timeToMSecs(standardDate, standardTime) + + ((rule.standardTimeBias + rule.daylightTimeBias) * 60000); + } else { + *stdMSecs = QTimeZonePrivate::invalidMSecs(); + } + + // The local time in Standard Time when switches to Daylight Time QDate daylightDate = calculateTransitionLocalDate(rule.daylightTimeRule, year); QTime daylightTime = QTime(rule.daylightTimeRule.wHour, rule.daylightTimeRule.wMinute, rule.daylightTimeRule.wSecond); - - *stdMSecs = timeToMSecs(standardDate, standardTime) - + ((rule.standardTimeBias + rule.daylightTimeBias) * 60000); - *dstMSecs = timeToMSecs(daylightDate, daylightTime) + (rule.standardTimeBias * 60000); + if (standardDate.isValid() && standardTime.isValid()) + *dstMSecs = timeToMSecs(daylightDate, daylightTime) + (rule.standardTimeBias * 60000); + else + *dstMSecs = QTimeZonePrivate::invalidMSecs(); } static QLocale::Country userCountry() @@ -465,15 +476,8 @@ bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const { - // Convert MSecs to year to get transitions for, but around 31 Dec/1 Jan may not be right year - // So get the year after we think we want transitions for, to be safe - QDate date = msecsToDate(forMSecsSinceEpoch); - int year; - int month; - int day; - date.getDate(&year, &month, &day); - if ((month == 12 && day == 31) || (month == 1 && day == 1)) - ++year; + // Convert MSecs to year to get transitions for, assumes no transitions around 31 Dec/1 Jan + int year = msecsToDate(forMSecsSinceEpoch).year(); qint64 first; qint64 second; @@ -485,20 +489,22 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) cons // Convert the transition rules into msecs for the year we want to try rule = ruleForYear(year); calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); - first = qMin(stdMSecs, dstMSecs); - second = qMax(stdMSecs, dstMSecs); - if (forMSecsSinceEpoch >= second) + if (stdMSecs < dstMSecs) { + first = stdMSecs; + second = dstMSecs; + } else { + first = dstMSecs; + second = stdMSecs; + } + if (forMSecsSinceEpoch >= second && second != invalidMSecs()) next = second; - else if (forMSecsSinceEpoch >= first) + else if (forMSecsSinceEpoch >= first && first != invalidMSecs()) next = first; // If didn't fall in this year, try the previous --year; - } while (forMSecsSinceEpoch < first && year >= MIN_YEAR); + } while (next == maxMSecs() && year >= MIN_YEAR); - if (next == dstMSecs) - return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::DaylightTime); - else - return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime); + return ruleToData(rule, forMSecsSinceEpoch, (next == dstMSecs) ? QTimeZone::DaylightTime : QTimeZone::StandardTime); } bool QWinTimeZonePrivate::hasTransitions() const @@ -512,79 +518,99 @@ bool QWinTimeZonePrivate::hasTransitions() const QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const { - // Convert MSecs to year to get transitions for, but around 31 Dec/1 Jan may not be right year - // Get the year before we think we want transitions for, to be safe - QDate date = msecsToDate(afterMSecsSinceEpoch); - int year; - int month; - int day; - date.getDate(&year, &month, &day); - if ((month == 12 && day == 31) || (month == 1 && day == 1)) - --year; + // Convert MSecs to year to get transitions for, assumes no transitions around 31 Dec/1 Jan + int year = msecsToDate(afterMSecsSinceEpoch).year(); + + QWinTransitionRule rule; + // If the required year falls after the last rule start year and the last rule has no + // valid future transition calculations then there is no next transition + if (year > m_tranRules.last().startYear) { + rule = ruleForYear(year); + // If the rules have either a fixed year, or no month, then no future trans + if (rule.standardTimeRule.wYear != 0 || rule.daylightTimeRule.wYear != 0 + || rule.standardTimeRule.wMonth == 0 || rule.daylightTimeRule.wMonth == 0) { + return invalidData(); + } + } + // Otherwise we have a valid rule for the required year that can be used + // to calculate this year or next qint64 first; qint64 second; qint64 next = minMSecs(); qint64 stdMSecs; qint64 dstMSecs; - QWinTransitionRule rule; do { // Convert the transition rules into msecs for the year we want to try rule = ruleForYear(year); calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); // Find the first and second transition for the year - first = qMin(stdMSecs, dstMSecs); - second = qMax(stdMSecs, dstMSecs); + if (stdMSecs < dstMSecs) { + first = stdMSecs; + second = dstMSecs; + } else { + first = dstMSecs; + second = stdMSecs; + } if (afterMSecsSinceEpoch < first) next = first; else if (afterMSecsSinceEpoch < second) next = second; // If didn't fall in this year, try the next ++year; - } while (afterMSecsSinceEpoch >= second && year <= MAX_YEAR); + } while (next == minMSecs() && year <= MAX_YEAR); - if (next == dstMSecs) - return ruleToData(rule, next, QTimeZone::DaylightTime); - else - return ruleToData(rule, next, QTimeZone::StandardTime); + if (next == minMSecs() || next == invalidMSecs()) + return invalidData(); + + return ruleToData(rule, next, (next == dstMSecs) ? QTimeZone::DaylightTime : QTimeZone::StandardTime); } QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const { - // Convert MSecs to year to get transitions for, but around 31 Dec/1 Jan may not be right year - // So get the year after we think we want transitions for, to be safe - QDate date = msecsToDate(beforeMSecsSinceEpoch); - int year; - int month; - int day; - date.getDate(&year, &month, &day); - if ((month == 12 && day == 31) || (month == 1 && day == 1)) - ++year; + // Convert MSecs to year to get transitions for, assumes no transitions around 31 Dec/1 Jan + int year = msecsToDate(beforeMSecsSinceEpoch).year(); + + QWinTransitionRule rule; + // If the required year falls before the first rule start year and the first rule has no + // valid transition calculations then there is no previous transition + if (year < m_tranRules.first().startYear) { + rule = ruleForYear(year); + // If the rules have either a fixed year, or no month, then no previous trans + if (rule.standardTimeRule.wYear != 0 || rule.daylightTimeRule.wYear != 0 + || rule.standardTimeRule.wMonth == 0 || rule.daylightTimeRule.wMonth == 0) { + return invalidData(); + } + } qint64 first; qint64 second; qint64 next = maxMSecs(); qint64 stdMSecs; qint64 dstMSecs; - QWinTransitionRule rule; do { // Convert the transition rules into msecs for the year we want to try rule = ruleForYear(year); calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); - first = qMin(stdMSecs, dstMSecs); - second = qMax(stdMSecs, dstMSecs); - if (beforeMSecsSinceEpoch > second) + if (stdMSecs < dstMSecs) { + first = stdMSecs; + second = dstMSecs; + } else { + first = dstMSecs; + second = stdMSecs; + } + if (beforeMSecsSinceEpoch > second && second != invalidMSecs()) next = second; - else if (beforeMSecsSinceEpoch > first) + else if (beforeMSecsSinceEpoch > first && first != invalidMSecs()) next = first; // If didn't fall in this year, try the previous --year; - } while (beforeMSecsSinceEpoch < first && year >= MIN_YEAR); + } while (next == maxMSecs() && year >= MIN_YEAR); - if (next == dstMSecs) - return ruleToData(rule, next, QTimeZone::DaylightTime); - else - return ruleToData(rule, next, QTimeZone::StandardTime); + if (next == maxMSecs()) + return invalidData(); + + return ruleToData(rule, next, (next == dstMSecs) ? QTimeZone::DaylightTime : QTimeZone::StandardTime); } QByteArray QWinTimeZonePrivate::systemTimeZoneId() const -- cgit v1.2.3