From 9a565f8fe47f09b3275d7cb425b62117e26be4e9 Mon Sep 17 00:00:00 2001 From: Drew Parsons Date: Mon, 23 Jun 2014 15:37:05 +1000 Subject: provide Android timezones in QTimeZone QTimeZone on a unix-based system expects IANA (Olson) timezones to be provided in /usr/share/zoneinfo or /usr/lib/zoneinfo. But on an Android system the timezone datafiles at this location are incomplete (Android instead uses the java class java.util.TimeZone). QTimeZone on Android therefore would only return the default UTC timezones, not the full set of IANA timezones. This patch invokes JNI on an Android system to make the full set of java timezones known to QTimeZone. The implementation adds a new QAndroidTimeZonePrivate class, invoked by the private implementation of QTimeZone in place of QTzTimeZonePrivate. QAndroidTimeZonePrivate contains adds a new QJNIObjectPrivate [java.util.TimeZone] androidTimeZone property which is used to access the java timezone API. Android limitations: 1) the java class java.util.TimeZone does not provide transitions (see http://developer.android.com/reference/java/util/TimeZone.html). 2) abbreviation( ) is provided using Java TimeZone::getDisplayName( ) with the java SHORT style. This sometimes generates a GMT reference instead of a three-letter code, e.g. America/Sao_Paulo returns "GMT-03:00" instead of "BRT" 3) hasDaylightTime() is handled using Java TimeZone::useDaylightTime(), which according to java (Android) documentation only tests for future transitions, not past transitions. This might conflict with the Qt documentation for this function (which is intended to test also for past transitions). [ChangeLog][Platform Specific Changes][Android][QtCore][QTimeZone] Android timezones are now available in QTimeZone. Change-Id: I165a39b7d4cb30b68f2da8556d85fc5b4480da4b Task-number: QTBUG-35908 Reviewed-by: Thiago Macieira --- src/corelib/tools/qtimezone.cpp | 4 + src/corelib/tools/qtimezoneprivate_android.cpp | 235 +++++++++++++++++++++++++ src/corelib/tools/qtimezoneprivate_p.h | 48 ++++- src/corelib/tools/tools.pri | 7 +- 4 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 src/corelib/tools/qtimezoneprivate_android.cpp (limited to 'src/corelib/tools') diff --git a/src/corelib/tools/qtimezone.cpp b/src/corelib/tools/qtimezone.cpp index 8f3db74131..fb5af2bf4a 100644 --- a/src/corelib/tools/qtimezone.cpp +++ b/src/corelib/tools/qtimezone.cpp @@ -56,6 +56,8 @@ static QTimeZonePrivate *newBackendTimeZone() #else #if defined Q_OS_MAC return new QMacTimeZonePrivate(); +#elif defined Q_OS_ANDROID + return new QAndroidTimeZonePrivate(); #elif defined Q_OS_UNIX return new QTzTimeZonePrivate(); // Registry based timezone backend not available on WinRT @@ -81,6 +83,8 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId) #else #if defined Q_OS_MAC return new QMacTimeZonePrivate(ianaId); +#elif defined Q_OS_ANDROID + return new QAndroidTimeZonePrivate(ianaId); #elif defined Q_OS_UNIX return new QTzTimeZonePrivate(ianaId); // Registry based timezone backend not available on WinRT diff --git a/src/corelib/tools/qtimezoneprivate_android.cpp b/src/corelib/tools/qtimezoneprivate_android.cpp new file mode 100644 index 0000000000..ab1ada2341 --- /dev/null +++ b/src/corelib/tools/qtimezoneprivate_android.cpp @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Drew Parsons +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "qtimezone.h" +#include "qtimezoneprivate_p.h" + +QT_BEGIN_NAMESPACE + +/* + Private + + Android implementation +*/ + +// Create the system default time zone +QAndroidTimeZonePrivate::QAndroidTimeZonePrivate() + : QTimeZonePrivate() +{ + // start with system time zone + androidTimeZone = QJNIObjectPrivate::callStaticObjectMethod("java.util.TimeZone", "getDefault", "()Ljava/util/TimeZone;"); + init("UTC"); +} + +// 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() +{ +} + + +void QAndroidTimeZonePrivate::init(const QByteArray &ianaId) +{ + QJNIObjectPrivate jo_ianaId = QJNIObjectPrivate::fromString( QString::fromUtf8(ianaId) ); + androidTimeZone = QJNIObjectPrivate::callStaticObjectMethod( "java.util.TimeZone", "getTimeZone", "(Ljava/lang/String;)Ljava/util/TimeZone;", static_cast(jo_ianaId.object()) ); + + if (ianaId.isEmpty()) + m_id = systemTimeZoneId(); + else + m_id = ianaId; +} + +QTimeZonePrivate *QAndroidTimeZonePrivate::clone() +{ + 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); + + QJNIObjectPrivate jlanguage = QJNIObjectPrivate::fromString(QLocale::languageToString(locale.language())); + QJNIObjectPrivate jcountry = QJNIObjectPrivate::fromString(QLocale::countryToString(locale.country())); + QJNIObjectPrivate jvariant = QJNIObjectPrivate::fromString(QLocale::scriptToString(locale.script())); + QJNIObjectPrivate jlocale("java.util.Locale", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", static_cast(jlanguage.object()), static_cast(jcountry.object()), static_cast(jvariant.object())); + + QJNIObjectPrivate jname = androidTimeZone.callObjectMethod("getDisplayName", "(ZILjava/util/Locale;)Ljava/lang/String;", daylightTime, style, jlocale.object()); + + name = jname.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 daylight transtions, not past */ + return androidTimeZone.callMethod("useDaylightTime" ); + else + return false; +} + +bool QAndroidTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const +{ + if ( androidTimeZone.isValid() ) { + QJNIObjectPrivate 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(); + } +} + +bool QAndroidTimeZonePrivate::hasTransitions() const +{ + // java.util.TimeZone does not directly provide transitions + return false; +} + +QTimeZonePrivate::Data QAndroidTimeZonePrivate::nextTransition(qint64 afterMSecsSinceEpoch) const +{ + // transitions not available on Android, so return an invalid data object + Q_UNUSED( afterMSecsSinceEpoch ); + return invalidData(); +} + +QTimeZonePrivate::Data QAndroidTimeZonePrivate::previousTransition(qint64 beforeMSecsSinceEpoch) const +{ + // transitions not available on Android, so return an invalid data object + Q_UNUSED( beforeMSecsSinceEpoch ); + return invalidData(); +} + +QByteArray QAndroidTimeZonePrivate::systemTimeZoneId() const +{ + QJNIObjectPrivate androidSystemTimeZone = QJNIObjectPrivate::callStaticObjectMethod("java.util.TimeZone", "getDefault", "()Ljava/util/TimeZone;"); + QJNIObjectPrivate systemTZIdAndroid = androidSystemTimeZone.callObjectMethod("getID"); + QByteArray systemTZid = systemTZIdAndroid.toString().toUtf8(); + + return systemTZid; +} + +QSet QAndroidTimeZonePrivate::availableTimeZoneIds() const +{ + QSet availableTimeZoneIdList; + QJNIObjectPrivate androidAvailableIdList = QJNIObjectPrivate::callStaticObjectMethod("java.util.TimeZone", "getAvailableIDs", "()[Ljava/lang/String;"); + + QJNIEnvironmentPrivate jniEnv; + int androidTZcount = jniEnv->GetArrayLength( static_cast(androidAvailableIdList.object()) ); + + // need separate jobject and QAndroidJniObject 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; + QJNIObjectPrivate androidTZ; + for (int i=0; iGetObjectArrayElement( static_cast( androidAvailableIdList.object() ), i ); + androidTZ = androidTZobject; + availableTimeZoneIdList.insert( androidTZ.toString().toUtf8() ); + jniEnv->DeleteLocalRef(androidTZobject); + } + + return availableTimeZoneIdList; +} + +QT_END_NAMESPACE diff --git a/src/corelib/tools/qtimezoneprivate_p.h b/src/corelib/tools/qtimezoneprivate_p.h index 5ba42de560..486f9c1ffa 100644 --- a/src/corelib/tools/qtimezoneprivate_p.h +++ b/src/corelib/tools/qtimezoneprivate_p.h @@ -65,6 +65,10 @@ class NSTimeZone; #include #endif // Q_OS_WIN +#ifdef Q_OS_ANDROID +#include +#endif + QT_BEGIN_NAMESPACE class Q_CORE_EXPORT QTimeZonePrivate : public QSharedData @@ -256,7 +260,7 @@ private: }; #endif // QT_USE_ICU -#if defined Q_OS_UNIX && !defined Q_OS_MAC +#if defined Q_OS_UNIX && !defined Q_OS_MAC && !defined Q_OS_ANDROID class Q_AUTOTEST_EXPORT QTzTimeZonePrivate Q_DECL_FINAL : public QTimeZonePrivate { public: @@ -424,6 +428,48 @@ private: }; #endif // Q_OS_WIN +#ifdef Q_OS_ANDROID +class QAndroidTimeZonePrivate Q_DECL_FINAL : public QTimeZonePrivate +{ +public: + // Create default time zone + QAndroidTimeZonePrivate(); + // Create named time zone + QAndroidTimeZonePrivate(const QByteArray &ianaId); + QAndroidTimeZonePrivate(const QAndroidTimeZonePrivate &other); + ~QAndroidTimeZonePrivate(); + + QTimeZonePrivate *clone(); + + QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, + const QLocale &locale) const Q_DECL_OVERRIDE; + QString abbreviation(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + int offsetFromUtc(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + int standardTimeOffset(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + int daylightTimeOffset(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + bool hasDaylightTime() const Q_DECL_OVERRIDE; + bool isDaylightTime(qint64 atMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + Data data(qint64 forMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + bool hasTransitions() const Q_DECL_OVERRIDE; + Data nextTransition(qint64 afterMSecsSinceEpoch) const Q_DECL_OVERRIDE; + Data previousTransition(qint64 beforeMSecsSinceEpoch) const Q_DECL_OVERRIDE; + + QByteArray systemTimeZoneId() const Q_DECL_OVERRIDE; + + QSet availableTimeZoneIds() const Q_DECL_OVERRIDE; + +private: + void init(const QByteArray &zoneId); + + QJNIObjectPrivate androidTimeZone; + +}; +#endif // Q_OS_ANDROID + QT_END_NAMESPACE #endif // QTIMEZONEPRIVATE_P_H diff --git a/src/corelib/tools/tools.pri b/src/corelib/tools/tools.pri index 6f6404f83c..01b514d329 100644 --- a/src/corelib/tools/tools.pri +++ b/src/corelib/tools/tools.pri @@ -136,7 +136,12 @@ else:blackberry { SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_blackberry.cpp tools/qtimezoneprivate_tz.cpp HEADERS += tools/qlocale_blackberry.h } -else:unix:SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp tools/qtimezoneprivate_tz.cpp +else:android { + SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp tools/qtimezoneprivate_android.cpp +} +else:unix { + SOURCES += tools/qelapsedtimer_unix.cpp tools/qlocale_unix.cpp tools/qtimezoneprivate_tz.cpp +} else:win32 { SOURCES += tools/qelapsedtimer_win.cpp tools/qlocale_win.cpp !winrt: SOURCES += tools/qtimezoneprivate_win.cpp -- cgit v1.2.3