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