Home | History | Annotate | Download | only in native
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "TimeZones"
     18 
     19 #include <map>
     20 #include <vector>
     21 
     22 #include "JNIHelp.h"
     23 #include "JniConstants.h"
     24 #include "JniException.h"
     25 #include "ScopedJavaUnicodeString.h"
     26 #include "ScopedLocalRef.h"
     27 #include "ScopedUtfChars.h"
     28 #include "UniquePtr.h"
     29 #include "unicode/smpdtfmt.h"
     30 #include "unicode/timezone.h"
     31 
     32 extern Locale getLocale(JNIEnv* env, jstring localeName);
     33 
     34 static jobjectArray TimeZones_forCountryCode(JNIEnv* env, jclass, jstring countryCode) {
     35     ScopedUtfChars countryChars(env, countryCode);
     36     if (countryChars.c_str() == NULL) {
     37         return NULL;
     38     }
     39 
     40     UniquePtr<StringEnumeration> ids(TimeZone::createEnumeration(countryChars.c_str()));
     41     if (ids.get() == NULL) {
     42         return NULL;
     43     }
     44     UErrorCode status = U_ZERO_ERROR;
     45     int32_t idCount = ids->count(status);
     46     if (maybeThrowIcuException(env, "StringEnumeration::count", status)) {
     47         return NULL;
     48     }
     49 
     50     jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringClass, NULL);
     51     for (int32_t i = 0; i < idCount; ++i) {
     52         const UnicodeString* id = ids->snext(status);
     53         if (maybeThrowIcuException(env, "StringEnumeration::snext", status)) {
     54             return NULL;
     55         }
     56         ScopedLocalRef<jstring> idString(env, env->NewString(id->getBuffer(), id->length()));
     57         env->SetObjectArrayElement(result, i, idString.get());
     58     }
     59     return result;
     60 }
     61 
     62 struct TimeZoneNames {
     63     TimeZone* tz;
     64 
     65     UnicodeString longStd;
     66     UnicodeString shortStd;
     67     UnicodeString longDst;
     68     UnicodeString shortDst;
     69 
     70     UDate standardDate;
     71     UDate daylightSavingDate;
     72 };
     73 
     74 static void setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const UnicodeString& s) {
     75     ScopedLocalRef<jstring> javaString(env, env->NewString(s.getBuffer(), s.length()));
     76     env->SetObjectArrayElement(array, i, javaString.get());
     77 }
     78 
     79 static bool isUtc(const UnicodeString& id) {
     80     static UnicodeString etcUct("Etc/UCT", 7, US_INV);
     81     static UnicodeString etcUtc("Etc/UTC", 7, US_INV);
     82     static UnicodeString etcUniversal("Etc/Universal", 13, US_INV);
     83     static UnicodeString etcZulu("Etc/Zulu", 8, US_INV);
     84 
     85     static UnicodeString uct("UCT", 3, US_INV);
     86     static UnicodeString utc("UTC", 3, US_INV);
     87     static UnicodeString universal("Universal", 9, US_INV);
     88     static UnicodeString zulu("Zulu", 4, US_INV);
     89 
     90     return id == etcUct || id == etcUtc || id == etcUniversal || id == etcZulu ||
     91             id == uct || id == utc || id == universal || id == zulu;
     92 }
     93 
     94 static jobjectArray TimeZones_getZoneStringsImpl(JNIEnv* env, jclass, jstring localeName, jobjectArray timeZoneIds) {
     95     Locale locale = getLocale(env, localeName);
     96 
     97     // We could use TimeZone::getDisplayName, but that's even slower
     98     // because it creates a new SimpleDateFormat each time.
     99     // We're better off using SimpleDateFormat directly.
    100 
    101     // We can't use DateFormatSymbols::getZoneStrings because that
    102     // uses its own set of time zone ids and contains empty strings
    103     // instead of GMT offsets (a pity, because it's a bit faster than this code).
    104 
    105     UErrorCode status = U_ZERO_ERROR;
    106     UnicodeString longPattern("zzzz", 4, US_INV);
    107     SimpleDateFormat longFormat(longPattern, locale, status);
    108     // 'z' only uses "common" abbreviations. 'V' allows all known abbreviations.
    109     // For example, "PST" is in common use in en_US, but "CET" isn't.
    110     UnicodeString commonShortPattern("z", 1, US_INV);
    111     SimpleDateFormat shortFormat(commonShortPattern, locale, status);
    112     UnicodeString allShortPattern("V", 1, US_INV);
    113     SimpleDateFormat allShortFormat(allShortPattern, locale, status);
    114 
    115     UnicodeString utc("UTC", 3, US_INV);
    116 
    117     // TODO: use of fixed dates prevents us from using the correct historical name when formatting dates.
    118     // TODO: use of dates not in the current year could cause us to output obsoleted names.
    119     // 15th January 2008
    120     UDate date1 = 1203105600000.0;
    121     // 15th July 2008
    122     UDate date2 = 1218826800000.0;
    123 
    124     // In the first pass, we get the long names for the time zone.
    125     // We also get any commonly-used abbreviations.
    126     std::vector<TimeZoneNames> table;
    127     typedef std::map<UnicodeString, UnicodeString*> AbbreviationMap;
    128     AbbreviationMap usedAbbreviations;
    129     size_t idCount = env->GetArrayLength(timeZoneIds);
    130     for (size_t i = 0; i < idCount; ++i) {
    131         ScopedLocalRef<jstring> javaZoneId(env,
    132                 reinterpret_cast<jstring>(env->GetObjectArrayElement(timeZoneIds, i)));
    133         ScopedJavaUnicodeString zoneId(env, javaZoneId.get());
    134         UnicodeString id(zoneId.unicodeString());
    135 
    136         TimeZoneNames row;
    137         if (isUtc(id)) {
    138             // ICU doesn't have names for the UTC zones; it just says "GMT+00:00" for both
    139             // long and short names. We don't want this. The best we can do is use "UTC"
    140             // for everything (since we don't know how to say "Universal Coordinated Time").
    141             row.tz = NULL;
    142             row.longStd = row.shortStd = row.longDst = row.shortDst = utc;
    143             table.push_back(row);
    144             usedAbbreviations[utc] = &utc;
    145             continue;
    146         }
    147 
    148         row.tz = TimeZone::createTimeZone(id);
    149         longFormat.setTimeZone(*row.tz);
    150         shortFormat.setTimeZone(*row.tz);
    151 
    152         int32_t daylightOffset;
    153         int32_t rawOffset;
    154         row.tz->getOffset(date1, false, rawOffset, daylightOffset, status);
    155         if (daylightOffset != 0) {
    156             // The TimeZone is reporting that we are in daylight time for the winter date.
    157             // The dates are for the wrong hemisphere, so swap them.
    158             row.standardDate = date2;
    159             row.daylightSavingDate = date1;
    160         } else {
    161             row.standardDate = date1;
    162             row.daylightSavingDate = date2;
    163         }
    164 
    165         longFormat.format(row.standardDate, row.longStd);
    166         shortFormat.format(row.standardDate, row.shortStd);
    167         if (row.tz->useDaylightTime()) {
    168             longFormat.format(row.daylightSavingDate, row.longDst);
    169             shortFormat.format(row.daylightSavingDate, row.shortDst);
    170         } else {
    171             row.longDst = row.longStd;
    172             row.shortDst = row.shortStd;
    173         }
    174 
    175         table.push_back(row);
    176         usedAbbreviations[row.shortStd] = &row.longStd;
    177         usedAbbreviations[row.shortDst] = &row.longDst;
    178     }
    179 
    180     // In the second pass, we create the Java String[][].
    181     // We also look for any uncommon abbreviations that don't conflict with ones we've already seen.
    182     jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringArrayClass, NULL);
    183     UnicodeString gmt("GMT", 3, US_INV);
    184     for (size_t i = 0; i < table.size(); ++i) {
    185         TimeZoneNames& row(table[i]);
    186         // Did we get a GMT offset instead of an abbreviation?
    187         if (row.shortStd.length() > 3 && row.shortStd.startsWith(gmt)) {
    188             // See if we can do better...
    189             UnicodeString uncommonStd, uncommonDst;
    190             allShortFormat.setTimeZone(*row.tz);
    191             allShortFormat.format(row.standardDate, uncommonStd);
    192             if (row.tz->useDaylightTime()) {
    193                 allShortFormat.format(row.daylightSavingDate, uncommonDst);
    194             } else {
    195                 uncommonDst = uncommonStd;
    196             }
    197 
    198             // If this abbreviation isn't already in use, we can use it.
    199             AbbreviationMap::iterator it = usedAbbreviations.find(uncommonStd);
    200             if (it == usedAbbreviations.end() || *(it->second) == row.longStd) {
    201                 row.shortStd = uncommonStd;
    202                 usedAbbreviations[row.shortStd] = &row.longStd;
    203             }
    204             it = usedAbbreviations.find(uncommonDst);
    205             if (it == usedAbbreviations.end() || *(it->second) == row.longDst) {
    206                 row.shortDst = uncommonDst;
    207                 usedAbbreviations[row.shortDst] = &row.longDst;
    208             }
    209         }
    210         // Fill in whatever we got.
    211         ScopedLocalRef<jobjectArray> javaRow(env, env->NewObjectArray(5, JniConstants::stringClass, NULL));
    212         ScopedLocalRef<jstring> id(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(timeZoneIds, i)));
    213         env->SetObjectArrayElement(javaRow.get(), 0, id.get());
    214         setStringArrayElement(env, javaRow.get(), 1, row.longStd);
    215         setStringArrayElement(env, javaRow.get(), 2, row.shortStd);
    216         setStringArrayElement(env, javaRow.get(), 3, row.longDst);
    217         setStringArrayElement(env, javaRow.get(), 4, row.shortDst);
    218         env->SetObjectArrayElement(result, i, javaRow.get());
    219         delete row.tz;
    220     }
    221 
    222     return result;
    223 }
    224 
    225 static JNINativeMethod gMethods[] = {
    226     NATIVE_METHOD(TimeZones, forCountryCode, "(Ljava/lang/String;)[Ljava/lang/String;"),
    227     NATIVE_METHOD(TimeZones, getZoneStringsImpl, "(Ljava/lang/String;[Ljava/lang/String;)[[Ljava/lang/String;"),
    228 };
    229 void register_libcore_icu_TimeZones(JNIEnv* env) {
    230     jniRegisterNativeMethods(env, "libcore/icu/TimeZones", gMethods, NELEM(gMethods));
    231 }
    232