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