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