/**************************************************************************** ** ** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2014 Drew Parsons ** 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 #include QT_BEGIN_NAMESPACE /* Private Android implementation Note that a QJniObject manages a global reference, so it serves as an owning smart-pointer, ensuring an object doesn't get garbage-collected before we're done with it. */ // Create the system default time zone QAndroidTimeZonePrivate::QAndroidTimeZonePrivate() : QTimeZonePrivate() { // Keep in sync with systemTimeZoneId(): androidTimeZone = QJniObject::callStaticObjectMethod( "java.util.TimeZone", "getDefault", "()Ljava/util/TimeZone;"); const QJniObject id = androidTimeZone.callObjectMethod("getID", "()Ljava/lang/String;"); m_id = id.toString().toUtf8(); } // Create a named time zone QAndroidTimeZonePrivate::QAndroidTimeZonePrivate(const QByteArray &ianaId) : QTimeZonePrivate() { init(ianaId); } QAndroidTimeZonePrivate::QAndroidTimeZonePrivate(const QAndroidTimeZonePrivate &other) : QTimeZonePrivate(other) { androidTimeZone = other.androidTimeZone; m_id = other.id(); } QAndroidTimeZonePrivate::~QAndroidTimeZonePrivate() { } static QJniObject getDisplayName(QJniObject zone, jint style, jboolean dst, const QLocale &locale) { QJniObject jlanguage = QJniObject::fromString(QLocale::languageToString(locale.language())); QJniObject jterritory = QJniObject::fromString(QLocale::territoryToString(locale.territory())); QJniObject jvariant = QJniObject::fromString(QLocale::scriptToString(locale.script())); QJniObject jlocale("java.util.Locale", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", jlanguage.object(), jterritory.object(), jvariant.object()); return zone.callObjectMethod("getDisplayName", "(ZILjava/util/Locale;)Ljava/lang/String;", dst, style, jlocale.object()); } void QAndroidTimeZonePrivate::init(const QByteArray &ianaId) { const QString iana = QString::fromUtf8(ianaId); androidTimeZone = QJniObject::callStaticObjectMethod( "java.util.TimeZone", "getTimeZone", "(Ljava/lang/String;)Ljava/util/TimeZone;", QJniObject::fromString(iana).object()); // The ID or display name of the zone we've got, if it looks like what we asked for: const auto match = [iana](const QJniObject &jname) -> QByteArray { const QString name = jname.toString(); if (iana.compare(name, Qt::CaseInsensitive) == 0) return name.toUtf8(); return QByteArray(); }; // Painfully, JNI gives us back a default zone object if it doesn't // recognize the name; so check for whether ianaId is a recognized name of // the zone object we got and ignore the zone if not. // Try checking ianaId against getID(), getDisplayName(): m_id = match(androidTimeZone.callObjectMethod("getID", "()Ljava/lang/String;")); for (int style = 1; m_id.isEmpty() && style >= 0; --style) { for (int dst = 1; m_id.isEmpty() && dst >= 0; --dst) { for (int pick = 2; m_id.isEmpty() && pick >= 0; --pick) { QLocale locale = (pick == 0 ? QLocale::system() : pick == 1 ? QLocale() : QLocale::c()); m_id = match(getDisplayName(androidTimeZone, style, jboolean(dst), locale)); } } } } QAndroidTimeZonePrivate *QAndroidTimeZonePrivate::clone() const { return new QAndroidTimeZonePrivate(*this); } QString QAndroidTimeZonePrivate::displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QLocale &locale) const { QString name; if (androidTimeZone.isValid()) { jboolean daylightTime = (timeType == QTimeZone::DaylightTime); // treat QTimeZone::GenericTime as QTimeZone::StandardTime // treat all NameTypes as java TimeZone style LONG (value 1), except of course QTimeZone::ShortName which is style SHORT (value 0); jint style = (nameType == QTimeZone::ShortName ? 0 : 1); name = getDisplayName(androidTimeZone, style, daylightTime, locale).toString(); } return name; } QString QAndroidTimeZonePrivate::abbreviation(qint64 atMSecsSinceEpoch) const { if ( isDaylightTime( atMSecsSinceEpoch ) ) return displayName(QTimeZone::DaylightTime, QTimeZone::ShortName, QLocale() ); else return displayName(QTimeZone::StandardTime, QTimeZone::ShortName, QLocale() ); } int QAndroidTimeZonePrivate::offsetFromUtc(qint64 atMSecsSinceEpoch) const { // offsetFromUtc( ) is invoked when androidTimeZone may not yet be set at startup, // so a validity test is needed here if ( androidTimeZone.isValid() ) // the java method getOffset() returns milliseconds, but QTimeZone returns seconds return androidTimeZone.callMethod( "getOffset", "(J)I", static_cast(atMSecsSinceEpoch) ) / 1000; else return 0; } int QAndroidTimeZonePrivate::standardTimeOffset(qint64 atMSecsSinceEpoch) const { // the java method does not use a reference time Q_UNUSED( atMSecsSinceEpoch ); if ( androidTimeZone.isValid() ) // the java method getRawOffset() returns milliseconds, but QTimeZone returns seconds return androidTimeZone.callMethod( "getRawOffset" ) / 1000; else return 0; } int QAndroidTimeZonePrivate::daylightTimeOffset(qint64 atMSecsSinceEpoch) const { return ( offsetFromUtc(atMSecsSinceEpoch) - standardTimeOffset(atMSecsSinceEpoch) ); } bool QAndroidTimeZonePrivate::hasDaylightTime() const { if ( androidTimeZone.isValid() ) /* note: the Java function only tests for future DST transitions, not past */ return androidTimeZone.callMethod("useDaylightTime" ); else return false; } bool QAndroidTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const { if ( androidTimeZone.isValid() ) { QJniObject jDate( "java/util/Date", "(J)V", static_cast(atMSecsSinceEpoch) ); return androidTimeZone.callMethod("inDaylightTime", "(Ljava/util/Date;)Z", jDate.object() ); } else return false; } QTimeZonePrivate::Data QAndroidTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const { if (androidTimeZone.isValid()) { Data data; data.atMSecsSinceEpoch = forMSecsSinceEpoch; data.standardTimeOffset = standardTimeOffset(forMSecsSinceEpoch); data.offsetFromUtc = offsetFromUtc(forMSecsSinceEpoch); data.daylightTimeOffset = data.offsetFromUtc - data.standardTimeOffset; data.abbreviation = abbreviation(forMSecsSinceEpoch); return data; } else { return invalidData(); } } // java.util.TimeZone does not directly provide transitions, // so don't over-ride QTZP's base implementation of transition methods. QByteArray QAndroidTimeZonePrivate::systemTimeZoneId() const { // Keep in sync with default constructor: QJniObject androidSystemTimeZone = QJniObject::callStaticObjectMethod( "java.util.TimeZone", "getDefault", "()Ljava/util/TimeZone;"); const QJniObject id = androidSystemTimeZone.callObjectMethod("getID"); return id.toString().toUtf8(); } QList QAndroidTimeZonePrivate::availableTimeZoneIds() const { QList availableTimeZoneIdList; QJniObject androidAvailableIdList = QJniObject::callStaticObjectMethod("java.util.TimeZone", "getAvailableIDs", "()[Ljava/lang/String;"); QJniEnvironment jniEnv; int androidTZcount = jniEnv->GetArrayLength(androidAvailableIdList.object()); // need separate jobject and QJniObject here so that we can delete (DeleteLocalRef) the reference to the jobject // (or else the JNI reference table fills after 512 entries from GetObjectArrayElement) jobject androidTZobject; QJniObject androidTZ; for (int i = 0; i < androidTZcount; i++) { androidTZobject = jniEnv->GetObjectArrayElement(androidAvailableIdList.object(), i); androidTZ = androidTZobject; availableTimeZoneIdList.append(androidTZ.toString().toUtf8()); jniEnv->DeleteLocalRef(androidTZobject); } return availableTimeZoneIdList; } QT_END_NAMESPACE