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