Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// Copyright (C) 2014 Drew Parsons <dparsons@emerall.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qtimezone.h"
#include "qtimezoneprivate_p.h"
#include <QtCore/QJniEnvironment>
#include <QtCore/QSet>
#include <QtCore/qjnitypes.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_JNI_CLASS(TimeZone, "java/util/TimeZone");
Q_DECLARE_JNI_CLASS(Locale, "java/util/Locale");
Q_DECLARE_JNI_CLASS(Date, "java/util/Date");
/*
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::callStaticMethod<QtJniTypes::TimeZone>(
QtJniTypes::Traits<QtJniTypes::TimeZone>::className(), "getDefault");
const QJniObject id = androidTimeZone.callMethod<jstring>("getID");
m_id = id.toString().toUtf8();
}
// Create a named time zone
QAndroidTimeZonePrivate::QAndroidTimeZonePrivate(QByteArrayView ianaId)
: QTimeZonePrivate()
{
init(ianaId);
}
QAndroidTimeZonePrivate::~QAndroidTimeZonePrivate()
{
}
static QString getDisplayName(QJniObject zone, jint style, jboolean dst,
const QLocale &locale)
{
QJniObject jbcpTag = QJniObject::fromString(locale.bcp47Name());
QJniObject jlocale = QJniObject::callStaticMethod<QtJniTypes::Locale>(
QtJniTypes::Traits<QtJniTypes::Locale>::className(), "forLanguageTag",
jbcpTag.object<jstring>());
return zone.callMethod<jstring>("getDisplayName", dst, style,
jlocale.object<QtJniTypes::Locale>()).toString();
}
void QAndroidTimeZonePrivate::init(QByteArrayView ianaId)
{
const QString iana = QString::fromUtf8(ianaId);
androidTimeZone = QJniObject::callStaticMethod<QtJniTypes::TimeZone>(
QtJniTypes::Traits<QtJniTypes::TimeZone>::className(), "getTimeZone",
QJniObject::fromString(iana).object<jstring>());
// The ID or display name of the zone we've got, if it looks like what we asked for:
const auto match = [iana](const QString &name) -> QByteArray {
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.callMethod<jstring>("getID").toString());
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);
}
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", 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 DST transitions, not past */
return androidTimeZone.callMethod<jboolean>("useDaylightTime" );
else
return false;
}
bool QAndroidTimeZonePrivate::isDaylightTime(qint64 atMSecsSinceEpoch) const
{
if ( androidTimeZone.isValid() ) {
QJniObject jDate = QJniObject::construct<QtJniTypes::Date>(
static_cast<jlong>(atMSecsSinceEpoch));
return androidTimeZone.callMethod<jboolean>("inDaylightTime",
jDate.object<QtJniTypes::Date>());
}
else
return false;
}
QTimeZonePrivate::Data QAndroidTimeZonePrivate::data(qint64 forMSecsSinceEpoch) const
{
if (androidTimeZone.isValid()) {
return Data(abbreviation(forMSecsSinceEpoch), forMSecsSinceEpoch,
offsetFromUtc(forMSecsSinceEpoch),
standardTimeOffset(forMSecsSinceEpoch));
}
return {};
}
// 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::callStaticMethod<QtJniTypes::TimeZone>(
QtJniTypes::Traits<QtJniTypes::TimeZone>::className(), "getDefault");
const QJniObject id = androidSystemTimeZone.callMethod<jstring>("getID");
return id.toString().toUtf8();
}
bool QAndroidTimeZonePrivate::isTimeZoneIdAvailable(QByteArrayView ianaId) const
{
QAndroidTimeZonePrivate probe(ianaId);
return probe.isValid();
}
QList<QByteArray> QAndroidTimeZonePrivate::availableTimeZoneIds() const
{
using namespace QtJniTypes;
const QJniArray androidAvailableIdList
= TimeZone::callStaticMethod<String[]>("getAvailableIDs");
// Does not document order of entries.
QList<QByteArray> result;
result.reserve(androidAvailableIdList.size());
for (const auto &id : androidAvailableIdList)
result.append(id.toString().toUtf8());
// (It appears to not need sort+uniquify, but we can't rely on that.)
return uniqueSortedAliasPadded(std::move(result));
}
QT_END_NAMESPACE