From 82ad4be4a2e0c2bccb6cd8ea2440aefee4ec48ec Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Wed, 27 Mar 2019 17:01:40 +0000 Subject: Fix various uncommon cases in QTzTimeZonePrivate backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Includes a fixup for 03fadc26e7617aece89949bc7d0acf50f6f050a9, which removed the check on empty transition list, needed when no data are available. Ensured that such a data-free zone would in fact be noticed as invalid during init(). Fixed handling of times before the epoch (we still want to consult a POSIX rule, if that's all that's available) while ensuring we (as documented) ignore DST for such times. Fixed handling of large times (milliseconds since epoch outside int range) when looking up POSIX rules. Gave QTimeZonePrivate a YearRange enum (to be moved to QTimeZone once this merges up to dev) so as to eliminate a magic number (and avoid adding another). Moved year-munging in POSIX rules after the one early return, which doesn't need the year range. Added test-cases for the distant past/future (just checking UTC's offsets; SLES has a minimal version of the UTC data-file that triggers the bugs fixed here for them). Fixes: QTBUG-74666 Fixes: QTBUG-74550 Change-Id: Ief7b7e55c62cf11064700934f404b2fc283614e1 Reviewed-by: Tony Sarajärvi Reviewed-by: Thiago Macieira --- src/corelib/tools/qdatetime_p.h | 4 +++ src/corelib/tools/qtimezoneprivate_tz.cpp | 55 ++++++++++++++++++------------- 2 files changed, 37 insertions(+), 22 deletions(-) (limited to 'src/corelib') diff --git a/src/corelib/tools/qdatetime_p.h b/src/corelib/tools/qdatetime_p.h index 4d30d4192b..b3d00745d0 100644 --- a/src/corelib/tools/qdatetime_p.h +++ b/src/corelib/tools/qdatetime_p.h @@ -140,6 +140,10 @@ public: // Inlined for its one caller in qdatetime.cpp inline void setUtcOffsetByTZ(qint64 atMSecsSinceEpoch); #endif // timezone + + // ### Qt 5.14: expose publicly in QDateTime + // The first and last years of which QDateTime can represent some part: + enum class YearRange : qint32 { First = -292275056, Last = +292278994 }; }; QT_END_NAMESPACE diff --git a/src/corelib/tools/qtimezoneprivate_tz.cpp b/src/corelib/tools/qtimezoneprivate_tz.cpp index f75a61977d..7d85bc077d 100644 --- a/src/corelib/tools/qtimezoneprivate_tz.cpp +++ b/src/corelib/tools/qtimezoneprivate_tz.cpp @@ -39,6 +39,7 @@ #include "qtimezone.h" #include "qtimezoneprivate_p.h" +#include "qdatetime_p.h" // ### Qt 5.14: remove once YearRange is on QDateTime #include #include @@ -520,19 +521,14 @@ PosixZone PosixZone::parse(const char *&pos, const char *end) static QVector calculatePosixTransitions(const QByteArray &posixRule, int startYear, int endYear, - int lastTranMSecs) + qint64 lastTranMSecs) { QVector result; - // Limit year by qint64 max size for msecs - if (startYear > 292278994) - startYear = 292278994; - if (endYear > 292278994) - endYear = 292278994; - // POSIX Format is like "TZ=CST6CDT,M3.2.0/2:00:00,M11.1.0/2:00:00" // i.e. "std offset dst [offset],start[/time],end[/time]" - // See the section about TZ at http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html + // See the section about TZ at + // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html QList parts = posixRule.split(','); PosixZone stdZone, dstZone = PosixZone::invalid(); @@ -583,6 +579,13 @@ static QVector calculatePosixTransitions(const QByteArra else stdTime = QTime(2, 0, 0); + // Limit year to the range QDateTime can represent: + const int minYear = int(QDateTimePrivate::YearRange::First); + const int maxYear = int(QDateTimePrivate::YearRange::Last); + startYear = qBound(minYear, startYear, maxYear); + endYear = qBound(minYear, endYear, maxYear); + Q_ASSERT(startYear <= endYear); + for (int year = startYear; year <= endYear; ++year) { QTimeZonePrivate::Data dstData; QDateTime dst(calculatePosixDate(dstDateRule, year), dstTime, Qt::UTC); @@ -598,13 +601,16 @@ static QVector calculatePosixTransitions(const QByteArra stdData.standardTimeOffset = stdZone.offset; stdData.daylightTimeOffset = 0; stdData.abbreviation = stdZone.name; - // Part of the high year will overflow - if (year == 292278994 && (dstData.atMSecsSinceEpoch < 0 || stdData.atMSecsSinceEpoch < 0)) { + // Part of maxYear will overflow (likewise for minYear, below): + if (year == maxYear && (dstData.atMSecsSinceEpoch < 0 || stdData.atMSecsSinceEpoch < 0)) { if (dstData.atMSecsSinceEpoch > 0) { result << dstData; } else if (stdData.atMSecsSinceEpoch > 0) { result << stdData; } + } else if (year < 1970) { // We ignore DST before the epoch. + if (year > minYear || stdData.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs()) + result << stdData; } else if (dst < std) { result << dstData << stdData; } else { @@ -794,6 +800,8 @@ void QTzTimeZonePrivate::init(const QByteArray &ianaId) tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000; m_tranTimes.append(tran); } + if (m_tranTimes.isEmpty() && m_posixRule.isEmpty()) + return; // Invalid after all ! if (ianaId.isEmpty()) m_id = systemTimeZoneId(); @@ -954,22 +962,25 @@ QVector QTzTimeZonePrivate::getPosixTransitions(qint64 m QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const { // If the required time is after the last transition (or there were none) - // and we have a POSIX rule then use it: - if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch) - && !m_posixRule.isEmpty() && forMSecsSinceEpoch >= 0) { + // and we have a POSIX rule, then use it: + if (!m_posixRule.isEmpty() + && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch)) { QVector posixTrans = getPosixTransitions(forMSecsSinceEpoch); auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), [forMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { return at.atMSecsSinceEpoch <= forMSecsSinceEpoch; }); - if (it > posixTrans.cbegin()) { - QTimeZonePrivate::Data data = *--it; + // Use most recent, if any in the past; or the first if we have no other rules: + if (it > posixTrans.cbegin() || (m_tranTimes.isEmpty() && it < posixTrans.cend())) { + QTimeZonePrivate::Data data = *(it > posixTrans.cbegin() ? it - 1 : it); data.atMSecsSinceEpoch = forMSecsSinceEpoch; return data; } } + if (m_tranTimes.isEmpty()) // Only possible if !isValid() + return invalidData(); - // Otherwise, if we can find a valid tran, then use its rule: + // Otherwise, use the rule for the most recent or first transition: auto last = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), [forMSecsSinceEpoch] (const QTzTransitionTime &at) { return at.atMSecsSinceEpoch <= forMSecsSinceEpoch; @@ -989,9 +1000,9 @@ bool QTzTimeZonePrivate::hasTransitions() const QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const { // If the required time is after the last transition (or there were none) - // and we have a POSIX rule then use it: - if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch) - && !m_posixRule.isEmpty() && afterMSecsSinceEpoch >= 0) { + // and we have a POSIX rule, then use it: + if (!m_posixRule.isEmpty() + && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) { QVector posixTrans = getPosixTransitions(afterMSecsSinceEpoch); auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), [afterMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { @@ -1012,9 +1023,9 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSince QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const { // If the required time is after the last transition (or there were none) - // and we have a POSIX rule then use it: - if ((m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch) - && !m_posixRule.isEmpty() && beforeMSecsSinceEpoch > 0) { + // and we have a POSIX rule, then use it: + if (!m_posixRule.isEmpty() + && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) { QVector posixTrans = getPosixTransitions(beforeMSecsSinceEpoch); auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), [beforeMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { -- cgit v1.2.3