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