From 605ae629441d5c00f2c9de7080f5c25558d69acb Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Mon, 19 Apr 2021 13:40:05 +0200 Subject: [PATCH] Correct the range of allowed hours for a POSIX rule's transition A POSIX rule's transition time is allowed an hour in the range from -137 to 137; in particular, a negative hour is allowed, and used by some Greenland zones using Europe's time-of-transition which, as they are more than two hours west of Greenwich, happens before midnight. This means the time of transition can't be represented by a QTime(), so propagate the int that represents it to the code that consumes it; and treat parsing failure as an error rather than "correcting" it - if the transition time is given, it must be valid. Changed tst_QTimeZone::isTimeZoneIdAvailable()'s verification of validity to report the name of the zone it thought was invalid. (A later change, validating POSIX rules, caued this to fail for America/Nuuk without the present fix.) Change-Id: I5c9127ac34d878554dd0aca1c1c7338c7e0e1c28 Reviewed-by: Thiago Macieira --- src/corelib/time/qtimezoneprivate_tz.cpp | 41 +++++++------------ .../corelib/time/qtimezone/tst_qtimezone.cpp | 2 +- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index 9daffbad5d..43043a9d4f 100644 --- a/src/corelib/time/qtimezoneprivate_tz.cpp +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -422,11 +422,11 @@ static int parsePosixTime(const char *begin, const char *end) // caller's responsibility to ensure that begin is part of a null-terminated // string. - const int maxHour = QTimeZone::MaxUtcOffsetSecs / 3600; + const int maxHour = 137; // POSIX's extended range. bool ok = false; const char *cut = begin; hour = qstrtoll(begin, &cut, 10, &ok); - if (!ok || hour < 0 || hour > maxHour || cut > begin + 2) + if (!ok || hour < -maxHour || hour > maxHour || cut > begin + 2) return INT_MIN; begin = cut; if (begin < end && *begin == ':') { @@ -454,15 +454,9 @@ static int parsePosixTime(const char *begin, const char *end) return (hour * 60 + min) * 60 + sec; } -static QTime parsePosixTransitionTime(const QByteArray &timeRule) +static int parsePosixTransitionTime(const QByteArray &timeRule) { - // Format "hh[:mm[:ss]]" - int value = parsePosixTime(timeRule.constBegin(), timeRule.constEnd()); - if (value == INT_MIN) { - // if we failed to parse, return 02:00 - return QTime(2, 0, 0); - } - return QTime::fromMSecsSinceStartOfDay(value * 1000); + return parsePosixTime(timeRule.constBegin(), timeRule.constEnd()); } static int parsePosixOffset(const char *begin, const char *end) @@ -601,24 +595,17 @@ static QList calculatePosixTransitions(const QByteArray return result; // Malformed. // Get the std to dst transtion details - QList dstParts = parts.at(1).split('/'); - QByteArray dstDateRule = dstParts.at(0); - QTime dstTime; - if (dstParts.count() > 1) - dstTime = parsePosixTransitionTime(dstParts.at(1)); - else - dstTime = QTime(2, 0, 0); + const int twoOClock = 7200; // Default transition time, when none specified + const auto dstParts = parts.at(1).split('/'); + const QByteArray dstDateRule = dstParts.at(0); + const int dstTime = dstParts.count() < 2 ? twoOClock : parsePosixTransitionTime(dstParts.at(1)); // Get the dst to std transtion details - QList stdParts = parts.at(2).split('/'); - QByteArray stdDateRule = stdParts.at(0); - QTime stdTime; - if (stdParts.count() > 1) - stdTime = parsePosixTransitionTime(stdParts.at(1)); - else - stdTime = QTime(2, 0, 0); + const auto stdParts = parts.at(2).split('/'); + const QByteArray stdDateRule = stdParts.at(0); + const int stdTime = stdParts.count() < 2 ? twoOClock : parsePosixTransitionTime(stdParts.at(1)); - if (dstDateRule.isEmpty() || stdDateRule.isEmpty() || !dstTime.isValid() || !stdTime.isValid()) + if (dstDateRule.isEmpty() || stdDateRule.isEmpty() || dstTime == INT_MIN || stdTime == INT_MIN) return result; // Malformed. // Limit year to the range QDateTime can represent: @@ -630,14 +617,14 @@ static QList calculatePosixTransitions(const QByteArray for (int year = startYear; year <= endYear; ++year) { QTimeZonePrivate::Data dstData; - QDateTime dst(calculatePosixDate(dstDateRule, year), dstTime, Qt::UTC); + QDateTime dst(calculatePosixDate(dstDateRule, year).startOfDay(Qt::UTC).addSecs(dstTime)); dstData.atMSecsSinceEpoch = dst.toMSecsSinceEpoch() - (stdZone.offset * 1000); dstData.offsetFromUtc = dstZone.offset; dstData.standardTimeOffset = stdZone.offset; dstData.daylightTimeOffset = dstZone.offset - stdZone.offset; dstData.abbreviation = dstZone.name; QTimeZonePrivate::Data stdData; - QDateTime std(calculatePosixDate(stdDateRule, year), stdTime, Qt::UTC); + QDateTime std(calculatePosixDate(stdDateRule, year).startOfDay(Qt::UTC).addSecs(stdTime)); stdData.atMSecsSinceEpoch = std.toMSecsSinceEpoch() - (dstZone.offset * 1000); stdData.offsetFromUtc = stdZone.offset; stdData.standardTimeOffset = stdZone.offset; diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp index 13300cee68..2e197cca4a 100644 --- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp @@ -405,7 +405,7 @@ void tst_QTimeZone::isTimeZoneIdAvailable() const QList available = QTimeZone::availableTimeZoneIds(); for (const QByteArray &id : available) { QVERIFY(QTimeZone::isTimeZoneIdAvailable(id)); - QVERIFY(QTimeZone(id).isValid()); + QVERIFY2(QTimeZone(id).isValid(), id); } }