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:
parent
9864d2c6f3
commit
8286daba03
@ -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();
|
||||||
|
@ -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;
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user