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)
{
// 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