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