tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

zonemeta.cpp (31792B)


      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 = nullptr;
     37 static icu::UInitOnce gCanonicalIDCacheInitOnce {};
     38 
     39 // Metazone mapping table
     40 static UHashtable *gOlsonToMeta = nullptr;
     41 static icu::UInitOnce gOlsonToMetaInitOnce {};
     42 
     43 // Available metazone IDs vector and table
     44 static icu::UVector *gMetaZoneIDs = nullptr;
     45 static UHashtable *gMetaZoneIDTable = nullptr;
     46 static icu::UInitOnce gMetaZoneIDsInitOnce {};
     47 
     48 // Country info vectors
     49 static icu::UVector *gSingleZoneCountries = nullptr;
     50 static icu::UVector *gMultiZonesCountries = nullptr;
     51 static icu::UInitOnce gCountryInfoVectorsInitOnce {};
     52 
     53 U_CDECL_BEGIN
     54 
     55 /**
     56 * Cleanup callback func
     57 */
     58 static 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 */
     94 static void U_CALLCONV
     95 deleteUCharString(void *obj) {
     96    char16_t *entry = (char16_t*)obj;
     97    uprv_free(entry);
     98 }
     99 
    100 /**
    101 * Deleter for OlsonToMetaMappingEntry
    102 */
    103 static void U_CALLCONV
    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 static const char gIanaMapTag[]         = "ianaMap";
    124 
    125 static const char gPrimaryZonesTag[]    = "primaryZones";
    126 
    127 static const char gWorldTag[]           = "001";
    128 
    129 static const char16_t gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001"
    130 
    131 static 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"
    133 static 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 
    136 static 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 */
    144 static UDate
    145 parseDate (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 
    215 static 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 
    228 const char16_t* U_EXPORT2
    229 ZoneMeta::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 = static_cast<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] = static_cast<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 = static_cast<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 = static_cast<const char16_t*>(uhash_put(gCanonicalIDCache, const_cast<char16_t*>(key), const_cast<char16_t*>(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 = static_cast<const char16_t*>(uhash_get(gCanonicalIDCache, canonicalID));
    358                if (canonicalInCache == nullptr) {
    359                    canonicalInCache = static_cast<const char16_t*>(uhash_put(gCanonicalIDCache, const_cast<char16_t*>(canonicalID), const_cast<char16_t*>(canonicalID), &status));
    360                    U_ASSERT(canonicalInCache == nullptr);
    361                }
    362            }
    363        }
    364        umtx_unlock(&gZoneMetaLock);
    365    }
    366 
    367    return canonicalID;
    368 }
    369 
    370 UnicodeString& U_EXPORT2
    371 ZoneMeta::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 
    381 const char16_t* U_EXPORT2
    382 ZoneMeta::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 
    393 UnicodeString& U_EXPORT2
    394 ZoneMeta::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 
    422 static 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 
    444 UnicodeString& U_EXPORT2
    445 ZoneMeta::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 
    547 UnicodeString& U_EXPORT2
    548 ZoneMeta::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 = static_cast<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 
    567 static 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 
    580 const UVector* U_EXPORT2
    581 ZoneMeta::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 = static_cast<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 = static_cast<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 = static_cast<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 
    648 UVector*
    649 ZoneMeta::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 
    730 UnicodeString& U_EXPORT2
    731 ZoneMeta::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 
    776 static 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(static_cast<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 
    838 const UVector*
    839 ZoneMeta::getAvailableMetazoneIDs() {
    840    umtx_initOnce(gMetaZoneIDsInitOnce, &initAvailableMetaZoneIDs);
    841    return gMetaZoneIDs;
    842 }
    843 
    844 const char16_t*
    845 ZoneMeta::findMetaZoneID(const UnicodeString& mzid) {
    846    umtx_initOnce(gMetaZoneIDsInitOnce, &initAvailableMetaZoneIDs);
    847    if (gMetaZoneIDTable == nullptr) {
    848        return nullptr;
    849    }
    850    return static_cast<const char16_t*>(uhash_get(gMetaZoneIDTable, &mzid));
    851 }
    852 
    853 const char16_t*
    854 ZoneMeta::findTimeZoneID(const UnicodeString& tzid) {
    855    return TimeZone::findID(tzid);
    856 }
    857 
    858 
    859 TimeZone*
    860 ZoneMeta::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 
    880 UnicodeString&
    881 ZoneMeta::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(static_cast<char16_t>(0x2D)); // '-'
    887        } else {
    888          id.append(static_cast<char16_t>(0x2B)); // '+'
    889        }
    890        // Always use US-ASCII digits
    891        id.append(static_cast<char16_t>(0x30 + (hour % 100) / 10));
    892        id.append(static_cast<char16_t>(0x30 + (hour % 10)));
    893        id.append(static_cast<char16_t>(0x3A)); // ':'
    894        id.append(static_cast<char16_t>(0x30 + (min % 100) / 10));
    895        id.append(static_cast<char16_t>(0x30 + (min % 10)));
    896        if (sec != 0) {
    897          id.append(static_cast<char16_t>(0x3A)); // ':'
    898          id.append(static_cast<char16_t>(0x30 + (sec % 100) / 10));
    899          id.append(static_cast<char16_t>(0x30 + (sec % 10)));
    900        }
    901    }
    902    return id;
    903 }
    904 
    905 const char16_t*
    906 ZoneMeta::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 
    919 const char16_t*
    920 ZoneMeta::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 
    929 const char16_t*
    930 ZoneMeta::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] = static_cast<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 
    956 U_NAMESPACE_END
    957 
    958 #endif /* #if !UCONFIG_NO_FORMATTING */