MS TZ data: avoid calculating a date in year 0

There is no year 0 in the proleptic Gregorian calendar, so QDate()
won't be happy if asked for a date in it. Tweak scanning of the data
we get from MS-Win so as to avoid a date calculation that could
otherwise happen in year 0 when constructing
QDateTime(QDate(1, 1, 1), QTime(0, 0, 0), QTimeZone("Australia/Sydney")).

Added a test for this case, which Oliver Wolff has kindly verified
does reproduce the assertion failure. However, Coin is unable to
reproduce, as all its MS builds are configured with -release, so
Q_ASSERT() does nothing. (The relevant code then skips over year 0,
albeit for the wrong reasons, and gets the right results, albeit
inefficiently, leaving no other symptom by which to detect the
problem.)

Fixes: QTBUG-78051
Change-Id: Ife8a7470e5bd450bc421e89b3f1e1211756fc889
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Edward Welbourne 2019-09-05 17:18:56 +02:00
parent 9864d2c6f3
commit 8286daba03
2 changed files with 37 additions and 13 deletions

View File

@ -371,6 +371,7 @@ QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year)
// Otherwise, the rule date is annual and relative:
const int dayOfWeek = rule.wDayOfWeek == 0 ? 7 : rule.wDayOfWeek;
QDate date(year, rule.wMonth, 1);
Q_ASSERT(date.isValid());
// How many days before was last dayOfWeek before target month ?
int adjust = dayOfWeek - date.dayOfWeek(); // -6 <= adjust < 7
if (adjust >= 0) // Ensure -7 <= adjust < 0:
@ -401,6 +402,7 @@ qint64 calculateTransitionForYear(const SYSTEMTIME &rule, int year, int bias)
{
// TODO Consider caching the calculated values - i.e. replace SYSTEMTIME in
// WinTransitionRule; do this in init() once and store the results.
Q_ASSERT(year);
const QDate date = calculateTransitionLocalDate(rule, year);
const QTime time = QTime(rule.wHour, rule.wMinute, rule.wSecond);
if (date.isValid() && time.isValid())
@ -479,6 +481,7 @@ struct TransitionTimePair
int yearEndOffset(const QWinTimeZonePrivate::QWinTransitionRule &rule, int year)
{
Q_ASSERT(year);
int offset = rule.standardTimeBias;
// Only needed to help another TransitionTimePair work out year + 1's start
// offset; and the oldYearOffset we use only affects an alleged transition
@ -743,11 +746,12 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) cons
const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
// Does this rule's period include any transition at all ?
if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
const int endYear = qMax(rule.startYear, year - 1);
int prior = year == 1 ? -1 : year - 1; // No year 0.
const int endYear = qMax(rule.startYear, prior);
while (year >= endYear) {
const int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1)
: yearEndOffset(rule, year - 1);
? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
: yearEndOffset(rule, prior);
const TransitionTimePair pair(rule, year, newYearOffset);
bool isDst = false;
if (pair.std != invalidMSecs() && pair.std <= forMSecsSinceEpoch) {
@ -755,7 +759,8 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) cons
} else if (pair.dst != invalidMSecs() && pair.dst <= forMSecsSinceEpoch) {
isDst = true;
} else {
--year; // Try an earlier year for this rule (once).
year = prior; // Try an earlier year for this rule (once).
prior = year == 1 ? -1 : year - 1; // No year 0.
continue;
}
return ruleToData(rule, forMSecsSinceEpoch,
@ -767,8 +772,11 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::data(qint64 forMSecsSinceEpoch) cons
// No transition, no DST, use the year's standard time.
return ruleToData(rule, forMSecsSinceEpoch, QTimeZone::StandardTime);
}
if (year >= rule.startYear)
if (year >= rule.startYear) {
year = rule.startYear - 1; // Seek last transition in new rule.
if (!year)
--year;
}
}
// We don't have relevant data :-(
return invalidData();
@ -795,9 +803,10 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinc
year = rule.startYear; // Seek first transition in this rule.
const int endYear = ruleIndex + 1 < m_tranRules.count()
? qMin(m_tranRules.at(ruleIndex + 1).startYear, year + 2) : (year + 2);
int prior = year == 1 ? -1 : year - 1; // No year 0.
int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1)
: yearEndOffset(rule, year - 1);
? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
: yearEndOffset(rule, prior);
while (year < endYear) {
const TransitionTimePair pair(rule, year, newYearOffset);
bool isDst = false;
@ -810,7 +819,9 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::nextTransition(qint64 afterMSecsSinc
newYearOffset = rule.standardTimeBias;
if (pair.dst > pair.std)
newYearOffset += rule.daylightTimeBias;
++year; // Try a later year for this rule (once).
// Try a later year for this rule (once).
prior = year;
year = year == -1 ? 1 : year + 1; // No year 0
continue;
}
@ -837,11 +848,12 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSec
const QWinTransitionRule &rule = m_tranRules.at(ruleIndex);
// Does this rule's period include any transition at all ?
if (rule.standardTimeRule.wMonth > 0 || rule.daylightTimeRule.wMonth > 0) {
const int endYear = qMax(rule.startYear, year - 1);
int prior = year == 1 ? -1 : year - 1; // No year 0.
const int endYear = qMax(rule.startYear, prior);
while (year >= endYear) {
const int newYearOffset = (year <= rule.startYear && ruleIndex > 0)
? yearEndOffset(m_tranRules.at(ruleIndex - 1), year - 1)
: yearEndOffset(rule, year - 1);
? yearEndOffset(m_tranRules.at(ruleIndex - 1), prior)
: yearEndOffset(rule, prior);
const TransitionTimePair pair(rule, year, newYearOffset);
bool isDst = false;
if (pair.std != invalidMSecs() && pair.std < beforeMSecsSinceEpoch) {
@ -849,7 +861,8 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSec
} else if (pair.dst != invalidMSecs() && pair.dst < beforeMSecsSinceEpoch) {
isDst = true;
} else {
--year; // Try an earlier year for this rule (once).
year = prior; // Try an earlier year for this rule (once).
prior = year == 1 ? -1 : year - 1; // No year 0.
continue;
}
if (isDst)
@ -863,8 +876,11 @@ QTimeZonePrivate::Data QWinTimeZonePrivate::previousTransition(qint64 beforeMSec
// rule:
return ruleToData(rule, startOfTime, QTimeZone::StandardTime, false);
} // else: no transition during rule's period
if (year >= rule.startYear)
if (year >= rule.startYear) {
year = rule.startYear - 1; // Seek last transition in new rule
if (!year)
--year;
}
}
// Apparently no transition before the given time:
return invalidData();

View File

@ -3352,6 +3352,14 @@ void tst_QDateTime::timeZones() const
QCOMPARE(dt3.timeSpec(), dt1.timeSpec());
QCOMPARE(dt3.timeZone(), dt1.timeZone());
// The start of year 1 should be *describable* in any zone (QTBUG-78051)
dt3 = QDateTime(QDate(1, 1, 1), QTime(0, 0, 0), ausTz);
QVERIFY(dt3.isValid());
// Likewise the end of year -1 (a.k.a. 1 BCE).
dt3 = dt3.addMSecs(-1);
QVERIFY(dt3.isValid());
QCOMPARE(dt3, QDateTime(QDate(-1, 12, 31), QTime(23, 59, 59, 999), ausTz));
// Check datastream serialises the time zone
QByteArray tmp;
{