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
30static UMutex gZoneMetaLock = U_MUTEX_INITIALIZER;
31
32// CLDR Canonical ID mapping table
33static UHashtable *gCanonicalIDCache = NULL;
34static UBool gCanonicalIDCacheInitialized = FALSE;
35
36// Metazone mapping table
37static UHashtable *gOlsonToMeta = NULL;
38static UBool gOlsonToMetaInitialized = FALSE;
39
40// Available metazone IDs vector and table
41static icu::UVector *gMetaZoneIDs = NULL;
42static UHashtable *gMetaZoneIDTable = NULL;
43static UBool gMetaZoneIDsInitialized = FALSE;
44
45// Country info vectors
46static icu::UVector *gSingleZoneCountries = NULL;
47static icu::UVector *gMultiZonesCountries = NULL;
48static UBool gCountryInfoVectorsInitialized = FALSE;
49
50U_CDECL_BEGIN
51
52/**
53 * Cleanup callback func
54 */
55static 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 */
87static void U_CALLCONV
88deleteUCharString(void *obj) {
89    UChar *entry = (UChar*)obj;
90    uprv_free(entry);
91}
92
93/**
94 * Deleter for UVector
95 */
96static void U_CALLCONV
97deleteUVector(void *obj) {
98   delete (icu::UVector*) obj;
99}
100
101/**
102 * Deleter for OlsonToMetaMappingEntry
103 */
104static void U_CALLCONV
105deleteOlsonToMetaMappingEntry(void *obj) {
106    icu::OlsonToMetaMappingEntry *entry = (icu::OlsonToMetaMappingEntry*)obj;
107    uprv_free(entry);
108}
109
110U_CDECL_END
111
112U_NAMESPACE_BEGIN
113
114#define ZID_KEY_MAX 128
115
116static const char gMetaZones[]          = "metaZones";
117static const char gMetazoneInfo[]       = "metazoneInfo";
118static const char gMapTimezonesTag[]    = "mapTimezones";
119
120static const char gKeyTypeData[]        = "keyTypeData";
121static const char gTypeAliasTag[]       = "typeAlias";
122static const char gTypeMapTag[]         = "typeMap";
123static const char gTimezoneTag[]        = "timezone";
124
125static const char gPrimaryZonesTag[]    = "primaryZones";
126
127static const char gWorldTag[]           = "001";
128
129static const UChar gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001"
130
131static 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"
133static 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
136static 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 */
144static UDate
145parseDate (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
215const UChar* U_EXPORT2
216ZoneMeta::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
373UnicodeString& U_EXPORT2
374ZoneMeta::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
384const UChar* U_EXPORT2
385ZoneMeta::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
396UnicodeString& U_EXPORT2
397ZoneMeta::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
529UnicodeString& U_EXPORT2
530ZoneMeta::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
549const UVector* U_EXPORT2
550ZoneMeta::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
639UVector*
640ZoneMeta::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
730UnicodeString& U_EXPORT2
731ZoneMeta::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
776void
777ZoneMeta::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
847const UVector*
848ZoneMeta::getAvailableMetazoneIDs() {
849    initAvailableMetaZoneIDs();
850    return gMetaZoneIDs;
851}
852
853const UChar*
854ZoneMeta::findMetaZoneID(const UnicodeString& mzid) {
855    initAvailableMetaZoneIDs();
856    return (const UChar*)uhash_get(gMetaZoneIDTable, &mzid);
857}
858
859const UChar*
860ZoneMeta::findTimeZoneID(const UnicodeString& tzid) {
861    return TimeZone::findID(tzid);
862}
863
864
865TimeZone*
866ZoneMeta::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
886UnicodeString&
887ZoneMeta::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
911const UChar*
912ZoneMeta::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
925const UChar*
926ZoneMeta::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
935const UChar*
936ZoneMeta::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
962U_NAMESPACE_END
963
964#endif /* #if !UCONFIG_NO_FORMATTING */
965