• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
26 static UMTX gZoneMetaLock = NULL;
27 
28 // Metazone mapping table
29 static UHashtable *gOlsonToMeta = NULL;
30 static UBool gOlsonToMetaInitialized = FALSE;
31 
32 // Country info vectors
33 static U_NAMESPACE_QUALIFIER UVector *gSingleZoneCountries = NULL;
34 static U_NAMESPACE_QUALIFIER UVector *gMultiZonesCountries = NULL;
35 static UBool gCountryInfoVectorsInitialized = FALSE;
36 
37 U_CDECL_BEGIN
38 
39 
40 /**
41  * Cleanup callback func
42  */
zoneMeta_cleanup(void)43 static 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  */
63 static void U_CALLCONV
deleteUCharString(void * obj)64 deleteUCharString(void *obj) {
65     UChar *entry = (UChar*)obj;
66     uprv_free(entry);
67 }
68 
69 /**
70  * Deleter for UVector
71  */
72 static void U_CALLCONV
deleteUVector(void * obj)73 deleteUVector(void *obj) {
74    delete (U_NAMESPACE_QUALIFIER UVector*) obj;
75 }
76 
77 /**
78  * Deleter for OlsonToMetaMappingEntry
79  */
80 static void U_CALLCONV
deleteOlsonToMetaMappingEntry(void * obj)81 deleteOlsonToMetaMappingEntry(void *obj) {
82     U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry *entry = (U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry*)obj;
83     uprv_free(entry);
84 }
85 
86 U_CDECL_END
87 
88 U_NAMESPACE_BEGIN
89 
90 #define ZID_KEY_MAX 128
91 
92 static const char gMetaZones[]          = "metaZones";
93 static const char gMetazoneInfo[]       = "metazoneInfo";
94 static const char gMapTimezonesTag[]    = "mapTimezones";
95 
96 static const char gTimeZoneTypes[]      = "timezoneTypes";
97 static const char gTypeAliasTag[]       = "typeAlias";
98 static const char gTypeMapTag[]         = "typeMap";
99 static const char gTimezoneTag[]        = "timezone";
100 
101 static const char gWorldTag[]           = "001";
102 
103 static const UChar gWorld[] = {0x30, 0x30, 0x31, 0x00}; // "001"
104 
105 static 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"
107 static 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  */
116 static UDate
parseDate(const UChar * text,UErrorCode & status)117 parseDate (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 
187 UnicodeString& U_EXPORT2
getCanonicalSystemID(const UnicodeString & tzid,UnicodeString & systemID,UErrorCode & status)188 ZoneMeta::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 
274 UnicodeString& U_EXPORT2
getCanonicalCountry(const UnicodeString & tzid,UnicodeString & canonicalCountry)275 ZoneMeta::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 
285 UnicodeString& U_EXPORT2
getSingleCountry(const UnicodeString & tzid,UnicodeString & country)286 ZoneMeta::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 
404 UnicodeString& U_EXPORT2
getMetazoneID(const UnicodeString & tzid,UDate date,UnicodeString & result)405 ZoneMeta::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 
424 const UVector* U_EXPORT2
getMetazoneMappings(const UnicodeString & tzid)425 ZoneMeta::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 
514 UVector*
createMetazoneMappings(const UnicodeString & tzid)515 ZoneMeta::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 
604 UnicodeString& U_EXPORT2
getZoneIdByMetazone(const UnicodeString & mzid,const UnicodeString & region,UnicodeString & result)605 ZoneMeta::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 
648 U_NAMESPACE_END
649 
650 #endif /* #if !UCONFIG_NO_FORMATTING */
651