From 2cc2618f9d87f3688d4f3f8072f843a4df96bd59 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Wed, 9 Apr 2025 17:00:15 +0200 Subject: [PATCH] Include "is known to ICU" check in QTimeZonePrivate::localeName() As long noted in the QIcuTimeZonePrivate constructor, we can't trust ucal_open()'s non-null return to mean it actually recognised the ID we passed it. So break out the "is available" check to a function in the QtTimeZoneLocale namespace so the check can be shared with localeName() when compiled with ICU but some other backend. We were previously getting GMT as display name for zones unknown to ICU. Change-Id: I57d57f94d8db7df76f24193a8ef1b5c71b08b0fc Reviewed-by: Ivan Solovev (cherry picked from commit fc9a26ea3a0b76de8a437d6417af334a7a9dfde5) Reviewed-by: Qt Cherry-pick Bot (cherry picked from commit b50bbd42694b24012e242c9bea0d0b2d96b6c5af) --- src/corelib/time/qtimezonelocale.cpp | 23 +++++++++++++++++++++++ src/corelib/time/qtimezonelocale_p.h | 2 ++ src/corelib/time/qtimezoneprivate_icu.cpp | 16 +--------------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/corelib/time/qtimezonelocale.cpp b/src/corelib/time/qtimezonelocale.cpp index c986b8ac280..e793a59f933 100644 --- a/src/corelib/time/qtimezonelocale.cpp +++ b/src/corelib/time/qtimezonelocale.cpp @@ -72,6 +72,24 @@ QString ucalTimeZoneDisplayName(UCalendar *ucal, return result; } +bool ucalKnownTimeZoneId(const QString &ianaStr) +{ + const UChar *const name = reinterpret_cast(ianaStr.constData()); + // We are not interested in the value, but we have to pass something. + // No known IANA zone name is (up to 2023) longer than 30 characters. + constexpr size_t size = 64; + UChar buffer[size]; + + // TODO: convert to ucal_getIanaTimeZoneID(), new draft in ICU 74, once we + // can rely on its availability, assuming it works the same once not draft. + UErrorCode status = U_ZERO_ERROR; + UBool isSys = false; + // Returns the length of the IANA zone name (but we don't care): + ucal_getCanonicalTimeZoneID(name, ianaStr.size(), buffer, size, &isSys, &status); + // We're only interested if the result is a "system" (i.e. IANA) ID: + return isSys; +} + } // QtTimeZoneLocale // Used by TZ backends when ICU is available: @@ -88,6 +106,11 @@ QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc return isoOffsetFormat(offsetFromUtc); const QString id = QString::fromUtf8(m_id); + // Need to check id is known to ICU, since ucal_open() will return a + // misleading "valid" GMT ucal when it doesn't recognise id. + if (!QtTimeZoneLocale::ucalKnownTimeZoneId(id)) + return QString(); + const QByteArray loc = locale.name().toUtf8(); UErrorCode status = U_ZERO_ERROR; UCalendar *ucal = ucal_open(reinterpret_cast(id.data()), id.size(), diff --git a/src/corelib/time/qtimezonelocale_p.h b/src/corelib/time/qtimezonelocale_p.h index 076e29d268d..dc5de597253 100644 --- a/src/corelib/time/qtimezonelocale_p.h +++ b/src/corelib/time/qtimezonelocale_p.h @@ -33,6 +33,8 @@ namespace QtTimeZoneLocale { QString ucalTimeZoneDisplayName(UCalendar *ucal, QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QByteArray &localeCode); + +bool ucalKnownTimeZoneId(const QString &id); #else // Define data types for QTZL_data_p.h diff --git a/src/corelib/time/qtimezoneprivate_icu.cpp b/src/corelib/time/qtimezoneprivate_icu.cpp index c7039e157ca..ef1c83b9d05 100644 --- a/src/corelib/time/qtimezoneprivate_icu.cpp +++ b/src/corelib/time/qtimezoneprivate_icu.cpp @@ -387,21 +387,7 @@ QByteArray QIcuTimeZonePrivate::systemTimeZoneId() const bool QIcuTimeZonePrivate::isTimeZoneIdAvailable(const QByteArray &ianaId) const { - const QString ianaStr = QString::fromUtf8(ianaId); - const UChar *const name = reinterpret_cast(ianaStr.constData()); - // We are not interested in the value, but we have to pass something. - // No known IANA zone name is (up to 2023) longer than 30 characters. - constexpr size_t size = 64; - UChar buffer[size]; - - // TODO: convert to ucal_getIanaTimeZoneID(), new draft in ICU 74, once we - // can rely on its availability, assuming it works the same once not draft. - UErrorCode status = U_ZERO_ERROR; - UBool isSys = false; - // Returns the length of the IANA zone name (but we don't care): - ucal_getCanonicalTimeZoneID(name, ianaStr.size(), buffer, size, &isSys, &status); - // We're only interested if the result is a "system" (i.e. IANA) ID: - return isSys; + return QtTimeZoneLocale::ucalKnownTimeZoneId(QString::fromUtf8(ianaId)); } QList QIcuTimeZonePrivate::availableTimeZoneIds() const