From 8e6258f059140ce735391b6438d5976dc9469e95 Mon Sep 17 00:00:00 2001 From: John Layt Date: Fri, 20 Sep 2013 18:35:08 +0200 Subject: QTimeZone - Fix TZ file abbreviations Fix parsing of TZ file abbreviations, to correctly return cases where POSIX rule doesn't have separate DST rules, and where abbreviation is a sub-string of another abbreviation, otherwise any toString() call will crash. Add test to exercise all available time zones, especially useful for TZ file to confirm all file format variations dealt with. Fix parsing of Version 3 of TZ file, and ICU display name, to allow all files generated from release 2013f to pass, otherwise isValid() call will crash. Task-number: QTBUG-34061 Change-Id: Ie0b6abc218adff1c8967eb33fdb0762041d2305f Reviewed-by: Thiago Macieira --- src/corelib/tools/qtimezone.cpp | 6 + src/corelib/tools/qtimezoneprivate.cpp | 5 +- src/corelib/tools/qtimezoneprivate_p.h | 1 + src/corelib/tools/qtimezoneprivate_tz.cpp | 259 +++++++++++++-------- .../auto/corelib/tools/qtimezone/tst_qtimezone.cpp | 54 ++++- 5 files changed, 221 insertions(+), 104 deletions(-) diff --git a/src/corelib/tools/qtimezone.cpp b/src/corelib/tools/qtimezone.cpp index 37d6e183a7..5fec06b30a 100644 --- a/src/corelib/tools/qtimezone.cpp +++ b/src/corelib/tools/qtimezone.cpp @@ -708,6 +708,9 @@ bool QTimeZone::hasTransitions() const This is most useful when you have a Transition time and wish to find the Transition after it. + If there is no transition after the given \a afterDateTime then an invalid + OffsetData will be returned with an invalid QDateTime. + The given \a afterDateTime is exclusive. \sa hasTransitions(), previousTransition(), transitions() @@ -726,6 +729,9 @@ QTimeZone::OffsetData QTimeZone::nextTransition(const QDateTime &afterDateTime) This is most useful when you have a Transition time and wish to find the Transition before it. + If there is no transition before the given \a beforeDateTime then an invalid + OffsetData will be returned with an invalid QDateTime. + The given \a beforeDateTime is exclusive. \sa hasTransitions(), nextTransition(), transitions() diff --git a/src/corelib/tools/qtimezoneprivate.cpp b/src/corelib/tools/qtimezoneprivate.cpp index cbf731a19e..dd4e18e01f 100644 --- a/src/corelib/tools/qtimezoneprivate.cpp +++ b/src/corelib/tools/qtimezoneprivate.cpp @@ -328,10 +328,11 @@ QTimeZonePrivate::DataList QTimeZonePrivate::transitions(qint64 fromMSecsSinceEp qint64 toMSecsSinceEpoch) const { DataList list; - if (toMSecsSinceEpoch > fromMSecsSinceEpoch) { + if (toMSecsSinceEpoch >= fromMSecsSinceEpoch) { // fromMSecsSinceEpoch is inclusive but nextTransitionTime() is exclusive so go back 1 msec Data next = nextTransition(fromMSecsSinceEpoch - 1); - while (next.atMSecsSinceEpoch <= toMSecsSinceEpoch) { + while (next.atMSecsSinceEpoch != invalidMSecs() + && next.atMSecsSinceEpoch <= toMSecsSinceEpoch) { list.append(next); next = nextTransition(next.atMSecsSinceEpoch); } diff --git a/src/corelib/tools/qtimezoneprivate_p.h b/src/corelib/tools/qtimezoneprivate_p.h index 186a352a2f..108aec2654 100644 --- a/src/corelib/tools/qtimezoneprivate_p.h +++ b/src/corelib/tools/qtimezoneprivate_p.h @@ -309,6 +309,7 @@ private: bool operator==(const QTzTransitionRule &other) { return (stdOffset == other.stdOffset && dstOffset == other.dstOffset && abbreviationIndex == other.abbreviationIndex); } }; + Data dataForTzTransition(QTzTransitionTime tran) const; QList m_tranTimes; QList m_tranRules; QList m_abbreviations; diff --git a/src/corelib/tools/qtimezoneprivate_tz.cpp b/src/corelib/tools/qtimezoneprivate_tz.cpp index 4bf19178fa..1fb6bb1b5a 100644 --- a/src/corelib/tools/qtimezoneprivate_tz.cpp +++ b/src/corelib/tools/qtimezoneprivate_tz.cpp @@ -149,11 +149,13 @@ static QTzHeader parseTzHeader(QDataStream &ds, bool *ok) if (memcmp(hdr.tzh_magic, TZ_MAGIC, 4) != 0 || ds.status() != QDataStream::Ok) return hdr; - // Parse Version, 1 byte, before 2005 was '\0', since 2005 a '2' + // Parse Version, 1 byte, before 2005 was '\0', since 2005 a '2', since 2013 a '3' ds >> ch; hdr.tzh_version = ch; - if (ds.status() != QDataStream::Ok || (hdr.tzh_version != '2' && hdr.tzh_version != '\0')) + if (ds.status() != QDataStream::Ok + || (hdr.tzh_version != '2' && hdr.tzh_version != '\0' && hdr.tzh_version != '3')) { return hdr; + } // Parse reserved space, 15 bytes ds.readRawData(hdr.tzh_reserved, 15); @@ -238,30 +240,31 @@ static QList parseTzTypes(QDataStream &ds, int tzh_typecnt) return typeList; } -static QMap parseTzAbbreviations(QDataStream &ds, int tzh_charcnt) +static QMap parseTzAbbreviations(QDataStream &ds, int tzh_charcnt, QList typeList) { // Parse the abbreviation list which is tzh_charcnt long with '\0' separated strings. The - // tz_abbrind index points to the first char of the abbreviation in the array, not the - // occurrence in the list. By parsing char at a time we can track the char index and convert - // to an occurrence index. By using a map with tz_abbrind as ordered key we get both index + // QTzType.tz_abbrind index points to the first char of the abbreviation in the array, not the + // occurrence in the list. It can also point to a partial string so we need to use the actual typeList + // index values when parsing. By using a map with tz_abbrind as ordered key we get both index // methods in one data structure and can convert the types afterwards. QMap map; quint8 ch; - QByteArray abbrev; - // Track the start position of each abbreviation - int tz_abbrind = 0; + QByteArray input; + // First parse the full abbrev string for (int i = 0; i < tzh_charcnt && ds.status() == QDataStream::Ok; ++i) { ds >> ch; - if (ds.status() == QDataStream::Ok) { - if (ch == '\0') { - // Have reached end of an abbreviation, so add to map - map[tz_abbrind] = abbrev; - tz_abbrind = i + 1; - abbrev.clear(); - } else { - abbrev.append((char)ch); - } - } + if (ds.status() == QDataStream::Ok) + input.append(char(ch)); + else + return map; + } + // Then extract all the substrings pointed to by typeList + foreach (const QTzType type, typeList) { + QByteArray abbrev; + for (int i = type.tz_abbrind; input.at(i) != '\0'; ++i) + abbrev.append(input.at(i)); + // Have reached end of an abbreviation, so add to map + map[type.tz_abbrind] = abbrev; } return map; } @@ -371,7 +374,7 @@ static QDate calculatePosixDate(const QByteArray dateRule, int year) } } -static QTime parsePosixTime(const QByteArray timeRule) +static QTime parsePosixTime(const QByteArray &timeRule) { // Format "HH:mm:ss", put check parts count just in case QList parts = timeRule.split(':'); @@ -385,7 +388,7 @@ static QTime parsePosixTime(const QByteArray timeRule) return QTime(2, 0, 0); } -static int parsePosixOffset(const QByteArray timeRule) +static int parsePosixOffset(const QByteArray &timeRule) { // Format "[+|-]hh[:mm[:ss]]" QList parts = timeRule.split(':'); @@ -399,7 +402,9 @@ static int parsePosixOffset(const QByteArray timeRule) return 0; } -static QList calculatePosixTransitions(const QByteArray &posixRule, int startYear, int endYear) +static QList calculatePosixTransitions(const QByteArray &posixRule, + int startYear, int endYear, + int lastTranMSecs) { QList list; @@ -443,11 +448,13 @@ static QList calculatePosixTransitions(const QByteArray // If only the name part then no transitions if (parts.count() == 1) { QTimeZonePrivate::Data data; - data.atMSecsSinceEpoch = 0; + data.atMSecsSinceEpoch = lastTranMSecs; data.offsetFromUtc = utcOffset; data.standardTimeOffset = utcOffset; data.daylightTimeOffset = 0; data.abbreviation = stdName; + list << data; + return list; } // If not populated the total dst offset is 1 hour @@ -572,7 +579,7 @@ void QTzTimeZonePrivate::init(const QByteArray &olsenId) QList typeList = parseTzTypes(ds, hdr.tzh_typecnt); if (ds.status() != QDataStream::Ok) return; - QMap abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt); + QMap abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList); if (ds.status() != QDataStream::Ok) return; parseTzLeapSeconds(ds, hdr.tzh_leapcnt, false); @@ -583,7 +590,7 @@ void QTzTimeZonePrivate::init(const QByteArray &olsenId) return; // If version 2 then parse the second block of data - if (hdr.tzh_version == '2') { + if (hdr.tzh_version == '2' || hdr.tzh_version == '3') { ok = false; QTzHeader hdr2 = parseTzHeader(ds, &ok); if (!ok || ds.status() != QDataStream::Ok) @@ -594,7 +601,7 @@ void QTzTimeZonePrivate::init(const QByteArray &olsenId) typeList = parseTzTypes(ds, hdr2.tzh_typecnt); if (ds.status() != QDataStream::Ok) return; - abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt); + abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList); if (ds.status() != QDataStream::Ok) return; parseTzLeapSeconds(ds, hdr2.tzh_leapcnt, true); @@ -682,12 +689,14 @@ QString QTzTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch, if (!m_icu) m_icu = new QIcuTimeZonePrivate(m_id); // TODO small risk may not match if tran times differ due to outdated files - return m_icu->displayName(atMSecsSinceEpoch, nameType, locale); + // TODO Some valid TZ names are not valid ICU names, use translation table? + if (m_icu->isValid()) + return m_icu->displayName(atMSecsSinceEpoch, nameType, locale); #else Q_UNUSED(nameType) Q_UNUSED(locale) - return abbreviation(atMSecsSinceEpoch); #endif // QT_USE_ICU + return abbreviation(atMSecsSinceEpoch); } QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType, @@ -698,19 +707,59 @@ QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType, if (!m_icu) m_icu = new QIcuTimeZonePrivate(m_id); // TODO small risk may not match if tran times differ due to outdated files - return m_icu->displayName(timeType, nameType, locale); + // TODO Some valid TZ names are not valid ICU names, use translation table? + if (m_icu->isValid()) + return m_icu->displayName(timeType, nameType, locale); #else Q_UNUSED(timeType) Q_UNUSED(nameType) Q_UNUSED(locale) - const int atMSecsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); - QTimeZonePrivate::Data tran = data(atMSecsSinceEpoch); - while ((timeType == QTimeZone::StandardTime && tran.daylightTimeOffset != 0) - || (timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset == 0)) { - tran = nextTransition(tran.atMSecsSinceEpoch); - } - return tran.abbreviation; #endif // QT_USE_ICU + // If no ICU available then have to use abbreviations instead + // Abbreviations don't have GenericTime + if (timeType == QTimeZone::GenericTime) + timeType = QTimeZone::StandardTime; + + // Get current tran, if valid and is what we want, then use it + const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch(); + QTimeZonePrivate::Data tran = data(currentMSecs); + if (tran.atMSecsSinceEpoch != invalidMSecs() + && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0) + || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) { + return tran.abbreviation; + } + + // Otherwise get next tran and if valid and is what we want, then use it + tran = nextTransition(currentMSecs); + if (tran.atMSecsSinceEpoch != invalidMSecs() + && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0) + || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) { + return tran.abbreviation; + } + + // Otherwise get prev tran and if valid and is what we want, then use it + tran = previousTransition(currentMSecs); + if (tran.atMSecsSinceEpoch != invalidMSecs()) + tran = previousTransition(tran.atMSecsSinceEpoch); + if (tran.atMSecsSinceEpoch != invalidMSecs() + && ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0) + || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0))) { + return tran.abbreviation; + } + + // Otherwise is strange sequence, so work backwards through trans looking for first match, if any + for (int i = m_tranTimes.size() - 1; i >= 0; --i) { + if (m_tranTimes.at(i).atMSecsSinceEpoch <= currentMSecs) { + tran = dataForTzTransition(m_tranTimes.at(i)); + if ((timeType == QTimeZone::DaylightTime && tran.daylightTimeOffset != 0) + || (timeType == QTimeZone::StandardTime && tran.daylightTimeOffset == 0)) { + return tran.abbreviation; + } + } + } + + // Otherwise if no match use current data + return data(currentMSecs).abbreviation; } QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const @@ -749,35 +798,55 @@ bool QTzTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const return (daylightTimeOffset(atMSecsSinceEpoch) != 0); } +QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime tran) const +{ + QTimeZonePrivate::Data data; + data.atMSecsSinceEpoch = tran.atMSecsSinceEpoch; + QTzTransitionRule rule = m_tranRules.at(tran.ruleIndex); + data.standardTimeOffset = rule.stdOffset; + data.daylightTimeOffset = rule.dstOffset; + data.offsetFromUtc = rule.stdOffset + rule.dstOffset; + data.abbreviation = QString::fromUtf8(m_abbreviations.at(rule.abbreviationIndex)); + return data; +} + QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const { - QTimeZonePrivate::Data data = invalidData(); - int lastTran = m_tranTimes.size() - 1; - int tran; - for (tran = lastTran; tran > 0; --tran) { - if (m_tranTimes.at(tran).atMSecsSinceEpoch <= forMSecsSinceEpoch) - break; - } - // If after the last transition time then we need to use the posix rule if available - if (tran >= lastTran && !m_posixRule.isEmpty()) { - QDateTime dt = QDateTime::fromMSecsSinceEpoch(forMSecsSinceEpoch); - int year = dt.date().year(); - QList posixTrans = calculatePosixTransitions(m_posixRule, year - 1, year + 1); - for (int i = posixTrans.size() - 1; i > 0; --i) { + // If the required time is after the last transition and we have a POSIX rule then use it + if (m_tranTimes.size() > 0 && m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch + &&!m_posixRule.isEmpty() && forMSecsSinceEpoch >= 0) { + const int year = QDateTime::fromMSecsSinceEpoch(forMSecsSinceEpoch, Qt::UTC).date().year(); + const int lastMSecs = (m_tranTimes.size() > 0) ? m_tranTimes.last().atMSecsSinceEpoch : 0; + QList posixTrans = calculatePosixTransitions(m_posixRule, year - 1, + year + 1, lastMSecs); + for (int i = posixTrans.size() - 1; i >= 0; --i) { if (posixTrans.at(i).atMSecsSinceEpoch <= forMSecsSinceEpoch) { + QTimeZonePrivate::Data data; data = posixTrans.at(i); data.atMSecsSinceEpoch = forMSecsSinceEpoch; return data; } } } - data.atMSecsSinceEpoch = forMSecsSinceEpoch; - QTzTransitionRule rule = m_tranRules.at(m_tranTimes.at(tran).ruleIndex); - data.standardTimeOffset = rule.stdOffset; - data.daylightTimeOffset = rule.dstOffset; - data.offsetFromUtc = rule.stdOffset + rule.dstOffset; - data.abbreviation = QString::fromUtf8(m_abbreviations.at(rule.abbreviationIndex)); - return data; + + // Otherwise if we can find a valid tran then use its rule + for (int i = m_tranTimes.size() - 1; i >= 0; --i) { + if (m_tranTimes.at(i).atMSecsSinceEpoch <= forMSecsSinceEpoch) { + Data data = dataForTzTransition(m_tranTimes.at(i)); + data.atMSecsSinceEpoch = forMSecsSinceEpoch; + return data; + } + } + + // Otherwise use the earliest transition we have + if (m_tranTimes.size() > 0) { + Data data = dataForTzTransition(m_tranTimes.at(0)); + data.atMSecsSinceEpoch = forMSecsSinceEpoch; + return data; + } + + // Otherwise we have no rules, so probably an invalid tz, so return invalid data + return invalidData(); } bool QTzTimeZonePrivate::hasTransitions() const @@ -787,60 +856,54 @@ bool QTzTimeZonePrivate::hasTransitions() const QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const { - int lastTran = m_tranTimes.size() - 1; - int tran; - for (tran = 0; tran < lastTran; ++tran) { - if (m_tranTimes.at(tran).atMSecsSinceEpoch > afterMSecsSinceEpoch) - break; - } - // If after the last transition time then we need to use the posix rule if available - if (tran >= lastTran && !m_posixRule.isEmpty()) { - QDateTime dt = QDateTime::fromMSecsSinceEpoch(afterMSecsSinceEpoch); - int year = dt.date().year(); - QList posixTrans = calculatePosixTransitions(m_posixRule, year - 1, year + 1); - for (int i = 0; i < posixTrans.size() - 1; ++i) { + // If the required time is after the last transition and we have a POSIX rule then use it + if (m_tranTimes.size() > 0 && m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch + &&!m_posixRule.isEmpty() && afterMSecsSinceEpoch >= 0) { + const int year = QDateTime::fromMSecsSinceEpoch(afterMSecsSinceEpoch, Qt::UTC).date().year(); + const int lastMSecs = (m_tranTimes.size() > 0) ? m_tranTimes.last().atMSecsSinceEpoch : 0; + QList posixTrans = calculatePosixTransitions(m_posixRule, year - 1, + year + 1, lastMSecs); + for (int i = 0; i < posixTrans.size(); ++i) { if (posixTrans.at(i).atMSecsSinceEpoch > afterMSecsSinceEpoch) return posixTrans.at(i); } } - // Otherwise use the transition we found - QTimeZonePrivate::Data data; - data.atMSecsSinceEpoch = m_tranTimes.at(tran).atMSecsSinceEpoch; - QTzTransitionRule rule = m_tranRules.at(m_tranTimes.at(tran).ruleIndex); - data.standardTimeOffset = rule.stdOffset; - data.daylightTimeOffset = rule.dstOffset; - data.offsetFromUtc = rule.stdOffset + rule.dstOffset; - data.abbreviation = QString::fromUtf8(m_abbreviations.at(rule.abbreviationIndex)); - return data; + + // Otherwise if we can find a valid tran then use its rule + for (int i = 0; i < m_tranTimes.size(); ++i) { + if (m_tranTimes.at(i).atMSecsSinceEpoch > afterMSecsSinceEpoch) { + return dataForTzTransition(m_tranTimes.at(i)); + } + } + + // Otherwise we have no rule, or there is no next transition, so return invalid data + return invalidData(); } QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const { - int lastTran = m_tranTimes.size() - 1; - int tran; - for (tran = lastTran; tran > 0; --tran) { - if (m_tranTimes.at(tran).atMSecsSinceEpoch < beforeMSecsSinceEpoch) - break; - } - // If after the last transition time then we need to use the posix rule if available - if (tran >= lastTran && !m_posixRule.isEmpty()) { - QDateTime dt = QDateTime::fromMSecsSinceEpoch(beforeMSecsSinceEpoch); - int year = dt.date().year(); - QList posixTrans = calculatePosixTransitions(m_posixRule, year - 1, year + 1); - for (int i = posixTrans.size() - 1; i > 0; --i) { + // If the required time is after the last transition and we have a POSIX rule then use it + if (m_tranTimes.size() > 0 && m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch + &&!m_posixRule.isEmpty() && beforeMSecsSinceEpoch > 0) { + const int year = QDateTime::fromMSecsSinceEpoch(beforeMSecsSinceEpoch, Qt::UTC).date().year(); + const int lastMSecs = (m_tranTimes.size() > 0) ? m_tranTimes.last().atMSecsSinceEpoch : 0; + QList posixTrans = calculatePosixTransitions(m_posixRule, year - 1, + year + 1, lastMSecs); + for (int i = posixTrans.size() - 1; i >= 0; --i) { if (posixTrans.at(i).atMSecsSinceEpoch < beforeMSecsSinceEpoch) return posixTrans.at(i); } } - // Otherwise use the transition we found - QTimeZonePrivate::Data data; - data.atMSecsSinceEpoch = m_tranTimes.at(tran).atMSecsSinceEpoch; - QTzTransitionRule rule = m_tranRules.at(m_tranTimes.at(tran).ruleIndex); - data.standardTimeOffset = rule.stdOffset; - data.daylightTimeOffset = rule.dstOffset; - data.offsetFromUtc = rule.stdOffset + rule.dstOffset; - data.abbreviation = QString::fromUtf8(m_abbreviations.at(rule.abbreviationIndex)); - return data; + + // Otherwise if we can find a valid tran then use its rule + for (int i = m_tranTimes.size() - 1; i >= 0; --i) { + if (m_tranTimes.at(i).atMSecsSinceEpoch < beforeMSecsSinceEpoch) { + return dataForTzTransition(m_tranTimes.at(i)); + } + } + + // Otherwise we have no rule, so return invalid data + return invalidData(); } // TODO Could cache the value and monitor the required files for any changes diff --git a/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp index ac8530bd54..cfb20f51e4 100644 --- a/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/tools/qtimezone/tst_qtimezone.cpp @@ -57,6 +57,7 @@ private slots: void nullTest(); void dataStreamTest(); void availableTimeZoneIds(); + void stressTest(); void windowsId(); // Backend tests void utcTest(); @@ -75,7 +76,7 @@ private: tst_QTimeZone::tst_QTimeZone() { - // Set to true to print debug output + // Set to true to print debug output, test Display Names and run long stress tests debug = false; } @@ -367,6 +368,50 @@ void tst_QTimeZone::availableTimeZoneIds() } } +void tst_QTimeZone::stressTest() +{ + QList idList = QTimeZone::availableTimeZoneIds(); + foreach (const QByteArray &id, idList) { + QTimeZone testZone = QTimeZone(id); + QCOMPARE(testZone.isValid(), true); + QCOMPARE(testZone.id(), id); + QDateTime testDate = QDateTime(QDate(2015, 1, 1), QTime(0, 0, 0), Qt::UTC); + testZone.country(); + testZone.comment(); + testZone.displayName(testDate); + testZone.displayName(QTimeZone::DaylightTime); + testZone.displayName(QTimeZone::StandardTime); + testZone.abbreviation(testDate); + testZone.offsetFromUtc(testDate); + testZone.standardTimeOffset(testDate); + testZone.daylightTimeOffset(testDate); + testZone.hasDaylightTime(); + testZone.isDaylightTime(testDate); + testZone.offsetData(testDate); + testZone.hasTransitions(); + testZone.nextTransition(testDate); + testZone.previousTransition(testDate); + // Dates known to be outside possible tz file pre-calculated rules range + QDateTime lowDate1 = QDateTime(QDate(1800, 1, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime lowDate2 = QDateTime(QDate(1800, 6, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime highDate1 = QDateTime(QDate(2200, 1, 1), QTime(0, 0, 0), Qt::UTC); + QDateTime highDate2 = QDateTime(QDate(2200, 6, 1), QTime(0, 0, 0), Qt::UTC); + testZone.nextTransition(lowDate1); + testZone.nextTransition(lowDate2); + testZone.previousTransition(lowDate2); + testZone.previousTransition(lowDate2); + testZone.nextTransition(highDate1); + testZone.nextTransition(highDate2); + testZone.previousTransition(highDate1); + testZone.previousTransition(highDate2); + if (debug) { + // This could take a long time, depending on platform and database + qDebug() << "Stress test calculating transistions for" << testZone.id(); + testZone.transitions(lowDate1, highDate1); + } + } +} + void tst_QTimeZone::windowsId() { /* @@ -639,10 +684,11 @@ void tst_QTimeZone::tzTest() QCOMPARE(dat.standardTimeOffset, 3600); QCOMPARE(dat.daylightTimeOffset, 0); + // Test previous to low value is invalid dat = tzp.previousTransition(-9999999999999); - QCOMPARE(dat.atMSecsSinceEpoch, (qint64)-2422054408000); - QCOMPARE(dat.standardTimeOffset, 3600); - QCOMPARE(dat.daylightTimeOffset, 0); + QCOMPARE(dat.atMSecsSinceEpoch, std::numeric_limits::min()); + QCOMPARE(dat.standardTimeOffset, std::numeric_limits::min()); + QCOMPARE(dat.daylightTimeOffset, std::numeric_limits::min()); dat = tzp.nextTransition(-9999999999999); QCOMPARE(dat.atMSecsSinceEpoch, (qint64)-2422054408000); -- cgit v1.2.3