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