2 *******************************************************************************
3 * Copyright (C) 2007-2010, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
8 #include "unicode/utypes.h"
10 #if !UCONFIG_NO_FORMATTING
14 #include "unicode/timezone.h"
15 #include "unicode/ustring.h"
16 #include "unicode/putil.h"
26 static UMTX gZoneMetaLock
= NULL
;
28 // Metazone mapping table
29 static UHashtable
*gOlsonToMeta
= NULL
;
30 static UBool gOlsonToMetaInitialized
= FALSE
;
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
;
41 * Cleanup callback func
43 static UBool U_CALLCONV
zoneMeta_cleanup(void)
45 umtx_destroy(&gZoneMetaLock
);
47 if (gOlsonToMeta
!= NULL
) {
48 uhash_close(gOlsonToMeta
);
51 gOlsonToMetaInitialized
= FALSE
;
53 delete gSingleZoneCountries
;
54 delete gMultiZonesCountries
;
55 gCountryInfoVectorsInitialized
= FALSE
;
61 * Deleter for UChar* string
63 static void U_CALLCONV
64 deleteUCharString(void *obj
) {
65 UChar
*entry
= (UChar
*)obj
;
72 static void U_CALLCONV
73 deleteUVector(void *obj
) {
74 delete (U_NAMESPACE_QUALIFIER UVector
*) obj
;
78 * Deleter for OlsonToMetaMappingEntry
80 static void U_CALLCONV
81 deleteOlsonToMetaMappingEntry(void *obj
) {
82 U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry
*entry
= (U_NAMESPACE_QUALIFIER OlsonToMetaMappingEntry
*)obj
;
90 #define ZID_KEY_MAX 128
92 static const char gMetaZones
[] = "metaZones";
93 static const char gMetazoneInfo
[] = "metazoneInfo";
94 static const char gMapTimezonesTag
[] = "mapTimezones";
96 static const char gTimeZoneTypes
[] = "timezoneTypes";
97 static const char gTypeAliasTag
[] = "typeAlias";
98 static const char gTypeMapTag
[] = "typeMap";
99 static const char gTimezoneTag
[] = "timezone";
101 static const char gWorldTag
[] = "001";
103 static const UChar gWorld
[] = {0x30, 0x30, 0x31, 0x00}; // "001"
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"
110 #define ASCII_DIGIT(c) (((c)>=0x30 && (c)<=0x39) ? (c)-0x30 : -1)
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".
117 parseDate (const UChar
*text
, UErrorCode
&status
) {
118 if (U_FAILURE(status
)) {
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
;
128 int32_t year
= 0, month
= 0, day
= 0, hour
= 0, min
= 0, n
;
132 for (idx
= 0; idx
<= 3 && U_SUCCESS(status
); idx
++) {
133 n
= ASCII_DIGIT((int32_t)text
[idx
]);
137 status
= U_INVALID_FORMAT_ERROR
;
141 for (idx
= 5; idx
<= 6 && U_SUCCESS(status
); idx
++) {
142 n
= ASCII_DIGIT((int32_t)text
[idx
]);
144 month
= 10*month
+ n
;
146 status
= U_INVALID_FORMAT_ERROR
;
150 for (idx
= 8; idx
<= 9 && U_SUCCESS(status
); idx
++) {
151 n
= ASCII_DIGIT((int32_t)text
[idx
]);
155 status
= U_INVALID_FORMAT_ERROR
;
160 for (idx
= 11; idx
<= 12 && U_SUCCESS(status
); idx
++) {
161 n
= ASCII_DIGIT((int32_t)text
[idx
]);
165 status
= U_INVALID_FORMAT_ERROR
;
169 for (idx
= 14; idx
<= 15 && U_SUCCESS(status
); idx
++) {
170 n
= ASCII_DIGIT((int32_t)text
[idx
]);
174 status
= U_INVALID_FORMAT_ERROR
;
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
;
187 UnicodeString
& U_EXPORT2
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
;
196 char id
[ZID_KEY_MAX
];
197 const UChar
* idChars
= tzid
.getBuffer();
199 u_UCharsToChars(idChars
,id
,len
);
200 id
[len
] = (char) 0; // Make sure it is null terminated.
202 // replace '/' with ':'
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
)) {
218 systemID
.setTo(tzid
);
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
)) {
233 systemID
.setTo(alias
);
237 // Dereference the input ID using the tz data
238 const UChar
*derefer
= TimeZone::dereferOlsonLink(tzid
);
239 if (derefer
== NULL
) {
241 status
= U_ILLEGAL_ARGUMENT_ERROR
;
244 len
= u_strlen(derefer
);
245 u_UCharsToChars(derefer
,id
,len
);
246 id
[len
] = (char) 0; // Make sure it is null terminated.
248 // replace '/' with ':'
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
259 tmpStatus
= U_ZERO_ERROR
;
260 const UChar
*alias
= ures_getStringByKey(rb
,id
,NULL
,&tmpStatus
);
261 if (U_SUCCESS(tmpStatus
)) {
263 systemID
.setTo(alias
);
265 systemID
.setTo(derefer
);
274 UnicodeString
& U_EXPORT2
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);
280 canonicalCountry
.remove();
282 return canonicalCountry
;
285 UnicodeString
& U_EXPORT2
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"
295 // Checking the cached results
296 UErrorCode status
= U_ZERO_ERROR
;
298 UMTX_CHECK(&gZoneMetaLock
, gCountryInfoVectorsInitialized
, initialized
);
300 // Create empty vectors
301 umtx_lock(&gZoneMetaLock
);
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
;
309 gMultiZonesCountries
= new UVector(NULL
, uhash_compareUChars
, status
);
310 if (gMultiZonesCountries
== NULL
) {
311 status
= U_MEMORY_ALLOCATION_ERROR
;
314 if (U_SUCCESS(status
)) {
315 gCountryInfoVectorsInitialized
= TRUE
;
317 delete gSingleZoneCountries
;
318 delete gMultiZonesCountries
;
322 umtx_unlock(&gZoneMetaLock
);
324 if (U_FAILURE(status
)) {
330 // Check if it was already cached
331 UBool cached
= FALSE
;
332 UBool multiZones
= FALSE
;
333 umtx_lock(&gZoneMetaLock
);
335 multiZones
= cached
= gMultiZonesCountries
->contains((void*)region
);
337 cached
= gSingleZoneCountries
->contains((void*)region
);
340 umtx_unlock(&gZoneMetaLock
);
343 // We need to go through all zones associated with the region.
344 // This is relatively heavy operation.
346 U_ASSERT(u_strlen(region
) == 2);
348 char buf
[] = {0, 0, 0};
349 u_UCharsToChars(region
, buf
, 2);
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
)) {
365 if (canonical
!= tmp
) {
366 // another canonical zone was found
373 if (U_FAILURE(status
)) {
374 // no single country by default for any error cases
380 umtx_lock(&gZoneMetaLock
);
382 UErrorCode ec
= U_ZERO_ERROR
;
384 if (!gMultiZonesCountries
->contains((void*)region
)) {
385 gMultiZonesCountries
->addElement((void*)region
, ec
);
388 if (!gSingleZoneCountries
->contains((void*)region
)) {
389 gSingleZoneCountries
->addElement((void*)region
, ec
);
393 umtx_unlock(&gZoneMetaLock
);
399 country
.setTo(region
, -1);
404 UnicodeString
& U_EXPORT2
405 ZoneMeta::getMetazoneID(const UnicodeString
&tzid
, UDate date
, UnicodeString
&result
) {
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);
424 const UVector
* U_EXPORT2
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
) {
434 UMTX_CHECK(&gZoneMetaLock
, gOlsonToMetaInitialized
, initialized
);
436 UHashtable
*tmpOlsonToMeta
= uhash_open(uhash_hashUChars
, uhash_compareUChars
, NULL
, &status
);
437 if (U_FAILURE(status
)) {
440 uhash_setKeyDeleter(tmpOlsonToMeta
, deleteUCharString
);
441 uhash_setValueDeleter(tmpOlsonToMeta
, deleteUVector
);
443 umtx_lock(&gZoneMetaLock
);
445 if (!gOlsonToMetaInitialized
) {
446 gOlsonToMeta
= tmpOlsonToMeta
;
447 tmpOlsonToMeta
= NULL
;
448 gOlsonToMetaInitialized
= TRUE
;
451 umtx_unlock(&gZoneMetaLock
);
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
);
460 // get the mapping from cache
461 const UVector
*result
= NULL
;
463 umtx_lock(&gZoneMetaLock
);
465 result
= (UVector
*) uhash_get(gOlsonToMeta
, tzidUChars
);
467 umtx_unlock(&gZoneMetaLock
);
469 if (result
!= NULL
) {
473 // miss the cache - create new one
474 UVector
*tmpResult
= createMetazoneMappings(tzid
);
475 if (tmpResult
== NULL
) {
480 // put the new one into the cache
481 umtx_lock(&gZoneMetaLock
);
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
));
490 // memory allocation error.. just return NULL
494 tzid
.extract(key
, tzidLen
, status
);
495 uhash_put(gOlsonToMeta
, key
, tmpResult
, &status
);
496 if (U_FAILURE(status
)) {
497 // delete the mapping
505 // another thread already put the one
509 umtx_unlock(&gZoneMetaLock
);
515 ZoneMeta::createMetazoneMappings(const UnicodeString
&tzid
) {
516 UVector
*mzMappings
= NULL
;
517 UErrorCode status
= U_ZERO_ERROR
;
519 UnicodeString canonicalID
;
520 UResourceBundle
*rb
= ures_openDirect(NULL
, gMetaZones
, &status
);
521 ures_getByKey(rb
, gMetazoneInfo
, rb
, &status
);
522 TimeZone::getCanonicalID(tzid
, canonicalID
, status
);
524 if (U_SUCCESS(status
)) {
525 char tzKey
[ZID_KEY_MAX
];
526 canonicalID
.extract(0, canonicalID
.length(), tzKey
, sizeof(tzKey
), US_INV
);
528 // tzid keys are using ':' as separators
537 ures_getByKey(rb
, tzKey
, rb
, &status
);
539 if (U_SUCCESS(status
)) {
540 UResourceBundle
*mz
= NULL
;
541 while (ures_hasNext(rb
)) {
542 mz
= ures_getNextResource(rb
, mz
, &status
);
544 const UChar
*mz_name
= ures_getStringByIndex(mz
, 0, NULL
, &status
);
545 const UChar
*mz_from
= gDefaultFrom
;
546 const UChar
*mz_to
= gDefaultTo
;
548 if (ures_getSize(mz
) == 3) {
549 mz_from
= ures_getStringByIndex(mz
, 1, NULL
, &status
);
550 mz_to
= ures_getStringByIndex(mz
, 2, NULL
, &status
);
553 if(U_FAILURE(status
)){
554 status
= U_ZERO_ERROR
;
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
;
567 OlsonToMetaMappingEntry
*entry
= (OlsonToMetaMappingEntry
*)uprv_malloc(sizeof(OlsonToMetaMappingEntry
));
569 status
= U_MEMORY_ALLOCATION_ERROR
;
572 entry
->mzid
= mz_name
;
576 if (mzMappings
== NULL
) {
577 mzMappings
= new UVector(deleteOlsonToMetaMappingEntry
, NULL
, status
);
578 if (U_FAILURE(status
)) {
580 deleteOlsonToMetaMappingEntry(entry
);
586 mzMappings
->addElement(entry
, status
);
587 if (U_FAILURE(status
)) {
592 if (U_FAILURE(status
)) {
593 if (mzMappings
!= NULL
) {
604 UnicodeString
& U_EXPORT2
605 ZoneMeta::getZoneIdByMetazone(const UnicodeString
&mzid
, const UnicodeString
®ion
, UnicodeString
&result
) {
606 UErrorCode status
= U_ZERO_ERROR
;
607 const UChar
*tzid
= NULL
;
609 char keyBuf
[ZID_KEY_MAX
+ 1];
612 if (mzid
.length() >= ZID_KEY_MAX
) {
617 keyLen
= mzid
.extract(0, mzid
.length(), keyBuf
, ZID_KEY_MAX
, US_INV
);
619 UResourceBundle
*rb
= ures_openDirect(NULL
, gMetaZones
, &status
);
620 ures_getByKey(rb
, gMapTimezonesTag
, rb
, &status
);
621 ures_getByKey(rb
, keyBuf
, rb
, &status
);
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
;
632 if (U_SUCCESS(status
) && tzid
== NULL
) {
634 tzid
= ures_getStringByKey(rb
, gWorldTag
, &tzidLen
, &status
);
642 result
.setTo(tzid
, tzidLen
);
650 #endif /* #if !UCONFIG_NO_FORMATTING */