tzgnames.cpp revision b26ce3a7367e4ed2ee7ddddcdc3f3d3377a455c2
1/* 2******************************************************************************* 3* Copyright (C) 2011, 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 "tzgnames.h" 13 14#include "unicode/basictz.h" 15#include "unicode/locdspnm.h" 16#include "unicode/msgfmt.h" 17#include "unicode/rbtz.h" 18#include "unicode/simpletz.h" 19#include "unicode/vtzone.h" 20 21#include "cmemory.h" 22#include "cstring.h" 23#include "uhash.h" 24#include "uassert.h" 25#include "umutex.h" 26#include "uresimp.h" 27#include "ureslocs.h" 28#include "zonemeta.h" 29#include "tznames_impl.h" 30#include "olsontz.h" 31 32U_NAMESPACE_BEGIN 33 34#define ZID_KEY_MAX 128 35 36static const char gZoneStrings[] = "zoneStrings"; 37 38static const char gRegionFormatTag[] = "regionFormat"; 39static const char gFallbackRegionFormatTag[] = "fallbackRegionFormat"; 40static const char gFallbackFormatTag[] = "fallbackFormat"; 41 42static const UChar gEmpty[] = {0x00}; 43 44static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}" 45static const UChar gDefFallbackRegionPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})" 46static const UChar gDefFallbackPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})" 47 48static const double kDstCheckRange = (double)184*U_MILLIS_PER_DAY; 49 50 51 52U_CDECL_BEGIN 53 54typedef struct PartialLocationKey { 55 const UChar* tzID; 56 const UChar* mzID; 57 UBool isLong; 58} PartialLocationKey; 59 60/** 61 * Hash function for partial location name hash key 62 */ 63static int32_t U_CALLCONV 64hashPartialLocationKey(const UHashTok key) { 65 // <tzID>&<mzID>#[L|S] 66 PartialLocationKey *p = (PartialLocationKey *)key.pointer; 67 UnicodeString str(p->tzID); 68 str.append((UChar)0x26) 69 .append(p->mzID) 70 .append((UChar)0x23) 71 .append((UChar)(p->isLong ? 0x4C : 0x53)); 72 return uhash_hashUCharsN(str.getBuffer(), str.length()); 73} 74 75/** 76 * Comparer for partial location name hash key 77 */ 78static UBool U_CALLCONV 79comparePartialLocationKey(const UHashTok key1, const UHashTok key2) { 80 PartialLocationKey *p1 = (PartialLocationKey *)key1.pointer; 81 PartialLocationKey *p2 = (PartialLocationKey *)key2.pointer; 82 83 if (p1 == p2) { 84 return TRUE; 85 } 86 if (p1 == NULL || p2 == NULL) { 87 return FALSE; 88 } 89 // We just check identity of tzID/mzID 90 return (p1->tzID == p2->tzID && p1->mzID == p2->mzID && p1->isLong == p2->isLong); 91} 92 93/** 94 * Deleter for GNameInfo 95 */ 96static void U_CALLCONV 97deleteGNameInfo(void *obj) { 98 uprv_free(obj); 99} 100 101/** 102 * GNameInfo stores zone name information in the local trie 103 */ 104typedef struct GNameInfo { 105 UTimeZoneGenericNameType type; 106 const UChar* tzID; 107} ZNameInfo; 108 109/** 110 * GMatchInfo stores zone name match information used by find method 111 */ 112typedef struct GMatchInfo { 113 const GNameInfo* gnameInfo; 114 int32_t matchLength; 115 UTimeZoneTimeType timeType; 116} ZMatchInfo; 117 118U_CDECL_END 119 120// --------------------------------------------------- 121// The class stores time zone generic name match information 122// --------------------------------------------------- 123TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector* matches) 124: fMatches(matches) { 125} 126 127TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() { 128 if (fMatches != NULL) { 129 delete fMatches; 130 } 131} 132 133int32_t 134TimeZoneGenericNameMatchInfo::size() const { 135 if (fMatches == NULL) { 136 return 0; 137 } 138 return fMatches->size(); 139} 140 141UTimeZoneGenericNameType 142TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index) const { 143 GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index); 144 if (minfo != NULL) { 145 return static_cast<UTimeZoneGenericNameType>(minfo->gnameInfo->type); 146 } 147 return UTZGNM_UNKNOWN; 148} 149 150int32_t 151TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index) const { 152 ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index); 153 if (minfo != NULL) { 154 return minfo->matchLength; 155 } 156 return -1; 157} 158 159UnicodeString& 160TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index, UnicodeString& tzID) const { 161 GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index); 162 if (minfo != NULL && minfo->gnameInfo->tzID != NULL) { 163 tzID.setTo(TRUE, minfo->gnameInfo->tzID, -1); 164 } else { 165 tzID.setToBogus(); 166 } 167 return tzID; 168} 169 170// --------------------------------------------------- 171// GNameSearchHandler 172// --------------------------------------------------- 173class GNameSearchHandler : public TextTrieMapSearchResultHandler { 174public: 175 GNameSearchHandler(uint32_t types); 176 virtual ~GNameSearchHandler(); 177 178 UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status); 179 UVector* getMatches(int32_t& maxMatchLen); 180 181private: 182 uint32_t fTypes; 183 UVector* fResults; 184 int32_t fMaxMatchLen; 185}; 186 187GNameSearchHandler::GNameSearchHandler(uint32_t types) 188: fTypes(types), fResults(NULL), fMaxMatchLen(0) { 189} 190 191GNameSearchHandler::~GNameSearchHandler() { 192 if (fResults != NULL) { 193 delete fResults; 194 } 195} 196 197UBool 198GNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) { 199 if (U_FAILURE(status)) { 200 return FALSE; 201 } 202 if (node->hasValues()) { 203 int32_t valuesCount = node->countValues(); 204 for (int32_t i = 0; i < valuesCount; i++) { 205 GNameInfo *nameinfo = (ZNameInfo *)node->getValue(i); 206 if (nameinfo == NULL) { 207 break; 208 } 209 if ((nameinfo->type & fTypes) != 0) { 210 // matches a requested type 211 if (fResults == NULL) { 212 fResults = new UVector(uhash_freeBlock, NULL, status); 213 if (fResults == NULL) { 214 status = U_MEMORY_ALLOCATION_ERROR; 215 } 216 } 217 if (U_SUCCESS(status)) { 218 GMatchInfo *gmatch = (GMatchInfo *)uprv_malloc(sizeof(GMatchInfo)); 219 if (gmatch == NULL) { 220 status = U_MEMORY_ALLOCATION_ERROR; 221 } else { 222 // add the match to the vector 223 gmatch->gnameInfo = nameinfo; 224 gmatch->matchLength = matchLength; 225 gmatch->timeType = UTZFMT_TIME_TYPE_UNKNOWN; 226 fResults->addElement(gmatch, status); 227 if (U_FAILURE(status)) { 228 uprv_free(gmatch); 229 } else { 230 if (matchLength > fMaxMatchLen) { 231 fMaxMatchLen = matchLength; 232 } 233 } 234 } 235 } 236 } 237 } 238 } 239 return TRUE; 240} 241 242UVector* 243GNameSearchHandler::getMatches(int32_t& maxMatchLen) { 244 // give the ownership to the caller 245 UVector *results = fResults; 246 maxMatchLen = fMaxMatchLen; 247 248 // reset 249 fResults = NULL; 250 fMaxMatchLen = 0; 251 return results; 252} 253 254// --------------------------------------------------- 255// TimeZoneGenericNames 256// 257// TimeZoneGenericNames is parallel to TimeZoneNames, 258// but handles run-time generated time zone names. 259// This is the main part of this module. 260// --------------------------------------------------- 261TimeZoneGenericNames::TimeZoneGenericNames(const Locale& locale, UErrorCode& status) 262: fLocale(locale), 263 fLock(NULL), 264 fTimeZoneNames(NULL), 265 fLocationNamesMap(NULL), 266 fPartialLocationNamesMap(NULL), 267 fRegionFormat(NULL), 268 fFallbackRegionFormat(NULL), 269 fFallbackFormat(NULL), 270 fLocaleDisplayNames(NULL), 271 fStringPool(status), 272 fGNamesTrie(TRUE, deleteGNameInfo), 273 fGNamesTrieFullyLoaded(FALSE) { 274 initialize(locale, status); 275} 276 277TimeZoneGenericNames::~TimeZoneGenericNames() { 278 cleanup(); 279 umtx_destroy(&fLock); 280} 281 282void 283TimeZoneGenericNames::initialize(const Locale& locale, UErrorCode& status) { 284 if (U_FAILURE(status)) { 285 return; 286 } 287 288 // TimeZoneNames 289 fTimeZoneNames = TimeZoneNames::createInstance(locale, status); 290 if (U_FAILURE(status)) { 291 return; 292 } 293 294 // Initialize format patterns 295 UnicodeString rpat(TRUE, gDefRegionPattern, -1); 296 UnicodeString frpat(TRUE, gDefFallbackRegionPattern, -1); 297 UnicodeString fpat(TRUE, gDefFallbackPattern, -1); 298 299 UErrorCode tmpsts = U_ZERO_ERROR; // OK with fallback warning.. 300 UResourceBundle *zoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts); 301 zoneStrings = ures_getByKeyWithFallback(zoneStrings, gZoneStrings, zoneStrings, &tmpsts); 302 303 if (U_SUCCESS(tmpsts)) { 304 const UChar *regionPattern = ures_getStringByKeyWithFallback(zoneStrings, gRegionFormatTag, NULL, &tmpsts); 305 if (U_SUCCESS(tmpsts) && u_strlen(regionPattern) > 0) { 306 rpat.setTo(regionPattern); 307 } 308 tmpsts = U_ZERO_ERROR; 309 const UChar *fallbackRegionPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackRegionFormatTag, NULL, &tmpsts); 310 if (U_SUCCESS(tmpsts) && u_strlen(fallbackRegionPattern) > 0) { 311 frpat.setTo(fallbackRegionPattern); 312 } 313 tmpsts = U_ZERO_ERROR; 314 const UChar *fallbackPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackFormatTag, NULL, &tmpsts); 315 if (U_SUCCESS(tmpsts) && u_strlen(fallbackPattern) > 0) { 316 fpat.setTo(fallbackPattern); 317 } 318 } 319 ures_close(zoneStrings); 320 321 fRegionFormat = new MessageFormat(rpat, status); 322 if (fRegionFormat == NULL) { 323 status = U_MEMORY_ALLOCATION_ERROR; 324 } 325 fFallbackRegionFormat = new MessageFormat(frpat, status); 326 if (fFallbackRegionFormat == NULL) { 327 status = U_MEMORY_ALLOCATION_ERROR; 328 } 329 fFallbackFormat = new MessageFormat(fpat, status); 330 if (fFallbackFormat == NULL) { 331 status = U_MEMORY_ALLOCATION_ERROR; 332 } 333 if (U_FAILURE(status)) { 334 cleanup(); 335 return; 336 } 337 338 // locale display names 339 fLocaleDisplayNames = LocaleDisplayNames::createInstance(locale); 340 341 // hash table for names - no key/value deleters 342 fLocationNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); 343 if (U_FAILURE(status)) { 344 cleanup(); 345 return; 346 } 347 348 fPartialLocationNamesMap = uhash_open(hashPartialLocationKey, comparePartialLocationKey, NULL, &status); 349 if (U_FAILURE(status)) { 350 cleanup(); 351 return; 352 } 353 uhash_setKeyDeleter(fPartialLocationNamesMap, uhash_freeBlock); 354 // no value deleter 355 356 // target region 357 const char* region = fLocale.getCountry(); 358 int32_t regionLen = uprv_strlen(region); 359 if (regionLen == 0) { 360 char loc[ULOC_FULLNAME_CAPACITY]; 361 uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status); 362 363 regionLen = uloc_getCountry(loc, fTargetRegion, sizeof(fTargetRegion), &status); 364 if (U_SUCCESS(status)) { 365 fTargetRegion[regionLen] = 0; 366 } else { 367 cleanup(); 368 return; 369 } 370 } else if (regionLen < (int32_t)sizeof(fTargetRegion)) { 371 uprv_strcpy(fTargetRegion, region); 372 } else { 373 fTargetRegion[0] = 0; 374 } 375 376 // preload generic names for the default zone 377 TimeZone *tz = TimeZone::createDefault(); 378 const UChar *tzID = ZoneMeta::getCanonicalCLDRID(*tz); 379 if (tzID != NULL) { 380 loadStrings(UnicodeString(tzID)); 381 } 382 delete tz; 383} 384 385void 386TimeZoneGenericNames::cleanup() { 387 if (fRegionFormat != NULL) { 388 delete fRegionFormat; 389 } 390 if (fFallbackRegionFormat != NULL) { 391 delete fFallbackRegionFormat; 392 } 393 if (fFallbackFormat != NULL) { 394 delete fFallbackFormat; 395 } 396 if (fLocaleDisplayNames != NULL) { 397 delete fLocaleDisplayNames; 398 } 399 if (fTimeZoneNames != NULL) { 400 delete fTimeZoneNames; 401 } 402 403 uhash_close(fLocationNamesMap); 404 uhash_close(fPartialLocationNamesMap); 405} 406 407UnicodeString& 408TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { 409 name.setToBogus(); 410 switch (type) { 411 case UTZGNM_LOCATION: 412 { 413 const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz); 414 if (tzCanonicalID != NULL) { 415 getGenericLocationName(UnicodeString(tzCanonicalID), name); 416 } 417 } 418 break; 419 case UTZGNM_LONG: 420 case UTZGNM_SHORT: 421 formatGenericNonLocationName(tz, type, date, name); 422 if (name.isEmpty()) { 423 const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz); 424 if (tzCanonicalID != NULL) { 425 getGenericLocationName(UnicodeString(tzCanonicalID), name); 426 } 427 } 428 break; 429 default: 430 break; 431 } 432 return name; 433} 434 435UnicodeString& 436TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { 437 if (tzCanonicalID.isEmpty()) { 438 name.setToBogus(); 439 return name; 440 } 441 442 const UChar *locname = NULL; 443 TimeZoneGenericNames *nonConstThis = const_cast<TimeZoneGenericNames *>(this); 444 umtx_lock(&nonConstThis->fLock); 445 { 446 locname = nonConstThis->getGenericLocationName(tzCanonicalID); 447 } 448 umtx_unlock(&nonConstThis->fLock); 449 450 if (locname == NULL) { 451 name.setToBogus(); 452 } else { 453 name.setTo(TRUE, locname, -1); 454 } 455 456 return name; 457} 458 459/* 460 * This method updates the cache and must be called with a lock 461 */ 462const UChar* 463TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID) { 464 U_ASSERT(!tzCanonicalID.isEmpty()); 465 if (tzCanonicalID.length() > ZID_KEY_MAX) { 466 return NULL; 467 } 468 469 UErrorCode status = U_ZERO_ERROR; 470 UChar tzIDKey[ZID_KEY_MAX + 1]; 471 int32_t tzIDKeyLen = tzCanonicalID.extract(tzIDKey, ZID_KEY_MAX + 1, status); 472 U_ASSERT(status == U_ZERO_ERROR); // already checked length above 473 tzIDKey[tzIDKeyLen] = 0; 474 475 const UChar *locname = (const UChar *)uhash_get(fLocationNamesMap, tzIDKey); 476 477 if (locname != NULL) { 478 // gEmpty indicate the name is not available 479 if (locname == gEmpty) { 480 return NULL; 481 } 482 return locname; 483 } 484 485 // Construct location name 486 UnicodeString name; 487 UBool isSingleCountry = FALSE; 488 UnicodeString usCountryCode; 489 ZoneMeta::getSingleCountry(tzCanonicalID, usCountryCode); 490 if (!usCountryCode.isEmpty()) { 491 isSingleCountry = TRUE; 492 } else { 493 ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode); 494 } 495 496 if (!usCountryCode.isEmpty()) { 497 char countryCode[ULOC_COUNTRY_CAPACITY]; 498 U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY); 499 int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV); 500 countryCode[ccLen] = 0; 501 502 UnicodeString country; 503 fLocaleDisplayNames->regionDisplayName(countryCode, country); 504 505 // Format 506 FieldPosition fpos; 507 if (isSingleCountry) { 508 // If the zone is only one zone in the country, do not add city 509 Formattable param[] = { 510 Formattable(country) 511 }; 512 fRegionFormat->format(param, 1, name, fpos, status); 513 } else { 514 // getExemplarLocationName should retur non-empty string 515 // if the time zone is associated with a region 516 UnicodeString city; 517 fTimeZoneNames->getExemplarLocationName(tzCanonicalID, city); 518 519 Formattable params[] = { 520 Formattable(city), 521 Formattable(country) 522 }; 523 fFallbackRegionFormat->format(params, 2, name, fpos, status); 524 } 525 if (U_FAILURE(status)) { 526 return NULL; 527 } 528 } 529 530 locname = name.isEmpty() ? NULL : fStringPool.get(name, status); 531 if (U_SUCCESS(status)) { 532 // Cache the result 533 const UChar* cacheID = ZoneMeta::findTimeZoneID(tzCanonicalID); 534 U_ASSERT(cacheID != NULL); 535 if (locname == NULL) { 536 // gEmpty to indicate - no location name available 537 uhash_put(fLocationNamesMap, (void *)cacheID, (void *)gEmpty, &status); 538 } else { 539 uhash_put(fLocationNamesMap, (void *)cacheID, (void *)locname, &status); 540 if (U_FAILURE(status)) { 541 locname = NULL; 542 } else { 543 // put the name info into the trie 544 GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo)); 545 if (nameinfo != NULL) { 546 nameinfo->type = UTZGNM_LOCATION; 547 nameinfo->tzID = cacheID; 548 fGNamesTrie.put(locname, nameinfo, status); 549 } 550 } 551 } 552 } 553 554 return locname; 555} 556 557UnicodeString& 558TimeZoneGenericNames::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { 559 U_ASSERT(type == UTZGNM_LONG || type == UTZGNM_SHORT); 560 name.setToBogus(); 561 562 const UChar* uID = ZoneMeta::getCanonicalCLDRID(tz); 563 if (uID == NULL) { 564 return name; 565 } 566 567 UnicodeString tzID(uID); 568 569 // Try to get a name from time zone first 570 UTimeZoneNameType nameType = (type == UTZGNM_LONG) ? UTZNM_LONG_GENERIC : UTZNM_SHORT_GENERIC; 571 fTimeZoneNames->getTimeZoneDisplayName(tzID, nameType, name); 572 573 if (!name.isEmpty()) { 574 return name; 575 } 576 577 // Try meta zone 578 UnicodeString mzID; 579 fTimeZoneNames->getMetaZoneID(tzID, date, mzID); 580 if (!mzID.isEmpty()) { 581 UErrorCode status = U_ZERO_ERROR; 582 UBool useStandard = FALSE; 583 int32_t raw, sav; 584 585 tz.getOffset(date, FALSE, raw, sav, status); 586 if (U_FAILURE(status)) { 587 return name; 588 } 589 590 if (sav == 0) { 591 useStandard = TRUE; 592 593 TimeZone *tmptz = tz.clone(); 594 // Check if the zone actually uses daylight saving time around the time 595 BasicTimeZone *btz = NULL; 596 if (dynamic_cast<OlsonTimeZone *>(tmptz) != NULL 597 || dynamic_cast<SimpleTimeZone *>(tmptz) != NULL 598 || dynamic_cast<RuleBasedTimeZone *>(tmptz) != NULL 599 || dynamic_cast<VTimeZone *>(tmptz) != NULL) { 600 btz = (BasicTimeZone*)tmptz; 601 } 602 603 if (btz != NULL) { 604 TimeZoneTransition before; 605 UBool beforTrs = btz->getPreviousTransition(date, TRUE, before); 606 if (beforTrs 607 && (date - before.getTime() < kDstCheckRange) 608 && before.getFrom()->getDSTSavings() != 0) { 609 useStandard = FALSE; 610 } else { 611 TimeZoneTransition after; 612 UBool afterTrs = btz->getNextTransition(date, FALSE, after); 613 if (afterTrs 614 && (after.getTime() - date < kDstCheckRange) 615 && after.getTo()->getDSTSavings() != 0) { 616 useStandard = FALSE; 617 } 618 } 619 } else { 620 // If not BasicTimeZone... only if the instance is not an ICU's implementation. 621 // We may get a wrong answer in edge case, but it should practically work OK. 622 tmptz->getOffset(date - kDstCheckRange, FALSE, raw, sav, status); 623 if (sav != 0) { 624 useStandard = FALSE; 625 } else { 626 tmptz->getOffset(date + kDstCheckRange, FALSE, raw, sav, status); 627 if (sav != 0){ 628 useStandard = FALSE; 629 } 630 } 631 if (U_FAILURE(status)) { 632 delete tmptz; 633 return name; 634 } 635 } 636 delete tmptz; 637 } 638 if (useStandard) { 639 UTimeZoneNameType stdNameType = (nameType == UTZNM_LONG_GENERIC) 640 ? UTZNM_LONG_STANDARD : UTZNM_SHORT_STANDARD_COMMONLY_USED; 641 UnicodeString stdName; 642 fTimeZoneNames->getDisplayName(tzID, stdNameType, date, stdName); 643 if (!stdName.isEmpty()) { 644 name.setTo(stdName); 645 646 // TODO: revisit this issue later 647 // In CLDR, a same display name is used for both generic and standard 648 // for some meta zones in some locales. This looks like a data bugs. 649 // For now, we check if the standard name is different from its generic 650 // name below. 651 UnicodeString mzGenericName; 652 fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzGenericName); 653 if (stdName.caseCompare(mzGenericName, 0) == 0) { 654 name.setToBogus(); 655 } 656 } 657 } 658 if (name.isEmpty()) { 659 // Get a name from meta zone 660 UnicodeString mzName; 661 fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzName); 662 if (!mzName.isEmpty()) { 663 // Check if we need to use a partial location format. 664 // This check is done by comparing offset with the meta zone's 665 // golden zone at the given date. 666 UnicodeString goldenID; 667 fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, goldenID); 668 if (!goldenID.isEmpty() && goldenID != tzID) { 669 TimeZone *goldenZone = TimeZone::createTimeZone(goldenID); 670 int32_t raw1, sav1; 671 672 // Check offset in the golden zone with wall time. 673 // With getOffset(date, false, offsets1), 674 // you may get incorrect results because of time overlap at DST->STD 675 // transition. 676 goldenZone->getOffset(date + raw + sav, TRUE, raw1, sav1, status); 677 delete goldenZone; 678 if (U_SUCCESS(status)) { 679 if (raw != raw1 || sav != sav1) { 680 // Now we need to use a partial location format 681 getPartialLocationName(tzID, mzID, (nameType == UTZNM_LONG_GENERIC), mzName, name); 682 } else { 683 name.setTo(mzName); 684 } 685 } 686 } else { 687 name.setTo(mzName); 688 } 689 } 690 } 691 } 692 return name; 693} 694 695UnicodeString& 696TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID, 697 const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName, 698 UnicodeString& name) const { 699 name.setToBogus(); 700 if (tzCanonicalID.isEmpty() || mzID.isEmpty() || mzDisplayName.isEmpty()) { 701 return name; 702 } 703 704 const UChar *uplname = NULL; 705 TimeZoneGenericNames *nonConstThis = const_cast<TimeZoneGenericNames *>(this); 706 umtx_lock(&nonConstThis->fLock); 707 { 708 uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName); 709 } 710 umtx_unlock(&nonConstThis->fLock); 711 712 if (uplname == NULL) { 713 name.setToBogus(); 714 } else { 715 name.setTo(TRUE, uplname, -1); 716 } 717 return name; 718} 719 720/* 721 * This method updates the cache and must be called with a lock 722 */ 723const UChar* 724TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID, 725 const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName) { 726 U_ASSERT(!tzCanonicalID.isEmpty()); 727 U_ASSERT(!mzID.isEmpty()); 728 U_ASSERT(!mzDisplayName.isEmpty()); 729 730 PartialLocationKey key; 731 key.tzID = ZoneMeta::findTimeZoneID(tzCanonicalID); 732 key.mzID = ZoneMeta::findMetaZoneID(mzID); 733 key.isLong = isLong; 734 U_ASSERT(key.tzID != NULL && key.mzID != NULL); 735 736 const UChar* uplname = (const UChar*)uhash_get(fPartialLocationNamesMap, (void *)&key); 737 if (uplname != NULL) { 738 return uplname; 739 } 740 741 UnicodeString location; 742 UnicodeString usCountryCode; 743 ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode); 744 if (!usCountryCode.isEmpty()) { 745 char countryCode[ULOC_COUNTRY_CAPACITY]; 746 U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY); 747 int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV); 748 countryCode[ccLen] = 0; 749 750 UnicodeString regionalGolden; 751 fTimeZoneNames->getReferenceZoneID(mzID, countryCode, regionalGolden); 752 if (tzCanonicalID == regionalGolden) { 753 // Use country name 754 fLocaleDisplayNames->regionDisplayName(countryCode, location); 755 } else { 756 // Otherwise, use exemplar city name 757 fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location); 758 } 759 } else { 760 fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location); 761 if (location.isEmpty()) { 762 // This could happen when the time zone is not associated with a country, 763 // and its ID is not hierarchical, for example, CST6CDT. 764 // We use the canonical ID itself as the location for this case. 765 location.setTo(tzCanonicalID); 766 } 767 } 768 769 UErrorCode status = U_ZERO_ERROR; 770 UnicodeString name; 771 772 FieldPosition fpos; 773 Formattable param[] = { 774 Formattable(location), 775 Formattable(mzDisplayName) 776 }; 777 fFallbackFormat->format(param, 2, name, fpos, status); 778 if (U_FAILURE(status)) { 779 return NULL; 780 } 781 782 uplname = fStringPool.get(name, status); 783 if (U_SUCCESS(status)) { 784 // Add the name to cache 785 PartialLocationKey* cacheKey = (PartialLocationKey *)uprv_malloc(sizeof(PartialLocationKey)); 786 if (cacheKey != NULL) { 787 cacheKey->tzID = key.tzID; 788 cacheKey->mzID = key.mzID; 789 cacheKey->isLong = key.isLong; 790 uhash_put(fPartialLocationNamesMap, (void *)cacheKey, (void *)uplname, &status); 791 if (U_FAILURE(status)) { 792 uprv_free(cacheKey); 793 } else { 794 // put the name to the local trie as well 795 GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo)); 796 if (nameinfo != NULL) { 797 nameinfo->type = isLong ? UTZGNM_LONG : UTZGNM_SHORT; 798 nameinfo->tzID = key.tzID; 799 fGNamesTrie.put(uplname, nameinfo, status); 800 } 801 } 802 } 803 } 804 return uplname; 805} 806 807/* 808 * This method updates the cache and must be called with a lock, 809 * except initializer. 810 */ 811void 812TimeZoneGenericNames::loadStrings(const UnicodeString& tzCanonicalID) { 813 // load the generic location name 814 getGenericLocationName(tzCanonicalID); 815 816 // partial location names 817 UErrorCode status = U_ZERO_ERROR; 818 819 const UnicodeString *mzID; 820 UnicodeString goldenID; 821 UnicodeString mzGenName; 822 UTimeZoneNameType genNonLocTypes[] = { 823 UTZNM_LONG_GENERIC, UTZNM_SHORT_GENERIC, 824 UTZNM_UNKNOWN /*terminator*/ 825 }; 826 827 StringEnumeration *mzIDs = fTimeZoneNames->getAvailableMetaZoneIDs(tzCanonicalID, status); 828 while ((mzID = mzIDs->snext(status))) { 829 if (U_FAILURE(status)) { 830 break; 831 } 832 // if this time zone is not the golden zone of the meta zone, 833 // partial location name (such as "PT (Los Angeles)") might be 834 // available. 835 fTimeZoneNames->getReferenceZoneID(*mzID, fTargetRegion, goldenID); 836 if (tzCanonicalID != goldenID) { 837 for (int32_t i = 0; genNonLocTypes[i] != UTZNM_UNKNOWN; i++) { 838 fTimeZoneNames->getMetaZoneDisplayName(*mzID, genNonLocTypes[i], mzGenName); 839 if (!mzGenName.isEmpty()) { 840 // getPartialLocationName formats a name and put it into the trie 841 getPartialLocationName(tzCanonicalID, *mzID, 842 (genNonLocTypes[i] == UTZNM_LONG_GENERIC), mzGenName); 843 } 844 } 845 } 846 } 847 if (mzIDs != NULL) { 848 delete mzIDs; 849 } 850} 851 852int32_t 853TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, 854 UnicodeString& tzID, UTimeZoneTimeType& timeType, UErrorCode& status) const { 855 timeType = UTZFMT_TIME_TYPE_UNKNOWN; 856 tzID.setToBogus(); 857 858 if (U_FAILURE(status)) { 859 return 0; 860 } 861 862 // Find matches in the TimeZoneNames first 863 TimeZoneNameMatchInfo *tznamesMatches = findTimeZoneNames(text, start, types, status); 864 if (U_FAILURE(status)) { 865 return 0; 866 } 867 868 int32_t bestMatchLen = 0; 869 UTimeZoneTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; 870 UnicodeString bestMatchTzID; 871 UBool isLongStandard = FALSE; // workaround - see the comments below 872 873 if (tznamesMatches != NULL) { 874 UnicodeString mzID; 875 for (int32_t i = 0; i < tznamesMatches->size(); i++) { 876 int32_t len = tznamesMatches->getMatchLength(i); 877 if (len > bestMatchLen) { 878 bestMatchLen = len; 879 tznamesMatches->getTimeZoneID(i, bestMatchTzID); 880 if (bestMatchTzID.isEmpty()) { 881 // name for a meta zone 882 tznamesMatches->getMetaZoneID(i, mzID); 883 U_ASSERT(mzID.length() > 0); 884 fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, bestMatchTzID); 885 } 886 UTimeZoneNameType nameType = tznamesMatches->getNameType(i); 887 switch (nameType) { 888 case UTZNM_LONG_STANDARD: 889 isLongStandard = TRUE; 890 case UTZNM_SHORT_STANDARD_COMMONLY_USED: 891 case UTZNM_SHORT_STANDARD: // this one is never used for generic, but just in case 892 bestMatchTimeType = UTZFMT_TIME_TYPE_STANDARD; 893 break; 894 case UTZNM_LONG_DAYLIGHT: 895 case UTZNM_SHORT_DAYLIGHT_COMMONLY_USED: 896 case UTZNM_SHORT_DAYLIGHT: // this one is never used for generic, but just in case 897 bestMatchTimeType = UTZFMT_TIME_TYPE_DAYLIGHT; 898 break; 899 default: 900 bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; 901 } 902 } 903 } 904 delete tznamesMatches; 905 906 if (bestMatchLen == (text.length() - start)) { 907 // Full match 908 909 //tzID.setTo(bestMatchTzID); 910 //timeType = bestMatchTimeType; 911 //return bestMatchLen; 912 913 // TODO Some time zone uses a same name for the long standard name 914 // and the location name. When the match is a long standard name, 915 // then we need to check if the name is same with the location name. 916 // This is probably a data error or a design bug. 917 if (!isLongStandard) { 918 tzID.setTo(bestMatchTzID); 919 timeType = bestMatchTimeType; 920 return bestMatchLen; 921 } 922 } 923 } 924 925 // Find matches in the local trie 926 TimeZoneGenericNameMatchInfo *localMatches = findLocal(text, start, types, status); 927 if (U_FAILURE(status)) { 928 return 0; 929 } 930 if (localMatches != NULL) { 931 for (int32_t i = 0; i < localMatches->size(); i++) { 932 int32_t len = localMatches->getMatchLength(i); 933 934 // TODO See the above TODO. We use len >= bestMatchLen 935 // because of the long standard/location name collision 936 // problem. If it is also a location name, carrying 937 // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a 938 // problem in SimpleDateFormat 939 if (len >= bestMatchLen) { 940 bestMatchLen = localMatches->getMatchLength(i); 941 bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; // because generic 942 localMatches->getTimeZoneID(i, bestMatchTzID); 943 } 944 } 945 delete localMatches; 946 } 947 948 if (bestMatchLen > 0) { 949 timeType = bestMatchTimeType; 950 tzID.setTo(bestMatchTzID); 951 } 952 return bestMatchLen; 953} 954 955TimeZoneGenericNameMatchInfo* 956TimeZoneGenericNames::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { 957 GNameSearchHandler handler(types); 958 959 TimeZoneGenericNames *nonConstThis = const_cast<TimeZoneGenericNames *>(this); 960 961 umtx_lock(&nonConstThis->fLock); 962 { 963 fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); 964 } 965 umtx_unlock(&nonConstThis->fLock); 966 967 if (U_FAILURE(status)) { 968 return NULL; 969 } 970 971 TimeZoneGenericNameMatchInfo *gmatchInfo = NULL; 972 973 int32_t maxLen = 0; 974 UVector *results = handler.getMatches(maxLen); 975 if (results != NULL && ((maxLen == (text.length() - start)) || fGNamesTrieFullyLoaded)) { 976 // perfect match 977 gmatchInfo = new TimeZoneGenericNameMatchInfo(results); 978 if (gmatchInfo == NULL) { 979 status = U_MEMORY_ALLOCATION_ERROR; 980 delete results; 981 return NULL; 982 } 983 return gmatchInfo; 984 } 985 986 if (results != NULL) { 987 delete results; 988 } 989 990 // All names are not yet loaded into the local trie. 991 // Load all available names into the trie. This could be very heavy. 992 umtx_lock(&nonConstThis->fLock); 993 { 994 if (!fGNamesTrieFullyLoaded) { 995 StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); 996 if (U_SUCCESS(status)) { 997 const UnicodeString *tzID; 998 while ((tzID = tzIDs->snext(status))) { 999 if (U_FAILURE(status)) { 1000 break; 1001 } 1002 nonConstThis->loadStrings(*tzID); 1003 } 1004 } 1005 if (tzIDs != NULL) { 1006 delete tzIDs; 1007 } 1008 1009 if (U_SUCCESS(status)) { 1010 nonConstThis->fGNamesTrieFullyLoaded = TRUE; 1011 } 1012 } 1013 } 1014 umtx_unlock(&nonConstThis->fLock); 1015 1016 if (U_FAILURE(status)) { 1017 return NULL; 1018 } 1019 1020 umtx_lock(&nonConstThis->fLock); 1021 { 1022 // now try it again 1023 fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); 1024 } 1025 umtx_unlock(&nonConstThis->fLock); 1026 1027 results = handler.getMatches(maxLen); 1028 if (results != NULL && maxLen > 0) { 1029 gmatchInfo = new TimeZoneGenericNameMatchInfo(results); 1030 if (gmatchInfo == NULL) { 1031 status = U_MEMORY_ALLOCATION_ERROR; 1032 delete results; 1033 return NULL; 1034 } 1035 } 1036 1037 return gmatchInfo; 1038} 1039 1040TimeZoneNameMatchInfo* 1041TimeZoneGenericNames::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { 1042 TimeZoneNameMatchInfo *matchInfo = NULL; 1043 1044 // Check if the target name typs is really in the TimeZoneNames 1045 uint32_t nameTypes = 0; 1046 if (types & UTZGNM_LONG) { 1047 nameTypes |= (UTZNM_LONG_GENERIC | UTZNM_LONG_STANDARD); 1048 } 1049 if (types & UTZGNM_SHORT) { 1050 nameTypes |= (UTZNM_SHORT_GENERIC | UTZNM_SHORT_STANDARD_COMMONLY_USED); 1051 } 1052 1053 if (types) { 1054 // Find matches in the TimeZoneNames 1055 matchInfo = fTimeZoneNames->find(text, start, nameTypes, status); 1056 } 1057 1058 return matchInfo; 1059} 1060 1061U_NAMESPACE_END 1062#endif 1063