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