diff options
Diffstat (limited to 'src/corelib/time/qtimezoneprivate_tz.cpp')
-rw-r--r-- | src/corelib/time/qtimezoneprivate_tz.cpp | 242 |
1 files changed, 116 insertions, 126 deletions
diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index b6a7d1418c..8d14e75193 100644 --- a/src/corelib/time/qtimezoneprivate_tz.cpp +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -33,10 +33,6 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -#if QT_CONFIG(icu) -Q_CONSTINIT static QBasicMutex s_icu_mutex; -#endif - /* Private @@ -54,7 +50,7 @@ typedef QHash<QByteArray, QTzTimeZone> QTzTimeZoneHash; static bool isTzFile(const QString &name); // Open a named file under the zone info directory: -static bool openZoneInfo(QString name, QFile *file) +static bool openZoneInfo(const QString &name, QFile *file) { // At least on Linux / glibc (see man 3 tzset), $TZDIR overrides the system // default location for zone info: @@ -521,12 +517,20 @@ struct PosixZone }; QString name; - int offset; + int offset = InvalidOffset; + bool hasValidOffset() const noexcept { return offset != InvalidOffset; } + QTimeZonePrivate::Data dataAt(qint64 when) + { + Q_ASSERT(hasValidOffset()); + return QTimeZonePrivate::Data(name, when, offset, offset); + } + QTimeZonePrivate::Data dataAtOffset(qint64 when, int standard) + { + Q_ASSERT(hasValidOffset()); + return QTimeZonePrivate::Data(name, when, offset, standard); + } - static PosixZone invalid() { return {QString(), InvalidOffset}; } static PosixZone parse(const char *&pos, const char *end); - - bool hasValidOffset() const noexcept { return offset != InvalidOffset; } }; } // unnamed namespace @@ -557,7 +561,7 @@ PosixZone PosixZone::parse(const char *&pos, const char *end) pos = nameEnd; } if (nameEnd - nameBegin < 3) - return invalid(); // name must be at least 3 characters long + return {}; // name must be at least 3 characters long // zone offset, form [+-]hh:mm:ss const char *zoneBegin = pos; @@ -576,7 +580,7 @@ PosixZone PosixZone::parse(const char *&pos, const char *end) // UTC+hh:mm:ss or GMT+hh:mm:ss should be read as offsets from UTC, not as a // POSIX rule naming a zone as UTC or GMT and specifying a non-zero offset. if (offset != 0 && (name =="UTC"_L1 || name == "GMT"_L1)) - return invalid(); + return {}; return {std::move(name), offset}; } @@ -646,7 +650,7 @@ static QList<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray // and the link in validatePosixRule(), above. QList<QByteArray> parts = posixRule.split(','); - PosixZone stdZone, dstZone = PosixZone::invalid(); + PosixZone stdZone, dstZone; { const QByteArray &zoneinfo = parts.at(0); const char *begin = zoneinfo.constBegin(); @@ -665,13 +669,9 @@ static QList<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray // If only the name part, or no DST specified, then no transitions if (parts.size() == 1 || !dstZone.hasValidOffset()) { - QTimeZonePrivate::Data data; - data.atMSecsSinceEpoch = lastTranMSecs; - data.offsetFromUtc = stdZone.offset; - data.standardTimeOffset = stdZone.offset; - data.daylightTimeOffset = 0; - data.abbreviation = stdZone.name.isEmpty() ? QString::fromUtf8(parts.at(0)) : stdZone.name; - result << data; + result.emplaceBack( + stdZone.name.isEmpty() ? QString::fromUtf8(parts.at(0)) : stdZone.name, + lastTranMSecs, stdZone.offset, stdZone.offset); return result; } if (parts.size() < 3 || parts.at(1).isEmpty() || parts.at(2).isEmpty()) @@ -704,40 +704,33 @@ static QList<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray // moments; the atMSecsSinceEpoch values computed from them are // correctly offse to be UTC-based. - QTimeZonePrivate::Data dstData; // Transition to DST + // Transition to daylight-saving time: QDateTime dst(calculatePosixDate(dstDateRule, year) .startOfDay(QTimeZone::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; // Transition to standard time + auto saving = dstZone.dataAtOffset(dst.toMSecsSinceEpoch() - stdZone.offset * 1000, + stdZone.offset); + // Transition to standard time: QDateTime std(calculatePosixDate(stdDateRule, year) .startOfDay(QTimeZone::UTC).addSecs(stdTime)); - stdData.atMSecsSinceEpoch = std.toMSecsSinceEpoch() - dstZone.offset * 1000; - stdData.offsetFromUtc = stdZone.offset; - stdData.standardTimeOffset = stdZone.offset; - stdData.daylightTimeOffset = 0; - stdData.abbreviation = stdZone.name; + auto standard = stdZone.dataAt(std.toMSecsSinceEpoch() - dstZone.offset * 1000); if (year == startYear) { // Handle the special case of fixed state, which may be represented // by fake transitions at start and end of each year: - if (dstData.atMSecsSinceEpoch < stdData.atMSecsSinceEpoch) { + if (saving.atMSecsSinceEpoch < standard.atMSecsSinceEpoch) { if (dst <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC) && std >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) { // Permanent DST: - dstData.atMSecsSinceEpoch = lastTranMSecs; - result << dstData; + saving.atMSecsSinceEpoch = lastTranMSecs; + result.emplaceBack(std::move(saving)); return result; } } else { if (std <= QDate(year, 1, 1).startOfDay(QTimeZone::UTC) && dst >= QDate(year, 12, 31).endOfDay(QTimeZone::UTC)) { // Permanent Standard time, perversely described: - stdData.atMSecsSinceEpoch = lastTranMSecs; - result << stdData; + standard.atMSecsSinceEpoch = lastTranMSecs; + result.emplaceBack(std::move(standard)); return result; } } @@ -746,14 +739,17 @@ static QList<QTimeZonePrivate::Data> calculatePosixTransitions(const QByteArray const bool useStd = std.isValid() && std.date().year() == year && !stdZone.name.isEmpty(); const bool useDst = dst.isValid() && dst.date().year() == year && !dstZone.name.isEmpty(); if (useStd && useDst) { - if (dst < std) - result << dstData << stdData; - else - result << stdData << dstData; + if (dst < std) { + result.emplaceBack(std::move(saving)); + result.emplaceBack(std::move(standard)); + } else { + result.emplaceBack(std::move(standard)); + result.emplaceBack(std::move(saving)); + } } else if (useStd) { - result << stdData; + result.emplaceBack(std::move(standard)); } else if (useDst) { - result << dstData; + result.emplaceBack(std::move(saving)); } } return result; @@ -771,9 +767,6 @@ QTzTimeZonePrivate::~QTzTimeZonePrivate() QTzTimeZonePrivate *QTzTimeZonePrivate::clone() const { -#if QT_CONFIG(icu) - const auto lock = qt_scoped_lock(s_icu_mutex); -#endif return new QTzTimeZonePrivate(*this); } @@ -884,8 +877,6 @@ QTzTimeZoneCacheEntry QTzTimeZoneCache::findEntry(const QByteArray &ianaId) // TODO: is typeList[0] always the "before zones" data ? It seems to be ... if (typeList.size()) ret.m_preZoneRule = { typeList.at(0).tz_gmtoff, 0, typeList.at(0).tz_abbrind }; - else - ret.m_preZoneRule = { 0, 0, 0 }; // Offsets are stored as total offset, want to know separate UTC and DST offsets // so find the first non-dst transition to use as base UTC Offset @@ -1009,15 +1000,7 @@ QTzTimeZonePrivate::QTzTimeZonePrivate(const QByteArray &ianaId) if (m_id.isEmpty()) { // This can only happen for the system zone, when we've read the // contents of /etc/localtime because it wasn't a symlink. -#if QT_CONFIG(icu) - // Use ICU's system zone, if only to avoid using the abbreviation as ID - // (ICU might mis-recognize it) in displayName(). - m_icu = new QIcuTimeZonePrivate(); - // Use its ID, as an alternate source of data: - m_id = m_icu->id(); - if (!m_id.isEmpty()) - return; -#endif + // TODO: use CLDR generic abbreviation for the zone. m_id = abbreviation(QDateTime::currentMSecsSinceEpoch()).toUtf8(); } } @@ -1036,70 +1019,19 @@ QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QLocale &locale) const { - // TZ DB lacks localized names (it only has IANA IDs), so delegate to ICU - // for those, when available. -#if QT_CONFIG(icu) - { - auto lock = qt_scoped_lock(s_icu_mutex); - // TODO Some valid TZ names are not valid ICU names, use translation table? - if (!m_icu) - m_icu = new QIcuTimeZonePrivate(m_id); - if (m_icu->isValid()) - return m_icu->displayName(timeType, nameType, locale); - } -#else - Q_UNUSED(timeType); - Q_UNUSED(nameType); - Q_UNUSED(locale); -#endif - // If ICU is unavailable, fall back to abbreviations. - // 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 - auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), - [currentMSecs](const QTzTransitionTime &at) { - return at.atMSecsSinceEpoch <= currentMSecs; - }); - - while (it != tranCache().cbegin()) { - --it; - tran = dataForTzTransition(*it); - int offset = tran.daylightTimeOffset; - if ((timeType == QTimeZone::DaylightTime) != (offset == 0)) - return tran.abbreviation; + // TZ only provides C-locale abbreviations and offset: + if (nameType != QTimeZone::LongName && isDataLocale(locale)) { + QTimeZonePrivate::Data tran = data(timeType); + if (tran.atMSecsSinceEpoch != invalidMSecs()) { + if (nameType == QTimeZone::ShortName) + return tran.abbreviation; + // Save base class repeating the data(timeType) query: + if (locale.language() == QLocale::C) + return isoOffsetFormat(tran.offsetFromUtc); + } } - - // Otherwise if no match use current data - return data(currentMSecs).abbreviation; + // Otherwise, fall back to base class (and qtimezonelocale.cpp): + return QTimeZonePrivate::displayName(timeType, nameType, locale); } QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const @@ -1141,8 +1073,8 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::dataForTzTransition(QTzTransitionTime QTimeZonePrivate::Data QTzTimeZonePrivate::dataFromRule(QTzTransitionRule rule, qint64 msecsSinceEpoch) const { - return { QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)), - msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset, rule.dstOffset }; + return Data(QString::fromUtf8(cached_data.m_abbreviations.at(rule.abbreviationIndex)), + msecsSinceEpoch, rule.stdOffset + rule.dstOffset, rule.stdOffset); } QList<QTimeZonePrivate::Data> QTzTimeZonePrivate::getPosixTransitions(qint64 msNear) const @@ -1172,7 +1104,7 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const } } if (tranCache().isEmpty()) // Only possible if !isValid() - return invalidData(); + return {}; // Otherwise, use the rule for the most recent or first transition: auto last = std::partition_point(tranCache().cbegin(), tranCache().cend(), @@ -1186,6 +1118,64 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const return dataFromRule(cached_data.m_tranRules.at(last->ruleIndex), forMSecsSinceEpoch); } +// Overridden because the final iteration over transitions only needs to look +// forward and backwards one transition within the POSIX rule (when there is +// one, as is common) to settle the whole period it covers, so we can then skip +// all other transitions of the POSIX rule and iterate tranCache() backwards +// from its most recent transition. +QTimeZonePrivate::Data QTzTimeZonePrivate::data(QTimeZone::TimeType timeType) const +{ + // True if tran is valid and has the DST-ness to match timeType: + const auto validMatch = [timeType](const QTimeZonePrivate::Data &tran) { + return tran.atMSecsSinceEpoch != invalidMSecs() + && ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0)); + }; + + // Get current tran, use if suitable: + const qint64 currentMSecs = QDateTime::currentMSecsSinceEpoch(); + QTimeZonePrivate::Data tran = data(currentMSecs); + if (validMatch(tran)) + return tran; + + // Otherwise, next tran probably flips DST-ness: + tran = nextTransition(currentMSecs); + if (validMatch(tran)) + return tran; + + // Failing that, prev (or present, if current MSecs is eactly a transition + // moment) tran defines what data() got us and the one before that probably + // flips DST-ness: + tran = previousTransition(currentMSecs + 1); + if (tran.atMSecsSinceEpoch != invalidMSecs()) + tran = previousTransition(tran.atMSecsSinceEpoch); + if (validMatch(tran)) + return tran; + + // Otherwise, we can look backwards through transitions for a match; if we + // have a POSIX rule, it clearly doesn't do DST (or we'd have hit it by + // now), so we only need to look in the tranCache() up to now. + const auto untilNow = [currentMSecs](const QTzTransitionTime &at) { + return at.atMSecsSinceEpoch <= currentMSecs; + }; + auto it = std::partition_point(tranCache().cbegin(), tranCache().cend(), untilNow); + // That's the end or first future transition; we don't want to look at it, + // but at all those before it. + while (it != tranCache().cbegin()) { + --it; + tran = dataForTzTransition(*it); + if ((timeType == QTimeZone::DaylightTime) != (tran.daylightTimeOffset == 0)) + return tran; + } + + return {}; +} + +bool QTzTimeZonePrivate::isDataLocale(const QLocale &locale) const +{ + // TZ data uses English / C locale names: + return locale.language() == QLocale::C || locale.language() == QLocale::English; +} + bool QTzTimeZonePrivate::hasTransitions() const { return true; @@ -1203,7 +1193,7 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSince return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch; }); - return it == posixTrans.cend() ? invalidData() : *it; + return it == posixTrans.cend() ? Data{} : *it; } // Otherwise, if we can find a valid tran, use its rule: @@ -1211,7 +1201,7 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::nextTransition(qint64 afterMSecsSince [afterMSecsSinceEpoch] (const QTzTransitionTime &at) { return at.atMSecsSinceEpoch <= afterMSecsSinceEpoch; }); - return last != tranCache().cend() ? dataForTzTransition(*last) : invalidData(); + return last != tranCache().cend() ? dataForTzTransition(*last) : Data{}; } QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const @@ -1228,7 +1218,7 @@ 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 tranCache().isEmpty() ? invalidData() : dataForTzTransition(tranCache().last()); + return tranCache().isEmpty() ? Data{} : dataForTzTransition(tranCache().last()); } // Otherwise if we can find a valid tran then use its rule @@ -1236,7 +1226,7 @@ QTimeZonePrivate::Data QTzTimeZonePrivate::previousTransition(qint64 beforeMSecs [beforeMSecsSinceEpoch] (const QTzTransitionTime &at) { return at.atMSecsSinceEpoch < beforeMSecsSinceEpoch; }); - return last > tranCache().cbegin() ? dataForTzTransition(*--last) : invalidData(); + return last > tranCache().cbegin() ? dataForTzTransition(*--last) : Data{}; } bool QTzTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const |