]>
Commit | Line | Data |
---|---|---|
46f4442e A |
1 | /* |
2 | ******************************************************************************* | |
3 | * Copyright (C) 2007-2009, 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 | ||
18 | #include "umutex.h" | |
19 | #include "uvector.h" | |
20 | #include "cmemory.h" | |
21 | #include "gregoimp.h" | |
22 | #include "cstring.h" | |
23 | #include "ucln_in.h" | |
24 | ||
25 | // Metazone mapping tables | |
26 | static UMTX gZoneMetaLock = NULL; | |
27 | static UHashtable *gCanonicalMap = NULL; | |
28 | static UHashtable *gOlsonToMeta = NULL; | |
29 | static UHashtable *gMetaToOlson = NULL; | |
30 | static UBool gCanonicalMapInitialized = FALSE; | |
31 | static UBool gOlsonToMetaInitialized = FALSE; | |
32 | static UBool gMetaToOlsonInitialized = FALSE; | |
33 | static UChar **gUStringTable = NULL; | |
34 | static int32_t gUStringCount = 0; | |
35 | static int32_t gUStringAlloc = 0; | |
36 | ||
37 | // Currently (ICU 4.1.3+), gUStringTable only contains strings allocated in the section of | |
38 | // createCanonicalMap that iterates over the enumerator created with TimeZone::createEnumeration. | |
39 | // And currently, that allocates a total of 22 strings. So USTRING_ALLOC_START is defined to | |
40 | // be adequate for that set, and USTRING_ALLOC_INCR is a reasonable expansion increment. In | |
41 | // future versions of ICU, these numbers may need adjusting to avoid excessive reallocs, or to | |
42 | // avoid allocating unused memory (but in any case the effects are small). | |
43 | #define USTRING_ALLOC_START 24 | |
44 | #define USTRING_ALLOC_INCR 12 | |
45 | ||
46 | U_CDECL_BEGIN | |
47 | ||
48 | // We have switched CanonicalMap to use const UChar* strings for the key and for the id field of | |
49 | // CanonicalMapEntry; that is because for the most part these now point into UChar strings in the | |
50 | // shared data file, in order to reduce process-specific dynamically-allocated memory. Consequently, | |
51 | // there is no longer a deleter for the key field, and the deleter for CanonicalMapEntry | |
52 | // no longer frees the id field. However, for the few strings that are obtained from the | |
53 | // TimeZone::createEnumeration() enumerator or from TimeZone::dereferOlsonLink instead of the | |
54 | // data file, we do need to allocate copies. In order to ensure that these strings are freed by | |
55 | // zoneMeta_cleanup(), we need to create a little memory manager for them; this is in the form of | |
56 | // a table that tracks the strings allocated for this purpose. The following three functions | |
57 | // (along with the gUStringXxxxx statics) are used to allocate and free such strings. | |
58 | ||
59 | // The following allocs space for a UChar* string of the specified length, puts a pointer to the string | |
60 | // in gUStringTable, and returns either a pointer to the allocated string space, or NULL for failure. | |
61 | static UChar * allocUStringInTable(int32_t uStringLen) { | |
62 | UChar * uStringSpace = NULL; | |
63 | // initialize the table if necessary | |
64 | umtx_lock(&gZoneMetaLock); | |
65 | if (gUStringTable == NULL) { | |
66 | gUStringTable = (UChar**)uprv_malloc(USTRING_ALLOC_START*sizeof(UChar*)); | |
67 | if (gUStringTable != NULL) { | |
68 | gUStringAlloc = USTRING_ALLOC_START; | |
69 | } | |
70 | } | |
71 | if (gUStringTable != NULL) { | |
72 | // expand the table if necessary | |
73 | if (gUStringCount == gUStringAlloc) { | |
74 | UChar ** newTable = (UChar**)uprv_realloc(gUStringTable, (gUStringAlloc+USTRING_ALLOC_INCR)*sizeof(UChar*)); | |
75 | if (newTable != NULL) { | |
76 | gUStringTable = newTable; | |
77 | gUStringAlloc += USTRING_ALLOC_INCR; | |
78 | } | |
79 | } | |
80 | // add the string if possible | |
81 | if (gUStringCount < gUStringAlloc) { | |
82 | uStringSpace = (UChar*)uprv_malloc(uStringLen*sizeof(UChar)); | |
83 | if (uStringSpace != NULL) { | |
84 | gUStringTable[gUStringCount++] = uStringSpace; | |
85 | } | |
86 | } | |
87 | } | |
88 | umtx_unlock(&gZoneMetaLock); | |
89 | return uStringSpace; | |
90 | } | |
91 | ||
92 | static void removeLastUStringFromTable(void) { | |
93 | umtx_lock(&gZoneMetaLock); | |
94 | if (gUStringCount > 0) { | |
95 | free(gUStringTable[--gUStringCount]); | |
96 | } | |
97 | umtx_unlock(&gZoneMetaLock); | |
98 | } | |
99 | ||
100 | static void freeUStringTable(void) { | |
101 | int32_t uStringCount = gUStringCount; | |
102 | gUStringCount = 0; | |
103 | gUStringAlloc = 0; | |
104 | if (gUStringTable != NULL) { | |
105 | while (uStringCount > 0) { | |
106 | free(gUStringTable[--uStringCount]); | |
107 | } | |
108 | free(gUStringTable); | |
109 | gUStringTable = NULL; | |
110 | } | |
111 | } | |
112 | ||
113 | /** | |
114 | * Cleanup callback func | |
115 | */ | |
116 | static UBool U_CALLCONV zoneMeta_cleanup(void) | |
117 | { | |
118 | umtx_destroy(&gZoneMetaLock); | |
119 | ||
120 | if (gCanonicalMap != NULL) { | |
121 | uhash_close(gCanonicalMap); | |
122 | gCanonicalMap = NULL; | |
123 | } | |
124 | gCanonicalMapInitialized = FALSE; | |
125 | ||
126 | if (gOlsonToMeta != NULL) { | |
127 | uhash_close(gOlsonToMeta); | |
128 | gOlsonToMeta = NULL; | |
129 | } | |
130 | gOlsonToMetaInitialized = FALSE; | |
131 | ||
132 | if (gMetaToOlson != NULL) { | |
133 | uhash_close(gMetaToOlson); | |
134 | gMetaToOlson = NULL; | |
135 | } | |
136 | gMetaToOlsonInitialized = FALSE; | |
137 | ||
138 | freeUStringTable(); | |
139 | ||
140 | return TRUE; | |
141 | } | |
142 | ||
143 | /** | |
144 | * Deleter for UChar* string | |
145 | */ | |
146 | static void U_CALLCONV | |
147 | deleteUCharString(void *obj) { | |
148 | UChar *entry = (UChar*)obj; | |
149 | uprv_free(entry); | |
150 | } | |
151 | ||
152 | /** | |
153 | * Deleter for UVector | |
154 | */ | |
155 | static void U_CALLCONV | |
156 | deleteUVector(void *obj) { | |
157 | delete (U_NAMESPACE_QUALIFIER UVector*) obj; | |
158 | } | |
159 | ||
160 | /** | |
161 | * Deleter for CanonicalMapEntry | |
162 | */ | |
163 | static void U_CALLCONV | |
164 | deleteCanonicalMapEntry(void *obj) { | |
165 | U_NAMESPACE_QUALIFIER CanonicalMapEntry *entry = (U_NAMESPACE_QUALIFIER CanonicalMapEntry*)obj; | |
166 | uprv_free(entry); | |
167 | } | |
168 | ||
169 | /** | |
170 | * Deleter for OlsonToMetaMappingEntry | |
171 | */ | |
172 | static void U_CALLCONV | |
173 | deleteOlsonToMetaMappingEntry(void *obj) { | |
174 | U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry *entry = (U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry*)obj; | |
175 | uprv_free(entry); | |
176 | } | |
177 | ||
178 | /** | |
179 | * Deleter for MetaToOlsonMappingEntry | |
180 | */ | |
181 | static void U_CALLCONV | |
182 | deleteMetaToOlsonMappingEntry(void *obj) { | |
183 | U_NAMESPACE_QUALIFIER MetaToOlsonMappingEntry *entry = (U_NAMESPACE_QUALIFIER MetaToOlsonMappingEntry*)obj; | |
184 | uprv_free(entry->territory); | |
185 | uprv_free(entry); | |
186 | } | |
187 | U_CDECL_END | |
188 | ||
189 | U_NAMESPACE_BEGIN | |
190 | ||
191 | #define ZID_KEY_MAX 128 | |
192 | static const char gZoneStringsTag[] = "zoneStrings"; | |
193 | static const char gUseMetazoneTag[] = "um"; | |
194 | ||
195 | static const char gSupplementalData[] = "supplementalData"; | |
196 | static const char gMapTimezonesTag[] = "mapTimezones"; | |
197 | static const char gMetazonesTag[] = "metazones"; | |
198 | static const char gZoneFormattingTag[] = "zoneFormatting"; | |
199 | static const char gCanonicalTag[] = "canonical"; | |
200 | static const char gTerritoryTag[] = "territory"; | |
201 | static const char gAliasesTag[] = "aliases"; | |
202 | static const char gMultizoneTag[] = "multizone"; | |
203 | ||
204 | static const char gMetazoneInfo[] = "metazoneInfo"; | |
205 | static const char gMetazoneMappings[] = "metazoneMappings"; | |
206 | ||
207 | #define MZID_PREFIX_LEN 5 | |
208 | static const char gMetazoneIdPrefix[] = "meta:"; | |
209 | ||
210 | static const UChar gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001" | |
211 | ||
212 | #define ASCII_DIGIT(c) (((c)>=0x30 && (c)<=0x39) ? (c)-0x30 : -1) | |
213 | ||
214 | /* | |
215 | * Convert a date string used by metazone mappings to UDate. | |
216 | * The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm". | |
217 | */ | |
218 | static UDate | |
219 | parseDate (const UChar *text, UErrorCode &status) { | |
220 | if (U_FAILURE(status)) { | |
221 | return 0; | |
222 | } | |
223 | int32_t len = u_strlen(text); | |
224 | if (len != 16 && len != 10) { | |
225 | // It must be yyyy-MM-dd HH:mm (length 16) or yyyy-MM-dd (length 10) | |
226 | status = U_INVALID_FORMAT_ERROR; | |
227 | return 0; | |
228 | } | |
229 | ||
230 | int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, n; | |
231 | int32_t idx; | |
232 | ||
233 | // "yyyy" (0 - 3) | |
234 | for (idx = 0; idx <= 3 && U_SUCCESS(status); idx++) { | |
235 | n = ASCII_DIGIT((int32_t)text[idx]); | |
236 | if (n >= 0) { | |
237 | year = 10*year + n; | |
238 | } else { | |
239 | status = U_INVALID_FORMAT_ERROR; | |
240 | } | |
241 | } | |
242 | // "MM" (5 - 6) | |
243 | for (idx = 5; idx <= 6 && U_SUCCESS(status); idx++) { | |
244 | n = ASCII_DIGIT((int32_t)text[idx]); | |
245 | if (n >= 0) { | |
246 | month = 10*month + n; | |
247 | } else { | |
248 | status = U_INVALID_FORMAT_ERROR; | |
249 | } | |
250 | } | |
251 | // "dd" (8 - 9) | |
252 | for (idx = 8; idx <= 9 && U_SUCCESS(status); idx++) { | |
253 | n = ASCII_DIGIT((int32_t)text[idx]); | |
254 | if (n >= 0) { | |
255 | day = 10*day + n; | |
256 | } else { | |
257 | status = U_INVALID_FORMAT_ERROR; | |
258 | } | |
259 | } | |
260 | if (len == 16) { | |
261 | // "HH" (11 - 12) | |
262 | for (idx = 11; idx <= 12 && U_SUCCESS(status); idx++) { | |
263 | n = ASCII_DIGIT((int32_t)text[idx]); | |
264 | if (n >= 0) { | |
265 | hour = 10*hour + n; | |
266 | } else { | |
267 | status = U_INVALID_FORMAT_ERROR; | |
268 | } | |
269 | } | |
270 | // "mm" (14 - 15) | |
271 | for (idx = 14; idx <= 15 && U_SUCCESS(status); idx++) { | |
272 | n = ASCII_DIGIT((int32_t)text[idx]); | |
273 | if (n >= 0) { | |
274 | min = 10*min + n; | |
275 | } else { | |
276 | status = U_INVALID_FORMAT_ERROR; | |
277 | } | |
278 | } | |
279 | } | |
280 | ||
281 | if (U_SUCCESS(status)) { | |
282 | UDate date = Grego::fieldsToDay(year, month - 1, day) * U_MILLIS_PER_DAY | |
283 | + hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE; | |
284 | return date; | |
285 | } | |
286 | return 0; | |
287 | } | |
288 | ||
289 | UHashtable* | |
290 | ZoneMeta::createCanonicalMap(void) { | |
291 | UErrorCode status = U_ZERO_ERROR; | |
292 | ||
293 | UHashtable *canonicalMap = NULL; | |
294 | UResourceBundle *zoneFormatting = NULL; | |
295 | UResourceBundle *tzitem = NULL; | |
296 | UResourceBundle *aliases = NULL; | |
297 | ||
298 | StringEnumeration* tzenum = NULL; | |
299 | int32_t numZones; | |
300 | ||
301 | canonicalMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); | |
302 | if (U_FAILURE(status)) { | |
303 | return NULL; | |
304 | } | |
305 | // no key deleter | |
306 | uhash_setValueDeleter(canonicalMap, deleteCanonicalMapEntry); | |
307 | ||
308 | zoneFormatting = ures_openDirect(NULL, gSupplementalData, &status); | |
309 | zoneFormatting = ures_getByKey(zoneFormatting, gZoneFormattingTag, zoneFormatting, &status); | |
310 | if (U_FAILURE(status)) { | |
311 | goto error_cleanup; | |
312 | } | |
313 | ||
314 | while (ures_hasNext(zoneFormatting)) { | |
315 | tzitem = ures_getNextResource(zoneFormatting, tzitem, &status); | |
316 | if (U_FAILURE(status)) { | |
317 | status = U_ZERO_ERROR; | |
318 | continue; | |
319 | } | |
320 | if (ures_getType(tzitem) != URES_TABLE) { | |
321 | continue; | |
322 | } | |
323 | ||
324 | int32_t canonicalLen; | |
325 | const UChar *canonical = ures_getStringByKey(tzitem, gCanonicalTag, &canonicalLen, &status); | |
326 | if (U_FAILURE(status)) { | |
327 | status = U_ZERO_ERROR; | |
328 | continue; | |
329 | } | |
330 | ||
331 | int32_t territoryLen; | |
332 | const UChar *territory = ures_getStringByKey(tzitem, gTerritoryTag, &territoryLen, &status); | |
333 | if (U_FAILURE(status)) { | |
334 | territory = NULL; | |
335 | status = U_ZERO_ERROR; | |
336 | } | |
337 | ||
338 | // Create canonical map entry | |
339 | CanonicalMapEntry *entry = (CanonicalMapEntry*)uprv_malloc(sizeof(CanonicalMapEntry)); | |
340 | if (entry == NULL) { | |
341 | status = U_MEMORY_ALLOCATION_ERROR; | |
342 | goto error_cleanup; | |
343 | } | |
344 | entry->id = canonical; | |
345 | if (territory == NULL || u_strcmp(territory, gWorld) == 0) { | |
346 | entry->country = NULL; | |
347 | } else { | |
348 | entry->country = territory; | |
349 | } | |
350 | ||
351 | // Put this entry in the hashtable. Since this hashtable has no key deleter, | |
352 | // key is treated as const, but must be passed as non-const. | |
353 | uhash_put(canonicalMap, (UChar*)canonical, entry, &status); | |
354 | if (U_FAILURE(status)) { | |
355 | goto error_cleanup; | |
356 | } | |
357 | ||
358 | // Get aliases | |
359 | aliases = ures_getByKey(tzitem, gAliasesTag, aliases, &status); | |
360 | if (U_FAILURE(status)) { | |
361 | // No aliases | |
362 | status = U_ZERO_ERROR; | |
363 | continue; | |
364 | } | |
365 | ||
366 | while (ures_hasNext(aliases)) { | |
367 | const UChar* alias = ures_getNextString(aliases, NULL, NULL, &status); | |
368 | if (U_FAILURE(status)) { | |
369 | status = U_ZERO_ERROR; | |
370 | continue; | |
371 | } | |
372 | // Create canonical map entry for this alias | |
373 | entry = (CanonicalMapEntry*)uprv_malloc(sizeof(CanonicalMapEntry)); | |
374 | if (entry == NULL) { | |
375 | status = U_MEMORY_ALLOCATION_ERROR; | |
376 | goto error_cleanup; | |
377 | } | |
378 | entry->id = canonical; | |
379 | if (territory == NULL || u_strcmp(territory, gWorld) == 0) { | |
380 | entry->country = NULL; | |
381 | } else { | |
382 | entry->country = territory; | |
383 | } | |
384 | ||
385 | // Put this entry in the hashtable. Since this hashtable has no key deleter, | |
386 | // key is treated as const, but must be passed as non-const. | |
387 | uhash_put(canonicalMap, (UChar*)alias, entry, &status); | |
388 | if (U_FAILURE(status)) { | |
389 | goto error_cleanup; | |
390 | } | |
391 | } | |
392 | } | |
393 | ||
394 | // Some available Olson zones are not included in CLDR data (such as Asia/Riyadh87). | |
395 | // Also, when we update Olson tzdata, new zones may be added. | |
396 | // This code scans all available zones in zoneinfo.res, and if any of them are | |
397 | // missing, add them to the map. | |
398 | tzenum = TimeZone::createEnumeration(); | |
399 | numZones = tzenum->count(status); | |
400 | if (U_SUCCESS(status)) { | |
401 | int32_t i; | |
402 | for (i = 0; i < numZones; i++) { | |
403 | const UnicodeString *zone = tzenum->snext(status); | |
404 | if (U_FAILURE(status)) { | |
405 | // We should not get here. | |
406 | status = U_ZERO_ERROR; | |
407 | continue; | |
408 | } | |
409 | UChar zoneUChars[ZID_KEY_MAX]; | |
410 | int32_t zoneUCharsLen = zone->extract(zoneUChars, ZID_KEY_MAX, status) + 1; // Add one for NUL termination | |
411 | if (U_FAILURE(status) || status==U_STRING_NOT_TERMINATED_WARNING) { | |
412 | status = U_ZERO_ERROR; | |
413 | continue; // zone id is too long to extract | |
414 | } | |
415 | CanonicalMapEntry *entry = (CanonicalMapEntry*)uhash_get(canonicalMap, zoneUChars); | |
416 | if (entry) { | |
417 | // Already included in CLDR data | |
418 | continue; | |
419 | } | |
420 | // Not in CLDR data, but it could be new one whose alias is available | |
421 | // in CLDR. | |
422 | int32_t nTzdataEquivalent = TimeZone::countEquivalentIDs(*zone); | |
423 | int32_t j; | |
424 | for (j = 0; j < nTzdataEquivalent; j++) { | |
425 | UnicodeString alias = TimeZone::getEquivalentID(*zone, j); | |
426 | if (alias == *zone) { | |
427 | continue; | |
428 | } | |
429 | UChar aliasUChars[ZID_KEY_MAX]; | |
430 | alias.extract(aliasUChars, ZID_KEY_MAX, status); | |
431 | if (U_FAILURE(status) || status==U_STRING_NOT_TERMINATED_WARNING) { | |
432 | status = U_ZERO_ERROR; | |
433 | continue; // zone id is too long to extract | |
434 | } | |
435 | entry = (CanonicalMapEntry*)uhash_get(canonicalMap, aliasUChars); | |
436 | if (entry != NULL) { | |
437 | break; | |
438 | } | |
439 | } | |
440 | // Create a new map entry | |
441 | CanonicalMapEntry* newEntry = (CanonicalMapEntry*)uprv_malloc(sizeof(CanonicalMapEntry)); | |
442 | int32_t idLen; | |
443 | if (newEntry == NULL) { | |
444 | status = U_MEMORY_ALLOCATION_ERROR; | |
445 | goto error_cleanup; | |
446 | } | |
447 | if (entry == NULL) { | |
448 | // Set dereferenced zone ID as the canonical ID | |
449 | UnicodeString derefZone; | |
450 | TimeZone::dereferOlsonLink(*zone, derefZone); | |
451 | if (derefZone.length() == 0) { | |
452 | // It should never happen.. but just in case | |
453 | derefZone = *zone; | |
454 | } | |
455 | idLen = derefZone.length() + 1; | |
456 | newEntry->id = allocUStringInTable(idLen); | |
457 | if (newEntry->id == NULL) { | |
458 | status = U_MEMORY_ALLOCATION_ERROR; | |
459 | uprv_free(newEntry); | |
460 | goto error_cleanup; | |
461 | } | |
462 | // Copy NULL terminated string | |
463 | derefZone.extract((UChar*)(newEntry->id), idLen, status); | |
464 | if (U_FAILURE(status)) { | |
465 | removeLastUStringFromTable(); | |
466 | uprv_free(newEntry); | |
467 | goto error_cleanup; | |
468 | } | |
469 | // No territory information available | |
470 | newEntry->country = NULL; | |
471 | } else { | |
472 | // Duplicate the entry | |
473 | newEntry->id = entry->id; | |
474 | newEntry->country = entry->country; | |
475 | } | |
476 | ||
477 | // Put this entry in the hashtable | |
478 | UChar *key = allocUStringInTable(zoneUCharsLen); | |
479 | if (key == NULL) { | |
480 | status = U_MEMORY_ALLOCATION_ERROR; | |
481 | deleteCanonicalMapEntry(newEntry); | |
482 | goto error_cleanup; | |
483 | } | |
484 | u_strncpy(key, zoneUChars, zoneUCharsLen); | |
485 | uhash_put(canonicalMap, key, newEntry, &status); | |
486 | if (U_FAILURE(status)) { | |
487 | goto error_cleanup; | |
488 | } | |
489 | } | |
490 | } | |
491 | ||
492 | normal_cleanup: | |
493 | ures_close(aliases); | |
494 | ures_close(tzitem); | |
495 | ures_close(zoneFormatting); | |
496 | delete tzenum; | |
497 | return canonicalMap; | |
498 | ||
499 | error_cleanup: | |
500 | if (canonicalMap != NULL) { | |
501 | uhash_close(canonicalMap); | |
502 | canonicalMap = NULL; | |
503 | } | |
504 | goto normal_cleanup; | |
505 | } | |
506 | ||
507 | /* | |
508 | * Creating Olson tzid to metazone mappings from resource (3.8.1 and beyond) | |
509 | */ | |
510 | UHashtable* | |
511 | ZoneMeta::createOlsonToMetaMap(void) { | |
512 | UErrorCode status = U_ZERO_ERROR; | |
513 | ||
514 | UHashtable *olsonToMeta = NULL; | |
515 | UResourceBundle *metazoneMappings = NULL; | |
516 | UResourceBundle *zoneItem = NULL; | |
517 | UResourceBundle *mz = NULL; | |
518 | StringEnumeration *tzids = NULL; | |
519 | ||
520 | olsonToMeta = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); | |
521 | if (U_FAILURE(status)) { | |
522 | return NULL; | |
523 | } | |
524 | uhash_setKeyDeleter(olsonToMeta, deleteUCharString); | |
525 | uhash_setValueDeleter(olsonToMeta, deleteUVector); | |
526 | ||
527 | // Read metazone mappings from metazoneInfo bundle | |
528 | metazoneMappings = ures_openDirect(NULL, gMetazoneInfo, &status); | |
529 | metazoneMappings = ures_getByKey(metazoneMappings, gMetazoneMappings, metazoneMappings, &status); | |
530 | if (U_FAILURE(status)) { | |
531 | goto error_cleanup; | |
532 | } | |
533 | ||
534 | // Walk through all canonical tzids | |
535 | char zidkey[ZID_KEY_MAX]; | |
536 | ||
537 | tzids = TimeZone::createEnumeration(); | |
538 | const UnicodeString *tzid; | |
539 | while ((tzid = tzids->snext(status))) { | |
540 | if (U_FAILURE(status)) { | |
541 | goto error_cleanup; | |
542 | } | |
543 | // We may skip aliases, because the bundle | |
544 | // contains only canonical IDs. For now, try | |
545 | // all of them. | |
546 | tzid->extract(0, tzid->length(), zidkey, sizeof(zidkey), US_INV); | |
547 | zidkey[sizeof(zidkey)-1] = 0; // NULL terminate just in case. | |
548 | ||
549 | // Replace '/' with ':' | |
550 | UBool foundSep = FALSE; | |
551 | char *p = zidkey; | |
552 | while (*p) { | |
553 | if (*p == '/') { | |
554 | *p = ':'; | |
555 | foundSep = TRUE; | |
556 | } | |
557 | p++; | |
558 | } | |
559 | if (!foundSep) { | |
560 | // A valid time zone key has at least one separator | |
561 | continue; | |
562 | } | |
563 | ||
564 | zoneItem = ures_getByKey(metazoneMappings, zidkey, zoneItem, &status); | |
565 | if (U_FAILURE(status)) { | |
566 | status = U_ZERO_ERROR; | |
567 | continue; | |
568 | } | |
569 | ||
570 | UVector *mzMappings = NULL; | |
571 | while (ures_hasNext(zoneItem)) { | |
572 | mz = ures_getNextResource(zoneItem, mz, &status); | |
573 | const UChar *mz_name = ures_getStringByIndex(mz, 0, NULL, &status); | |
574 | const UChar *mz_from = ures_getStringByIndex(mz, 1, NULL, &status); | |
575 | const UChar *mz_to = ures_getStringByIndex(mz, 2, NULL, &status); | |
576 | ||
577 | if(U_FAILURE(status)){ | |
578 | status = U_ZERO_ERROR; | |
579 | continue; | |
580 | } | |
581 | // We do not want to use SimpleDateformat to parse boundary dates, | |
582 | // because this code could be triggered by the initialization code | |
583 | // used by SimpleDateFormat. | |
584 | UDate from = parseDate(mz_from, status); | |
585 | UDate to = parseDate(mz_to, status); | |
586 | if (U_FAILURE(status)) { | |
587 | status = U_ZERO_ERROR; | |
588 | continue; | |
589 | } | |
590 | ||
591 | OlsonToMetaMappingEntry *entry = (OlsonToMetaMappingEntry*)uprv_malloc(sizeof(OlsonToMetaMappingEntry)); | |
592 | if (entry == NULL) { | |
593 | status = U_MEMORY_ALLOCATION_ERROR; | |
594 | break; | |
595 | } | |
596 | entry->mzid = mz_name; | |
597 | entry->from = from; | |
598 | entry->to = to; | |
599 | ||
600 | if (mzMappings == NULL) { | |
601 | mzMappings = new UVector(deleteOlsonToMetaMappingEntry, NULL, status); | |
602 | if (U_FAILURE(status)) { | |
603 | delete mzMappings; | |
604 | deleteOlsonToMetaMappingEntry(entry); | |
605 | uprv_free(entry); | |
606 | break; | |
607 | } | |
608 | } | |
609 | ||
610 | mzMappings->addElement(entry, status); | |
611 | if (U_FAILURE(status)) { | |
612 | break; | |
613 | } | |
614 | } | |
615 | ||
616 | if (U_FAILURE(status)) { | |
617 | if (mzMappings != NULL) { | |
618 | delete mzMappings; | |
619 | } | |
620 | goto error_cleanup; | |
621 | } | |
622 | if (mzMappings != NULL) { | |
623 | // Add to hashtable | |
624 | int32_t tzidLen = tzid->length() + 1; // Add one for NUL terminator | |
625 | UChar *key = (UChar*)uprv_malloc(tzidLen * sizeof(UChar)); | |
626 | if (key == NULL) { | |
627 | status = U_MEMORY_ALLOCATION_ERROR; | |
628 | delete mzMappings; | |
629 | goto error_cleanup; | |
630 | } | |
631 | tzid->extract(key, tzidLen, status); | |
632 | uhash_put(olsonToMeta, key, mzMappings, &status); | |
633 | if (U_FAILURE(status)) { | |
634 | goto error_cleanup; | |
635 | } | |
636 | } | |
637 | } | |
638 | ||
639 | normal_cleanup: | |
640 | if (tzids != NULL) { | |
641 | delete tzids; | |
642 | } | |
643 | ures_close(zoneItem); | |
644 | ures_close(mz); | |
645 | ures_close(metazoneMappings); | |
646 | return olsonToMeta; | |
647 | ||
648 | error_cleanup: | |
649 | if (olsonToMeta != NULL) { | |
650 | uhash_close(olsonToMeta); | |
651 | olsonToMeta = NULL; | |
652 | } | |
653 | goto normal_cleanup; | |
654 | } | |
655 | ||
656 | UHashtable* | |
657 | ZoneMeta::createMetaToOlsonMap(void) { | |
658 | UErrorCode status = U_ZERO_ERROR; | |
659 | ||
660 | UHashtable *metaToOlson = NULL; | |
661 | UResourceBundle *metazones = NULL; | |
662 | UResourceBundle *mz = NULL; | |
663 | ||
664 | metaToOlson = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); | |
665 | if (U_FAILURE(status)) { | |
666 | return NULL; | |
667 | } | |
668 | uhash_setKeyDeleter(metaToOlson, deleteUCharString); | |
669 | uhash_setValueDeleter(metaToOlson, deleteUVector); | |
670 | ||
671 | metazones = ures_openDirect(NULL, gSupplementalData, &status); | |
672 | metazones = ures_getByKey(metazones, gMapTimezonesTag, metazones, &status); | |
673 | metazones = ures_getByKey(metazones, gMetazonesTag, metazones, &status); | |
674 | if (U_FAILURE(status)) { | |
675 | goto error_cleanup; | |
676 | } | |
677 | ||
678 | while (ures_hasNext(metazones)) { | |
679 | mz = ures_getNextResource(metazones, mz, &status); | |
680 | if (U_FAILURE(status)) { | |
681 | status = U_ZERO_ERROR; | |
682 | continue; | |
683 | } | |
684 | const char *mzkey = ures_getKey(mz); | |
685 | if (uprv_strncmp(mzkey, gMetazoneIdPrefix, MZID_PREFIX_LEN) == 0) { | |
686 | const char *mzid = mzkey + MZID_PREFIX_LEN; | |
687 | const char *territory = uprv_strrchr(mzid, '_'); | |
688 | int32_t mzidLen = 0; | |
689 | int32_t territoryLen = 0; | |
690 | if (territory) { | |
691 | mzidLen = territory - mzid; | |
692 | territory++; | |
693 | territoryLen = uprv_strlen(territory); | |
694 | } | |
695 | if (mzidLen > 0 && territoryLen > 0) { | |
696 | int32_t tzidLen; | |
697 | const UChar *tzid = ures_getStringByIndex(mz, 0, &tzidLen, &status); | |
698 | if (U_SUCCESS(status)) { | |
699 | // Create MetaToOlsonMappingEntry | |
700 | MetaToOlsonMappingEntry *entry = (MetaToOlsonMappingEntry*)uprv_malloc(sizeof(MetaToOlsonMappingEntry)); | |
701 | if (entry == NULL) { | |
702 | status = U_MEMORY_ALLOCATION_ERROR; | |
703 | goto error_cleanup; | |
704 | } | |
705 | entry->id = tzid; | |
706 | entry->territory = (UChar*)uprv_malloc((territoryLen + 1) * sizeof(UChar)); | |
707 | if (entry->territory == NULL) { | |
708 | status = U_MEMORY_ALLOCATION_ERROR; | |
709 | uprv_free(entry); | |
710 | goto error_cleanup; | |
711 | } | |
712 | u_charsToUChars(territory, entry->territory, territoryLen + 1); | |
713 | ||
714 | // Check if mapping entries for metazone is already available | |
715 | if (mzidLen < ZID_KEY_MAX) { | |
716 | UChar mzidUChars[ZID_KEY_MAX]; | |
717 | u_charsToUChars(mzid, mzidUChars, mzidLen); | |
718 | mzidUChars[mzidLen++] = 0; // Add NUL terminator | |
719 | UVector *tzMappings = (UVector*)uhash_get(metaToOlson, mzidUChars); | |
720 | if (tzMappings == NULL) { | |
721 | // Create new UVector and put it into the hashtable | |
722 | tzMappings = new UVector(deleteMetaToOlsonMappingEntry, NULL, status); | |
723 | if (U_FAILURE(status)) { | |
724 | deleteMetaToOlsonMappingEntry(entry); | |
725 | goto error_cleanup; | |
726 | } | |
727 | UChar *key = (UChar*)uprv_malloc(mzidLen * sizeof(UChar)); | |
728 | if (key == NULL) { | |
729 | status = U_MEMORY_ALLOCATION_ERROR; | |
730 | delete tzMappings; | |
731 | deleteMetaToOlsonMappingEntry(entry); | |
732 | goto error_cleanup; | |
733 | } | |
734 | u_strncpy(key, mzidUChars, mzidLen); | |
735 | uhash_put(metaToOlson, key, tzMappings, &status); | |
736 | if (U_FAILURE(status)) { | |
737 | goto error_cleanup; | |
738 | } | |
739 | } | |
740 | tzMappings->addElement(entry, status); | |
741 | if (U_FAILURE(status)) { | |
742 | goto error_cleanup; | |
743 | } | |
744 | } else { | |
745 | deleteMetaToOlsonMappingEntry(entry); | |
746 | } | |
747 | } else { | |
748 | status = U_ZERO_ERROR; | |
749 | } | |
750 | } | |
751 | } | |
752 | } | |
753 | ||
754 | normal_cleanup: | |
755 | ures_close(mz); | |
756 | ures_close(metazones); | |
757 | return metaToOlson; | |
758 | ||
759 | error_cleanup: | |
760 | if (metaToOlson != NULL) { | |
761 | uhash_close(metaToOlson); | |
762 | metaToOlson = NULL; | |
763 | } | |
764 | goto normal_cleanup; | |
765 | } | |
766 | ||
767 | /* | |
768 | * Initialize global objects | |
769 | */ | |
770 | void | |
771 | ZoneMeta::initializeCanonicalMap(void) { | |
772 | UBool initialized; | |
773 | UMTX_CHECK(&gZoneMetaLock, gCanonicalMapInitialized, initialized); | |
774 | if (initialized) { | |
775 | return; | |
776 | } | |
777 | // Initialize hash table | |
778 | UHashtable *tmpCanonicalMap = createCanonicalMap(); | |
779 | ||
780 | umtx_lock(&gZoneMetaLock); | |
781 | if (!gCanonicalMapInitialized) { | |
782 | gCanonicalMap = tmpCanonicalMap; | |
783 | tmpCanonicalMap = NULL; | |
784 | gCanonicalMapInitialized = TRUE; | |
785 | } | |
786 | umtx_unlock(&gZoneMetaLock); | |
787 | ||
788 | // OK to call the following multiple times with the same function | |
789 | ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); | |
790 | if (tmpCanonicalMap != NULL) { | |
791 | uhash_close(tmpCanonicalMap); | |
792 | } | |
793 | } | |
794 | ||
795 | void | |
796 | ZoneMeta::initializeOlsonToMeta(void) { | |
797 | UBool initialized; | |
798 | UMTX_CHECK(&gZoneMetaLock, gOlsonToMetaInitialized, initialized); | |
799 | if (initialized) { | |
800 | return; | |
801 | } | |
802 | // Initialize hash tables | |
803 | UHashtable *tmpOlsonToMeta = createOlsonToMetaMap(); | |
804 | ||
805 | umtx_lock(&gZoneMetaLock); | |
806 | if (!gOlsonToMetaInitialized) { | |
807 | gOlsonToMeta = tmpOlsonToMeta; | |
808 | tmpOlsonToMeta = NULL; | |
809 | gOlsonToMetaInitialized = TRUE; | |
810 | } | |
811 | umtx_unlock(&gZoneMetaLock); | |
812 | ||
813 | // OK to call the following multiple times with the same function | |
814 | ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); | |
815 | if (tmpOlsonToMeta != NULL) { | |
816 | uhash_close(tmpOlsonToMeta); | |
817 | } | |
818 | } | |
819 | ||
820 | void | |
821 | ZoneMeta::initializeMetaToOlson(void) { | |
822 | UBool initialized; | |
823 | UMTX_CHECK(&gZoneMetaLock, gMetaToOlsonInitialized, initialized); | |
824 | if (initialized) { | |
825 | return; | |
826 | } | |
827 | // Initialize hash table | |
828 | UHashtable *tmpMetaToOlson = createMetaToOlsonMap(); | |
829 | ||
830 | umtx_lock(&gZoneMetaLock); | |
831 | if (!gMetaToOlsonInitialized) { | |
832 | gMetaToOlson = tmpMetaToOlson; | |
833 | tmpMetaToOlson = NULL; | |
834 | gMetaToOlsonInitialized = TRUE; | |
835 | } | |
836 | umtx_unlock(&gZoneMetaLock); | |
837 | ||
838 | // OK to call the following multiple times with the same function | |
839 | ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); | |
840 | if (tmpMetaToOlson != NULL) { | |
841 | uhash_close(tmpMetaToOlson); | |
842 | } | |
843 | } | |
844 | ||
845 | UnicodeString& U_EXPORT2 | |
846 | ZoneMeta::getCanonicalSystemID(const UnicodeString &tzid, UnicodeString &systemID, UErrorCode& status) { | |
847 | const CanonicalMapEntry *entry = getCanonicalInfo(tzid); | |
848 | if (entry != NULL) { | |
849 | systemID.setTo(entry->id); | |
850 | } else { | |
851 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
852 | } | |
853 | return systemID; | |
854 | } | |
855 | ||
856 | UnicodeString& U_EXPORT2 | |
857 | ZoneMeta::getCanonicalCountry(const UnicodeString &tzid, UnicodeString &canonicalCountry) { | |
858 | const CanonicalMapEntry *entry = getCanonicalInfo(tzid); | |
859 | if (entry != NULL && entry->country != NULL) { | |
860 | canonicalCountry.setTo(entry->country); | |
861 | } else { | |
862 | // Use the input tzid | |
863 | canonicalCountry.remove(); | |
864 | } | |
865 | return canonicalCountry; | |
866 | } | |
867 | ||
868 | const CanonicalMapEntry* U_EXPORT2 | |
869 | ZoneMeta::getCanonicalInfo(const UnicodeString &tzid) { | |
870 | initializeCanonicalMap(); | |
871 | CanonicalMapEntry *entry = NULL; | |
872 | if (gCanonicalMap != NULL) { | |
873 | UErrorCode status = U_ZERO_ERROR; | |
874 | UChar tzidUChars[ZID_KEY_MAX]; | |
875 | tzid.extract(tzidUChars, ZID_KEY_MAX, status); | |
876 | if (U_SUCCESS(status) && status!=U_STRING_NOT_TERMINATED_WARNING) { | |
877 | entry = (CanonicalMapEntry*)uhash_get(gCanonicalMap, tzidUChars); | |
878 | } | |
879 | } | |
880 | return entry; | |
881 | } | |
882 | ||
883 | UnicodeString& U_EXPORT2 | |
884 | ZoneMeta::getSingleCountry(const UnicodeString &tzid, UnicodeString &country) { | |
885 | UErrorCode status = U_ZERO_ERROR; | |
886 | ||
887 | // Get canonical country for the zone | |
888 | getCanonicalCountry(tzid, country); | |
889 | ||
890 | if (!country.isEmpty()) { | |
891 | UResourceBundle *supplementalDataBundle = ures_openDirect(NULL, gSupplementalData, &status); | |
892 | UResourceBundle *zoneFormatting = ures_getByKey(supplementalDataBundle, gZoneFormattingTag, NULL, &status); | |
893 | UResourceBundle *multizone = ures_getByKey(zoneFormatting, gMultizoneTag, NULL, &status); | |
894 | ||
895 | if (U_SUCCESS(status)) { | |
896 | while (ures_hasNext(multizone)) { | |
897 | int32_t len; | |
898 | const UChar* multizoneCountry = ures_getNextString(multizone, &len, NULL, &status); | |
899 | if (country.compare(multizoneCountry, len) == 0) { | |
900 | // Included in the multizone country list | |
901 | country.remove(); | |
902 | break; | |
903 | } | |
904 | } | |
905 | } | |
906 | ||
907 | ures_close(multizone); | |
908 | ures_close(zoneFormatting); | |
909 | ures_close(supplementalDataBundle); | |
910 | } | |
911 | ||
912 | return country; | |
913 | } | |
914 | ||
915 | UnicodeString& U_EXPORT2 | |
916 | ZoneMeta::getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &result) { | |
917 | UBool isSet = FALSE; | |
918 | const UVector *mappings = getMetazoneMappings(tzid); | |
919 | if (mappings != NULL) { | |
920 | for (int32_t i = 0; i < mappings->size(); i++) { | |
921 | OlsonToMetaMappingEntry *mzm = (OlsonToMetaMappingEntry*)mappings->elementAt(i); | |
922 | if (mzm->from <= date && mzm->to > date) { | |
923 | result.setTo(mzm->mzid, -1); | |
924 | isSet = TRUE; | |
925 | break; | |
926 | } | |
927 | } | |
928 | } | |
929 | if (!isSet) { | |
930 | result.remove(); | |
931 | } | |
932 | return result; | |
933 | } | |
934 | ||
935 | const UVector* U_EXPORT2 | |
936 | ZoneMeta::getMetazoneMappings(const UnicodeString &tzid) { | |
937 | initializeOlsonToMeta(); | |
938 | const UVector *result = NULL; | |
939 | if (gOlsonToMeta != NULL) { | |
940 | UErrorCode status = U_ZERO_ERROR; | |
941 | UChar tzidUChars[ZID_KEY_MAX]; | |
942 | tzid.extract(tzidUChars, ZID_KEY_MAX, status); | |
943 | if (U_SUCCESS(status) && status!=U_STRING_NOT_TERMINATED_WARNING) { | |
944 | result = (UVector*)uhash_get(gOlsonToMeta, tzidUChars); | |
945 | } | |
946 | } | |
947 | return result; | |
948 | } | |
949 | ||
950 | UnicodeString& U_EXPORT2 | |
951 | ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString ®ion, UnicodeString &result) { | |
952 | initializeMetaToOlson(); | |
953 | UBool isSet = FALSE; | |
954 | if (gMetaToOlson != NULL) { | |
955 | UErrorCode status = U_ZERO_ERROR; | |
956 | UChar mzidUChars[ZID_KEY_MAX]; | |
957 | mzid.extract(mzidUChars, ZID_KEY_MAX, status); | |
958 | if (U_SUCCESS(status) && status!=U_STRING_NOT_TERMINATED_WARNING) { | |
959 | UVector *mappings = (UVector*)uhash_get(gMetaToOlson, mzidUChars); | |
960 | if (mappings != NULL) { | |
961 | // Find a preferred time zone for the given region. | |
962 | for (int32_t i = 0; i < mappings->size(); i++) { | |
963 | MetaToOlsonMappingEntry *olsonmap = (MetaToOlsonMappingEntry*)mappings->elementAt(i); | |
964 | if (region.compare(olsonmap->territory, -1) == 0) { | |
965 | result.setTo(olsonmap->id); | |
966 | isSet = TRUE; | |
967 | break; | |
968 | } else if (u_strcmp(olsonmap->territory, gWorld) == 0) { | |
969 | result.setTo(olsonmap->id); | |
970 | isSet = TRUE; | |
971 | } | |
972 | } | |
973 | } | |
974 | } | |
975 | } | |
976 | if (!isSet) { | |
977 | result.remove(); | |
978 | } | |
979 | return result; | |
980 | } | |
981 | ||
982 | ||
983 | U_NAMESPACE_END | |
984 | ||
985 | #endif /* #if !UCONFIG_NO_FORMATTING */ |