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
TimeZones_forCountryCode(JNIEnv * env,jclass,jstring countryCode)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, 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, 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
setStringArrayElement(JNIEnv * env,jobjectArray array,int i,const UnicodeString & s)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
isUtc(const UnicodeString & id)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
TimeZones_getZoneStringsImpl(JNIEnv * env,jclass,jstring localeName,jobjectArray timeZoneIds)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 };
register_libcore_icu_TimeZones(JNIEnv * env)229 void register_libcore_icu_TimeZones(JNIEnv* env) {
230 jniRegisterNativeMethods(env, "libcore/icu/TimeZones", gMethods, NELEM(gMethods));
231 }
232