| /* |
| ******************************************************************************* |
| * Copyright (C) 2007-2009, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| |
| #include "unicode/utypes.h" |
| |
| #if !UCONFIG_NO_FORMATTING |
| |
| #include "zonemeta.h" |
| |
| #include "unicode/timezone.h" |
| #include "unicode/ustring.h" |
| #include "unicode/putil.h" |
| |
| #include "umutex.h" |
| #include "uvector.h" |
| #include "cmemory.h" |
| #include "gregoimp.h" |
| #include "cstring.h" |
| #include "ucln_in.h" |
| |
| // Metazone mapping tables |
| static UMTX gZoneMetaLock = NULL; |
| static UHashtable *gCanonicalMap = NULL; |
| static UHashtable *gOlsonToMeta = NULL; |
| static UHashtable *gMetaToOlson = NULL; |
| static UBool gCanonicalMapInitialized = FALSE; |
| static UBool gOlsonToMetaInitialized = FALSE; |
| static UBool gMetaToOlsonInitialized = FALSE; |
| static UChar **gUStringTable = NULL; |
| static int32_t gUStringCount = 0; |
| static int32_t gUStringAlloc = 0; |
| |
| // Currently (ICU 4.1.3+), gUStringTable only contains strings allocated in the section of |
| // createCanonicalMap that iterates over the enumerator created with TimeZone::createEnumeration. |
| // And currently, that allocates a total of 22 strings. So USTRING_ALLOC_START is defined to |
| // be adequate for that set, and USTRING_ALLOC_INCR is a reasonable expansion increment. In |
| // future versions of ICU, these numbers may need adjusting to avoid excessive reallocs, or to |
| // avoid allocating unused memory (but in any case the effects are small). |
| #define USTRING_ALLOC_START 24 |
| #define USTRING_ALLOC_INCR 12 |
| |
| U_CDECL_BEGIN |
| |
| // We have switched CanonicalMap to use const UChar* strings for the key and for the id field of |
| // CanonicalMapEntry; that is because for the most part these now point into UChar strings in the |
| // shared data file, in order to reduce process-specific dynamically-allocated memory. Consequently, |
| // there is no longer a deleter for the key field, and the deleter for CanonicalMapEntry |
| // no longer frees the id field. However, for the few strings that are obtained from the |
| // TimeZone::createEnumeration() enumerator or from TimeZone::dereferOlsonLink instead of the |
| // data file, we do need to allocate copies. In order to ensure that these strings are freed by |
| // zoneMeta_cleanup(), we need to create a little memory manager for them; this is in the form of |
| // a table that tracks the strings allocated for this purpose. The following three functions |
| // (along with the gUStringXxxxx statics) are used to allocate and free such strings. |
| |
| // The following allocs space for a UChar* string of the specified length, puts a pointer to the string |
| // in gUStringTable, and returns either a pointer to the allocated string space, or NULL for failure. |
| static UChar * allocUStringInTable(int32_t uStringLen) { |
| UChar * uStringSpace = NULL; |
| // initialize the table if necessary |
| umtx_lock(&gZoneMetaLock); |
| if (gUStringTable == NULL) { |
| gUStringTable = (UChar**)uprv_malloc(USTRING_ALLOC_START*sizeof(UChar*)); |
| if (gUStringTable != NULL) { |
| gUStringAlloc = USTRING_ALLOC_START; |
| } |
| } |
| if (gUStringTable != NULL) { |
| // expand the table if necessary |
| if (gUStringCount == gUStringAlloc) { |
| UChar ** newTable = (UChar**)uprv_realloc(gUStringTable, (gUStringAlloc+USTRING_ALLOC_INCR)*sizeof(UChar*)); |
| if (newTable != NULL) { |
| gUStringTable = newTable; |
| gUStringAlloc += USTRING_ALLOC_INCR; |
| } |
| } |
| // add the string if possible |
| if (gUStringCount < gUStringAlloc) { |
| uStringSpace = (UChar*)uprv_malloc(uStringLen*sizeof(UChar)); |
| if (uStringSpace != NULL) { |
| gUStringTable[gUStringCount++] = uStringSpace; |
| } |
| } |
| } |
| umtx_unlock(&gZoneMetaLock); |
| return uStringSpace; |
| } |
| |
| static void removeLastUStringFromTable(void) { |
| umtx_lock(&gZoneMetaLock); |
| if (gUStringCount > 0) { |
| free(gUStringTable[--gUStringCount]); |
| } |
| umtx_unlock(&gZoneMetaLock); |
| } |
| |
| static void freeUStringTable(void) { |
| int32_t uStringCount = gUStringCount; |
| gUStringCount = 0; |
| gUStringAlloc = 0; |
| if (gUStringTable != NULL) { |
| while (uStringCount > 0) { |
| free(gUStringTable[--uStringCount]); |
| } |
| free(gUStringTable); |
| gUStringTable = NULL; |
| } |
| } |
| |
| /** |
| * Cleanup callback func |
| */ |
| static UBool U_CALLCONV zoneMeta_cleanup(void) |
| { |
| umtx_destroy(&gZoneMetaLock); |
| |
| if (gCanonicalMap != NULL) { |
| uhash_close(gCanonicalMap); |
| gCanonicalMap = NULL; |
| } |
| gCanonicalMapInitialized = FALSE; |
| |
| if (gOlsonToMeta != NULL) { |
| uhash_close(gOlsonToMeta); |
| gOlsonToMeta = NULL; |
| } |
| gOlsonToMetaInitialized = FALSE; |
| |
| if (gMetaToOlson != NULL) { |
| uhash_close(gMetaToOlson); |
| gMetaToOlson = NULL; |
| } |
| gMetaToOlsonInitialized = FALSE; |
| |
| freeUStringTable(); |
| |
| return TRUE; |
| } |
| |
| /** |
| * Deleter for UChar* string |
| */ |
| static void U_CALLCONV |
| deleteUCharString(void *obj) { |
| UChar *entry = (UChar*)obj; |
| uprv_free(entry); |
| } |
| |
| /** |
| * Deleter for UVector |
| */ |
| static void U_CALLCONV |
| deleteUVector(void *obj) { |
| delete (U_NAMESPACE_QUALIFIER UVector*) obj; |
| } |
| |
| /** |
| * Deleter for CanonicalMapEntry |
| */ |
| static void U_CALLCONV |
| deleteCanonicalMapEntry(void *obj) { |
| U_NAMESPACE_QUALIFIER CanonicalMapEntry *entry = (U_NAMESPACE_QUALIFIER CanonicalMapEntry*)obj; |
| uprv_free(entry); |
| } |
| |
| /** |
| * Deleter for OlsonToMetaMappingEntry |
| */ |
| static void U_CALLCONV |
| deleteOlsonToMetaMappingEntry(void *obj) { |
| U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry *entry = (U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry*)obj; |
| uprv_free(entry); |
| } |
| |
| /** |
| * Deleter for MetaToOlsonMappingEntry |
| */ |
| static void U_CALLCONV |
| deleteMetaToOlsonMappingEntry(void *obj) { |
| U_NAMESPACE_QUALIFIER MetaToOlsonMappingEntry *entry = (U_NAMESPACE_QUALIFIER MetaToOlsonMappingEntry*)obj; |
| uprv_free(entry->territory); |
| uprv_free(entry); |
| } |
| U_CDECL_END |
| |
| U_NAMESPACE_BEGIN |
| |
| #define ZID_KEY_MAX 128 |
| static const char gZoneStringsTag[] = "zoneStrings"; |
| static const char gUseMetazoneTag[] = "um"; |
| |
| static const char gSupplementalData[] = "supplementalData"; |
| static const char gMapTimezonesTag[] = "mapTimezones"; |
| static const char gMetazonesTag[] = "metazones"; |
| static const char gZoneFormattingTag[] = "zoneFormatting"; |
| static const char gCanonicalTag[] = "canonical"; |
| static const char gTerritoryTag[] = "territory"; |
| static const char gAliasesTag[] = "aliases"; |
| static const char gMultizoneTag[] = "multizone"; |
| |
| static const char gMetazoneInfo[] = "metazoneInfo"; |
| static const char gMetazoneMappings[] = "metazoneMappings"; |
| |
| #define MZID_PREFIX_LEN 5 |
| static const char gMetazoneIdPrefix[] = "meta:"; |
| |
| static const UChar gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001" |
| |
| #define ASCII_DIGIT(c) (((c)>=0x30 && (c)<=0x39) ? (c)-0x30 : -1) |
| |
| /* |
| * Convert a date string used by metazone mappings to UDate. |
| * The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm". |
| */ |
| static UDate |
| parseDate (const UChar *text, UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| return 0; |
| } |
| int32_t len = u_strlen(text); |
| if (len != 16 && len != 10) { |
| // It must be yyyy-MM-dd HH:mm (length 16) or yyyy-MM-dd (length 10) |
| status = U_INVALID_FORMAT_ERROR; |
| return 0; |
| } |
| |
| int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, n; |
| int32_t idx; |
| |
| // "yyyy" (0 - 3) |
| for (idx = 0; idx <= 3 && U_SUCCESS(status); idx++) { |
| n = ASCII_DIGIT((int32_t)text[idx]); |
| if (n >= 0) { |
| year = 10*year + n; |
| } else { |
| status = U_INVALID_FORMAT_ERROR; |
| } |
| } |
| // "MM" (5 - 6) |
| for (idx = 5; idx <= 6 && U_SUCCESS(status); idx++) { |
| n = ASCII_DIGIT((int32_t)text[idx]); |
| if (n >= 0) { |
| month = 10*month + n; |
| } else { |
| status = U_INVALID_FORMAT_ERROR; |
| } |
| } |
| // "dd" (8 - 9) |
| for (idx = 8; idx <= 9 && U_SUCCESS(status); idx++) { |
| n = ASCII_DIGIT((int32_t)text[idx]); |
| if (n >= 0) { |
| day = 10*day + n; |
| } else { |
| status = U_INVALID_FORMAT_ERROR; |
| } |
| } |
| if (len == 16) { |
| // "HH" (11 - 12) |
| for (idx = 11; idx <= 12 && U_SUCCESS(status); idx++) { |
| n = ASCII_DIGIT((int32_t)text[idx]); |
| if (n >= 0) { |
| hour = 10*hour + n; |
| } else { |
| status = U_INVALID_FORMAT_ERROR; |
| } |
| } |
| // "mm" (14 - 15) |
| for (idx = 14; idx <= 15 && U_SUCCESS(status); idx++) { |
| n = ASCII_DIGIT((int32_t)text[idx]); |
| if (n >= 0) { |
| min = 10*min + n; |
| } else { |
| status = U_INVALID_FORMAT_ERROR; |
| } |
| } |
| } |
| |
| if (U_SUCCESS(status)) { |
| UDate date = Grego::fieldsToDay(year, month - 1, day) * U_MILLIS_PER_DAY |
| + hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE; |
| return date; |
| } |
| return 0; |
| } |
| |
| UHashtable* |
| ZoneMeta::createCanonicalMap(void) { |
| UErrorCode status = U_ZERO_ERROR; |
| |
| UHashtable *canonicalMap = NULL; |
| UResourceBundle *zoneFormatting = NULL; |
| UResourceBundle *tzitem = NULL; |
| UResourceBundle *aliases = NULL; |
| |
| StringEnumeration* tzenum = NULL; |
| int32_t numZones; |
| |
| canonicalMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| // no key deleter |
| uhash_setValueDeleter(canonicalMap, deleteCanonicalMapEntry); |
| |
| zoneFormatting = ures_openDirect(NULL, gSupplementalData, &status); |
| zoneFormatting = ures_getByKey(zoneFormatting, gZoneFormattingTag, zoneFormatting, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| |
| while (ures_hasNext(zoneFormatting)) { |
| tzitem = ures_getNextResource(zoneFormatting, tzitem, &status); |
| if (U_FAILURE(status)) { |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| if (ures_getType(tzitem) != URES_TABLE) { |
| continue; |
| } |
| |
| int32_t canonicalLen; |
| const UChar *canonical = ures_getStringByKey(tzitem, gCanonicalTag, &canonicalLen, &status); |
| if (U_FAILURE(status)) { |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| |
| int32_t territoryLen; |
| const UChar *territory = ures_getStringByKey(tzitem, gTerritoryTag, &territoryLen, &status); |
| if (U_FAILURE(status)) { |
| territory = NULL; |
| status = U_ZERO_ERROR; |
| } |
| |
| // Create canonical map entry |
| CanonicalMapEntry *entry = (CanonicalMapEntry*)uprv_malloc(sizeof(CanonicalMapEntry)); |
| if (entry == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| goto error_cleanup; |
| } |
| entry->id = canonical; |
| if (territory == NULL || u_strcmp(territory, gWorld) == 0) { |
| entry->country = NULL; |
| } else { |
| entry->country = territory; |
| } |
| |
| // Put this entry in the hashtable. Since this hashtable has no key deleter, |
| // key is treated as const, but must be passed as non-const. |
| uhash_put(canonicalMap, (UChar*)canonical, entry, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| |
| // Get aliases |
| aliases = ures_getByKey(tzitem, gAliasesTag, aliases, &status); |
| if (U_FAILURE(status)) { |
| // No aliases |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| |
| while (ures_hasNext(aliases)) { |
| const UChar* alias = ures_getNextString(aliases, NULL, NULL, &status); |
| if (U_FAILURE(status)) { |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| // Create canonical map entry for this alias |
| entry = (CanonicalMapEntry*)uprv_malloc(sizeof(CanonicalMapEntry)); |
| if (entry == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| goto error_cleanup; |
| } |
| entry->id = canonical; |
| if (territory == NULL || u_strcmp(territory, gWorld) == 0) { |
| entry->country = NULL; |
| } else { |
| entry->country = territory; |
| } |
| |
| // Put this entry in the hashtable. Since this hashtable has no key deleter, |
| // key is treated as const, but must be passed as non-const. |
| uhash_put(canonicalMap, (UChar*)alias, entry, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| } |
| } |
| |
| // Some available Olson zones are not included in CLDR data (such as Asia/Riyadh87). |
| // Also, when we update Olson tzdata, new zones may be added. |
| // This code scans all available zones in zoneinfo.res, and if any of them are |
| // missing, add them to the map. |
| tzenum = TimeZone::createEnumeration(); |
| numZones = tzenum->count(status); |
| if (U_SUCCESS(status)) { |
| int32_t i; |
| for (i = 0; i < numZones; i++) { |
| const UnicodeString *zone = tzenum->snext(status); |
| if (U_FAILURE(status)) { |
| // We should not get here. |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| UChar zoneUChars[ZID_KEY_MAX]; |
| int32_t zoneUCharsLen = zone->extract(zoneUChars, ZID_KEY_MAX, status) + 1; // Add one for NUL termination |
| if (U_FAILURE(status) || status==U_STRING_NOT_TERMINATED_WARNING) { |
| status = U_ZERO_ERROR; |
| continue; // zone id is too long to extract |
| } |
| CanonicalMapEntry *entry = (CanonicalMapEntry*)uhash_get(canonicalMap, zoneUChars); |
| if (entry) { |
| // Already included in CLDR data |
| continue; |
| } |
| // Not in CLDR data, but it could be new one whose alias is available |
| // in CLDR. |
| int32_t nTzdataEquivalent = TimeZone::countEquivalentIDs(*zone); |
| int32_t j; |
| for (j = 0; j < nTzdataEquivalent; j++) { |
| UnicodeString alias = TimeZone::getEquivalentID(*zone, j); |
| if (alias == *zone) { |
| continue; |
| } |
| UChar aliasUChars[ZID_KEY_MAX]; |
| alias.extract(aliasUChars, ZID_KEY_MAX, status); |
| if (U_FAILURE(status) || status==U_STRING_NOT_TERMINATED_WARNING) { |
| status = U_ZERO_ERROR; |
| continue; // zone id is too long to extract |
| } |
| entry = (CanonicalMapEntry*)uhash_get(canonicalMap, aliasUChars); |
| if (entry != NULL) { |
| break; |
| } |
| } |
| // Create a new map entry |
| CanonicalMapEntry* newEntry = (CanonicalMapEntry*)uprv_malloc(sizeof(CanonicalMapEntry)); |
| int32_t idLen; |
| if (newEntry == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| goto error_cleanup; |
| } |
| if (entry == NULL) { |
| // Set dereferenced zone ID as the canonical ID |
| UnicodeString derefZone; |
| TimeZone::dereferOlsonLink(*zone, derefZone); |
| if (derefZone.length() == 0) { |
| // It should never happen.. but just in case |
| derefZone = *zone; |
| } |
| idLen = derefZone.length() + 1; |
| newEntry->id = allocUStringInTable(idLen); |
| if (newEntry->id == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| uprv_free(newEntry); |
| goto error_cleanup; |
| } |
| // Copy NULL terminated string |
| derefZone.extract((UChar*)(newEntry->id), idLen, status); |
| if (U_FAILURE(status)) { |
| removeLastUStringFromTable(); |
| uprv_free(newEntry); |
| goto error_cleanup; |
| } |
| // No territory information available |
| newEntry->country = NULL; |
| } else { |
| // Duplicate the entry |
| newEntry->id = entry->id; |
| newEntry->country = entry->country; |
| } |
| |
| // Put this entry in the hashtable |
| UChar *key = allocUStringInTable(zoneUCharsLen); |
| if (key == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| deleteCanonicalMapEntry(newEntry); |
| goto error_cleanup; |
| } |
| u_strncpy(key, zoneUChars, zoneUCharsLen); |
| uhash_put(canonicalMap, key, newEntry, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| } |
| } |
| |
| normal_cleanup: |
| ures_close(aliases); |
| ures_close(tzitem); |
| ures_close(zoneFormatting); |
| delete tzenum; |
| return canonicalMap; |
| |
| error_cleanup: |
| if (canonicalMap != NULL) { |
| uhash_close(canonicalMap); |
| canonicalMap = NULL; |
| } |
| goto normal_cleanup; |
| } |
| |
| /* |
| * Creating Olson tzid to metazone mappings from resource (3.8.1 and beyond) |
| */ |
| UHashtable* |
| ZoneMeta::createOlsonToMetaMap(void) { |
| UErrorCode status = U_ZERO_ERROR; |
| |
| UHashtable *olsonToMeta = NULL; |
| UResourceBundle *metazoneMappings = NULL; |
| UResourceBundle *zoneItem = NULL; |
| UResourceBundle *mz = NULL; |
| StringEnumeration *tzids = NULL; |
| |
| olsonToMeta = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| uhash_setKeyDeleter(olsonToMeta, deleteUCharString); |
| uhash_setValueDeleter(olsonToMeta, deleteUVector); |
| |
| // Read metazone mappings from metazoneInfo bundle |
| metazoneMappings = ures_openDirect(NULL, gMetazoneInfo, &status); |
| metazoneMappings = ures_getByKey(metazoneMappings, gMetazoneMappings, metazoneMappings, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| |
| // Walk through all canonical tzids |
| char zidkey[ZID_KEY_MAX]; |
| |
| tzids = TimeZone::createEnumeration(); |
| const UnicodeString *tzid; |
| while ((tzid = tzids->snext(status))) { |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| // We may skip aliases, because the bundle |
| // contains only canonical IDs. For now, try |
| // all of them. |
| tzid->extract(0, tzid->length(), zidkey, sizeof(zidkey), US_INV); |
| zidkey[sizeof(zidkey)-1] = 0; // NULL terminate just in case. |
| |
| // Replace '/' with ':' |
| UBool foundSep = FALSE; |
| char *p = zidkey; |
| while (*p) { |
| if (*p == '/') { |
| *p = ':'; |
| foundSep = TRUE; |
| } |
| p++; |
| } |
| if (!foundSep) { |
| // A valid time zone key has at least one separator |
| continue; |
| } |
| |
| zoneItem = ures_getByKey(metazoneMappings, zidkey, zoneItem, &status); |
| if (U_FAILURE(status)) { |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| |
| UVector *mzMappings = NULL; |
| while (ures_hasNext(zoneItem)) { |
| mz = ures_getNextResource(zoneItem, mz, &status); |
| const UChar *mz_name = ures_getStringByIndex(mz, 0, NULL, &status); |
| const UChar *mz_from = ures_getStringByIndex(mz, 1, NULL, &status); |
| const UChar *mz_to = ures_getStringByIndex(mz, 2, NULL, &status); |
| |
| if(U_FAILURE(status)){ |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| // We do not want to use SimpleDateformat to parse boundary dates, |
| // because this code could be triggered by the initialization code |
| // used by SimpleDateFormat. |
| UDate from = parseDate(mz_from, status); |
| UDate to = parseDate(mz_to, status); |
| if (U_FAILURE(status)) { |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| |
| OlsonToMetaMappingEntry *entry = (OlsonToMetaMappingEntry*)uprv_malloc(sizeof(OlsonToMetaMappingEntry)); |
| if (entry == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| break; |
| } |
| entry->mzid = mz_name; |
| entry->from = from; |
| entry->to = to; |
| |
| if (mzMappings == NULL) { |
| mzMappings = new UVector(deleteOlsonToMetaMappingEntry, NULL, status); |
| if (U_FAILURE(status)) { |
| delete mzMappings; |
| deleteOlsonToMetaMappingEntry(entry); |
| uprv_free(entry); |
| break; |
| } |
| } |
| |
| mzMappings->addElement(entry, status); |
| if (U_FAILURE(status)) { |
| break; |
| } |
| } |
| |
| if (U_FAILURE(status)) { |
| if (mzMappings != NULL) { |
| delete mzMappings; |
| } |
| goto error_cleanup; |
| } |
| if (mzMappings != NULL) { |
| // Add to hashtable |
| int32_t tzidLen = tzid->length() + 1; // Add one for NUL terminator |
| UChar *key = (UChar*)uprv_malloc(tzidLen * sizeof(UChar)); |
| if (key == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| delete mzMappings; |
| goto error_cleanup; |
| } |
| tzid->extract(key, tzidLen, status); |
| uhash_put(olsonToMeta, key, mzMappings, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| } |
| } |
| |
| normal_cleanup: |
| if (tzids != NULL) { |
| delete tzids; |
| } |
| ures_close(zoneItem); |
| ures_close(mz); |
| ures_close(metazoneMappings); |
| return olsonToMeta; |
| |
| error_cleanup: |
| if (olsonToMeta != NULL) { |
| uhash_close(olsonToMeta); |
| olsonToMeta = NULL; |
| } |
| goto normal_cleanup; |
| } |
| |
| UHashtable* |
| ZoneMeta::createMetaToOlsonMap(void) { |
| UErrorCode status = U_ZERO_ERROR; |
| |
| UHashtable *metaToOlson = NULL; |
| UResourceBundle *metazones = NULL; |
| UResourceBundle *mz = NULL; |
| |
| metaToOlson = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| uhash_setKeyDeleter(metaToOlson, deleteUCharString); |
| uhash_setValueDeleter(metaToOlson, deleteUVector); |
| |
| metazones = ures_openDirect(NULL, gSupplementalData, &status); |
| metazones = ures_getByKey(metazones, gMapTimezonesTag, metazones, &status); |
| metazones = ures_getByKey(metazones, gMetazonesTag, metazones, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| |
| while (ures_hasNext(metazones)) { |
| mz = ures_getNextResource(metazones, mz, &status); |
| if (U_FAILURE(status)) { |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| const char *mzkey = ures_getKey(mz); |
| if (uprv_strncmp(mzkey, gMetazoneIdPrefix, MZID_PREFIX_LEN) == 0) { |
| const char *mzid = mzkey + MZID_PREFIX_LEN; |
| const char *territory = uprv_strrchr(mzid, '_'); |
| int32_t mzidLen = 0; |
| int32_t territoryLen = 0; |
| if (territory) { |
| mzidLen = territory - mzid; |
| territory++; |
| territoryLen = uprv_strlen(territory); |
| } |
| if (mzidLen > 0 && territoryLen > 0) { |
| int32_t tzidLen; |
| const UChar *tzid = ures_getStringByIndex(mz, 0, &tzidLen, &status); |
| if (U_SUCCESS(status)) { |
| // Create MetaToOlsonMappingEntry |
| MetaToOlsonMappingEntry *entry = (MetaToOlsonMappingEntry*)uprv_malloc(sizeof(MetaToOlsonMappingEntry)); |
| if (entry == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| goto error_cleanup; |
| } |
| entry->id = tzid; |
| entry->territory = (UChar*)uprv_malloc((territoryLen + 1) * sizeof(UChar)); |
| if (entry->territory == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| uprv_free(entry); |
| goto error_cleanup; |
| } |
| u_charsToUChars(territory, entry->territory, territoryLen + 1); |
| |
| // Check if mapping entries for metazone is already available |
| if (mzidLen < ZID_KEY_MAX) { |
| UChar mzidUChars[ZID_KEY_MAX]; |
| u_charsToUChars(mzid, mzidUChars, mzidLen); |
| mzidUChars[mzidLen++] = 0; // Add NUL terminator |
| UVector *tzMappings = (UVector*)uhash_get(metaToOlson, mzidUChars); |
| if (tzMappings == NULL) { |
| // Create new UVector and put it into the hashtable |
| tzMappings = new UVector(deleteMetaToOlsonMappingEntry, NULL, status); |
| if (U_FAILURE(status)) { |
| deleteMetaToOlsonMappingEntry(entry); |
| goto error_cleanup; |
| } |
| UChar *key = (UChar*)uprv_malloc(mzidLen * sizeof(UChar)); |
| if (key == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| delete tzMappings; |
| deleteMetaToOlsonMappingEntry(entry); |
| goto error_cleanup; |
| } |
| u_strncpy(key, mzidUChars, mzidLen); |
| uhash_put(metaToOlson, key, tzMappings, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| } |
| tzMappings->addElement(entry, status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| } else { |
| deleteMetaToOlsonMappingEntry(entry); |
| } |
| } else { |
| status = U_ZERO_ERROR; |
| } |
| } |
| } |
| } |
| |
| normal_cleanup: |
| ures_close(mz); |
| ures_close(metazones); |
| return metaToOlson; |
| |
| error_cleanup: |
| if (metaToOlson != NULL) { |
| uhash_close(metaToOlson); |
| metaToOlson = NULL; |
| } |
| goto normal_cleanup; |
| } |
| |
| /* |
| * Initialize global objects |
| */ |
| void |
| ZoneMeta::initializeCanonicalMap(void) { |
| UBool initialized; |
| UMTX_CHECK(&gZoneMetaLock, gCanonicalMapInitialized, initialized); |
| if (initialized) { |
| return; |
| } |
| // Initialize hash table |
| UHashtable *tmpCanonicalMap = createCanonicalMap(); |
| |
| umtx_lock(&gZoneMetaLock); |
| if (!gCanonicalMapInitialized) { |
| gCanonicalMap = tmpCanonicalMap; |
| tmpCanonicalMap = NULL; |
| gCanonicalMapInitialized = TRUE; |
| } |
| umtx_unlock(&gZoneMetaLock); |
| |
| // OK to call the following multiple times with the same function |
| ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); |
| if (tmpCanonicalMap != NULL) { |
| uhash_close(tmpCanonicalMap); |
| } |
| } |
| |
| void |
| ZoneMeta::initializeOlsonToMeta(void) { |
| UBool initialized; |
| UMTX_CHECK(&gZoneMetaLock, gOlsonToMetaInitialized, initialized); |
| if (initialized) { |
| return; |
| } |
| // Initialize hash tables |
| UHashtable *tmpOlsonToMeta = createOlsonToMetaMap(); |
| |
| umtx_lock(&gZoneMetaLock); |
| if (!gOlsonToMetaInitialized) { |
| gOlsonToMeta = tmpOlsonToMeta; |
| tmpOlsonToMeta = NULL; |
| gOlsonToMetaInitialized = TRUE; |
| } |
| umtx_unlock(&gZoneMetaLock); |
| |
| // OK to call the following multiple times with the same function |
| ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); |
| if (tmpOlsonToMeta != NULL) { |
| uhash_close(tmpOlsonToMeta); |
| } |
| } |
| |
| void |
| ZoneMeta::initializeMetaToOlson(void) { |
| UBool initialized; |
| UMTX_CHECK(&gZoneMetaLock, gMetaToOlsonInitialized, initialized); |
| if (initialized) { |
| return; |
| } |
| // Initialize hash table |
| UHashtable *tmpMetaToOlson = createMetaToOlsonMap(); |
| |
| umtx_lock(&gZoneMetaLock); |
| if (!gMetaToOlsonInitialized) { |
| gMetaToOlson = tmpMetaToOlson; |
| tmpMetaToOlson = NULL; |
| gMetaToOlsonInitialized = TRUE; |
| } |
| umtx_unlock(&gZoneMetaLock); |
| |
| // OK to call the following multiple times with the same function |
| ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); |
| if (tmpMetaToOlson != NULL) { |
| uhash_close(tmpMetaToOlson); |
| } |
| } |
| |
| UnicodeString& U_EXPORT2 |
| ZoneMeta::getCanonicalSystemID(const UnicodeString &tzid, UnicodeString &systemID, UErrorCode& status) { |
| const CanonicalMapEntry *entry = getCanonicalInfo(tzid); |
| if (entry != NULL) { |
| systemID.setTo(entry->id); |
| } else { |
| status = U_ILLEGAL_ARGUMENT_ERROR; |
| } |
| return systemID; |
| } |
| |
| UnicodeString& U_EXPORT2 |
| ZoneMeta::getCanonicalCountry(const UnicodeString &tzid, UnicodeString &canonicalCountry) { |
| const CanonicalMapEntry *entry = getCanonicalInfo(tzid); |
| if (entry != NULL && entry->country != NULL) { |
| canonicalCountry.setTo(entry->country); |
| } else { |
| // Use the input tzid |
| canonicalCountry.remove(); |
| } |
| return canonicalCountry; |
| } |
| |
| const CanonicalMapEntry* U_EXPORT2 |
| ZoneMeta::getCanonicalInfo(const UnicodeString &tzid) { |
| initializeCanonicalMap(); |
| CanonicalMapEntry *entry = NULL; |
| if (gCanonicalMap != NULL) { |
| UErrorCode status = U_ZERO_ERROR; |
| UChar tzidUChars[ZID_KEY_MAX]; |
| tzid.extract(tzidUChars, ZID_KEY_MAX, status); |
| if (U_SUCCESS(status) && status!=U_STRING_NOT_TERMINATED_WARNING) { |
| entry = (CanonicalMapEntry*)uhash_get(gCanonicalMap, tzidUChars); |
| } |
| } |
| return entry; |
| } |
| |
| UnicodeString& U_EXPORT2 |
| ZoneMeta::getSingleCountry(const UnicodeString &tzid, UnicodeString &country) { |
| UErrorCode status = U_ZERO_ERROR; |
| |
| // Get canonical country for the zone |
| getCanonicalCountry(tzid, country); |
| |
| if (!country.isEmpty()) { |
| UResourceBundle *supplementalDataBundle = ures_openDirect(NULL, gSupplementalData, &status); |
| UResourceBundle *zoneFormatting = ures_getByKey(supplementalDataBundle, gZoneFormattingTag, NULL, &status); |
| UResourceBundle *multizone = ures_getByKey(zoneFormatting, gMultizoneTag, NULL, &status); |
| |
| if (U_SUCCESS(status)) { |
| while (ures_hasNext(multizone)) { |
| int32_t len; |
| const UChar* multizoneCountry = ures_getNextString(multizone, &len, NULL, &status); |
| if (country.compare(multizoneCountry, len) == 0) { |
| // Included in the multizone country list |
| country.remove(); |
| break; |
| } |
| } |
| } |
| |
| ures_close(multizone); |
| ures_close(zoneFormatting); |
| ures_close(supplementalDataBundle); |
| } |
| |
| return country; |
| } |
| |
| UnicodeString& U_EXPORT2 |
| ZoneMeta::getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &result) { |
| UBool isSet = FALSE; |
| const UVector *mappings = getMetazoneMappings(tzid); |
| if (mappings != NULL) { |
| for (int32_t i = 0; i < mappings->size(); i++) { |
| OlsonToMetaMappingEntry *mzm = (OlsonToMetaMappingEntry*)mappings->elementAt(i); |
| if (mzm->from <= date && mzm->to > date) { |
| result.setTo(mzm->mzid, -1); |
| isSet = TRUE; |
| break; |
| } |
| } |
| } |
| if (!isSet) { |
| result.remove(); |
| } |
| return result; |
| } |
| |
| const UVector* U_EXPORT2 |
| ZoneMeta::getMetazoneMappings(const UnicodeString &tzid) { |
| initializeOlsonToMeta(); |
| const UVector *result = NULL; |
| if (gOlsonToMeta != NULL) { |
| UErrorCode status = U_ZERO_ERROR; |
| UChar tzidUChars[ZID_KEY_MAX]; |
| tzid.extract(tzidUChars, ZID_KEY_MAX, status); |
| if (U_SUCCESS(status) && status!=U_STRING_NOT_TERMINATED_WARNING) { |
| result = (UVector*)uhash_get(gOlsonToMeta, tzidUChars); |
| } |
| } |
| return result; |
| } |
| |
| UnicodeString& U_EXPORT2 |
| ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString ®ion, UnicodeString &result) { |
| initializeMetaToOlson(); |
| UBool isSet = FALSE; |
| if (gMetaToOlson != NULL) { |
| UErrorCode status = U_ZERO_ERROR; |
| UChar mzidUChars[ZID_KEY_MAX]; |
| mzid.extract(mzidUChars, ZID_KEY_MAX, status); |
| if (U_SUCCESS(status) && status!=U_STRING_NOT_TERMINATED_WARNING) { |
| UVector *mappings = (UVector*)uhash_get(gMetaToOlson, mzidUChars); |
| if (mappings != NULL) { |
| // Find a preferred time zone for the given region. |
| for (int32_t i = 0; i < mappings->size(); i++) { |
| MetaToOlsonMappingEntry *olsonmap = (MetaToOlsonMappingEntry*)mappings->elementAt(i); |
| if (region.compare(olsonmap->territory, -1) == 0) { |
| result.setTo(olsonmap->id); |
| isSet = TRUE; |
| break; |
| } else if (u_strcmp(olsonmap->territory, gWorld) == 0) { |
| result.setTo(olsonmap->id); |
| isSet = TRUE; |
| } |
| } |
| } |
| } |
| } |
| if (!isSet) { |
| result.remove(); |
| } |
| return result; |
| } |
| |
| |
| U_NAMESPACE_END |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |