diff options
author | Robin Burchell <robin.burchell@crimson.no> | 2019-05-03 20:02:10 +0200 |
---|---|---|
committer | Edward Welbourne <edward.welbourne@qt.io> | 2020-03-20 16:58:09 +0200 |
commit | 2af04860f6536bbbf82ee21d6aa95ca33a60fbf5 (patch) | |
tree | b5388ea57e36cccc6a2f50acd5013681bd6b019f | |
parent | d535dfea1f2d801268396f328c5c99968ff164ec (diff) | |
download | qtbase-2af04860f6536bbbf82ee21d6aa95ca33a60fbf5.tar.gz |
qtimezoneprivate_tz: Apply a cache over the top of timezone data
Constantly re-reading the timezone information only to be told the exact
same thing is wildly expensive, which can hurt in operations that cause
a lot of QTimeZone creation, for example, V4's DateObject - which
creates them a lot (in DaylightSavingTA).
This performance problem was identified when I noticed that a
QDateTime binding updated once per frame was causing >100% CPU usage
(on a desktop!) thanks to a QtQuickControls 1 Calendar (which has a
number of bindings to the date's properties like getMonth() and so
on).
The newly added tst_QTimeZone::systemTimeZone benchmark gets a ~90%
decrease in instruction count:
--- before
+++ after
PASS : tst_QTimeZone::systemTimeZone()
RESULT : tst_QTimeZone::systemTimeZone():
- 0.024 msecs per iteration (total: 51, iterations: 2048)
+ 0.0036 msecs per iteration (total: 59, iterations: 16384)
Also impacted (over in QDateTime) is
tst_QDateTime::setMSecsSinceEpochTz(). The results here are - on the
surface - less impressive (~0.17% drop), however, it isn't even
creating QTimeZone on a hot path to begin with, so a large drop would
have been a surprise.
Added several further benchmarks to cover non-system zones and
traverse transitions.
Done-With: Edward Welbourne <edward.welbourne@qt.io>
Task-number: QTBUG-75585
Change-Id: I044a84fc2d3a2dc965f63cd3a3299fc509750bf7
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
-rw-r--r-- | src/corelib/time/qtimezoneprivate_p.h | 16 | ||||
-rw-r--r-- | src/corelib/time/qtimezoneprivate_tz.cpp | 141 | ||||
-rw-r--r-- | tests/benchmarks/corelib/time/qtimezone/main.cpp | 122 |
3 files changed, 222 insertions, 57 deletions
diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h index 5f6491ef81..c3e9064b8c 100644 --- a/src/corelib/time/qtimezoneprivate_p.h +++ b/src/corelib/time/qtimezoneprivate_p.h @@ -287,6 +287,16 @@ Q_DECL_CONSTEXPR inline bool operator==(const QTzTransitionRule &lhs, const QTzT Q_DECL_CONSTEXPR inline bool operator!=(const QTzTransitionRule &lhs, const QTzTransitionRule &rhs) noexcept { return !operator==(lhs, rhs); } +// These are stored separately from QTzTimeZonePrivate so that they can be +// cached, avoiding the need to re-parse them from disk constantly. +struct QTzTimeZoneCacheEntry +{ + QVector<QTzTransitionTime> m_tranTimes; + QVector<QTzTransitionRule> m_tranRules; + QList<QByteArray> m_abbreviations; + QByteArray m_posixRule; +}; + class Q_AUTOTEST_EXPORT QTzTimeZonePrivate final : public QTimeZonePrivate { QTzTimeZonePrivate(const QTzTimeZonePrivate &) = default; @@ -334,13 +344,11 @@ private: QVector<QTimeZonePrivate::Data> getPosixTransitions(qint64 msNear) const; Data dataForTzTransition(QTzTransitionTime tran) const; - QVector<QTzTransitionTime> m_tranTimes; - QVector<QTzTransitionRule> m_tranRules; - QList<QByteArray> m_abbreviations; #if QT_CONFIG(icu) mutable QSharedDataPointer<QTimeZonePrivate> m_icu; #endif - QByteArray m_posixRule; + QTzTimeZoneCacheEntry cached_data; + QVector<QTzTransitionTime> tranCache() const { return cached_data.m_tranTimes; } }; #endif // Q_OS_UNIX diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index 3c2695a789..a94ed6b7a9 100644 --- a/src/corelib/time/qtimezoneprivate_tz.cpp +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 Crimson AS <info@crimson.no> ** Copyright (C) 2013 John Layt <jlayt@kde.org> ** Contact: https://www.qt.io/licensing/ ** @@ -42,6 +43,7 @@ #include "private/qlocale_tools_p.h" #include <QtCore/QFile> +#include <QtCore/QMutex> #include <QtCore/QHash> #include <QtCore/QDataStream> #include <QtCore/QDateTime> @@ -649,14 +651,26 @@ QTzTimeZonePrivate *QTzTimeZonePrivate::clone() const return new QTzTimeZonePrivate(*this); } -void QTzTimeZonePrivate::init(const QByteArray &ianaId) +class QTzTimeZoneCache +{ +public: + QTzTimeZoneCacheEntry fetchEntry(const QByteArray &ianaId); + +private: + QTzTimeZoneCacheEntry findEntry(const QByteArray &ianaId); + QHash<QByteArray, QTzTimeZoneCacheEntry> m_cache; + QMutex m_mutex; +}; + +QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId) { + QTzTimeZoneCacheEntry ret; QFile tzif; if (ianaId.isEmpty()) { // Open system tz tzif.setFileName(QStringLiteral("/etc/localtime")); if (!tzif.open(QIODevice::ReadOnly)) - return; + return ret; } else { // Open named tz, try modern path first, if fails try legacy path tzif.setFileName(QLatin1String("/usr/share/zoneinfo/") + QString::fromLocal8Bit(ianaId)); @@ -669,9 +683,9 @@ void QTzTimeZonePrivate::init(const QByteArray &ianaId) if (PosixZone::parse(begin, zoneInfo.constEnd()).hasValidOffset() && (begin == zoneInfo.constEnd() || PosixZone::parse(begin, zoneInfo.constEnd()).hasValidOffset())) { - m_id = m_posixRule = ianaId; + ret.m_posixRule = ianaId; } - return; + return ret; } } } @@ -682,59 +696,59 @@ void QTzTimeZonePrivate::init(const QByteArray &ianaId) bool ok = false; QTzHeader hdr = parseTzHeader(ds, &ok); if (!ok || ds.status() != QDataStream::Ok) - return; + return ret; QVector<QTzTransition> tranList = parseTzTransitions(ds, hdr.tzh_timecnt, false); if (ds.status() != QDataStream::Ok) - return; + return ret; QVector<QTzType> typeList = parseTzTypes(ds, hdr.tzh_typecnt); if (ds.status() != QDataStream::Ok) - return; + return ret; QMap<int, QByteArray> abbrevMap = parseTzAbbreviations(ds, hdr.tzh_charcnt, typeList); if (ds.status() != QDataStream::Ok) - return; + return ret; parseTzLeapSeconds(ds, hdr.tzh_leapcnt, false); if (ds.status() != QDataStream::Ok) - return; + return ret; typeList = parseTzIndicators(ds, typeList, hdr.tzh_ttisstdcnt, hdr.tzh_ttisgmtcnt); if (ds.status() != QDataStream::Ok) - return; + return ret; // If version 2 then parse the second block of data if (hdr.tzh_version == '2' || hdr.tzh_version == '3') { ok = false; QTzHeader hdr2 = parseTzHeader(ds, &ok); if (!ok || ds.status() != QDataStream::Ok) - return; + return ret; tranList = parseTzTransitions(ds, hdr2.tzh_timecnt, true); if (ds.status() != QDataStream::Ok) - return; + return ret; typeList = parseTzTypes(ds, hdr2.tzh_typecnt); if (ds.status() != QDataStream::Ok) - return; + return ret; abbrevMap = parseTzAbbreviations(ds, hdr2.tzh_charcnt, typeList); if (ds.status() != QDataStream::Ok) - return; + return ret; parseTzLeapSeconds(ds, hdr2.tzh_leapcnt, true); if (ds.status() != QDataStream::Ok) - return; + return ret; typeList = parseTzIndicators(ds, typeList, hdr2.tzh_ttisstdcnt, hdr2.tzh_ttisgmtcnt); if (ds.status() != QDataStream::Ok) - return; - m_posixRule = parseTzPosixRule(ds); + return ret; + ret.m_posixRule = parseTzPosixRule(ds); if (ds.status() != QDataStream::Ok) - return; + return ret; } // Translate the TZ file into internal format // Translate the array index based tz_abbrind into list index const int size = abbrevMap.size(); - m_abbreviations.clear(); - m_abbreviations.reserve(size); + ret.m_abbreviations.clear(); + ret.m_abbreviations.reserve(size); QVector<int> abbrindList; abbrindList.reserve(size); for (auto it = abbrevMap.cbegin(), end = abbrevMap.cend(); it != end; ++it) { - m_abbreviations.append(it.value()); + ret.m_abbreviations.append(it.value()); abbrindList.append(it.key()); } for (int i = 0; i < typeList.size(); ++i) @@ -752,7 +766,7 @@ void QTzTimeZonePrivate::init(const QByteArray &ianaId) // Now for each transition time calculate and store our rule: const int tranCount = tranList.count();; - m_tranTimes.reserve(tranCount); + ret.m_tranTimes.reserve(tranCount); // The DST offset when in effect: usually stable, usually an hour: int lastDstOff = 3600; for (int i = 0; i < tranCount; i++) { @@ -806,24 +820,45 @@ void QTzTimeZonePrivate::init(const QByteArray &ianaId) rule.abbreviationIndex = tz_type.tz_abbrind; // If the rule already exist then use that, otherwise add it - int ruleIndex = m_tranRules.indexOf(rule); + int ruleIndex = ret.m_tranRules.indexOf(rule); if (ruleIndex == -1) { - m_tranRules.append(rule); - tran.ruleIndex = m_tranRules.size() - 1; + ret.m_tranRules.append(rule); + tran.ruleIndex = ret.m_tranRules.size() - 1; } else { tran.ruleIndex = ruleIndex; } tran.atMSecsSinceEpoch = tz_tran.tz_time * 1000; - m_tranTimes.append(tran); + ret.m_tranTimes.append(tran); } - if (m_tranTimes.isEmpty() && m_posixRule.isEmpty()) + + return ret; +} + +QTzTimeZoneCacheEntry QTzTimeZoneCache::fetchEntry(const QByteArray &ianaId) +{ + QMutexLocker locker(&m_mutex); + + // search the cache... + const auto& it = m_cache.find(ianaId); + if (it != m_cache.constEnd()) + return *it; + + // ... or build a new entry from scratch + QTzTimeZoneCacheEntry ret = findEntry(ianaId); + m_cache[ianaId] = ret; + return ret; +} + +void QTzTimeZonePrivate::init(const QByteArray &ianaId) +{ + static QTzTimeZoneCache tzCache; + const auto &entry = tzCache.fetchEntry(ianaId); + if (entry.m_tranTimes.isEmpty() && entry.m_posixRule.isEmpty()) return; // Invalid after all ! - if (ianaId.isEmpty()) - m_id = systemTimeZoneId(); - else - m_id = ianaId; + cached_data = std::move(entry); + m_id = ianaId.isEmpty() ? systemTimeZoneId() : ianaId; } QLocale::Country QTzTimeZonePrivate::country() const @@ -903,12 +938,12 @@ QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType, } // Otherwise is strange sequence, so work backwards through trans looking for first match, if any - auto it = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), + auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), [currentMSecs](const QTzTransitionTime &at) { return at.atMSecsSinceEpoch <= currentMSecs; }); - while (it != m_tranTimes.cbegin()) { + while (it != tranCache().cbegin()) { --it; tran = dataForTzTransition(*it); int offset = tran.daylightTimeOffset; @@ -944,7 +979,7 @@ int QTzTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const bool QTzTimeZonePrivate::hasDaylightTime() const { // TODO Perhaps cache as frequently accessed? - for (const QTzTransitionRule &rule : m_tranRules) { + for (const QTzTransitionRule &rule : cached_data.m_tranRules) { if (rule.dstOffset != 0) return true; } @@ -960,11 +995,11 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime { QTimeZonePrivate::Data data; data.atMSecsSinceEpoch = tran.atMSecsSinceEpoch; - QTzTransitionRule rule = m_tranRules.at(tran.ruleIndex); + QTzTransitionRule rule = cached_data.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)); + data.abbreviation = QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)); return data; } @@ -972,37 +1007,37 @@ QVector<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 m { const int year = QDateTime::fromMSecsSinceEpoch(msNear, Qt::UTC).date().year(); // The Data::atMSecsSinceEpoch of the single entry if zone is constant: - qint64 atTime = m_tranTimes.isEmpty() ? msNear : m_tranTimes.last().atMSecsSinceEpoch; - return calculatePosixTransitions(m_posixRule, year - 1, year + 1, atTime); + qint64 atTime = tranCache().isEmpty() ? msNear : tranCache().last().atMSecsSinceEpoch; + return calculatePosixTransitions(cached_data.m_posixRule, year - 1, year + 1, atTime); } 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_posixRule.isEmpty() - && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < forMSecsSinceEpoch)) { + if (!cached_data.m_posixRule.isEmpty() + && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < forMSecsSinceEpoch)) { QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(forMSecsSinceEpoch); auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), [forMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { return at.atMSecsSinceEpoch <= forMSecsSinceEpoch; }); // 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())) { + if (it > posixTrans.cbegin() || (tranCache().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() + if (tranCache().isEmpty()) // Only possible if !isValid() return invalidData(); // Otherwise, use the rule for the most recent or first transition: - auto last = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), + auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(), [forMSecsSinceEpoch] (const QTzTransitionTime &at) { return at.atMSecsSinceEpoch <= forMSecsSinceEpoch; }); - if (last > m_tranTimes.cbegin()) + if (last > tranCache().cbegin()) --last; Data data = dataForTzTransition(*last); data.atMSecsSinceEpoch = forMSecsSinceEpoch; @@ -1018,8 +1053,8 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSince { // If the required time is after the last transition (or there were none) // and we have a POSIX rule, then use it: - if (!m_posixRule.isEmpty() - && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) { + if (!cached_data.m_posixRule.isEmpty() + && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < afterMSecsSinceEpoch)) { QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(afterMSecsSinceEpoch); auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), [afterMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { @@ -1030,19 +1065,19 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSince } // Otherwise, if we can find a valid tran, use its rule: - auto last = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), + auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(), [afterMSecsSinceEpoch] (const QTzTransitionTime &at) { return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch; }); - return last != m_tranTimes.cend() ? dataForTzTransition(*last) : invalidData(); + return last != tranCache().cend() ? dataForTzTransition(*last) : invalidData(); } 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_posixRule.isEmpty() - && (m_tranTimes.isEmpty() || m_tranTimes.last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) { + if (!cached_data.m_posixRule.isEmpty() + && (tranCache().isEmpty() || tranCache().last().atMSecsSinceEpoch < beforeMSecsSinceEpoch)) { QVector<QTimeZonePrivate::Data> posixTrans = getPosixTransitions(beforeMSecsSinceEpoch); auto it = std::partition_point(posixTrans.cbegin(), posixTrans.cend(), [beforeMSecsSinceEpoch] (const QTimeZonePrivate::Data &at) { @@ -1051,15 +1086,15 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecs if (it > posixTrans.cbegin()) return *--it; // It fell between the last transition (if any) and the first of the POSIX rule: - return m_tranTimes.isEmpty() ? invalidData() : dataForTzTransition(m_tranTimes.last()); + return tranCache().isEmpty() ? invalidData() : dataForTzTransition(tranCache().last()); } // Otherwise if we can find a valid tran then use its rule - auto last = std::partition_point(m_tranTimes.cbegin(), m_tranTimes.cend(), + auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(), [beforeMSecsSinceEpoch] (const QTzTransitionTime &at) { return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch; }); - return last > m_tranTimes.cbegin() ? dataForTzTransition(*--last) : invalidData(); + return last > tranCache().cbegin() ? dataForTzTransition(*--last) : invalidData(); } static long getSymloopMax() diff --git a/tests/benchmarks/corelib/time/qtimezone/main.cpp b/tests/benchmarks/corelib/time/qtimezone/main.cpp index 65455a7261..133e6451bc 100644 --- a/tests/benchmarks/corelib/time/qtimezone/main.cpp +++ b/tests/benchmarks/corelib/time/qtimezone/main.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 Crimson AS <info@crimson.no> ** Copyright (C) 2018 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com> ** Contact: https://www.qt.io/licensing/ ** @@ -30,14 +31,57 @@ #include <QTest> #include <qdebug.h> +// Enable to test *every* zone, rather than a hand-picked few, in some _data() sets: +// #define EXHAUSTIVE + class tst_QTimeZone : public QObject { Q_OBJECT private Q_SLOTS: void isTimeZoneIdAvailable(); + void systemTimeZone(); + void zoneByName_data(); + void zoneByName(); + void transitionList_data(); + void transitionList(); + void transitionsForward_data() { transitionList_data(); } + void transitionsForward(); + void transitionsReverse_data() { transitionList_data(); } + void transitionsReverse(); }; +static QVector<QByteArray> enoughZones() +{ +#ifdef EXHAUSTIVE + auto available = QTimeZone::availableTimeZoneIds(); + QVector<QByteArray> result; + result.reserve(available.size() + 1); + for (conat auto &name : available) + result << name; +#else + QVector<QByteArray> result{ + QByteArray("UTC"), + // Those named overtly in tst_QDateTime: + QByteArray("Europe/Oslo"), + QByteArray("America/Vancouver"), + QByteArray("Europe/Berlin"), + QByteArray("America/Sao_Paulo"), + QByteArray("Pacific/Auckland"), + QByteArray("Australia/Eucla"), + QByteArray("Asia/Kathmandu"), + QByteArray("Pacific/Kiritimati"), + QByteArray("Pacific/Apia"), + QByteArray("UTC+12:00"), + QByteArray("Australia/Sydney"), + QByteArray("Asia/Singapore"), + QByteArray("Australia/Brisbane") + }; +#endif + result << QByteArray("Vulcan/ShiKahr"); // invalid: also worth testing + return result; +} + void tst_QTimeZone::isTimeZoneIdAvailable() { const QList<QByteArray> available = QTimeZone::availableTimeZoneIds(); @@ -47,6 +91,84 @@ void tst_QTimeZone::isTimeZoneIdAvailable() } } +void tst_QTimeZone::systemTimeZone() +{ + QBENCHMARK { + QTimeZone::systemTimeZone(); + } +} + +void tst_QTimeZone::zoneByName_data() +{ + QTest::addColumn<QByteArray>("name"); + + const auto names = enoughZones(); + for (const auto &name : names) + QTest::newRow(name.constData()) << name; +} + +void tst_QTimeZone::zoneByName() +{ + QFETCH(QByteArray, name); + QTimeZone zone; + QBENCHMARK { + zone = QTimeZone(name); + } + Q_UNUSED(zone); +} + +void tst_QTimeZone::transitionList_data() +{ + QTest::addColumn<QByteArray>("name"); + QTest::newRow("system") << QByteArray(); // Handled specially in the test. + + const auto names = enoughZones(); + for (const auto &name : names) { + QTimeZone zone(name); + if (zone.isValid() && zone.hasTransitions()) + QTest::newRow(name.constData()) << name; + } +} + +void tst_QTimeZone::transitionList() +{ + QFETCH(QByteArray, name); + const QTimeZone zone = name.isEmpty() ? QTimeZone::systemTimeZone() : QTimeZone(name); + const QDateTime early = QDate(1625, 6, 8).startOfDay(Qt::UTC); // Cassini's birth date + const QDateTime late // End of 32-bit signed time_t + = QDateTime::fromSecsSinceEpoch(std::numeric_limits<qint32>::max(), Qt::UTC); + QTimeZone::OffsetDataList seq; + QBENCHMARK { + seq = zone.transitions(early, late); + } + Q_UNUSED(seq); +} + +void tst_QTimeZone::transitionsForward() +{ + QFETCH(QByteArray, name); + const QTimeZone zone = name.isEmpty() ? QTimeZone::systemTimeZone() : QTimeZone(name); + const QDateTime early = QDate(1625, 6, 8).startOfDay(Qt::UTC); // Cassini's birth date + QBENCHMARK { + QTimeZone::OffsetData tran = zone.nextTransition(early); + while (tran.atUtc.isValid()) + tran = zone.nextTransition(tran.atUtc); + } +} + +void tst_QTimeZone::transitionsReverse() +{ + QFETCH(QByteArray, name); + const QTimeZone zone = name.isEmpty() ? QTimeZone::systemTimeZone() : QTimeZone(name); + const QDateTime late // End of 32-bit signed time_t + = QDateTime::fromSecsSinceEpoch(std::numeric_limits<qint32>::max(), Qt::UTC); + QBENCHMARK { + QTimeZone::OffsetData tran = zone.previousTransition(late); + while (tran.atUtc.isValid()) + tran = zone.previousTransition(tran.atUtc); + } +} + QTEST_MAIN(tst_QTimeZone) #include "main.moc" |