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 <mitch.curtis@digia.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
unknown 2013-10-31 11:07:47 +01:00 committed by The Qt Project
parent 9b7e6cb83d
commit f767d3a1b2

View File

@ -254,6 +254,10 @@ static QByteArray windowsSystemZoneId()
static QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year) 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; SYSTEMTIME time = rule;
// If the year isn't set, then the rule date is relative // If the year isn't set, then the rule date is relative
if (time.wYear == 0) { if (time.wYear == 0) {
@ -269,9 +273,10 @@ static QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year)
while (date.month() != time.wMonth) while (date.month() != time.wMonth)
date = date.addDays(-7); date = date.addDays(-7);
return date; 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 // Converts a date/time value into msecs
@ -285,19 +290,25 @@ static void calculateTransitionsForYear(const QWinTimeZonePrivate::QWinTransitio
qint64 *stdMSecs, qint64 *dstMSecs) qint64 *stdMSecs, qint64 *dstMSecs)
{ {
// TODO Consider caching the calculated values // 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); QDate standardDate = calculateTransitionLocalDate(rule.standardTimeRule, year);
QTime standardTime = QTime(rule.standardTimeRule.wHour, rule.standardTimeRule.wMinute, QTime standardTime = QTime(rule.standardTimeRule.wHour, rule.standardTimeRule.wMinute,
rule.standardTimeRule.wSecond); 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); QDate daylightDate = calculateTransitionLocalDate(rule.daylightTimeRule, year);
QTime daylightTime = QTime(rule.daylightTimeRule.wHour, rule.daylightTimeRule.wMinute, QTime daylightTime = QTime(rule.daylightTimeRule.wHour, rule.daylightTimeRule.wMinute,
rule.daylightTimeRule.wSecond); rule.daylightTimeRule.wSecond);
if (standardDate.isValid() && standardTime.isValid())
*stdMSecs = timeToMSecs(standardDate, standardTime)
+ ((rule.standardTimeBias + rule.daylightTimeBias) * 60000);
*dstMSecs = timeToMSecs(daylightDate, daylightTime) + (rule.standardTimeBias * 60000); *dstMSecs = timeToMSecs(daylightDate, daylightTime) + (rule.standardTimeBias * 60000);
else
*dstMSecs = QTimeZonePrivate::invalidMSecs();
} }
static QLocale::Country userCountry() static QLocale::Country userCountry()
@ -465,15 +476,8 @@ bool QWinTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) 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 // Convert MSecs to year to get transitions for, assumes no transitions around 31 Dec/1 Jan
// So get the year after we think we want transitions for, to be safe int year = msecsToDate(forMSecsSinceEpoch).year();
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;
qint64 first; qint64 first;
qint64 second; 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 // Convert the transition rules into msecs for the year we want to try
rule = ruleForYear(year); rule = ruleForYear(year);
calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs);
first = qMin(stdMSecs, dstMSecs); if (stdMSecs < dstMSecs) {
second = qMax(stdMSecs, dstMSecs); first = stdMSecs;
if (forMSecsSinceEpoch >= second) second = dstMSecs;
} else {
first = dstMSecs;
second = stdMSecs;
}
if (forMSecsSinceEpoch >= second && second != invalidMSecs())
next = second; next = second;
else if (forMSecsSinceEpoch >= first) else if (forMSecsSinceEpoch >= first && first != invalidMSecs())
next = first; next = first;
// If didn't fall in this year, try the previous // If didn't fall in this year, try the previous
--year; --year;
} while (forMSecsSinceEpoch < first && year >= MIN_YEAR); } while (next == maxMSecs() && year >= MIN_YEAR);
if (next == dstMSecs) return ruleToData(rule, forMSecsSinceEpoch, (next == dstMSecs) ? QTimeZone::DaylightTime : QTimeZone::StandardTime);
return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::DaylightTime);
else
return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime);
} }
bool QWinTimeZonePrivate::hasTransitions() const bool QWinTimeZonePrivate::hasTransitions() const
@ -512,79 +518,99 @@ bool QWinTimeZonePrivate::hasTransitions() const
QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) 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 // Convert MSecs to year to get transitions for, assumes no transitions around 31 Dec/1 Jan
// Get the year before we think we want transitions for, to be safe int year = msecsToDate(afterMSecsSinceEpoch).year();
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;
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 first;
qint64 second; qint64 second;
qint64 next = minMSecs(); qint64 next = minMSecs();
qint64 stdMSecs; qint64 stdMSecs;
qint64 dstMSecs; qint64 dstMSecs;
QWinTransitionRule rule;
do { do {
// Convert the transition rules into msecs for the year we want to try // Convert the transition rules into msecs for the year we want to try
rule = ruleForYear(year); rule = ruleForYear(year);
calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs);
// Find the first and second transition for the year // Find the first and second transition for the year
first = qMin(stdMSecs, dstMSecs); if (stdMSecs < dstMSecs) {
second = qMax(stdMSecs, dstMSecs); first = stdMSecs;
second = dstMSecs;
} else {
first = dstMSecs;
second = stdMSecs;
}
if (afterMSecsSinceEpoch < first) if (afterMSecsSinceEpoch < first)
next = first; next = first;
else if (afterMSecsSinceEpoch < second) else if (afterMSecsSinceEpoch < second)
next = second; next = second;
// If didn't fall in this year, try the next // If didn't fall in this year, try the next
++year; ++year;
} while (afterMSecsSinceEpoch >= second && year <= MAX_YEAR); } while (next == minMSecs() && year <= MAX_YEAR);
if (next == dstMSecs) if (next == minMSecs() || next == invalidMSecs())
return ruleToData(rule, next, QTimeZone::DaylightTime); return invalidData();
else
return ruleToData(rule, next, QTimeZone::StandardTime); return ruleToData(rule, next, (next == dstMSecs) ? QTimeZone::DaylightTime : QTimeZone::StandardTime);
} }
QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const 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 // Convert MSecs to year to get transitions for, assumes no transitions around 31 Dec/1 Jan
// So get the year after we think we want transitions for, to be safe int year = msecsToDate(beforeMSecsSinceEpoch).year();
QDate date = msecsToDate(beforeMSecsSinceEpoch);
int year; QWinTransitionRule rule;
int month; // If the required year falls before the first rule start year and the first rule has no
int day; // valid transition calculations then there is no previous transition
date.getDate(&year, &month, &day); if (year < m_tranRules.first().startYear) {
if ((month == 12 && day == 31) || (month == 1 && day == 1)) rule = ruleForYear(year);
++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 first;
qint64 second; qint64 second;
qint64 next = maxMSecs(); qint64 next = maxMSecs();
qint64 stdMSecs; qint64 stdMSecs;
qint64 dstMSecs; qint64 dstMSecs;
QWinTransitionRule rule;
do { do {
// Convert the transition rules into msecs for the year we want to try // Convert the transition rules into msecs for the year we want to try
rule = ruleForYear(year); rule = ruleForYear(year);
calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs); calculateTransitionsForYear(rule, year, &stdMSecs, &dstMSecs);
first = qMin(stdMSecs, dstMSecs); if (stdMSecs < dstMSecs) {
second = qMax(stdMSecs, dstMSecs); first = stdMSecs;
if (beforeMSecsSinceEpoch > second) second = dstMSecs;
} else {
first = dstMSecs;
second = stdMSecs;
}
if (beforeMSecsSinceEpoch > second && second != invalidMSecs())
next = second; next = second;
else if (beforeMSecsSinceEpoch > first) else if (beforeMSecsSinceEpoch > first && first != invalidMSecs())
next = first; next = first;
// If didn't fall in this year, try the previous // If didn't fall in this year, try the previous
--year; --year;
} while (beforeMSecsSinceEpoch < first && year >= MIN_YEAR); } while (next == maxMSecs() && year >= MIN_YEAR);
if (next == dstMSecs) if (next == maxMSecs())
return ruleToData(rule, next, QTimeZone::DaylightTime); return invalidData();
else
return ruleToData(rule, next, QTimeZone::StandardTime); return ruleToData(rule, next, (next == dstMSecs) ? QTimeZone::DaylightTime : QTimeZone::StandardTime);
} }
QByteArray QWinTimeZonePrivate::systemTimeZoneId() const QByteArray QWinTimeZonePrivate::systemTimeZoneId() const