/* * 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 #include #include "ErrorCode.h" #include "JNIHelp.h" #include "JniConstants.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 ids(TimeZone::createEnumeration(countryChars.c_str())); if (ids.get() == NULL) { return NULL; } UErrorCode status = U_ZERO_ERROR; int32_t idCount = ids->count(status); if (U_FAILURE(status)) { icu4jni_error(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 (U_FAILURE(status)) { icu4jni_error(env, status); return NULL; } ScopedLocalRef 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 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 table; typedef std::map AbbreviationMap; AbbreviationMap usedAbbreviations; size_t idCount = env->GetArrayLength(timeZoneIds); for (size_t i = 0; i < idCount; ++i) { ScopedLocalRef javaZoneId(env, reinterpret_cast(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 javaRow(env, env->NewObjectArray(5, JniConstants::stringClass, NULL)); ScopedLocalRef id(env, reinterpret_cast(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)); }