| /**************************************************************************** |
| ** |
| ** Copyright (C) 2013 John Layt <jlayt@kde.org> |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtCore module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qtimezone.h" |
| #include "qtimezoneprivate_p.h" |
| |
| #include <unicode/ucal.h> |
| |
| #include <qdebug.h> |
| #include <qlist.h> |
| |
| #include <algorithm> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /* |
| Private |
| |
| ICU implementation |
| */ |
| |
| // 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() |
| { |
| int32_t size = 30; |
| QString result(size, Qt::Uninitialized); |
| UErrorCode status = U_ZERO_ERROR; |
| |
| // size = ucal_getDefaultTimeZone(result, resultLength, status) |
| size = ucal_getDefaultTimeZone(reinterpret_cast<UChar *>(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_getDefaultTimeZone(reinterpret_cast<UChar *>(result.data()), size, &status); |
| } |
| |
| // If successful on first or second go, resize and return |
| if (U_SUCCESS(status)) { |
| result.resize(size); |
| return std::move(result).toUtf8(); |
| } |
| |
| 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<UChar *>(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<UChar *>(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) |
| { |
| *utcOffset = 0; |
| *dstOffset = 0; |
| |
| // Clone the ucal so we don't change the shared object |
| UErrorCode status = U_ZERO_ERROR; |
| UCalendar *ucal = ucal_clone(m_ucal, &status); |
| if (!U_SUCCESS(status)) |
| return false; |
| |
| // Set the date to find the offset for |
| status = U_ZERO_ERROR; |
| ucal_setMillis(ucal, atMSecsSinceEpoch, &status); |
| |
| int32_t utc = 0; |
| if (U_SUCCESS(status)) { |
| status = U_ZERO_ERROR; |
| // Returns msecs |
| utc = ucal_get(ucal, UCAL_ZONE_OFFSET, &status) / 1000; |
| } |
| |
| int32_t dst = 0; |
| if (U_SUCCESS(status)) { |
| status = U_ZERO_ERROR; |
| // Returns msecs |
| dst = ucal_get(ucal, UCAL_DST_OFFSET, &status) / 1000; |
| } |
| |
| ucal_close(ucal); |
| if (U_SUCCESS(status)) { |
| *utcOffset = utc; |
| *dstOffset = dst; |
| return true; |
| } |
| return false; |
| } |
| |
| // ICU Draft api in v50, should be stable in ICU v51. Available in C++ api from ICU v3.8 |
| #if U_ICU_VERSION_MAJOR_NUM == 50 |
| // Qt wrapper around qt_ucal_getTimeZoneTransitionDate & ucal_get |
| static QTimeZonePrivate::Data ucalTimeZoneTransition(UCalendar *m_ucal, |
| UTimeZoneTransitionType type, |
| qint64 atMSecsSinceEpoch) |
| { |
| QTimeZonePrivate::Data tran = QTimeZonePrivate::invalidData(); |
| |
| // Clone the ucal so we don't change the shared object |
| UErrorCode status = U_ZERO_ERROR; |
| UCalendar *ucal = ucal_clone(m_ucal, &status); |
| if (!U_SUCCESS(status)) |
| return tran; |
| |
| // Set the date to find the transition for |
| status = U_ZERO_ERROR; |
| ucal_setMillis(ucal, atMSecsSinceEpoch, &status); |
| |
| // Find the transition time |
| UDate tranMSecs = 0; |
| status = U_ZERO_ERROR; |
| bool ok = ucal_getTimeZoneTransitionDate(ucal, type, &tranMSecs, &status); |
| |
| // Set the transition time to find the offsets for |
| if (U_SUCCESS(status) && ok) { |
| status = U_ZERO_ERROR; |
| ucal_setMillis(ucal, tranMSecs, &status); |
| } |
| |
| int32_t utc = 0; |
| if (U_SUCCESS(status) && ok) { |
| status = U_ZERO_ERROR; |
| utc = ucal_get(ucal, UCAL_ZONE_OFFSET, &status) / 1000; |
| } |
| |
| int32_t dst = 0; |
| if (U_SUCCESS(status) && ok) { |
| status = U_ZERO_ERROR; |
| dst = ucal_get(ucal, UCAL_DST_OFFSET, &status) / 1000; |
| } |
| |
| ucal_close(ucal); |
| if (!U_SUCCESS(status) || !ok) |
| return tran; |
| tran.atMSecsSinceEpoch = tranMSecs; |
| 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()); |
| return tran; |
| } |
| #endif // U_ICU_VERSION_SHORT |
| |
| // Convert a uenum to a QList<QByteArray> |
| static QList<QByteArray> uenumToIdList(UEnumeration *uenum) |
| { |
| QList<QByteArray> list; |
| int32_t size = 0; |
| UErrorCode status = U_ZERO_ERROR; |
| // TODO Perhaps use uenum_unext instead? |
| QByteArray result = uenum_next(uenum, &size, &status); |
| while (U_SUCCESS(status) && !result.isEmpty()) { |
| list << result; |
| status = U_ZERO_ERROR; |
| result = uenum_next(uenum, &size, &status); |
| } |
| std::sort(list.begin(), list.end()); |
| list.erase(std::unique(list.begin(), list.end()), list.end()); |
| return list; |
| } |
| |
| // Qt wrapper around ucal_getDSTSavings() |
| static int ucalDaylightOffset(const QByteArray &id) |
| { |
| UErrorCode status = U_ZERO_ERROR; |
| const int32_t dstMSecs = ucal_getDSTSavings(reinterpret_cast<const UChar *>(id.data()), &status); |
| if (U_SUCCESS(status)) |
| return (dstMSecs / 1000); |
| else |
| return 0; |
| } |
| |
| // Create the system default time zone |
| QIcuTimeZonePrivate::QIcuTimeZonePrivate() |
| : m_ucal(nullptr) |
| { |
| // TODO No ICU C API to obtain sysem tz, assume default hasn't been changed |
| init(ucalDefaultTimeZoneId()); |
| } |
| |
| // Create a named time zone |
| QIcuTimeZonePrivate::QIcuTimeZonePrivate(const QByteArray &ianaId) |
| : m_ucal(nullptr) |
| { |
| // Need to check validity here as ICu will create a GMT tz if name is invalid |
| if (availableTimeZoneIds().contains(ianaId)) |
| init(ianaId); |
| } |
| |
| QIcuTimeZonePrivate::QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other) |
| : QTimeZonePrivate(other), m_ucal(nullptr) |
| { |
| // Clone the ucal so we don't close the shared object |
| UErrorCode status = U_ZERO_ERROR; |
| m_ucal = ucal_clone(other.m_ucal, &status); |
| if (!U_SUCCESS(status)) { |
| m_id.clear(); |
| m_ucal = nullptr; |
| } |
| } |
| |
| QIcuTimeZonePrivate::~QIcuTimeZonePrivate() |
| { |
| ucal_close(m_ucal); |
| } |
| |
| QIcuTimeZonePrivate *QIcuTimeZonePrivate::clone() const |
| { |
| return new QIcuTimeZonePrivate(*this); |
| } |
| |
| void QIcuTimeZonePrivate::init(const QByteArray &ianaId) |
| { |
| m_id = ianaId; |
| |
| const QString id = QString::fromUtf8(m_id); |
| UErrorCode status = U_ZERO_ERROR; |
| //TODO Use UCAL_GREGORIAN for now to match QLocale, change to UCAL_DEFAULT once full ICU support |
| m_ucal = ucal_open(reinterpret_cast<const UChar *>(id.data()), id.size(), |
| QLocale().name().toUtf8(), UCAL_GREGORIAN, &status); |
| |
| if (!U_SUCCESS(status)) { |
| m_id.clear(); |
| m_ucal = nullptr; |
| } |
| } |
| |
| QString QIcuTimeZonePrivate::displayName(QTimeZone::TimeType timeType, |
| QTimeZone::NameType nameType, |
| const QLocale &locale) const |
| { |
| // Return standard offset format name as ICU C api doesn't support it yet |
| if (nameType == QTimeZone::OffsetName) { |
| const Data nowData = data(QDateTime::currentMSecsSinceEpoch()); |
| // We can't use transitions reliably to find out right dst offset |
| // Instead use dst offset api to try get it if needed |
| if (timeType == QTimeZone::DaylightTime) |
| return isoOffsetFormat(nowData.standardTimeOffset + ucalDaylightOffset(m_id)); |
| else |
| return isoOffsetFormat(nowData.standardTimeOffset); |
| } |
| return ucalTimeZoneDisplayName(m_ucal, timeType, nameType, locale.name()); |
| } |
| |
| QString QIcuTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const |
| { |
| // TODO No ICU API, use short name instead |
| if (isDaylightTime(atMSecsSinceEpoch)) |
| return displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, QString()); |
| else |
| return displayName(QTimeZone::StandardTime, QTimeZone::ShortName, QString()); |
| } |
| |
| int QIcuTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const |
| { |
| int stdOffset = 0; |
| int dstOffset = 0; |
| ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); |
| return stdOffset + dstOffset; |
| } |
| |
| int QIcuTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const |
| { |
| int stdOffset = 0; |
| int dstOffset = 0; |
| ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); |
| return stdOffset; |
| } |
| |
| int QIcuTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const |
| { |
| int stdOffset = 0; |
| int dstOffset = 0; |
| ucalOffsetsAtTime(m_ucal, atMSecsSinceEpoch, &stdOffset, & dstOffset); |
| return dstOffset; |
| } |
| |
| bool QIcuTimeZonePrivate::hasDaylightTime() const |
| { |
| // TODO No direct ICU C api, work-around below not reliable? Find a better way? |
| return (ucalDaylightOffset(m_id) != 0); |
| } |
| |
| bool QIcuTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const |
| { |
| // Clone the ucal so we don't change the shared object |
| UErrorCode status = U_ZERO_ERROR; |
| UCalendar *ucal = ucal_clone(m_ucal, &status); |
| if (!U_SUCCESS(status)) |
| return false; |
| |
| // Set the date to find the offset for |
| status = U_ZERO_ERROR; |
| ucal_setMillis(ucal, atMSecsSinceEpoch, &status); |
| |
| bool result = false; |
| if (U_SUCCESS(status)) { |
| status = U_ZERO_ERROR; |
| result = ucal_inDaylightTime(ucal, &status); |
| } |
| |
| ucal_close(ucal); |
| return result; |
| } |
| |
| QTimeZonePrivate::Data QIcuTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const |
| { |
| // Available in ICU C++ api, and draft C api in v50 |
| // TODO When v51 released see if api is stable |
| QTimeZonePrivate::Data data = invalidData(); |
| #if U_ICU_VERSION_MAJOR_NUM == 50 |
| data = ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_PREVIOUS_INCLUSIVE, |
| forMSecsSinceEpoch); |
| #else |
| ucalOffsetsAtTime(m_ucal, forMSecsSinceEpoch, &data.standardTimeOffset, |
| &data.daylightTimeOffset); |
| data.offsetFromUtc = data.standardTimeOffset + data.daylightTimeOffset; |
| data.abbreviation = abbreviation(forMSecsSinceEpoch); |
| #endif // U_ICU_VERSION_MAJOR_NUM == 50 |
| data.atMSecsSinceEpoch = forMSecsSinceEpoch; |
| return data; |
| } |
| |
| bool QIcuTimeZonePrivate::hasTransitions() const |
| { |
| // Available in ICU C++ api, and draft C api in v50 |
| // TODO When v51 released see if api is stable |
| #if U_ICU_VERSION_MAJOR_NUM == 50 |
| return true; |
| #else |
| return false; |
| #endif // U_ICU_VERSION_MAJOR_NUM == 50 |
| } |
| |
| QTimeZonePrivate::Data QIcuTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const |
| { |
| // Available in ICU C++ api, and draft C api in v50 |
| // TODO When v51 released see if api is stable |
| #if U_ICU_VERSION_MAJOR_NUM == 50 |
| return ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_NEXT, afterMSecsSinceEpoch); |
| #else |
| Q_UNUSED(afterMSecsSinceEpoch) |
| return invalidData(); |
| #endif // U_ICU_VERSION_MAJOR_NUM == 50 |
| } |
| |
| QTimeZonePrivate::Data QIcuTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const |
| { |
| // Available in ICU C++ api, and draft C api in v50 |
| // TODO When v51 released see if api is stable |
| #if U_ICU_VERSION_MAJOR_NUM == 50 |
| return ucalTimeZoneTransition(m_ucal, UCAL_TZ_TRANSITION_PREVIOUS, beforeMSecsSinceEpoch); |
| #else |
| Q_UNUSED(beforeMSecsSinceEpoch) |
| return invalidData(); |
| #endif // U_ICU_VERSION_MAJOR_NUM == 50 |
| } |
| |
| QByteArray QIcuTimeZonePrivate::systemTimeZoneId() const |
| { |
| // No ICU C API to obtain sysem tz |
| // TODO Assume default hasn't been changed and is the latests system |
| return ucalDefaultTimeZoneId(); |
| } |
| |
| QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds() const |
| { |
| UErrorCode status = U_ZERO_ERROR; |
| UEnumeration *uenum = ucal_openTimeZones(&status); |
| QList<QByteArray> result; |
| if (U_SUCCESS(status)) |
| result = uenumToIdList(uenum); |
| uenum_close(uenum); |
| return result; |
| } |
| |
| QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds(QLocale::Country country) const |
| { |
| const QLatin1String regionCode = QLocalePrivate::countryToCode(country); |
| const QByteArray regionCodeUtf8 = QString(regionCode).toUtf8(); |
| UErrorCode status = U_ZERO_ERROR; |
| UEnumeration *uenum = ucal_openCountryTimeZones(regionCodeUtf8.data(), &status); |
| QList<QByteArray> result; |
| if (U_SUCCESS(status)) |
| result = uenumToIdList(uenum); |
| uenum_close(uenum); |
| return result; |
| } |
| |
| QList<QByteArray> QIcuTimeZonePrivate::availableTimeZoneIds(int offsetFromUtc) const |
| { |
| // TODO Available directly in C++ api but not C api, from 4.8 onwards new filter method works |
| #if U_ICU_VERSION_MAJOR_NUM >= 49 || (U_ICU_VERSION_MAJOR_NUM == 4 && U_ICU_VERSION_MINOR_NUM == 8) |
| UErrorCode status = U_ZERO_ERROR; |
| UEnumeration *uenum = ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, nullptr, |
| &offsetFromUtc, &status); |
| QList<QByteArray> result; |
| if (U_SUCCESS(status)) |
| result = uenumToIdList(uenum); |
| uenum_close(uenum); |
| return result; |
| #else |
| return QTimeZonePrivate::availableTimeZoneIds(offsetFromUtc); |
| #endif |
| } |
| |
| QT_END_NAMESPACE |