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