diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp index 2ad0d874b6..6d522733aa 100644 --- a/src/corelib/time/qtimezoneprivate.cpp +++ b/src/corelib/time/qtimezoneprivate.cpp @@ -172,22 +172,29 @@ QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch, QTimeZone::NameType nameType, const QLocale &locale) const { - if (nameType == QTimeZone::OffsetName) - return isoOffsetFormat(offsetFromUtc(atMSecsSinceEpoch)); + const Data tran = data(atMSecsSinceEpoch); + if (tran.atMSecsSinceEpoch != invalidMSecs()) { + if (nameType == QTimeZone::OffsetName && locale.language() == QLocale::C) + return isoOffsetFormat(tran.offsetFromUtc); + if (nameType == QTimeZone::ShortName && isDataLocale(locale)) + return tran.abbreviation; - if (isDaylightTime(atMSecsSinceEpoch)) - return displayName(QTimeZone::DaylightTime, nameType, locale); - else - return displayName(QTimeZone::StandardTime, nameType, locale); + QTimeZone::TimeType timeType + = tran.daylightTimeOffset != 0 ? QTimeZone::DaylightTime : QTimeZone::StandardTime; + return displayName(timeType, nameType, locale); + } + return QString(); } QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QLocale &locale) const { - Q_UNUSED(timeType); - Q_UNUSED(nameType); - Q_UNUSED(locale); + if (nameType == QTimeZone::OffsetName && isDataLocale(locale)) { + const Data tran = data(timeType); + if (tran.atMSecsSinceEpoch != invalidMSecs()) + return isoOffsetFormat(tran.offsetFromUtc); + } return QString(); } @@ -227,6 +234,56 @@ bool QTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const return false; } +QTimeZonePrivate::Data QTimeZonePrivate::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; + + if (hasTransitions()) { + // Otherwise, next tran probably flips DST-ness: + tran = nextTransition(currentMSecs); + if (validMatch(tran)) + return tran; + + // Failing that, prev (or present, if current MSecs is exactly a + // transition moment) tran defines what data() got us and the one before + // that probably flips DST-ness; failing that, keep marching backwards + // in search of a DST interval: + tran = previousTransition(currentMSecs + 1); + while (tran.atMSecsSinceEpoch != invalidMSecs()) { + tran = previousTransition(tran.atMSecsSinceEpoch); + if (validMatch(tran)) + return tran; + } + } + return {}; +} + +/*! + \internal + + Returns true if the abbreviation given in data()'s returns is appropriate + for use in the given \a locale. + + Base implementation assumes data() corresponds to the system locale; derived + classes should override if their data() is something else (such as + C/English). +*/ +bool QTimeZonePrivate::isDataLocale(const QLocale &locale) const +{ + // Guess data is for the system locale unless backend overrides that. + return locale == QLocale::system(); +} + QTimeZonePrivate::Data QTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const { Q_UNUSED(forMSecsSinceEpoch); @@ -906,6 +963,19 @@ QTimeZonePrivate::Data QUtcTimeZonePrivate::data(qint64 forMSecsSinceEpoch) cons return d; } +// Override to shortcut past base's complications: +QTimeZonePrivate::Data QUtcTimeZonePrivate::data(QTimeZone::TimeType timeType) const +{ + Q_UNUSED(timeType); + return data(QDateTime::currentMSecsSinceEpoch()); +} + +bool QUtcTimeZonePrivate::isDataLocale(const QLocale &locale) const +{ + // Officially only supports C locale names; these are surely also viable for English. + return locale.language() == QLocale::C || locale.language() == QLocale::English; +} + void QUtcTimeZonePrivate::init(const QByteArray &zoneId) { m_id = zoneId; @@ -933,6 +1003,15 @@ QString QUtcTimeZonePrivate::comment() const return m_comment; } +// Override to bypass complications in base-class: +QString QUtcTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + Q_UNUSED(atMSecsSinceEpoch); + return displayName(QTimeZone::StandardTime, nameType, locale); +} + QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QLocale &locale) const diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h index 506acaa1f7..e0632e7cec 100644 --- a/src/corelib/time/qtimezoneprivate_p.h +++ b/src/corelib/time/qtimezoneprivate_p.h @@ -99,6 +99,8 @@ public: virtual bool isDaylightTime(qint64 atMSecsSinceEpoch) const; virtual Data data(qint64 forMSecsSinceEpoch) const; + virtual Data data(QTimeZone::TimeType timeType) const; + virtual bool isDataLocale(const QLocale &locale) const; QDateTimePrivate::ZoneState stateAtZoneTime(qint64 forLocalMSecs, QDateTimePrivate::TransitionOptions resolve) const; @@ -179,11 +181,15 @@ public: QUtcTimeZonePrivate *clone() const override; Data data(qint64 forMSecsSinceEpoch) const override; + Data data(QTimeZone::TimeType timeType) const override; + bool isDataLocale(const QLocale &locale) const override; QLocale::Territory territory() const override; QString comment() const override; - using QTimeZonePrivate::displayName; + QString displayName(qint64 atMSecsSinceEpoch, + QTimeZone::NameType nameType, + const QLocale &locale) const override; QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QLocale &locale) const override; @@ -241,6 +247,7 @@ public: bool hasDaylightTime() const override; bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + using QTimeZonePrivate::data; Data data(qint64 forMSecsSinceEpoch) const override; bool hasTransitions() const override; @@ -321,6 +328,8 @@ public: bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; Data data(qint64 forMSecsSinceEpoch) const override; + Data data(QTimeZone::TimeType timeType) const override; + bool isDataLocale(const QLocale &locale) const override; bool hasTransitions() const override; Data nextTransition(qint64 afterMSecsSinceEpoch) const override; @@ -378,6 +387,7 @@ public: bool hasDaylightTime() const override; bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + using QTimeZonePrivate::data; Data data(qint64 forMSecsSinceEpoch) const override; bool hasTransitions() const override; @@ -432,6 +442,7 @@ public: bool hasDaylightTime() const override; bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + using QTimeZonePrivate::data; Data data(qint64 forMSecsSinceEpoch) const override; bool hasTransitions() const override; @@ -481,6 +492,7 @@ public: bool hasDaylightTime() const override; bool isDaylightTime(qint64 atMSecsSinceEpoch) const override; + using QTimeZonePrivate::data; Data data(qint64 forMSecsSinceEpoch) const override; QByteArray systemTimeZoneId() const override; diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index f6156fe93e..d057dae37f 100644 --- a/src/corelib/time/qtimezoneprivate_tz.cpp +++ b/src/corelib/time/qtimezoneprivate_tz.cpp @@ -1045,59 +1045,20 @@ QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType, 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; + // 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 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; - } - - // Otherwise if no match use current data - return data(currentMSecs).abbreviation; + // Otherwise, fall back to base class: + return QTimeZonePrivate::displayName(timeType, nameType, locale); } QString QTzTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const @@ -1184,6 +1145,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;