diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index ce3def3cd6..3707d5f886 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -897,7 +897,7 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_timezone AND UNIX AND NOT AN qt_internal_extend_target(Core CONDITION - QT_FEATURE_icu AND QT_FEATURE_timezone AND NOT ANDROID AND NOT APPLE + QT_FEATURE_icu AND QT_FEATURE_timezone AND NOT UNIX SOURCES time/qtimezoneprivate_icu.cpp ) diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index 18c09d2241..83983dfa2e 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -860,7 +860,8 @@ qt_feature("timezone_locale" PRIVATE SECTION "Utilities" LABEL "QTimeZone" PURPOSE "Provides support for localized time-zone display names." - DISABLE ON # Implementation is currently incomplete, so leave turned off + CONDITION + QT_FEATURE_timezone AND ( ( UNIX AND NOT APPLE AND NOT ANDROID ) OR QT_FEATURE_icu ) ) qt_feature("datetimeparser" PRIVATE SECTION "Utilities" diff --git a/src/corelib/time/qtimezonelocale.cpp b/src/corelib/time/qtimezonelocale.cpp index 5757e55d28..cf3a84b317 100644 --- a/src/corelib/time/qtimezonelocale.cpp +++ b/src/corelib/time/qtimezonelocale.cpp @@ -4,7 +4,9 @@ #include #include -#if !QT_CONFIG(icu) // Use data generated from CLDR: +#if !QT_CONFIG(icu) +# include +// Use data generated from CLDR: # include # include #endif @@ -12,9 +14,90 @@ QT_BEGIN_NAMESPACE #if QT_CONFIG(icu) // Get data from ICU: +namespace { + +// Convert TimeType and NameType into ICU UCalendarDisplayNameType +constexpr UCalendarDisplayNameType ucalDisplayNameType(QTimeZone::TimeType timeType, + QTimeZone::NameType nameType) +{ + // TODO ICU C UCalendarDisplayNameType does not support full set of C++ TimeZone::EDisplayType + // For now, treat Generic as Standard + switch (nameType) { + case QTimeZone::OffsetName: + Q_UNREACHABLE(); // Callers of ucalTimeZoneDisplayName() should take care of OffsetName. + case QTimeZone::ShortName: + return timeType == QTimeZone::DaylightTime ? UCAL_SHORT_DST : UCAL_SHORT_STANDARD; + case QTimeZone::DefaultName: + case QTimeZone::LongName: + return timeType == QTimeZone::DaylightTime ? UCAL_DST : UCAL_STANDARD; + } + Q_UNREACHABLE_RETURN(UCAL_STANDARD); +} + +} // nameless namespace + namespace QtTimeZoneLocale { +// Qt wrapper around ucal_getTimeZoneDisplayName() +// Used directly by ICU backend; indirectly by TZ (see below). +QString ucalTimeZoneDisplayName(UCalendar *ucal, + QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QByteArray &localeCode) +{ + constexpr int32_t BigNameLength = 50; + int32_t size = BigNameLength; + QString result(size, Qt::Uninitialized); + auto dst = [&result]() { return reinterpret_cast(result.data()); }; + UErrorCode status = U_ZERO_ERROR; + const UCalendarDisplayNameType utype = ucalDisplayNameType(timeType, nameType); + + // size = ucal_getTimeZoneDisplayName(cal, type, locale, result, resultLength, status) + size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(), + dst(), size, &status); + + // If overflow, then resize and retry + if (size > BigNameLength || status == U_BUFFER_OVERFLOW_ERROR) { + result.resize(size); + status = U_ZERO_ERROR; + size = ucal_getTimeZoneDisplayName(ucal, utype, localeCode.constData(), + dst(), size, &status); + } + + if (!U_SUCCESS(status)) + return QString(); + + // Resize and return: + result.resize(size); + return result; +} + } // QtTimeZoneLocale + +// Used by TZ backends when ICU is available: +QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc, + QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + Q_UNUSED(atMSecsSinceEpoch); + // TODO: use CLDR data for the offset name. + // No ICU API for offset formats, so fall back to our ISO one, even if + // locale isn't C: + if (nameType == QTimeZone::OffsetName) + return isoOffsetFormat(offsetFromUtc); + + const QString id = QString::fromUtf8(m_id); + const QByteArray loc = locale.name().toUtf8(); + UErrorCode status = U_ZERO_ERROR; + UCalendar *ucal = ucal_open(reinterpret_cast(id.data()), id.size(), + loc.constData(), UCAL_DEFAULT, &status); + if (ucal && U_SUCCESS(status)) { + auto tidier = qScopeGuard([ucal]() { ucal_close(ucal); }); + return QtTimeZoneLocale::ucalTimeZoneDisplayName(ucal, timeType, nameType, loc); + } + return QString(); +} #else // No ICU, use QTZ[LP}_data_p.h data for feature timezone_locale. namespace { using namespace QtTimeZoneLocale; // QTZL_data_p.h @@ -24,6 +107,20 @@ using namespace QtTimeZoneCldr; // QTZP_data_p.h // Accessors for the QTZP_data_p.h } // nameless namespace + +QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc, + QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const +{ + Q_ASSERT(nameType != QTimeZone::OffsetName || locale.language() != QLocale::C); + // Get data from QTZ[LP]_data_p.h + + Q_UNUSED(atMSecsSinceEpoch); + Q_UNUSED(offsetFromUtc); + Q_UNUSED(timeType); + return QString(); +} #endif // ICU QT_END_NAMESPACE diff --git a/src/corelib/time/qtimezonelocale_p.h b/src/corelib/time/qtimezonelocale_p.h index adc8e83b35..6e6c6b51fd 100644 --- a/src/corelib/time/qtimezonelocale_p.h +++ b/src/corelib/time/qtimezonelocale_p.h @@ -14,18 +14,31 @@ // // We mean it. // +#include +#include #include +#if QT_CONFIG(icu) +#include +#endif + QT_REQUIRE_CONFIG(timezone); QT_REQUIRE_CONFIG(timezone_locale); +QT_BEGIN_NAMESPACE + namespace QtTimeZoneLocale { #if QT_CONFIG(icu) +QString ucalTimeZoneDisplayName(UCalendar *ucal, QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QString &localeCode); #else // Define data types for QTZL_data_p.h #endif } +QT_END_NAMESPACE + #endif // QTIMEZONELOCALE_P_H diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp index 6d522733aa..f3dc43df6a 100644 --- a/src/corelib/time/qtimezoneprivate.cpp +++ b/src/corelib/time/qtimezoneprivate.cpp @@ -5,6 +5,9 @@ #include "qtimezone.h" #include "qtimezoneprivate_p.h" +#if QT_CONFIG(timezone_locale) +# include "qtimezonelocale_p.h" +#endif #include "qtimezoneprivate_data_p.h" #include @@ -181,7 +184,11 @@ QString QTimeZonePrivate::displayName(qint64 atMSecsSinceEpoch, QTimeZone::TimeType timeType = tran.daylightTimeOffset != 0 ? QTimeZone::DaylightTime : QTimeZone::StandardTime; +#if QT_CONFIG(timezone_locale) + return localeName(atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale); +#else return displayName(timeType, nameType, locale); +#endif } return QString(); } @@ -190,10 +197,14 @@ QString QTimeZonePrivate::displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QLocale &locale) const { - if (nameType == QTimeZone::OffsetName && isDataLocale(locale)) { - const Data tran = data(timeType); - if (tran.atMSecsSinceEpoch != invalidMSecs()) + const Data tran = data(timeType); + if (tran.atMSecsSinceEpoch != invalidMSecs()) { + if (nameType == QTimeZone::OffsetName && isDataLocale(locale)) return isoOffsetFormat(tran.offsetFromUtc); + +#if QT_CONFIG(timezone_locale) + return localeName(tran.atMSecsSinceEpoch, tran.offsetFromUtc, timeType, nameType, locale); +#endif } return QString(); } diff --git a/src/corelib/time/qtimezoneprivate_icu.cpp b/src/corelib/time/qtimezoneprivate_icu.cpp index 1a3baa70d0..a9fe68b83c 100644 --- a/src/corelib/time/qtimezoneprivate_icu.cpp +++ b/src/corelib/time/qtimezoneprivate_icu.cpp @@ -4,6 +4,7 @@ #include "qtimezone.h" #include "qtimezoneprivate_p.h" +#include "qtimezonelocale_p.h" #include @@ -22,27 +23,6 @@ QT_BEGIN_NAMESPACE // ICU utilities -// Convert TimeType and NameType into ICU UCalendarDisplayNameType -static UCalendarDisplayNameType ucalDisplayNameType(QTimeZone::TimeType timeType, QTimeZone::NameType nameType) -{ - // TODO ICU C UCalendarDisplayNameType does not support full set of C++ TimeZone::EDisplayType - switch (nameType) { - case QTimeZone::ShortName : - case QTimeZone::OffsetName : - if (timeType == QTimeZone::DaylightTime) - return UCAL_SHORT_DST; - // Includes GenericTime - return UCAL_SHORT_STANDARD; - case QTimeZone::DefaultName : - case QTimeZone::LongName : - if (timeType == QTimeZone::DaylightTime) - return UCAL_DST; - // Includes GenericTime - return UCAL_STANDARD; - } - return UCAL_STANDARD; -} - // Qt wrapper around ucal_getDefaultTimeZone() static QByteArray ucalDefaultTimeZoneId() { @@ -69,44 +49,6 @@ static QByteArray ucalDefaultTimeZoneId() return QByteArray(); } -// Qt wrapper around ucal_getTimeZoneDisplayName() -static QString ucalTimeZoneDisplayName(UCalendar *ucal, QTimeZone::TimeType timeType, - QTimeZone::NameType nameType, - const QString &localeCode) -{ - int32_t size = 50; - QString result(size, Qt::Uninitialized); - UErrorCode status = U_ZERO_ERROR; - - // size = ucal_getTimeZoneDisplayName(cal, type, locale, result, resultLength, status) - size = ucal_getTimeZoneDisplayName(ucal, - ucalDisplayNameType(timeType, nameType), - localeCode.toUtf8(), - reinterpret_cast(result.data()), - size, - &status); - - // If overflow, then resize and retry - if (status == U_BUFFER_OVERFLOW_ERROR) { - result.resize(size); - status = U_ZERO_ERROR; - size = ucal_getTimeZoneDisplayName(ucal, - ucalDisplayNameType(timeType, nameType), - localeCode.toUtf8(), - reinterpret_cast(result.data()), - size, - &status); - } - - // If successful on first or second go, resize and return - if (U_SUCCESS(status)) { - result.resize(size); - return result; - } - - return QString(); -} - // Qt wrapper around ucal_get() for offsets static bool ucalOffsetsAtTime(UCalendar *m_ucal, qint64 atMSecsSinceEpoch, int *utcOffset, int *dstOffset) @@ -203,13 +145,11 @@ static QTimeZonePrivate::Data ucalTimeZoneTransition(UCalendar *m_ucal, tran.offsetFromUtc = utc + dst; tran.standardTimeOffset = utc; tran.daylightTimeOffset = dst; - // TODO No ICU API, use short name instead - if (dst == 0) - tran.abbreviation = ucalTimeZoneDisplayName(m_ucal, QTimeZone::StandardTime, - QTimeZone::ShortName, QLocale().name()); - else - tran.abbreviation = ucalTimeZoneDisplayName(m_ucal, QTimeZone::DaylightTime, - QTimeZone::ShortName, QLocale().name()); + // TODO No ICU API, use short name as abbreviation. + QTimeZone::TimeType timeType = dst == 0 ? QTimeZone::StandardTime : QTimeZone::DaylightTime; + using namespace QtTimeZoneLocale; + tran.abbreviation = ucalTimeZoneDisplayName(m_ucal, timeType, + QTimeZone::ShortName, QLocale().name()); return tran; } #endif // U_ICU_VERSION_SHORT @@ -317,6 +257,7 @@ QString QIcuTimeZonePrivate::displayName(QTimeZone::TimeType timeType, } // Technically this may be suspect, if locale isn't QLocale(), since that's // what we used when constructing m_ucal; does ICU cope with inconsistency ? + using namespace QtTimeZoneLocale; return ucalTimeZoneDisplayName(m_ucal, timeType, nameType, locale.name()); } diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h index e0632e7cec..5d57ed7558 100644 --- a/src/corelib/time/qtimezoneprivate_p.h +++ b/src/corelib/time/qtimezoneprivate_p.h @@ -152,6 +152,15 @@ public: return QByteArrayLiteral("UTC"); } +#if QT_CONFIG(timezone_locale) +private: + // Defined in qtimezonelocale.cpp + QString localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc, + QTimeZone::TimeType timeType, + QTimeZone::NameType nameType, + const QLocale &locale) const; +#endif // L10n helpers. + protected: QByteArray m_id; }; @@ -223,7 +232,7 @@ private: // TODO: shuffle (almost reverse) order of and rework #if-ery here to use #elif // and match the #if-ery in each of QTZ's newBackendTimeZone() cascades for // backend selection. -#if QT_CONFIG(icu) +#if QT_CONFIG(icu) && !defined(Q_OS_UNIX) class Q_AUTOTEST_EXPORT QIcuTimeZonePrivate final : public QTimeZonePrivate { public: @@ -266,7 +275,7 @@ private: UCalendar *m_ucal; }; -#endif // ICU +#endif // ICU not on Unix. #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) struct QTzTransitionTime @@ -347,14 +356,6 @@ private: Data dataForTzTransition(QTzTransitionTime tran) const; Data dataFromRule(QTzTransitionRule rule, qint64 msecsSinceEpoch) const; -#if QT_CONFIG(icu) -# ifdef __cpp_lib_is_final - static_assert(std::is_final::value, - "if QIcuTimeZonePrivate isn't final, we may need to specialize " - "QExplicitlySharedDataPointer::clone() to call QTimeZonePrivate::clone()"); -# endif - mutable QExplicitlySharedDataPointer m_icu; -#endif QTzTimeZoneCacheEntry cached_data; const QList &tranCache() const { return cached_data.m_tranTimes; } }; diff --git a/src/corelib/time/qtimezoneprivate_tz.cpp b/src/corelib/time/qtimezoneprivate_tz.cpp index d057dae37f..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 @@ -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); } @@ -1007,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(); } } @@ -1034,18 +1019,6 @@ 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); - } -#endif // TZ only provides C-locale abbreviations and offset: if (nameType != QTimeZone::LongName && isDataLocale(locale)) { QTimeZonePrivate::Data tran = data(timeType); @@ -1057,7 +1030,7 @@ QString QTzTimeZonePrivate::displayName(QTimeZone::TimeType timeType, return isoOffsetFormat(tran.offsetFromUtc); } } - // Otherwise, fall back to base class: + // Otherwise, fall back to base class (and qtimezonelocale.cpp): return QTimeZonePrivate::displayName(timeType, nameType, locale); } diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp index d2e8f63f24..7f6bc96aa6 100644 --- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp +++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp @@ -1280,7 +1280,7 @@ void tst_QTimeZone::utcTest() void tst_QTimeZone::icuTest() { -#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(icu) +#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(icu) && !defined(Q_OS_UNIX) // Known datetimes qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch(); @@ -1323,7 +1323,7 @@ void tst_QTimeZone::icuTest() if (QTest::currentTestFailed()) return; testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto")); -#endif // icu +#endif // ICU not on Unix } void tst_QTimeZone::tzTest() @@ -1527,7 +1527,7 @@ void tst_QTimeZone::tzTest() QDateTime dt(QDate(2016, 3, 28), QTime(0, 0), UTC); QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, QString("+07")); } -#endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !Q_OS_DARWIN +#endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !Q_OS_DARWIN && !Q_OS_ANDROID } void tst_QTimeZone::macTest()