|
1 /* |
|
2 ******************************************************************************* |
|
3 * Copyright (C) 2007-2013, International Business Machines Corporation and |
|
4 * others. All Rights Reserved. |
|
5 ******************************************************************************* |
|
6 */ |
|
7 |
|
8 #include "unicode/utypes.h" |
|
9 |
|
10 #if !UCONFIG_NO_FORMATTING |
|
11 |
|
12 #include "zonemeta.h" |
|
13 |
|
14 #include "unicode/timezone.h" |
|
15 #include "unicode/ustring.h" |
|
16 #include "unicode/putil.h" |
|
17 #include "unicode/simpletz.h" |
|
18 |
|
19 #include "umutex.h" |
|
20 #include "uvector.h" |
|
21 #include "cmemory.h" |
|
22 #include "gregoimp.h" |
|
23 #include "cstring.h" |
|
24 #include "ucln_in.h" |
|
25 #include "uassert.h" |
|
26 #include "uresimp.h" |
|
27 #include "uhash.h" |
|
28 #include "olsontz.h" |
|
29 |
|
30 static UMutex gZoneMetaLock = U_MUTEX_INITIALIZER; |
|
31 |
|
32 // CLDR Canonical ID mapping table |
|
33 static UHashtable *gCanonicalIDCache = NULL; |
|
34 static icu::UInitOnce gCanonicalIDCacheInitOnce = U_INITONCE_INITIALIZER; |
|
35 |
|
36 // Metazone mapping table |
|
37 static UHashtable *gOlsonToMeta = NULL; |
|
38 static icu::UInitOnce gOlsonToMetaInitOnce = U_INITONCE_INITIALIZER; |
|
39 |
|
40 // Available metazone IDs vector and table |
|
41 static icu::UVector *gMetaZoneIDs = NULL; |
|
42 static UHashtable *gMetaZoneIDTable = NULL; |
|
43 static icu::UInitOnce gMetaZoneIDsInitOnce = U_INITONCE_INITIALIZER; |
|
44 |
|
45 // Country info vectors |
|
46 static icu::UVector *gSingleZoneCountries = NULL; |
|
47 static icu::UVector *gMultiZonesCountries = NULL; |
|
48 static icu::UInitOnce gCountryInfoVectorsInitOnce = U_INITONCE_INITIALIZER; |
|
49 |
|
50 U_CDECL_BEGIN |
|
51 |
|
52 /** |
|
53 * Cleanup callback func |
|
54 */ |
|
55 static UBool U_CALLCONV zoneMeta_cleanup(void) |
|
56 { |
|
57 if (gCanonicalIDCache != NULL) { |
|
58 uhash_close(gCanonicalIDCache); |
|
59 gCanonicalIDCache = NULL; |
|
60 } |
|
61 gCanonicalIDCacheInitOnce.reset(); |
|
62 |
|
63 if (gOlsonToMeta != NULL) { |
|
64 uhash_close(gOlsonToMeta); |
|
65 gOlsonToMeta = NULL; |
|
66 } |
|
67 gOlsonToMetaInitOnce.reset(); |
|
68 |
|
69 if (gMetaZoneIDTable != NULL) { |
|
70 uhash_close(gMetaZoneIDTable); |
|
71 gMetaZoneIDTable = NULL; |
|
72 } |
|
73 // delete after closing gMetaZoneIDTable, because it holds |
|
74 // value objects held by the hashtable |
|
75 delete gMetaZoneIDs; |
|
76 gMetaZoneIDs = NULL; |
|
77 gMetaZoneIDsInitOnce.reset(); |
|
78 |
|
79 delete gSingleZoneCountries; |
|
80 gSingleZoneCountries = NULL; |
|
81 delete gMultiZonesCountries; |
|
82 gMultiZonesCountries = NULL; |
|
83 gCountryInfoVectorsInitOnce.reset(); |
|
84 |
|
85 return TRUE; |
|
86 } |
|
87 |
|
88 /** |
|
89 * Deleter for UChar* string |
|
90 */ |
|
91 static void U_CALLCONV |
|
92 deleteUCharString(void *obj) { |
|
93 UChar *entry = (UChar*)obj; |
|
94 uprv_free(entry); |
|
95 } |
|
96 |
|
97 /** |
|
98 * Deleter for UVector |
|
99 */ |
|
100 static void U_CALLCONV |
|
101 deleteUVector(void *obj) { |
|
102 delete (icu::UVector*) obj; |
|
103 } |
|
104 |
|
105 /** |
|
106 * Deleter for OlsonToMetaMappingEntry |
|
107 */ |
|
108 static void U_CALLCONV |
|
109 deleteOlsonToMetaMappingEntry(void *obj) { |
|
110 icu::OlsonToMetaMappingEntry *entry = (icu::OlsonToMetaMappingEntry*)obj; |
|
111 uprv_free(entry); |
|
112 } |
|
113 |
|
114 U_CDECL_END |
|
115 |
|
116 U_NAMESPACE_BEGIN |
|
117 |
|
118 #define ZID_KEY_MAX 128 |
|
119 |
|
120 static const char gMetaZones[] = "metaZones"; |
|
121 static const char gMetazoneInfo[] = "metazoneInfo"; |
|
122 static const char gMapTimezonesTag[] = "mapTimezones"; |
|
123 |
|
124 static const char gKeyTypeData[] = "keyTypeData"; |
|
125 static const char gTypeAliasTag[] = "typeAlias"; |
|
126 static const char gTypeMapTag[] = "typeMap"; |
|
127 static const char gTimezoneTag[] = "timezone"; |
|
128 |
|
129 static const char gPrimaryZonesTag[] = "primaryZones"; |
|
130 |
|
131 static const char gWorldTag[] = "001"; |
|
132 |
|
133 static const UChar gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001" |
|
134 |
|
135 static const UChar gDefaultFrom[] = {0x31, 0x39, 0x37, 0x30, 0x2D, 0x30, 0x31, 0x2D, 0x30, 0x31, |
|
136 0x20, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x00}; // "1970-01-01 00:00" |
|
137 static const UChar gDefaultTo[] = {0x39, 0x39, 0x39, 0x39, 0x2D, 0x31, 0x32, 0x2D, 0x33, 0x31, |
|
138 0x20, 0x32, 0x33, 0x3A, 0x35, 0x39, 0x00}; // "9999-12-31 23:59" |
|
139 |
|
140 static const UChar gCustomTzPrefix[] = {0x47, 0x4D, 0x54, 0}; // "GMT" |
|
141 |
|
142 #define ASCII_DIGIT(c) (((c)>=0x30 && (c)<=0x39) ? (c)-0x30 : -1) |
|
143 |
|
144 /* |
|
145 * Convert a date string used by metazone mappings to UDate. |
|
146 * The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm". |
|
147 */ |
|
148 static UDate |
|
149 parseDate (const UChar *text, UErrorCode &status) { |
|
150 if (U_FAILURE(status)) { |
|
151 return 0; |
|
152 } |
|
153 int32_t len = u_strlen(text); |
|
154 if (len != 16 && len != 10) { |
|
155 // It must be yyyy-MM-dd HH:mm (length 16) or yyyy-MM-dd (length 10) |
|
156 status = U_INVALID_FORMAT_ERROR; |
|
157 return 0; |
|
158 } |
|
159 |
|
160 int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, n; |
|
161 int32_t idx; |
|
162 |
|
163 // "yyyy" (0 - 3) |
|
164 for (idx = 0; idx <= 3 && U_SUCCESS(status); idx++) { |
|
165 n = ASCII_DIGIT((int32_t)text[idx]); |
|
166 if (n >= 0) { |
|
167 year = 10*year + n; |
|
168 } else { |
|
169 status = U_INVALID_FORMAT_ERROR; |
|
170 } |
|
171 } |
|
172 // "MM" (5 - 6) |
|
173 for (idx = 5; idx <= 6 && U_SUCCESS(status); idx++) { |
|
174 n = ASCII_DIGIT((int32_t)text[idx]); |
|
175 if (n >= 0) { |
|
176 month = 10*month + n; |
|
177 } else { |
|
178 status = U_INVALID_FORMAT_ERROR; |
|
179 } |
|
180 } |
|
181 // "dd" (8 - 9) |
|
182 for (idx = 8; idx <= 9 && U_SUCCESS(status); idx++) { |
|
183 n = ASCII_DIGIT((int32_t)text[idx]); |
|
184 if (n >= 0) { |
|
185 day = 10*day + n; |
|
186 } else { |
|
187 status = U_INVALID_FORMAT_ERROR; |
|
188 } |
|
189 } |
|
190 if (len == 16) { |
|
191 // "HH" (11 - 12) |
|
192 for (idx = 11; idx <= 12 && U_SUCCESS(status); idx++) { |
|
193 n = ASCII_DIGIT((int32_t)text[idx]); |
|
194 if (n >= 0) { |
|
195 hour = 10*hour + n; |
|
196 } else { |
|
197 status = U_INVALID_FORMAT_ERROR; |
|
198 } |
|
199 } |
|
200 // "mm" (14 - 15) |
|
201 for (idx = 14; idx <= 15 && U_SUCCESS(status); idx++) { |
|
202 n = ASCII_DIGIT((int32_t)text[idx]); |
|
203 if (n >= 0) { |
|
204 min = 10*min + n; |
|
205 } else { |
|
206 status = U_INVALID_FORMAT_ERROR; |
|
207 } |
|
208 } |
|
209 } |
|
210 |
|
211 if (U_SUCCESS(status)) { |
|
212 UDate date = Grego::fieldsToDay(year, month - 1, day) * U_MILLIS_PER_DAY |
|
213 + hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE; |
|
214 return date; |
|
215 } |
|
216 return 0; |
|
217 } |
|
218 |
|
219 static void U_CALLCONV initCanonicalIDCache(UErrorCode &status) { |
|
220 gCanonicalIDCache = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); |
|
221 if (gCanonicalIDCache == NULL) { |
|
222 status = U_MEMORY_ALLOCATION_ERROR; |
|
223 } |
|
224 if (U_FAILURE(status)) { |
|
225 gCanonicalIDCache = NULL; |
|
226 } |
|
227 // No key/value deleters - keys/values are from a resource bundle |
|
228 ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); |
|
229 } |
|
230 |
|
231 |
|
232 const UChar* U_EXPORT2 |
|
233 ZoneMeta::getCanonicalCLDRID(const UnicodeString &tzid, UErrorCode& status) { |
|
234 if (U_FAILURE(status)) { |
|
235 return NULL; |
|
236 } |
|
237 |
|
238 int32_t len = tzid.length(); |
|
239 if (len > ZID_KEY_MAX) { |
|
240 status = U_ILLEGAL_ARGUMENT_ERROR; |
|
241 return NULL; |
|
242 } |
|
243 |
|
244 // Checking the cached results |
|
245 umtx_initOnce(gCanonicalIDCacheInitOnce, &initCanonicalIDCache, status); |
|
246 if (U_FAILURE(status)) { |
|
247 return NULL; |
|
248 } |
|
249 |
|
250 const UChar *canonicalID = NULL; |
|
251 |
|
252 UErrorCode tmpStatus = U_ZERO_ERROR; |
|
253 UChar utzid[ZID_KEY_MAX + 1]; |
|
254 tzid.extract(utzid, ZID_KEY_MAX + 1, tmpStatus); |
|
255 U_ASSERT(tmpStatus == U_ZERO_ERROR); // we checked the length of tzid already |
|
256 |
|
257 // Check if it was already cached |
|
258 umtx_lock(&gZoneMetaLock); |
|
259 { |
|
260 canonicalID = (const UChar *)uhash_get(gCanonicalIDCache, utzid); |
|
261 } |
|
262 umtx_unlock(&gZoneMetaLock); |
|
263 |
|
264 if (canonicalID != NULL) { |
|
265 return canonicalID; |
|
266 } |
|
267 |
|
268 // If not, resolve CLDR canonical ID with resource data |
|
269 UBool isInputCanonical = FALSE; |
|
270 char id[ZID_KEY_MAX + 1]; |
|
271 const UChar* idChars = tzid.getBuffer(); |
|
272 |
|
273 u_UCharsToChars(idChars,id,len); |
|
274 id[len] = (char) 0; // Make sure it is null terminated. |
|
275 |
|
276 // replace '/' with ':' |
|
277 char *p = id; |
|
278 while (*p++) { |
|
279 if (*p == '/') { |
|
280 *p = ':'; |
|
281 } |
|
282 } |
|
283 |
|
284 UResourceBundle *top = ures_openDirect(NULL, gKeyTypeData, &tmpStatus); |
|
285 UResourceBundle *rb = ures_getByKey(top, gTypeMapTag, NULL, &tmpStatus); |
|
286 ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus); |
|
287 ures_getByKey(rb, id, rb, &tmpStatus); |
|
288 if (U_SUCCESS(tmpStatus)) { |
|
289 // type entry (canonical) found |
|
290 // the input is the canonical ID. resolve to const UChar* |
|
291 canonicalID = TimeZone::findID(tzid); |
|
292 isInputCanonical = TRUE; |
|
293 } |
|
294 |
|
295 if (canonicalID == NULL) { |
|
296 // If a map element not found, then look for an alias |
|
297 tmpStatus = U_ZERO_ERROR; |
|
298 ures_getByKey(top, gTypeAliasTag, rb, &tmpStatus); |
|
299 ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus); |
|
300 const UChar *canonical = ures_getStringByKey(rb,id,NULL,&tmpStatus); |
|
301 if (U_SUCCESS(tmpStatus)) { |
|
302 // canonical map found |
|
303 canonicalID = canonical; |
|
304 } |
|
305 |
|
306 if (canonicalID == NULL) { |
|
307 // Dereference the input ID using the tz data |
|
308 const UChar *derefer = TimeZone::dereferOlsonLink(tzid); |
|
309 if (derefer == NULL) { |
|
310 status = U_ILLEGAL_ARGUMENT_ERROR; |
|
311 } else { |
|
312 len = u_strlen(derefer); |
|
313 u_UCharsToChars(derefer,id,len); |
|
314 id[len] = (char) 0; // Make sure it is null terminated. |
|
315 |
|
316 // replace '/' with ':' |
|
317 char *p = id; |
|
318 while (*p++) { |
|
319 if (*p == '/') { |
|
320 *p = ':'; |
|
321 } |
|
322 } |
|
323 |
|
324 // If a dereference turned something up then look for an alias. |
|
325 // rb still points to the alias table, so we don't have to go looking |
|
326 // for it. |
|
327 tmpStatus = U_ZERO_ERROR; |
|
328 canonical = ures_getStringByKey(rb,id,NULL,&tmpStatus); |
|
329 if (U_SUCCESS(tmpStatus)) { |
|
330 // canonical map for the dereferenced ID found |
|
331 canonicalID = canonical; |
|
332 } else { |
|
333 canonicalID = derefer; |
|
334 isInputCanonical = TRUE; |
|
335 } |
|
336 } |
|
337 } |
|
338 } |
|
339 ures_close(rb); |
|
340 ures_close(top); |
|
341 |
|
342 if (U_SUCCESS(status)) { |
|
343 U_ASSERT(canonicalID != NULL); // canocanilD must be non-NULL here |
|
344 |
|
345 // Put the resolved canonical ID to the cache |
|
346 umtx_lock(&gZoneMetaLock); |
|
347 { |
|
348 const UChar* idInCache = (const UChar *)uhash_get(gCanonicalIDCache, utzid); |
|
349 if (idInCache == NULL) { |
|
350 const UChar* key = ZoneMeta::findTimeZoneID(tzid); |
|
351 U_ASSERT(key != NULL); |
|
352 if (key != NULL) { |
|
353 idInCache = (const UChar *)uhash_put(gCanonicalIDCache, (void *)key, (void *)canonicalID, &status); |
|
354 U_ASSERT(idInCache == NULL); |
|
355 } |
|
356 } |
|
357 if (U_SUCCESS(status) && isInputCanonical) { |
|
358 // Also put canonical ID itself into the cache if not exist |
|
359 const UChar *canonicalInCache = (const UChar*)uhash_get(gCanonicalIDCache, canonicalID); |
|
360 if (canonicalInCache == NULL) { |
|
361 canonicalInCache = (const UChar *)uhash_put(gCanonicalIDCache, (void *)canonicalID, (void *)canonicalID, &status); |
|
362 U_ASSERT(canonicalInCache == NULL); |
|
363 } |
|
364 } |
|
365 } |
|
366 umtx_unlock(&gZoneMetaLock); |
|
367 } |
|
368 |
|
369 return canonicalID; |
|
370 } |
|
371 |
|
372 UnicodeString& U_EXPORT2 |
|
373 ZoneMeta::getCanonicalCLDRID(const UnicodeString &tzid, UnicodeString &systemID, UErrorCode& status) { |
|
374 const UChar *canonicalID = getCanonicalCLDRID(tzid, status); |
|
375 if (U_FAILURE(status) || canonicalID == NULL) { |
|
376 systemID.setToBogus(); |
|
377 return systemID; |
|
378 } |
|
379 systemID.setTo(TRUE, canonicalID, -1); |
|
380 return systemID; |
|
381 } |
|
382 |
|
383 const UChar* U_EXPORT2 |
|
384 ZoneMeta::getCanonicalCLDRID(const TimeZone& tz) { |
|
385 if (dynamic_cast<const OlsonTimeZone *>(&tz) != NULL) { |
|
386 // short cut for OlsonTimeZone |
|
387 const OlsonTimeZone *otz = (const OlsonTimeZone*)&tz; |
|
388 return otz->getCanonicalID(); |
|
389 } |
|
390 UErrorCode status = U_ZERO_ERROR; |
|
391 UnicodeString tzID; |
|
392 return getCanonicalCLDRID(tz.getID(tzID), status); |
|
393 } |
|
394 |
|
395 static void U_CALLCONV countryInfoVectorsInit(UErrorCode &status) { |
|
396 // Create empty vectors |
|
397 // No deleters for these UVectors, it's a reference to a resource bundle string. |
|
398 gSingleZoneCountries = new UVector(NULL, uhash_compareUChars, status); |
|
399 if (gSingleZoneCountries == NULL) { |
|
400 status = U_MEMORY_ALLOCATION_ERROR; |
|
401 } |
|
402 gMultiZonesCountries = new UVector(NULL, uhash_compareUChars, status); |
|
403 if (gMultiZonesCountries == NULL) { |
|
404 status = U_MEMORY_ALLOCATION_ERROR; |
|
405 } |
|
406 |
|
407 if (U_FAILURE(status)) { |
|
408 delete gSingleZoneCountries; |
|
409 delete gMultiZonesCountries; |
|
410 gSingleZoneCountries = NULL; |
|
411 gMultiZonesCountries = NULL; |
|
412 } |
|
413 ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); |
|
414 } |
|
415 |
|
416 |
|
417 UnicodeString& U_EXPORT2 |
|
418 ZoneMeta::getCanonicalCountry(const UnicodeString &tzid, UnicodeString &country, UBool *isPrimary /* = NULL */) { |
|
419 if (isPrimary != NULL) { |
|
420 *isPrimary = FALSE; |
|
421 } |
|
422 |
|
423 const UChar *region = TimeZone::getRegion(tzid); |
|
424 if (region != NULL && u_strcmp(gWorld, region) != 0) { |
|
425 country.setTo(region, -1); |
|
426 } else { |
|
427 country.setToBogus(); |
|
428 return country; |
|
429 } |
|
430 |
|
431 if (isPrimary != NULL) { |
|
432 char regionBuf[] = {0, 0, 0}; |
|
433 |
|
434 // Checking the cached results |
|
435 UErrorCode status = U_ZERO_ERROR; |
|
436 umtx_initOnce(gCountryInfoVectorsInitOnce, &countryInfoVectorsInit, status); |
|
437 if (U_FAILURE(status)) { |
|
438 return country; |
|
439 } |
|
440 |
|
441 // Check if it was already cached |
|
442 UBool cached = FALSE; |
|
443 UBool singleZone = FALSE; |
|
444 umtx_lock(&gZoneMetaLock); |
|
445 { |
|
446 singleZone = cached = gSingleZoneCountries->contains((void*)region); |
|
447 if (!cached) { |
|
448 cached = gMultiZonesCountries->contains((void*)region); |
|
449 } |
|
450 } |
|
451 umtx_unlock(&gZoneMetaLock); |
|
452 |
|
453 if (!cached) { |
|
454 // We need to go through all zones associated with the region. |
|
455 // This is relatively heavy operation. |
|
456 |
|
457 U_ASSERT(u_strlen(region) == 2); |
|
458 |
|
459 u_UCharsToChars(region, regionBuf, 2); |
|
460 |
|
461 StringEnumeration *ids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL_LOCATION, regionBuf, NULL, status); |
|
462 int32_t idsLen = ids->count(status); |
|
463 if (U_SUCCESS(status) && idsLen == 1) { |
|
464 // only the single zone is available for the region |
|
465 singleZone = TRUE; |
|
466 } |
|
467 delete ids; |
|
468 |
|
469 // Cache the result |
|
470 umtx_lock(&gZoneMetaLock); |
|
471 { |
|
472 UErrorCode ec = U_ZERO_ERROR; |
|
473 if (singleZone) { |
|
474 if (!gSingleZoneCountries->contains((void*)region)) { |
|
475 gSingleZoneCountries->addElement((void*)region, ec); |
|
476 } |
|
477 } else { |
|
478 if (!gMultiZonesCountries->contains((void*)region)) { |
|
479 gMultiZonesCountries->addElement((void*)region, ec); |
|
480 } |
|
481 } |
|
482 } |
|
483 umtx_unlock(&gZoneMetaLock); |
|
484 } |
|
485 |
|
486 if (singleZone) { |
|
487 *isPrimary = TRUE; |
|
488 } else { |
|
489 // Note: We may cache the primary zone map in future. |
|
490 |
|
491 // Even a country has multiple zones, one of them might be |
|
492 // dominant and treated as a primary zone |
|
493 int32_t idLen = 0; |
|
494 if (regionBuf[0] == 0) { |
|
495 u_UCharsToChars(region, regionBuf, 2); |
|
496 } |
|
497 |
|
498 UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status); |
|
499 ures_getByKey(rb, gPrimaryZonesTag, rb, &status); |
|
500 const UChar *primaryZone = ures_getStringByKey(rb, regionBuf, &idLen, &status); |
|
501 if (U_SUCCESS(status)) { |
|
502 if (tzid.compare(primaryZone, idLen) == 0) { |
|
503 *isPrimary = TRUE; |
|
504 } else { |
|
505 // The given ID might not be a canonical ID |
|
506 UnicodeString canonicalID; |
|
507 TimeZone::getCanonicalID(tzid, canonicalID, status); |
|
508 if (U_SUCCESS(status) && canonicalID.compare(primaryZone, idLen) == 0) { |
|
509 *isPrimary = TRUE; |
|
510 } |
|
511 } |
|
512 } |
|
513 ures_close(rb); |
|
514 } |
|
515 } |
|
516 |
|
517 return country; |
|
518 } |
|
519 |
|
520 UnicodeString& U_EXPORT2 |
|
521 ZoneMeta::getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &result) { |
|
522 UBool isSet = FALSE; |
|
523 const UVector *mappings = getMetazoneMappings(tzid); |
|
524 if (mappings != NULL) { |
|
525 for (int32_t i = 0; i < mappings->size(); i++) { |
|
526 OlsonToMetaMappingEntry *mzm = (OlsonToMetaMappingEntry*)mappings->elementAt(i); |
|
527 if (mzm->from <= date && mzm->to > date) { |
|
528 result.setTo(mzm->mzid, -1); |
|
529 isSet = TRUE; |
|
530 break; |
|
531 } |
|
532 } |
|
533 } |
|
534 if (!isSet) { |
|
535 result.setToBogus(); |
|
536 } |
|
537 return result; |
|
538 } |
|
539 |
|
540 static void U_CALLCONV olsonToMetaInit(UErrorCode &status) { |
|
541 U_ASSERT(gOlsonToMeta == NULL); |
|
542 ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); |
|
543 gOlsonToMeta = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); |
|
544 if (U_FAILURE(status)) { |
|
545 gOlsonToMeta = NULL; |
|
546 } else { |
|
547 uhash_setKeyDeleter(gOlsonToMeta, deleteUCharString); |
|
548 uhash_setValueDeleter(gOlsonToMeta, deleteUVector); |
|
549 } |
|
550 } |
|
551 |
|
552 |
|
553 const UVector* U_EXPORT2 |
|
554 ZoneMeta::getMetazoneMappings(const UnicodeString &tzid) { |
|
555 UErrorCode status = U_ZERO_ERROR; |
|
556 UChar tzidUChars[ZID_KEY_MAX + 1]; |
|
557 tzid.extract(tzidUChars, ZID_KEY_MAX + 1, status); |
|
558 if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) { |
|
559 return NULL; |
|
560 } |
|
561 |
|
562 umtx_initOnce(gOlsonToMetaInitOnce, &olsonToMetaInit, status); |
|
563 if (U_FAILURE(status)) { |
|
564 return NULL; |
|
565 } |
|
566 |
|
567 // get the mapping from cache |
|
568 const UVector *result = NULL; |
|
569 |
|
570 umtx_lock(&gZoneMetaLock); |
|
571 { |
|
572 result = (UVector*) uhash_get(gOlsonToMeta, tzidUChars); |
|
573 } |
|
574 umtx_unlock(&gZoneMetaLock); |
|
575 |
|
576 if (result != NULL) { |
|
577 return result; |
|
578 } |
|
579 |
|
580 // miss the cache - create new one |
|
581 UVector *tmpResult = createMetazoneMappings(tzid); |
|
582 if (tmpResult == NULL) { |
|
583 // not available |
|
584 return NULL; |
|
585 } |
|
586 |
|
587 // put the new one into the cache |
|
588 umtx_lock(&gZoneMetaLock); |
|
589 { |
|
590 // make sure it's already created |
|
591 result = (UVector*) uhash_get(gOlsonToMeta, tzidUChars); |
|
592 if (result == NULL) { |
|
593 // add the one just created |
|
594 int32_t tzidLen = tzid.length() + 1; |
|
595 UChar *key = (UChar*)uprv_malloc(tzidLen * sizeof(UChar)); |
|
596 if (key == NULL) { |
|
597 // memory allocation error.. just return NULL |
|
598 result = NULL; |
|
599 delete tmpResult; |
|
600 } else { |
|
601 tzid.extract(key, tzidLen, status); |
|
602 uhash_put(gOlsonToMeta, key, tmpResult, &status); |
|
603 if (U_FAILURE(status)) { |
|
604 // delete the mapping |
|
605 result = NULL; |
|
606 delete tmpResult; |
|
607 } else { |
|
608 result = tmpResult; |
|
609 } |
|
610 } |
|
611 } else { |
|
612 // another thread already put the one |
|
613 delete tmpResult; |
|
614 } |
|
615 } |
|
616 umtx_unlock(&gZoneMetaLock); |
|
617 |
|
618 return result; |
|
619 } |
|
620 |
|
621 UVector* |
|
622 ZoneMeta::createMetazoneMappings(const UnicodeString &tzid) { |
|
623 UVector *mzMappings = NULL; |
|
624 UErrorCode status = U_ZERO_ERROR; |
|
625 |
|
626 UnicodeString canonicalID; |
|
627 UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status); |
|
628 ures_getByKey(rb, gMetazoneInfo, rb, &status); |
|
629 getCanonicalCLDRID(tzid, canonicalID, status); |
|
630 |
|
631 if (U_SUCCESS(status)) { |
|
632 char tzKey[ZID_KEY_MAX + 1]; |
|
633 int32_t tzKeyLen = canonicalID.extract(0, canonicalID.length(), tzKey, sizeof(tzKey), US_INV); |
|
634 tzKey[tzKeyLen] = 0; |
|
635 |
|
636 // tzid keys are using ':' as separators |
|
637 char *p = tzKey; |
|
638 while (*p) { |
|
639 if (*p == '/') { |
|
640 *p = ':'; |
|
641 } |
|
642 p++; |
|
643 } |
|
644 |
|
645 ures_getByKey(rb, tzKey, rb, &status); |
|
646 |
|
647 if (U_SUCCESS(status)) { |
|
648 UResourceBundle *mz = NULL; |
|
649 while (ures_hasNext(rb)) { |
|
650 mz = ures_getNextResource(rb, mz, &status); |
|
651 |
|
652 const UChar *mz_name = ures_getStringByIndex(mz, 0, NULL, &status); |
|
653 const UChar *mz_from = gDefaultFrom; |
|
654 const UChar *mz_to = gDefaultTo; |
|
655 |
|
656 if (ures_getSize(mz) == 3) { |
|
657 mz_from = ures_getStringByIndex(mz, 1, NULL, &status); |
|
658 mz_to = ures_getStringByIndex(mz, 2, NULL, &status); |
|
659 } |
|
660 |
|
661 if(U_FAILURE(status)){ |
|
662 status = U_ZERO_ERROR; |
|
663 continue; |
|
664 } |
|
665 // We do not want to use SimpleDateformat to parse boundary dates, |
|
666 // because this code could be triggered by the initialization code |
|
667 // used by SimpleDateFormat. |
|
668 UDate from = parseDate(mz_from, status); |
|
669 UDate to = parseDate(mz_to, status); |
|
670 if (U_FAILURE(status)) { |
|
671 status = U_ZERO_ERROR; |
|
672 continue; |
|
673 } |
|
674 |
|
675 OlsonToMetaMappingEntry *entry = (OlsonToMetaMappingEntry*)uprv_malloc(sizeof(OlsonToMetaMappingEntry)); |
|
676 if (entry == NULL) { |
|
677 status = U_MEMORY_ALLOCATION_ERROR; |
|
678 break; |
|
679 } |
|
680 entry->mzid = mz_name; |
|
681 entry->from = from; |
|
682 entry->to = to; |
|
683 |
|
684 if (mzMappings == NULL) { |
|
685 mzMappings = new UVector(deleteOlsonToMetaMappingEntry, NULL, status); |
|
686 if (U_FAILURE(status)) { |
|
687 delete mzMappings; |
|
688 deleteOlsonToMetaMappingEntry(entry); |
|
689 uprv_free(entry); |
|
690 break; |
|
691 } |
|
692 } |
|
693 |
|
694 mzMappings->addElement(entry, status); |
|
695 if (U_FAILURE(status)) { |
|
696 break; |
|
697 } |
|
698 } |
|
699 ures_close(mz); |
|
700 if (U_FAILURE(status)) { |
|
701 if (mzMappings != NULL) { |
|
702 delete mzMappings; |
|
703 mzMappings = NULL; |
|
704 } |
|
705 } |
|
706 } |
|
707 } |
|
708 ures_close(rb); |
|
709 return mzMappings; |
|
710 } |
|
711 |
|
712 UnicodeString& U_EXPORT2 |
|
713 ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString ®ion, UnicodeString &result) { |
|
714 UErrorCode status = U_ZERO_ERROR; |
|
715 const UChar *tzid = NULL; |
|
716 int32_t tzidLen = 0; |
|
717 char keyBuf[ZID_KEY_MAX + 1]; |
|
718 int32_t keyLen = 0; |
|
719 |
|
720 if (mzid.length() > ZID_KEY_MAX) { |
|
721 result.setToBogus(); |
|
722 return result; |
|
723 } |
|
724 |
|
725 keyLen = mzid.extract(0, mzid.length(), keyBuf, ZID_KEY_MAX + 1, US_INV); |
|
726 keyBuf[keyLen] = 0; |
|
727 |
|
728 UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status); |
|
729 ures_getByKey(rb, gMapTimezonesTag, rb, &status); |
|
730 ures_getByKey(rb, keyBuf, rb, &status); |
|
731 |
|
732 if (U_SUCCESS(status)) { |
|
733 // check region mapping |
|
734 if (region.length() == 2 || region.length() == 3) { |
|
735 keyLen = region.extract(0, region.length(), keyBuf, ZID_KEY_MAX + 1, US_INV); |
|
736 keyBuf[keyLen] = 0; |
|
737 tzid = ures_getStringByKey(rb, keyBuf, &tzidLen, &status); |
|
738 if (status == U_MISSING_RESOURCE_ERROR) { |
|
739 status = U_ZERO_ERROR; |
|
740 } |
|
741 } |
|
742 if (U_SUCCESS(status) && tzid == NULL) { |
|
743 // try "001" |
|
744 tzid = ures_getStringByKey(rb, gWorldTag, &tzidLen, &status); |
|
745 } |
|
746 } |
|
747 ures_close(rb); |
|
748 |
|
749 if (tzid == NULL) { |
|
750 result.setToBogus(); |
|
751 } else { |
|
752 result.setTo(tzid, tzidLen); |
|
753 } |
|
754 |
|
755 return result; |
|
756 } |
|
757 |
|
758 static void U_CALLCONV initAvailableMetaZoneIDs () { |
|
759 U_ASSERT(gMetaZoneIDs == NULL); |
|
760 U_ASSERT(gMetaZoneIDTable == NULL); |
|
761 ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); |
|
762 |
|
763 UErrorCode status = U_ZERO_ERROR; |
|
764 gMetaZoneIDTable = uhash_open(uhash_hashUnicodeString, uhash_compareUnicodeString, NULL, &status); |
|
765 if (U_FAILURE(status) || gMetaZoneIDTable == NULL) { |
|
766 gMetaZoneIDTable = NULL; |
|
767 return; |
|
768 } |
|
769 uhash_setKeyDeleter(gMetaZoneIDTable, uprv_deleteUObject); |
|
770 // No valueDeleter, because the vector maintain the value objects |
|
771 gMetaZoneIDs = new UVector(NULL, uhash_compareUChars, status); |
|
772 if (U_FAILURE(status) || gMetaZoneIDs == NULL) { |
|
773 gMetaZoneIDs = NULL; |
|
774 uhash_close(gMetaZoneIDTable); |
|
775 gMetaZoneIDTable = NULL; |
|
776 return; |
|
777 } |
|
778 gMetaZoneIDs->setDeleter(uprv_free); |
|
779 |
|
780 UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status); |
|
781 UResourceBundle *bundle = ures_getByKey(rb, gMapTimezonesTag, NULL, &status); |
|
782 UResourceBundle res; |
|
783 ures_initStackObject(&res); |
|
784 while (U_SUCCESS(status) && ures_hasNext(bundle)) { |
|
785 ures_getNextResource(bundle, &res, &status); |
|
786 if (U_FAILURE(status)) { |
|
787 break; |
|
788 } |
|
789 const char *mzID = ures_getKey(&res); |
|
790 int32_t len = uprv_strlen(mzID); |
|
791 UChar *uMzID = (UChar*)uprv_malloc(sizeof(UChar) * (len + 1)); |
|
792 if (uMzID == NULL) { |
|
793 status = U_MEMORY_ALLOCATION_ERROR; |
|
794 break; |
|
795 } |
|
796 u_charsToUChars(mzID, uMzID, len); |
|
797 uMzID[len] = 0; |
|
798 UnicodeString *usMzID = new UnicodeString(uMzID); |
|
799 if (uhash_get(gMetaZoneIDTable, usMzID) == NULL) { |
|
800 gMetaZoneIDs->addElement((void *)uMzID, status); |
|
801 uhash_put(gMetaZoneIDTable, (void *)usMzID, (void *)uMzID, &status); |
|
802 } else { |
|
803 uprv_free(uMzID); |
|
804 delete usMzID; |
|
805 } |
|
806 } |
|
807 ures_close(&res); |
|
808 ures_close(bundle); |
|
809 ures_close(rb); |
|
810 |
|
811 if (U_FAILURE(status)) { |
|
812 uhash_close(gMetaZoneIDTable); |
|
813 delete gMetaZoneIDs; |
|
814 gMetaZoneIDTable = NULL; |
|
815 gMetaZoneIDs = NULL; |
|
816 } |
|
817 } |
|
818 |
|
819 const UVector* |
|
820 ZoneMeta::getAvailableMetazoneIDs() { |
|
821 umtx_initOnce(gMetaZoneIDsInitOnce, &initAvailableMetaZoneIDs); |
|
822 return gMetaZoneIDs; |
|
823 } |
|
824 |
|
825 const UChar* |
|
826 ZoneMeta::findMetaZoneID(const UnicodeString& mzid) { |
|
827 umtx_initOnce(gMetaZoneIDsInitOnce, &initAvailableMetaZoneIDs); |
|
828 if (gMetaZoneIDTable == NULL) { |
|
829 return NULL; |
|
830 } |
|
831 return (const UChar*)uhash_get(gMetaZoneIDTable, &mzid); |
|
832 } |
|
833 |
|
834 const UChar* |
|
835 ZoneMeta::findTimeZoneID(const UnicodeString& tzid) { |
|
836 return TimeZone::findID(tzid); |
|
837 } |
|
838 |
|
839 |
|
840 TimeZone* |
|
841 ZoneMeta::createCustomTimeZone(int32_t offset) { |
|
842 UBool negative = FALSE; |
|
843 int32_t tmp = offset; |
|
844 if (offset < 0) { |
|
845 negative = TRUE; |
|
846 tmp = -offset; |
|
847 } |
|
848 int32_t hour, min, sec; |
|
849 |
|
850 tmp /= 1000; |
|
851 sec = tmp % 60; |
|
852 tmp /= 60; |
|
853 min = tmp % 60; |
|
854 hour = tmp / 60; |
|
855 |
|
856 UnicodeString zid; |
|
857 formatCustomID(hour, min, sec, negative, zid); |
|
858 return new SimpleTimeZone(offset, zid); |
|
859 } |
|
860 |
|
861 UnicodeString& |
|
862 ZoneMeta::formatCustomID(uint8_t hour, uint8_t min, uint8_t sec, UBool negative, UnicodeString& id) { |
|
863 // Create normalized time zone ID - GMT[+|-]HH:mm[:ss] |
|
864 id.setTo(gCustomTzPrefix, -1); |
|
865 if (hour != 0 || min != 0) { |
|
866 if (negative) { |
|
867 id.append((UChar)0x2D); // '-' |
|
868 } else { |
|
869 id.append((UChar)0x2B); // '+' |
|
870 } |
|
871 // Always use US-ASCII digits |
|
872 id.append((UChar)(0x30 + (hour%100)/10)); |
|
873 id.append((UChar)(0x30 + (hour%10))); |
|
874 id.append((UChar)0x3A); // ':' |
|
875 id.append((UChar)(0x30 + (min%100)/10)); |
|
876 id.append((UChar)(0x30 + (min%10))); |
|
877 if (sec != 0) { |
|
878 id.append((UChar)0x3A); // ':' |
|
879 id.append((UChar)(0x30 + (sec%100)/10)); |
|
880 id.append((UChar)(0x30 + (sec%10))); |
|
881 } |
|
882 } |
|
883 return id; |
|
884 } |
|
885 |
|
886 const UChar* |
|
887 ZoneMeta::getShortID(const TimeZone& tz) { |
|
888 const UChar* canonicalID = NULL; |
|
889 if (dynamic_cast<const OlsonTimeZone *>(&tz) != NULL) { |
|
890 // short cut for OlsonTimeZone |
|
891 const OlsonTimeZone *otz = (const OlsonTimeZone*)&tz; |
|
892 canonicalID = otz->getCanonicalID(); |
|
893 } |
|
894 if (canonicalID == NULL) { |
|
895 return NULL; |
|
896 } |
|
897 return getShortIDFromCanonical(canonicalID); |
|
898 } |
|
899 |
|
900 const UChar* |
|
901 ZoneMeta::getShortID(const UnicodeString& id) { |
|
902 UErrorCode status = U_ZERO_ERROR; |
|
903 const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(id, status); |
|
904 if (U_FAILURE(status) || canonicalID == NULL) { |
|
905 return NULL; |
|
906 } |
|
907 return ZoneMeta::getShortIDFromCanonical(canonicalID); |
|
908 } |
|
909 |
|
910 const UChar* |
|
911 ZoneMeta::getShortIDFromCanonical(const UChar* canonicalID) { |
|
912 const UChar* shortID = NULL; |
|
913 int32_t len = u_strlen(canonicalID); |
|
914 char tzidKey[ZID_KEY_MAX + 1]; |
|
915 |
|
916 u_UCharsToChars(canonicalID, tzidKey, len); |
|
917 tzidKey[len] = (char) 0; // Make sure it is null terminated. |
|
918 |
|
919 // replace '/' with ':' |
|
920 char *p = tzidKey; |
|
921 while (*p++) { |
|
922 if (*p == '/') { |
|
923 *p = ':'; |
|
924 } |
|
925 } |
|
926 |
|
927 UErrorCode status = U_ZERO_ERROR; |
|
928 UResourceBundle *rb = ures_openDirect(NULL, gKeyTypeData, &status); |
|
929 ures_getByKey(rb, gTypeMapTag, rb, &status); |
|
930 ures_getByKey(rb, gTimezoneTag, rb, &status); |
|
931 shortID = ures_getStringByKey(rb, tzidKey, NULL, &status); |
|
932 ures_close(rb); |
|
933 |
|
934 return shortID; |
|
935 } |
|
936 |
|
937 U_NAMESPACE_END |
|
938 |
|
939 #endif /* #if !UCONFIG_NO_FORMATTING */ |