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

View File

@ -3352,6 +3352,14 @@ void tst_QDateTime::timeZones() const
QCOMPARE(dt3.timeSpec(), dt1.timeSpec()); QCOMPARE(dt3.timeSpec(), dt1.timeSpec());
QCOMPARE(dt3.timeZone(), dt1.timeZone()); 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 // Check datastream serialises the time zone
QByteArray tmp; QByteArray tmp;
{ {