blob: 9da108d17f0ce87704f7005e4f70a444670b38c5 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "TimeZones"
#include <map>
#include <vector>
#include "JNIHelp.h"
#include "JniConstants.h"
#include "JniException.h"
#include "ScopedJavaUnicodeString.h"
#include "ScopedLocalRef.h"
#include "ScopedUtfChars.h"
#include "UniquePtr.h"
#include "unicode/smpdtfmt.h"
#include "unicode/timezone.h"
extern Locale getLocale(JNIEnv* env, jstring localeName);
static jobjectArray TimeZones_forCountryCode(JNIEnv* env, jclass, jstring countryCode) {
ScopedUtfChars countryChars(env, countryCode);
if (countryChars.c_str() == NULL) {
return NULL;
}
UniquePtr<StringEnumeration> ids(TimeZone::createEnumeration(countryChars.c_str()));
if (ids.get() == NULL) {
return NULL;
}
UErrorCode status = U_ZERO_ERROR;
int32_t idCount = ids->count(status);
if (maybeThrowIcuException(env, status)) {
return NULL;
}
jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringClass, NULL);
for (int32_t i = 0; i < idCount; ++i) {
const UnicodeString* id = ids->snext(status);
if (maybeThrowIcuException(env, status)) {
return NULL;
}
ScopedLocalRef<jstring> idString(env, env->NewString(id->getBuffer(), id->length()));
env->SetObjectArrayElement(result, i, idString.get());
}
return result;
}
struct TimeZoneNames {
TimeZone* tz;
UnicodeString longStd;
UnicodeString shortStd;
UnicodeString longDst;
UnicodeString shortDst;
UDate standardDate;
UDate daylightSavingDate;
};
static void setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const UnicodeString& s) {
ScopedLocalRef<jstring> javaString(env, env->NewString(s.getBuffer(), s.length()));
env->SetObjectArrayElement(array, i, javaString.get());
}
static bool isUtc(const UnicodeString& id) {
static UnicodeString etcUct("Etc/UCT", 7, US_INV);
static UnicodeString etcUtc("Etc/UTC", 7, US_INV);
static UnicodeString etcUniversal("Etc/Universal", 13, US_INV);
static UnicodeString etcZulu("Etc/Zulu", 8, US_INV);
static UnicodeString uct("UCT", 3, US_INV);
static UnicodeString utc("UTC", 3, US_INV);
static UnicodeString universal("Universal", 9, US_INV);
static UnicodeString zulu("Zulu", 4, US_INV);
return id == etcUct || id == etcUtc || id == etcUniversal || id == etcZulu ||
id == uct || id == utc || id == universal || id == zulu;
}
static jobjectArray TimeZones_getZoneStringsImpl(JNIEnv* env, jclass, jstring localeName, jobjectArray timeZoneIds) {
Locale locale = getLocale(env, localeName);
// We could use TimeZone::getDisplayName, but that's even slower
// because it creates a new SimpleDateFormat each time.
// We're better off using SimpleDateFormat directly.
// We can't use DateFormatSymbols::getZoneStrings because that
// uses its own set of time zone ids and contains empty strings
// instead of GMT offsets (a pity, because it's a bit faster than this code).
UErrorCode status = U_ZERO_ERROR;
UnicodeString longPattern("zzzz", 4, US_INV);
SimpleDateFormat longFormat(longPattern, locale, status);
// 'z' only uses "common" abbreviations. 'V' allows all known abbreviations.
// For example, "PST" is in common use in en_US, but "CET" isn't.
UnicodeString commonShortPattern("z", 1, US_INV);
SimpleDateFormat shortFormat(commonShortPattern, locale, status);
UnicodeString allShortPattern("V", 1, US_INV);
SimpleDateFormat allShortFormat(allShortPattern, locale, status);
UnicodeString utc("UTC", 3, US_INV);
// TODO: use of fixed dates prevents us from using the correct historical name when formatting dates.
// TODO: use of dates not in the current year could cause us to output obsoleted names.
// 15th January 2008
UDate date1 = 1203105600000.0;
// 15th July 2008
UDate date2 = 1218826800000.0;
// In the first pass, we get the long names for the time zone.
// We also get any commonly-used abbreviations.
std::vector<TimeZoneNames> table;
typedef std::map<UnicodeString, UnicodeString*> AbbreviationMap;
AbbreviationMap usedAbbreviations;
size_t idCount = env->GetArrayLength(timeZoneIds);
for (size_t i = 0; i < idCount; ++i) {
ScopedLocalRef<jstring> javaZoneId(env,
reinterpret_cast<jstring>(env->GetObjectArrayElement(timeZoneIds, i)));
ScopedJavaUnicodeString zoneId(env, javaZoneId.get());
UnicodeString id(zoneId.unicodeString());
TimeZoneNames row;
if (isUtc(id)) {
// ICU doesn't have names for the UTC zones; it just says "GMT+00:00" for both
// long and short names. We don't want this. The best we can do is use "UTC"
// for everything (since we don't know how to say "Universal Coordinated Time").
row.tz = NULL;
row.longStd = row.shortStd = row.longDst = row.shortDst = utc;
table.push_back(row);
usedAbbreviations[utc] = &utc;
continue;
}
row.tz = TimeZone::createTimeZone(id);
longFormat.setTimeZone(*row.tz);
shortFormat.setTimeZone(*row.tz);
int32_t daylightOffset;
int32_t rawOffset;
row.tz->getOffset(date1, false, rawOffset, daylightOffset, status);
if (daylightOffset != 0) {
// The TimeZone is reporting that we are in daylight time for the winter date.
// The dates are for the wrong hemisphere, so swap them.
row.standardDate = date2;
row.daylightSavingDate = date1;
} else {
row.standardDate = date1;
row.daylightSavingDate = date2;
}
longFormat.format(row.standardDate, row.longStd);
shortFormat.format(row.standardDate, row.shortStd);
if (row.tz->useDaylightTime()) {
longFormat.format(row.daylightSavingDate, row.longDst);
shortFormat.format(row.daylightSavingDate, row.shortDst);
} else {
row.longDst = row.longStd;
row.shortDst = row.shortStd;
}
table.push_back(row);
usedAbbreviations[row.shortStd] = &row.longStd;
usedAbbreviations[row.shortDst] = &row.longDst;
}
// In the second pass, we create the Java String[][].
// We also look for any uncommon abbreviations that don't conflict with ones we've already seen.
jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringArrayClass, NULL);
UnicodeString gmt("GMT", 3, US_INV);
for (size_t i = 0; i < table.size(); ++i) {
TimeZoneNames& row(table[i]);
// Did we get a GMT offset instead of an abbreviation?
if (row.shortStd.length() > 3 && row.shortStd.startsWith(gmt)) {
// See if we can do better...
UnicodeString uncommonStd, uncommonDst;
allShortFormat.setTimeZone(*row.tz);
allShortFormat.format(row.standardDate, uncommonStd);
if (row.tz->useDaylightTime()) {
allShortFormat.format(row.daylightSavingDate, uncommonDst);
} else {
uncommonDst = uncommonStd;
}
// If this abbreviation isn't already in use, we can use it.
AbbreviationMap::iterator it = usedAbbreviations.find(uncommonStd);
if (it == usedAbbreviations.end() || *(it->second) == row.longStd) {
row.shortStd = uncommonStd;
usedAbbreviations[row.shortStd] = &row.longStd;
}
it = usedAbbreviations.find(uncommonDst);
if (it == usedAbbreviations.end() || *(it->second) == row.longDst) {
row.shortDst = uncommonDst;
usedAbbreviations[row.shortDst] = &row.longDst;
}
}
// Fill in whatever we got.
ScopedLocalRef<jobjectArray> javaRow(env, env->NewObjectArray(5, JniConstants::stringClass, NULL));
ScopedLocalRef<jstring> id(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(timeZoneIds, i)));
env->SetObjectArrayElement(javaRow.get(), 0, id.get());
setStringArrayElement(env, javaRow.get(), 1, row.longStd);
setStringArrayElement(env, javaRow.get(), 2, row.shortStd);
setStringArrayElement(env, javaRow.get(), 3, row.longDst);
setStringArrayElement(env, javaRow.get(), 4, row.shortDst);
env->SetObjectArrayElement(result, i, javaRow.get());
delete row.tz;
}
return result;
}
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(TimeZones, forCountryCode, "(Ljava/lang/String;)[Ljava/lang/String;"),
NATIVE_METHOD(TimeZones, getZoneStringsImpl, "(Ljava/lang/String;[Ljava/lang/String;)[[Ljava/lang/String;"),
};
int register_libcore_icu_TimeZones(JNIEnv* env) {
return jniRegisterNativeMethods(env, "libcore/icu/TimeZones", gMethods, NELEM(gMethods));
}