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