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