summaryrefslogtreecommitdiffstats
path: root/src/corelib/tools/qtimezoneprivate_android.cpp
diff options
context:
space:
mode:
authorDrew Parsons <dparsons@emerall.com>2014-06-23 15:37:05 +1000
committerDrew Parsons <dparsons@emerall.com>2014-11-26 01:43:10 +0100
commit9a565f8fe47f09b3275d7cb425b62117e26be4e9 (patch)
treec1ad95f0074e085accc4aa59cd9e4ce7375e3a23 /src/corelib/tools/qtimezoneprivate_android.cpp
parent1611adc17eda0f4a85aea8d9cb4c5d01be0eda41 (diff)
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 <thiago.macieira@intel.com>
Diffstat (limited to 'src/corelib/tools/qtimezoneprivate_android.cpp')
-rw-r--r--src/corelib/tools/qtimezoneprivate_android.cpp235
1 files changed, 235 insertions, 0 deletions
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 <dparsons@emerall.com>
+** 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 <QtCore/QSet>
+#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<jstring>(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<jstring>(jlanguage.object()), static_cast<jstring>(jcountry.object()), static_cast<jstring>(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<jint>( "getOffset", "(J)I", static_cast<jlong>(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<jint>( "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<jboolean>("useDaylightTime" );
+ else
+ return false;
+}
+
+bool QAndroidTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
+{
+ if ( androidTimeZone.isValid() ) {
+ QJNIObjectPrivate jDate( "java/util/Date", "(J)V", static_cast<jlong>(atMSecsSinceEpoch) );
+ return androidTimeZone.callMethod<jboolean>("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<jstring>("getID");
+ QByteArray systemTZid = systemTZIdAndroid.toString().toUtf8();
+
+ return systemTZid;
+}
+
+QSet<QByteArray> QAndroidTimeZonePrivate::availableTimeZoneIds() const
+{
+ QSet<QByteArray> availableTimeZoneIdList;
+ QJNIObjectPrivate androidAvailableIdList = QJNIObjectPrivate::callStaticObjectMethod("java.util.TimeZone", "getAvailableIDs", "()[Ljava/lang/String;");
+
+ QJNIEnvironmentPrivate jniEnv;
+ int androidTZcount = jniEnv->GetArrayLength( static_cast<jarray>(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; i<androidTZcount; i++ ) {
+ androidTZobject = jniEnv->GetObjectArrayElement( static_cast<jobjectArray>( androidAvailableIdList.object() ), i );
+ androidTZ = androidTZobject;
+ availableTimeZoneIdList.insert( androidTZ.toString().toUtf8() );
+ jniEnv->DeleteLocalRef(androidTZobject);
+ }
+
+ return availableTimeZoneIdList;
+}
+
+QT_END_NAMESPACE