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