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>
This commit is contained in:
parent
d535dfea1f
commit
2af04860f6
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user