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