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 ®ion, 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