1/*
2*******************************************************************************
3* Copyright (C) 2007-2010, 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
18#include "umutex.h"
19#include "uvector.h"
20#include "cmemory.h"
21#include "gregoimp.h"
22#include "cstring.h"
23#include "ucln_in.h"
24#include "uassert.h"
25
26static UMTX gZoneMetaLock = NULL;
27
28// Metazone mapping table
29static UHashtable *gOlsonToMeta = NULL;
30static UBool gOlsonToMetaInitialized = FALSE;
31
32// Country info vectors
33static U_NAMESPACE_QUALIFIER UVector *gSingleZoneCountries = NULL;
34static U_NAMESPACE_QUALIFIER UVector *gMultiZonesCountries = NULL;
35static UBool gCountryInfoVectorsInitialized = FALSE;
36
37U_CDECL_BEGIN
38
39
40/**
41 * Cleanup callback func
42 */
43static UBool U_CALLCONV zoneMeta_cleanup(void)
44{
45     umtx_destroy(&gZoneMetaLock);
46
47    if (gOlsonToMeta != NULL) {
48        uhash_close(gOlsonToMeta);
49        gOlsonToMeta = NULL;
50    }
51    gOlsonToMetaInitialized = FALSE;
52
53    delete gSingleZoneCountries;
54    delete gMultiZonesCountries;
55    gCountryInfoVectorsInitialized = FALSE;
56
57    return TRUE;
58}
59
60/**
61 * Deleter for UChar* string
62 */
63static void U_CALLCONV
64deleteUCharString(void *obj) {
65    UChar *entry = (UChar*)obj;
66    uprv_free(entry);
67}
68
69/**
70 * Deleter for UVector
71 */
72static void U_CALLCONV
73deleteUVector(void *obj) {
74   delete (U_NAMESPACE_QUALIFIER UVector*) obj;
75}
76
77/**
78 * Deleter for OlsonToMetaMappingEntry
79 */
80static void U_CALLCONV
81deleteOlsonToMetaMappingEntry(void *obj) {
82    U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry *entry = (U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry*)obj;
83    uprv_free(entry);
84}
85
86U_CDECL_END
87
88U_NAMESPACE_BEGIN
89
90#define ZID_KEY_MAX 128
91
92static const char gMetaZones[]          = "metaZones";
93static const char gMetazoneInfo[]       = "metazoneInfo";
94static const char gMapTimezonesTag[]    = "mapTimezones";
95
96static const char gTimeZoneTypes[]      = "timezoneTypes";
97static const char gTypeAliasTag[]       = "typeAlias";
98static const char gTypeMapTag[]         = "typeMap";
99static const char gTimezoneTag[]        = "timezone";
100
101static const char gWorldTag[]           = "001";
102
103static const UChar gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001"
104
105static const UChar gDefaultFrom[] = {0x31, 0x39, 0x37, 0x30, 0x2D, 0x30, 0x31, 0x2D, 0x30, 0x31,
106                                     0x20, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x00}; // "1970-01-01 00:00"
107static const UChar gDefaultTo[]   = {0x39, 0x39, 0x39, 0x39, 0x2D, 0x31, 0x32, 0x2D, 0x33, 0x31,
108                                     0x20, 0x32, 0x33, 0x3A, 0x35, 0x39, 0x00}; // "9999-12-31 23:59"
109
110#define ASCII_DIGIT(c) (((c)>=0x30 && (c)<=0x39) ? (c)-0x30 : -1)
111
112/*
113 * Convert a date string used by metazone mappings to UDate.
114 * The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm".
115 */
116static UDate
117parseDate (const UChar *text, UErrorCode &status) {
118    if (U_FAILURE(status)) {
119        return 0;
120    }
121    int32_t len = u_strlen(text);
122    if (len != 16 && len != 10) {
123        // It must be yyyy-MM-dd HH:mm (length 16) or yyyy-MM-dd (length 10)
124        status = U_INVALID_FORMAT_ERROR;
125        return 0;
126    }
127
128    int32_t year = 0, month = 0, day = 0, hour = 0, min = 0, n;
129    int32_t idx;
130
131    // "yyyy" (0 - 3)
132    for (idx = 0; idx <= 3 && U_SUCCESS(status); idx++) {
133        n = ASCII_DIGIT((int32_t)text[idx]);
134        if (n >= 0) {
135            year = 10*year + n;
136        } else {
137            status = U_INVALID_FORMAT_ERROR;
138        }
139    }
140    // "MM" (5 - 6)
141    for (idx = 5; idx <= 6 && U_SUCCESS(status); idx++) {
142        n = ASCII_DIGIT((int32_t)text[idx]);
143        if (n >= 0) {
144            month = 10*month + n;
145        } else {
146            status = U_INVALID_FORMAT_ERROR;
147        }
148    }
149    // "dd" (8 - 9)
150    for (idx = 8; idx <= 9 && U_SUCCESS(status); idx++) {
151        n = ASCII_DIGIT((int32_t)text[idx]);
152        if (n >= 0) {
153            day = 10*day + n;
154        } else {
155            status = U_INVALID_FORMAT_ERROR;
156        }
157    }
158    if (len == 16) {
159        // "HH" (11 - 12)
160        for (idx = 11; idx <= 12 && U_SUCCESS(status); idx++) {
161            n = ASCII_DIGIT((int32_t)text[idx]);
162            if (n >= 0) {
163                hour = 10*hour + n;
164            } else {
165                status = U_INVALID_FORMAT_ERROR;
166            }
167        }
168        // "mm" (14 - 15)
169        for (idx = 14; idx <= 15 && U_SUCCESS(status); idx++) {
170            n = ASCII_DIGIT((int32_t)text[idx]);
171            if (n >= 0) {
172                min = 10*min + n;
173            } else {
174                status = U_INVALID_FORMAT_ERROR;
175            }
176        }
177    }
178
179    if (U_SUCCESS(status)) {
180        UDate date = Grego::fieldsToDay(year, month - 1, day) * U_MILLIS_PER_DAY
181            + hour * U_MILLIS_PER_HOUR + min * U_MILLIS_PER_MINUTE;
182        return date;
183    }
184    return 0;
185}
186
187UnicodeString& U_EXPORT2
188ZoneMeta::getCanonicalSystemID(const UnicodeString &tzid, UnicodeString &systemID, UErrorCode& status) {
189    int32_t len = tzid.length();
190    if ( len >= ZID_KEY_MAX ) {
191        status = U_ILLEGAL_ARGUMENT_ERROR;
192        systemID.remove();
193        return systemID;
194    }
195
196    char id[ZID_KEY_MAX];
197    const UChar* idChars = tzid.getBuffer();
198
199    u_UCharsToChars(idChars,id,len);
200    id[len] = (char) 0; // Make sure it is null terminated.
201
202    // replace '/' with ':'
203    char *p = id;
204    while (*p++) {
205        if (*p == '/') {
206            *p = ':';
207        }
208    }
209
210
211    UErrorCode tmpStatus = U_ZERO_ERROR;
212    UResourceBundle *top = ures_openDirect(NULL, gTimeZoneTypes, &tmpStatus);
213    UResourceBundle *rb = ures_getByKey(top, gTypeMapTag, NULL, &tmpStatus);
214    ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus);
215    ures_getByKey(rb, id, rb, &tmpStatus);
216    if (U_SUCCESS(tmpStatus)) {
217        // direct map found
218        systemID.setTo(tzid);
219        ures_close(rb);
220        ures_close(top);
221        return systemID;
222    }
223
224    // If a map element not found, then look for an alias
225    tmpStatus = U_ZERO_ERROR;
226    ures_getByKey(top, gTypeAliasTag, rb, &tmpStatus);
227    ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus);
228    const UChar *alias = ures_getStringByKey(rb,id,NULL,&tmpStatus);
229    if (U_SUCCESS(tmpStatus)) {
230        // alias found
231        ures_close(rb);
232        ures_close(top);
233        systemID.setTo(alias);
234        return systemID;
235    }
236
237    // Dereference the input ID using the tz data
238    const UChar *derefer = TimeZone::dereferOlsonLink(tzid);
239    if (derefer == NULL) {
240        systemID.remove();
241        status = U_ILLEGAL_ARGUMENT_ERROR;
242    } else {
243
244        len = u_strlen(derefer);
245        u_UCharsToChars(derefer,id,len);
246        id[len] = (char) 0; // Make sure it is null terminated.
247
248        // replace '/' with ':'
249        char *p = id;
250        while (*p++) {
251            if (*p == '/') {
252                *p = ':';
253            }
254        }
255
256        // If a dereference turned something up then look for an alias.
257        // rb still points to the alias table, so we don't have to go looking
258        // for it.
259        tmpStatus = U_ZERO_ERROR;
260        const UChar *alias = ures_getStringByKey(rb,id,NULL,&tmpStatus);
261        if (U_SUCCESS(tmpStatus)) {
262            // alias found
263            systemID.setTo(alias);
264        } else {
265            systemID.setTo(derefer);
266        }
267    }
268
269     ures_close(rb);
270     ures_close(top);
271     return systemID;
272}
273
274UnicodeString& U_EXPORT2
275ZoneMeta::getCanonicalCountry(const UnicodeString &tzid, UnicodeString &canonicalCountry) {
276    const UChar *region = TimeZone::getRegion(tzid);
277    if (u_strcmp(gWorld, region) != 0) {
278        canonicalCountry.setTo(region, -1);
279    } else {
280        canonicalCountry.remove();
281    }
282    return canonicalCountry;
283}
284
285UnicodeString& U_EXPORT2
286ZoneMeta::getSingleCountry(const UnicodeString &tzid, UnicodeString &country) {
287    // Get canonical country for the zone
288    const UChar *region = TimeZone::getRegion(tzid);
289    if (u_strcmp(gWorld, region) == 0) {
290        // special case - "001"
291        country.remove();
292        return country;
293    }
294
295    // Checking the cached results
296    UErrorCode status = U_ZERO_ERROR;
297    UBool initialized;
298    UMTX_CHECK(&gZoneMetaLock, gCountryInfoVectorsInitialized, initialized);
299    if (!initialized) {
300        // Create empty vectors
301        umtx_lock(&gZoneMetaLock);
302        {
303            if (!gCountryInfoVectorsInitialized) {
304                // No deleters for these UVectors, it's a reference to a resource bundle string.
305                gSingleZoneCountries = new UVector(NULL, uhash_compareUChars, status);
306                if (gSingleZoneCountries == NULL) {
307                    status = U_MEMORY_ALLOCATION_ERROR;
308                }
309                gMultiZonesCountries = new UVector(NULL, uhash_compareUChars, status);
310                if (gMultiZonesCountries == NULL) {
311                    status = U_MEMORY_ALLOCATION_ERROR;
312                }
313
314                if (U_SUCCESS(status)) {
315                    gCountryInfoVectorsInitialized = TRUE;
316                } else {
317                    delete gSingleZoneCountries;
318                    delete gMultiZonesCountries;
319                }
320            }
321        }
322        umtx_unlock(&gZoneMetaLock);
323
324        if (U_FAILURE(status)) {
325            country.remove();
326            return country;
327        }
328    }
329
330    // Check if it was already cached
331    UBool cached = FALSE;
332    UBool multiZones = FALSE;
333    umtx_lock(&gZoneMetaLock);
334    {
335        multiZones = cached = gMultiZonesCountries->contains((void*)region);
336        if (!multiZones) {
337            cached = gSingleZoneCountries->contains((void*)region);
338        }
339    }
340    umtx_unlock(&gZoneMetaLock);
341
342    if (!cached) {
343        // We need to go through all zones associated with the region.
344        // This is relatively heavy operation.
345
346        U_ASSERT(u_strlen(region) == 2);
347
348        char buf[] = {0, 0, 0};
349        u_UCharsToChars(region, buf, 2);
350
351        StringEnumeration *ids = TimeZone::createEnumeration(buf);
352        int32_t idsLen = ids->count(status);
353        if (U_SUCCESS(status) && idsLen > 1) {
354            // multiple zones are available for the region
355            UnicodeString canonical, tmp;
356            const UnicodeString *id = ids->snext(status);
357            getCanonicalSystemID(*id, canonical, status);
358            if (U_SUCCESS(status)) {
359                // check if there are any other canonical zone in the group
360                while ((id = ids->snext(status))!=NULL) {
361                    getCanonicalSystemID(*id, tmp, status);
362                    if (U_FAILURE(status)) {
363                        break;
364                    }
365                    if (canonical != tmp) {
366                        // another canonical zone was found
367                        multiZones = TRUE;
368                        break;
369                    }
370                }
371            }
372        }
373        if (U_FAILURE(status)) {
374            // no single country by default for any error cases
375            multiZones = TRUE;
376        }
377        delete ids;
378
379        // Cache the result
380        umtx_lock(&gZoneMetaLock);
381        {
382            UErrorCode ec = U_ZERO_ERROR;
383            if (multiZones) {
384                if (!gMultiZonesCountries->contains((void*)region)) {
385                    gMultiZonesCountries->addElement((void*)region, ec);
386                }
387            } else {
388                if (!gSingleZoneCountries->contains((void*)region)) {
389                    gSingleZoneCountries->addElement((void*)region, ec);
390                }
391            }
392        }
393        umtx_unlock(&gZoneMetaLock);
394    }
395
396    if (multiZones) {
397        country.remove();
398    } else {
399        country.setTo(region, -1);
400    }
401    return country;
402}
403
404UnicodeString& U_EXPORT2
405ZoneMeta::getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &result) {
406    UBool isSet = FALSE;
407    const UVector *mappings = getMetazoneMappings(tzid);
408    if (mappings != NULL) {
409        for (int32_t i = 0; i < mappings->size(); i++) {
410            OlsonToMetaMappingEntry *mzm = (OlsonToMetaMappingEntry*)mappings->elementAt(i);
411            if (mzm->from <= date && mzm->to > date) {
412                result.setTo(mzm->mzid, -1);
413                isSet = TRUE;
414                break;
415            }
416        }
417    }
418    if (!isSet) {
419        result.remove();
420    }
421    return result;
422}
423
424const UVector* U_EXPORT2
425ZoneMeta::getMetazoneMappings(const UnicodeString &tzid) {
426    UErrorCode status = U_ZERO_ERROR;
427    UChar tzidUChars[ZID_KEY_MAX];
428    tzid.extract(tzidUChars, ZID_KEY_MAX, status);
429    if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) {
430        return NULL;
431    }
432
433    UBool initialized;
434    UMTX_CHECK(&gZoneMetaLock, gOlsonToMetaInitialized, initialized);
435    if (!initialized) {
436        UHashtable *tmpOlsonToMeta = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status);
437        if (U_FAILURE(status)) {
438            return NULL;
439        }
440        uhash_setKeyDeleter(tmpOlsonToMeta, deleteUCharString);
441        uhash_setValueDeleter(tmpOlsonToMeta, deleteUVector);
442
443        umtx_lock(&gZoneMetaLock);
444        {
445            if (!gOlsonToMetaInitialized) {
446                gOlsonToMeta = tmpOlsonToMeta;
447                tmpOlsonToMeta = NULL;
448                gOlsonToMetaInitialized = TRUE;
449            }
450        }
451        umtx_unlock(&gZoneMetaLock);
452
453        // OK to call the following multiple times with the same function
454        ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup);
455        if (tmpOlsonToMeta != NULL) {
456            uhash_close(tmpOlsonToMeta);
457        }
458    }
459
460    // get the mapping from cache
461    const UVector *result = NULL;
462
463    umtx_lock(&gZoneMetaLock);
464    {
465        result = (UVector*) uhash_get(gOlsonToMeta, tzidUChars);
466    }
467    umtx_unlock(&gZoneMetaLock);
468
469    if (result != NULL) {
470        return result;
471    }
472
473    // miss the cache - create new one
474    UVector *tmpResult = createMetazoneMappings(tzid);
475    if (tmpResult == NULL) {
476        // not available
477        return NULL;
478    }
479
480    // put the new one into the cache
481    umtx_lock(&gZoneMetaLock);
482    {
483        // make sure it's already created
484        result = (UVector*) uhash_get(gOlsonToMeta, tzidUChars);
485        if (result == NULL) {
486            // add the one just created
487            int32_t tzidLen = tzid.length() + 1;
488            UChar *key = (UChar*)uprv_malloc(tzidLen * sizeof(UChar));
489            if (key == NULL) {
490                // memory allocation error..  just return NULL
491                result = NULL;
492                delete tmpResult;
493            } else {
494                tzid.extract(key, tzidLen, status);
495                uhash_put(gOlsonToMeta, key, tmpResult, &status);
496                if (U_FAILURE(status)) {
497                    // delete the mapping
498                    result = NULL;
499                    delete tmpResult;
500                } else {
501                    result = tmpResult;
502                }
503            }
504        } else {
505            // another thread already put the one
506            delete tmpResult;
507        }
508    }
509    umtx_unlock(&gZoneMetaLock);
510
511    return result;
512}
513
514UVector*
515ZoneMeta::createMetazoneMappings(const UnicodeString &tzid) {
516    UVector *mzMappings = NULL;
517    UErrorCode status = U_ZERO_ERROR;
518
519    UnicodeString canonicalID;
520    UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status);
521    ures_getByKey(rb, gMetazoneInfo, rb, &status);
522    TimeZone::getCanonicalID(tzid, canonicalID, status);
523
524    if (U_SUCCESS(status)) {
525        char tzKey[ZID_KEY_MAX];
526        canonicalID.extract(0, canonicalID.length(), tzKey, sizeof(tzKey), US_INV);
527
528        // tzid keys are using ':' as separators
529        char *p = tzKey;
530        while (*p) {
531            if (*p == '/') {
532                *p = ':';
533            }
534            p++;
535        }
536
537        ures_getByKey(rb, tzKey, rb, &status);
538
539        if (U_SUCCESS(status)) {
540            UResourceBundle *mz = NULL;
541            while (ures_hasNext(rb)) {
542                mz = ures_getNextResource(rb, mz, &status);
543
544                const UChar *mz_name = ures_getStringByIndex(mz, 0, NULL, &status);
545                const UChar *mz_from = gDefaultFrom;
546                const UChar *mz_to = gDefaultTo;
547
548                if (ures_getSize(mz) == 3) {
549                    mz_from = ures_getStringByIndex(mz, 1, NULL, &status);
550                    mz_to   = ures_getStringByIndex(mz, 2, NULL, &status);
551                }
552
553                if(U_FAILURE(status)){
554                    status = U_ZERO_ERROR;
555                    continue;
556                }
557                // We do not want to use SimpleDateformat to parse boundary dates,
558                // because this code could be triggered by the initialization code
559                // used by SimpleDateFormat.
560                UDate from = parseDate(mz_from, status);
561                UDate to = parseDate(mz_to, status);
562                if (U_FAILURE(status)) {
563                    status = U_ZERO_ERROR;
564                    continue;
565                }
566
567                OlsonToMetaMappingEntry *entry = (OlsonToMetaMappingEntry*)uprv_malloc(sizeof(OlsonToMetaMappingEntry));
568                if (entry == NULL) {
569                    status = U_MEMORY_ALLOCATION_ERROR;
570                    break;
571                }
572                entry->mzid = mz_name;
573                entry->from = from;
574                entry->to = to;
575
576                if (mzMappings == NULL) {
577                    mzMappings = new UVector(deleteOlsonToMetaMappingEntry, NULL, status);
578                    if (U_FAILURE(status)) {
579                        delete mzMappings;
580                        deleteOlsonToMetaMappingEntry(entry);
581                        uprv_free(entry);
582                        break;
583                    }
584                }
585
586                mzMappings->addElement(entry, status);
587                if (U_FAILURE(status)) {
588                    break;
589                }
590            }
591            ures_close(mz);
592            if (U_FAILURE(status)) {
593                if (mzMappings != NULL) {
594                    delete mzMappings;
595                    mzMappings = NULL;
596                }
597            }
598        }
599    }
600    ures_close(rb);
601    return mzMappings;
602}
603
604UnicodeString& U_EXPORT2
605ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString &region, UnicodeString &result) {
606    UErrorCode status = U_ZERO_ERROR;
607    const UChar *tzid = NULL;
608    int32_t tzidLen = 0;
609    char keyBuf[ZID_KEY_MAX + 1];
610    int32_t keyLen = 0;
611
612    if (mzid.length() >= ZID_KEY_MAX) {
613        result.remove();
614        return result;
615    }
616
617    keyLen = mzid.extract(0, mzid.length(), keyBuf, ZID_KEY_MAX, US_INV);
618
619    UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status);
620    ures_getByKey(rb, gMapTimezonesTag, rb, &status);
621    ures_getByKey(rb, keyBuf, rb, &status);
622
623    if (U_SUCCESS(status)) {
624        // check region mapping
625        if (region.length() == 2 || region.length() == 3) {
626            region.extract(0, region.length(), keyBuf, ZID_KEY_MAX, US_INV);
627            tzid = ures_getStringByKey(rb, keyBuf, &tzidLen, &status);
628            if (status == U_MISSING_RESOURCE_ERROR) {
629                status = U_ZERO_ERROR;
630            }
631        }
632        if (U_SUCCESS(status) && tzid == NULL) {
633            // try "001"
634            tzid = ures_getStringByKey(rb, gWorldTag, &tzidLen, &status);
635        }
636    }
637    ures_close(rb);
638
639    if (tzid == NULL) {
640        result.remove();
641    } else {
642        result.setTo(tzid, tzidLen);
643    }
644
645    return result;
646}
647
648U_NAMESPACE_END
649
650#endif /* #if !UCONFIG_NO_FORMATTING */
651