Rework QTZP::displayName() to use locale and data() more

We should only use the isoOffsetFormat() version of offset name for
QLocale::C - while other locales may coincide with it, we don't know
so shouldn't just assume. Add a query for whether a locale is the one
used by the backend for the abbreviation in its data() and, when it
is, use that abbreviation as short name. The TZ backend was searching
its transitions for help with the abbreviation; we can do that also in
the base-class, so introduce a data(TimeType) that will search
transition history (if available) for a suitable transition from which
to take the abbreviation (when appropriate).

This shall make displayName() return an empty string in various cases
where it did not, before; this is more honest than returning a string
that's inappropriate for the locale for which it was meant to be
answering.

Task-number: QTBUG-114914
Task-number: QTBUG-115158
Task-number: QTBUG-122448
Change-Id: I523229def63aafcd2009b8406b2c3d13f7c01d9c
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Edward Welbourne 2024-02-26 16:11:31 +01:00
parent f3bf304de7
commit e813acbb02
3 changed files with 171 additions and 61 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;