Add QTimeZone::aliasMatches()

This makes it possible for the stdCompatibility() test to cope with
the backend (based on the Windows Registry, which disagrees with MS's
ICU-based std::chrono::tzdb) successfully constructing a zone, by
finding a known alias, instead of failing because it doesn't know the
name. It may also be useful to client code when dealing with legacy
names.

Amended some existing tests to only expect what they now should and
added some tests specific to aliasMatches(). Also added some
explanative comments where it isn't needed.

Task-number: QTBUG-115158
Change-Id: I095bdbead78df339e29b29518d5010ef905fa8b2
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Edward Welbourne 2024-05-16 11:31:30 +02:00
parent c464f67db4
commit e2e5ab0932
4 changed files with 99 additions and 4 deletions

View File

@ -829,6 +829,34 @@ QByteArray QTimeZone::id() const
return QByteArray();
}
/*!
\since 6.8
Returns \c true if \a alias is an alternative name for this timezone.
The IANA (formerly Olson) database has renamed some zones during its
history. There are also some zones that only differed prior to 1970 but are
now treated as synonymous. Some backends may have data reaching to before
1970 and produce distinct zones in the latter case. Others may produce zones
indistinguishable except by id(). This method determines whether an ID
refers (at least since 1970) to the same zone that this timezone object
describes.
This method is only available when feature \c timezone is enabled.
*/
bool QTimeZone::aliasMatches(QByteArrayView alias) const
{
if (alias == id())
return true;
QByteArray mine = QTimeZonePrivate::aliasToIana(id());
// Empty if id() aliases to itself, which we've already checked:
if (!mine.isEmpty() && alias == mine)
return true;
QByteArray its = QTimeZonePrivate::aliasToIana(alias);
// Empty if alias aliases to itself, which we've already compared to id()
// and, where relevant, mine.
return !its.isEmpty() && its == (mine.isEmpty() ? id() : mine);
}
/*!
\since 6.2

View File

@ -166,6 +166,7 @@ public:
};
typedef QList<OffsetData> OffsetDataList;
bool aliasMatches(QByteArrayView alias) const;
QByteArray id() const;
QLocale::Territory territory() const;
# if QT_DEPRECATED_SINCE(6, 6)

View File

@ -780,6 +780,9 @@ QByteArray QTimeZonePrivate::aliasToIana(QByteArrayView alias)
alias, earlierAliasId);
if (data != std::end(aliasMappingTable) && data->aliasId() == alias)
return data->ianaId().toByteArray();
// Note: empty return means not an alias, which is true of an ID that others
// are aliases to, as the table omits self-alias entries. Let caller sort
// that out, rather than allocating to return alias.toByteArray().
return {};
}

View File

@ -7,6 +7,7 @@
#include <private/qcomparisontesthelper_p.h>
#include <qlocale.h>
#include <qscopeguard.h>
#if defined(Q_OS_WIN)
#include <QOperatingSystemVersion>
@ -16,6 +17,8 @@
# define USING_WIN_TZ
#endif
using namespace Qt::StringLiterals;
class tst_QTimeZone : public QObject
{
Q_OBJECT
@ -38,6 +41,8 @@ private Q_SLOTS:
void availableTimeZoneIds();
void utcOffsetId_data();
void utcOffsetId();
void aliasMatches_data();
void aliasMatches();
void specificTransition_data();
void specificTransition();
void transitionEachZone_data();
@ -558,9 +563,12 @@ void tst_QTimeZone::isTimeZoneIdAvailable()
const QList<QByteArray> available = QTimeZone::availableTimeZoneIds();
for (const QByteArray &id : available) {
QVERIFY2(QTimeZone::isTimeZoneIdAvailable(id), id);
QVERIFY2(QTimeZone(id).isValid(), id);
QCOMPARE(QTimeZone(id).id(), id);
const QTimeZone zone(id);
QVERIFY2(zone.isValid(), id);
QVERIFY2(zone.aliasMatches(id), zone.id() + " != " + id);
}
// availableTimeZoneIds() doesn't list all possible offset IDs, but
// isTimeZoneIdAvailable() should accept them.
for (qint32 offset = QTimeZone::MinUtcOffsetSecs;
offset <= QTimeZone::MinUtcOffsetSecs; ++offset) {
const QByteArray id = QTimeZone(offset).id();
@ -700,6 +708,61 @@ void tst_QTimeZone::utcOffsetId()
}
}
void tst_QTimeZone::aliasMatches_data()
{
QTest::addColumn<QByteArray>("iana");
QTest::addColumn<QByteArray>("alias");
QTest::newRow("Montreal=Toronto") << "America/Toronto"_ba << "America/Montreal"_ba;
QTest::newRow("Asmera=Asmara") << "Africa/Asmara"_ba << "Africa/Asmera"_ba;
QTest::newRow("Argentina/Catamarca")
<< "America/Argentina/Catamarca"_ba << "America/Catamarca"_ba;
QTest::newRow("Godthab=Nuuk") << "America/Nuuk"_ba << "America/Godthab"_ba;
QTest::newRow("Indiana/Indianapolis")
<< "America/Indiana/Indianapolis"_ba << "America/Indianapolis"_ba;
QTest::newRow("Kentucky/Louisville")
<< "America/Kentucky/Louisville"_ba << "America/Louisville"_ba;
QTest::newRow("Calcutta=Kolkata") << "Asia/Kolkata"_ba << "Asia/Calcutta"_ba;
QTest::newRow("Katmandu=Kathmandu") << "Asia/Kathmandu"_ba << "Asia/Katmandu"_ba;
QTest::newRow("Rangoon=Yangon") << "Asia/Yangon"_ba << "Asia/Rangoon"_ba;
QTest::newRow("Saigon=Ho_Chi_Minh") << "Asia/Ho_Chi_Minh"_ba << "Asia/Saigon"_ba;
QTest::newRow("Faeroe=Faroe") << "Atlantic/Faroe"_ba << "Atlantic/Faeroe"_ba;
QTest::newRow("Currie=Hobart") << "Australia/Hobart"_ba << "Australia/Currie"_ba;
QTest::newRow("Kiev=Kyiv") << "Europe/Kyiv"_ba << "Europe/Kiev"_ba;
QTest::newRow("Uzhgorod=Kyiv") << "Europe/Kyiv"_ba << "Europe/Uzhgorod"_ba;
QTest::newRow("Zaporozhye=Kyiv") << "Europe/Kyiv"_ba << "Europe/Zaporozhye"_ba;
QTest::newRow("Fiji=Fiji") << "Pacific/Fiji"_ba << "Pacific/Fiji"_ba;
QTest::newRow("Enderbury=Enderbury") << "Pacific/Enderbury"_ba << "Pacific/Enderbury"_ba;
}
void tst_QTimeZone::aliasMatches()
{
QFETCH(const QByteArray, iana);
QFETCH(const QByteArray, alias);
const QTimeZone zone(iana);
const QTimeZone peer(alias);
if (!zone.isValid())
QSKIP("Backend doesn't support IANA ID");
auto report = qScopeGuard([zone, peer]() {
const QByteArray zid = zone.id(), pid = peer.id();
qDebug("Using %s and %s", zid.constData(), pid.constData());
});
QVERIFY2(peer.isValid(), "Construction should have fallen back on IANA ID");
QVERIFY(zone.aliasMatches(zone.id()));
QVERIFY(zone.aliasMatches(iana));
QVERIFY(peer.aliasMatches(peer.id()));
QVERIFY(peer.aliasMatches(alias));
QEXPECT_FAIL("Currie=Hobart", "Needs update to CLDR v44.1", Abort);
QEXPECT_FAIL("Uzhgorod=Kyiv", "Needs update to CLDR v44.1", Abort);
QEXPECT_FAIL("Zaporozhye=Kyiv", "Needs update to CLDR v44.1", Abort);
QVERIFY(zone.aliasMatches(peer.id()));
QVERIFY(zone.aliasMatches(alias));
QVERIFY(peer.aliasMatches(zone.id()));
QVERIFY(peer.aliasMatches(iana));
report.dismiss();
}
void tst_QTimeZone::specificTransition_data()
{
QTest::addColumn<QByteArray>("zone");
@ -924,7 +987,7 @@ void tst_QTimeZone::stressTest()
for (const QByteArray &id : idList) {
QTimeZone testZone = QTimeZone(id);
QCOMPARE(testZone.isValid(), true);
QCOMPARE(testZone.id(), id);
QVERIFY2(testZone.aliasMatches(id), testZone.id() + " != " + id);
QDateTime testDate = QDateTime(QDate(2015, 1, 1), QTime(0, 0), UTC);
testZone.territory();
testZone.comment();
@ -1860,7 +1923,7 @@ void tst_QTimeZone::stdCompatibility()
QByteArrayView zoneName = QByteArrayView(timeZone->name());
QTimeZone tz = QTimeZone::fromStdTimeZonePtr(timeZone);
if (tz.isValid())
QCOMPARE(tz.id(), zoneName);
QVERIFY2(tz.aliasMatches(zoneName), tz.id().constData());
else
QVERIFY(!QTimeZone::isTimeZoneIdAvailable(zoneName.toByteArray()));
#else