1/* 2******************************************************************************* 3* Copyright (C) 2011-2014, International Business Machines Corporation and 4* others. All Rights Reserved. 5******************************************************************************* 6* 7* File TZNAMES_IMPL.CPP 8* 9******************************************************************************* 10*/ 11 12#include "unicode/utypes.h" 13 14#if !UCONFIG_NO_FORMATTING 15 16#include "unicode/ustring.h" 17#include "unicode/timezone.h" 18 19#include "tznames_impl.h" 20#include "cmemory.h" 21#include "cstring.h" 22#include "uassert.h" 23#include "mutex.h" 24#include "uresimp.h" 25#include "ureslocs.h" 26#include "zonemeta.h" 27#include "ucln_in.h" 28#include "uvector.h" 29#include "olsontz.h" 30 31 32U_NAMESPACE_BEGIN 33 34#define ZID_KEY_MAX 128 35#define MZ_PREFIX_LEN 5 36 37static const char gZoneStrings[] = "zoneStrings"; 38static const char gMZPrefix[] = "meta:"; 39 40static const char* KEYS[] = {"lg", "ls", "ld", "sg", "ss", "sd"}; 41static const int32_t KEYS_SIZE = (sizeof KEYS / sizeof KEYS[0]); 42 43static const char gEcTag[] = "ec"; 44 45static const char EMPTY[] = "<empty>"; // place holder for empty ZNames/TZNames 46 47static const UTimeZoneNameType ALL_NAME_TYPES[] = { 48 UTZNM_LONG_GENERIC, UTZNM_LONG_STANDARD, UTZNM_LONG_DAYLIGHT, 49 UTZNM_SHORT_GENERIC, UTZNM_SHORT_STANDARD, UTZNM_SHORT_DAYLIGHT, 50 UTZNM_EXEMPLAR_LOCATION, 51 UTZNM_UNKNOWN // unknown as the last one 52}; 53 54// stuff for TZDBTimeZoneNames 55static const char* TZDBNAMES_KEYS[] = {"ss", "sd"}; 56static const int32_t TZDBNAMES_KEYS_SIZE = (sizeof TZDBNAMES_KEYS / sizeof TZDBNAMES_KEYS[0]); 57 58static UMutex gTZDBNamesMapLock = U_MUTEX_INITIALIZER; 59 60static UHashtable* gTZDBNamesMap = NULL; 61static icu::UInitOnce gTZDBNamesMapInitOnce = U_INITONCE_INITIALIZER; 62 63static TextTrieMap* gTZDBNamesTrie = NULL; 64static icu::UInitOnce gTZDBNamesTrieInitOnce = U_INITONCE_INITIALIZER; 65 66U_CDECL_BEGIN 67static UBool U_CALLCONV tzdbTimeZoneNames_cleanup(void) { 68 if (gTZDBNamesMap != NULL) { 69 uhash_close(gTZDBNamesMap); 70 gTZDBNamesMap = NULL; 71 } 72 gTZDBNamesMapInitOnce.reset(); 73 74 if (gTZDBNamesTrie != NULL) { 75 delete gTZDBNamesTrie; 76 gTZDBNamesTrie = NULL; 77 } 78 gTZDBNamesTrieInitOnce.reset(); 79 80 return TRUE; 81} 82U_CDECL_END 83 84#define DEFAULT_CHARACTERNODE_CAPACITY 1 85 86// --------------------------------------------------- 87// CharacterNode class implementation 88// --------------------------------------------------- 89void CharacterNode::clear() { 90 uprv_memset(this, 0, sizeof(*this)); 91} 92 93void CharacterNode::deleteValues(UObjectDeleter *valueDeleter) { 94 if (fValues == NULL) { 95 // Do nothing. 96 } else if (!fHasValuesVector) { 97 if (valueDeleter) { 98 valueDeleter(fValues); 99 } 100 } else { 101 delete (UVector *)fValues; 102 } 103} 104 105void 106CharacterNode::addValue(void *value, UObjectDeleter *valueDeleter, UErrorCode &status) { 107 if (U_FAILURE(status)) { 108 if (valueDeleter) { 109 valueDeleter(value); 110 } 111 return; 112 } 113 if (fValues == NULL) { 114 fValues = value; 115 } else { 116 // At least one value already. 117 if (!fHasValuesVector) { 118 // There is only one value so far, and not in a vector yet. 119 // Create a vector and add the old value. 120 UVector *values = new UVector(valueDeleter, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status); 121 if (U_FAILURE(status)) { 122 if (valueDeleter) { 123 valueDeleter(value); 124 } 125 return; 126 } 127 values->addElement(fValues, status); 128 fValues = values; 129 fHasValuesVector = TRUE; 130 } 131 // Add the new value. 132 ((UVector *)fValues)->addElement(value, status); 133 } 134} 135 136// --------------------------------------------------- 137// TextTrieMapSearchResultHandler class implementation 138// --------------------------------------------------- 139TextTrieMapSearchResultHandler::~TextTrieMapSearchResultHandler(){ 140} 141 142// --------------------------------------------------- 143// TextTrieMap class implementation 144// --------------------------------------------------- 145TextTrieMap::TextTrieMap(UBool ignoreCase, UObjectDeleter *valueDeleter) 146: fIgnoreCase(ignoreCase), fNodes(NULL), fNodesCapacity(0), fNodesCount(0), 147 fLazyContents(NULL), fIsEmpty(TRUE), fValueDeleter(valueDeleter) { 148} 149 150TextTrieMap::~TextTrieMap() { 151 int32_t index; 152 for (index = 0; index < fNodesCount; ++index) { 153 fNodes[index].deleteValues(fValueDeleter); 154 } 155 uprv_free(fNodes); 156 if (fLazyContents != NULL) { 157 for (int32_t i=0; i<fLazyContents->size(); i+=2) { 158 if (fValueDeleter) { 159 fValueDeleter(fLazyContents->elementAt(i+1)); 160 } 161 } 162 delete fLazyContents; 163 } 164} 165 166int32_t TextTrieMap::isEmpty() const { 167 // Use a separate field for fIsEmpty because it will remain unchanged once the 168 // Trie is built, while fNodes and fLazyContents change with the lazy init 169 // of the nodes structure. Trying to test the changing fields has 170 // thread safety complications. 171 return fIsEmpty; 172} 173 174 175// We defer actually building the TextTrieMap node structure until the first time a 176// search is performed. put() simply saves the parameters in case we do 177// eventually need to build it. 178// 179void 180TextTrieMap::put(const UnicodeString &key, void *value, ZNStringPool &sp, UErrorCode &status) { 181 const UChar *s = sp.get(key, status); 182 put(s, value, status); 183} 184 185// This method is for designed for a persistent key, such as string key stored in 186// resource bundle. 187void 188TextTrieMap::put(const UChar *key, void *value, UErrorCode &status) { 189 fIsEmpty = FALSE; 190 if (fLazyContents == NULL) { 191 fLazyContents = new UVector(status); 192 if (fLazyContents == NULL) { 193 status = U_MEMORY_ALLOCATION_ERROR; 194 } 195 } 196 if (U_FAILURE(status)) { 197 return; 198 } 199 U_ASSERT(fLazyContents != NULL); 200 UChar *s = const_cast<UChar *>(key); 201 fLazyContents->addElement(s, status); 202 fLazyContents->addElement(value, status); 203} 204 205void 206TextTrieMap::putImpl(const UnicodeString &key, void *value, UErrorCode &status) { 207 if (fNodes == NULL) { 208 fNodesCapacity = 512; 209 fNodes = (CharacterNode *)uprv_malloc(fNodesCapacity * sizeof(CharacterNode)); 210 fNodes[0].clear(); // Init root node. 211 fNodesCount = 1; 212 } 213 214 UnicodeString foldedKey; 215 const UChar *keyBuffer; 216 int32_t keyLength; 217 if (fIgnoreCase) { 218 // Ok to use fastCopyFrom() because we discard the copy when we return. 219 foldedKey.fastCopyFrom(key).foldCase(); 220 keyBuffer = foldedKey.getBuffer(); 221 keyLength = foldedKey.length(); 222 } else { 223 keyBuffer = key.getBuffer(); 224 keyLength = key.length(); 225 } 226 227 CharacterNode *node = fNodes; 228 int32_t index; 229 for (index = 0; index < keyLength; ++index) { 230 node = addChildNode(node, keyBuffer[index], status); 231 } 232 node->addValue(value, fValueDeleter, status); 233} 234 235UBool 236TextTrieMap::growNodes() { 237 if (fNodesCapacity == 0xffff) { 238 return FALSE; // We use 16-bit node indexes. 239 } 240 int32_t newCapacity = fNodesCapacity + 1000; 241 if (newCapacity > 0xffff) { 242 newCapacity = 0xffff; 243 } 244 CharacterNode *newNodes = (CharacterNode *)uprv_malloc(newCapacity * sizeof(CharacterNode)); 245 if (newNodes == NULL) { 246 return FALSE; 247 } 248 uprv_memcpy(newNodes, fNodes, fNodesCount * sizeof(CharacterNode)); 249 uprv_free(fNodes); 250 fNodes = newNodes; 251 fNodesCapacity = newCapacity; 252 return TRUE; 253} 254 255CharacterNode* 256TextTrieMap::addChildNode(CharacterNode *parent, UChar c, UErrorCode &status) { 257 if (U_FAILURE(status)) { 258 return NULL; 259 } 260 // Linear search of the sorted list of children. 261 uint16_t prevIndex = 0; 262 uint16_t nodeIndex = parent->fFirstChild; 263 while (nodeIndex > 0) { 264 CharacterNode *current = fNodes + nodeIndex; 265 UChar childCharacter = current->fCharacter; 266 if (childCharacter == c) { 267 return current; 268 } else if (childCharacter > c) { 269 break; 270 } 271 prevIndex = nodeIndex; 272 nodeIndex = current->fNextSibling; 273 } 274 275 // Ensure capacity. Grow fNodes[] if needed. 276 if (fNodesCount == fNodesCapacity) { 277 int32_t parentIndex = (int32_t)(parent - fNodes); 278 if (!growNodes()) { 279 status = U_MEMORY_ALLOCATION_ERROR; 280 return NULL; 281 } 282 parent = fNodes + parentIndex; 283 } 284 285 // Insert a new child node with c in sorted order. 286 CharacterNode *node = fNodes + fNodesCount; 287 node->clear(); 288 node->fCharacter = c; 289 node->fNextSibling = nodeIndex; 290 if (prevIndex == 0) { 291 parent->fFirstChild = (uint16_t)fNodesCount; 292 } else { 293 fNodes[prevIndex].fNextSibling = (uint16_t)fNodesCount; 294 } 295 ++fNodesCount; 296 return node; 297} 298 299CharacterNode* 300TextTrieMap::getChildNode(CharacterNode *parent, UChar c) const { 301 // Linear search of the sorted list of children. 302 uint16_t nodeIndex = parent->fFirstChild; 303 while (nodeIndex > 0) { 304 CharacterNode *current = fNodes + nodeIndex; 305 UChar childCharacter = current->fCharacter; 306 if (childCharacter == c) { 307 return current; 308 } else if (childCharacter > c) { 309 break; 310 } 311 nodeIndex = current->fNextSibling; 312 } 313 return NULL; 314} 315 316// Mutex for protecting the lazy creation of the Trie node structure on the first call to search(). 317static UMutex TextTrieMutex = U_MUTEX_INITIALIZER; 318 319// buildTrie() - The Trie node structure is needed. Create it from the data that was 320// saved at the time the ZoneStringFormatter was created. The Trie is only 321// needed for parsing operations, which are less common than formatting, 322// and the Trie is big, which is why its creation is deferred until first use. 323void TextTrieMap::buildTrie(UErrorCode &status) { 324 if (fLazyContents != NULL) { 325 for (int32_t i=0; i<fLazyContents->size(); i+=2) { 326 const UChar *key = (UChar *)fLazyContents->elementAt(i); 327 void *val = fLazyContents->elementAt(i+1); 328 UnicodeString keyString(TRUE, key, -1); // Aliasing UnicodeString constructor. 329 putImpl(keyString, val, status); 330 } 331 delete fLazyContents; 332 fLazyContents = NULL; 333 } 334} 335 336void 337TextTrieMap::search(const UnicodeString &text, int32_t start, 338 TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { 339 { 340 // TODO: if locking the mutex for each check proves to be a performance problem, 341 // add a flag of type atomic_int32_t to class TextTrieMap, and use only 342 // the ICU atomic safe functions for assigning and testing. 343 // Don't test the pointer fLazyContents. 344 // Don't do unless it's really required. 345 Mutex lock(&TextTrieMutex); 346 if (fLazyContents != NULL) { 347 TextTrieMap *nonConstThis = const_cast<TextTrieMap *>(this); 348 nonConstThis->buildTrie(status); 349 } 350 } 351 if (fNodes == NULL) { 352 return; 353 } 354 search(fNodes, text, start, start, handler, status); 355} 356 357void 358TextTrieMap::search(CharacterNode *node, const UnicodeString &text, int32_t start, 359 int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { 360 if (U_FAILURE(status)) { 361 return; 362 } 363 if (node->hasValues()) { 364 if (!handler->handleMatch(index - start, node, status)) { 365 return; 366 } 367 if (U_FAILURE(status)) { 368 return; 369 } 370 } 371 UChar32 c = text.char32At(index); 372 if (fIgnoreCase) { 373 // size of character may grow after fold operation 374 UnicodeString tmp(c); 375 tmp.foldCase(); 376 int32_t tmpidx = 0; 377 while (tmpidx < tmp.length()) { 378 c = tmp.char32At(tmpidx); 379 node = getChildNode(node, c); 380 if (node == NULL) { 381 break; 382 } 383 tmpidx = tmp.moveIndex32(tmpidx, 1); 384 } 385 } else { 386 node = getChildNode(node, c); 387 } 388 if (node != NULL) { 389 search(node, text, start, index+1, handler, status); 390 } 391} 392 393// --------------------------------------------------- 394// ZNStringPool class implementation 395// --------------------------------------------------- 396static const int32_t POOL_CHUNK_SIZE = 2000; 397struct ZNStringPoolChunk: public UMemory { 398 ZNStringPoolChunk *fNext; // Ptr to next pool chunk 399 int32_t fLimit; // Index to start of unused area at end of fStrings 400 UChar fStrings[POOL_CHUNK_SIZE]; // Strings array 401 ZNStringPoolChunk(); 402}; 403 404ZNStringPoolChunk::ZNStringPoolChunk() { 405 fNext = NULL; 406 fLimit = 0; 407} 408 409ZNStringPool::ZNStringPool(UErrorCode &status) { 410 fChunks = NULL; 411 fHash = NULL; 412 if (U_FAILURE(status)) { 413 return; 414 } 415 fChunks = new ZNStringPoolChunk; 416 if (fChunks == NULL) { 417 status = U_MEMORY_ALLOCATION_ERROR; 418 return; 419 } 420 421 fHash = uhash_open(uhash_hashUChars /* keyHash */, 422 uhash_compareUChars /* keyComp */, 423 uhash_compareUChars /* valueComp */, 424 &status); 425 if (U_FAILURE(status)) { 426 return; 427 } 428} 429 430ZNStringPool::~ZNStringPool() { 431 if (fHash != NULL) { 432 uhash_close(fHash); 433 fHash = NULL; 434 } 435 436 while (fChunks != NULL) { 437 ZNStringPoolChunk *nextChunk = fChunks->fNext; 438 delete fChunks; 439 fChunks = nextChunk; 440 } 441} 442 443static const UChar EmptyString = 0; 444 445const UChar *ZNStringPool::get(const UChar *s, UErrorCode &status) { 446 const UChar *pooledString; 447 if (U_FAILURE(status)) { 448 return &EmptyString; 449 } 450 451 pooledString = static_cast<UChar *>(uhash_get(fHash, s)); 452 if (pooledString != NULL) { 453 return pooledString; 454 } 455 456 int32_t length = u_strlen(s); 457 int32_t remainingLength = POOL_CHUNK_SIZE - fChunks->fLimit; 458 if (remainingLength <= length) { 459 U_ASSERT(length < POOL_CHUNK_SIZE); 460 if (length >= POOL_CHUNK_SIZE) { 461 status = U_INTERNAL_PROGRAM_ERROR; 462 return &EmptyString; 463 } 464 ZNStringPoolChunk *oldChunk = fChunks; 465 fChunks = new ZNStringPoolChunk; 466 if (fChunks == NULL) { 467 status = U_MEMORY_ALLOCATION_ERROR; 468 return &EmptyString; 469 } 470 fChunks->fNext = oldChunk; 471 } 472 473 UChar *destString = &fChunks->fStrings[fChunks->fLimit]; 474 u_strcpy(destString, s); 475 fChunks->fLimit += (length + 1); 476 uhash_put(fHash, destString, destString, &status); 477 return destString; 478} 479 480 481// 482// ZNStringPool::adopt() Put a string into the hash, but do not copy the string data 483// into the pool's storage. Used for strings from resource bundles, 484// which will perisist for the life of the zone string formatter, and 485// therefore can be used directly without copying. 486const UChar *ZNStringPool::adopt(const UChar * s, UErrorCode &status) { 487 const UChar *pooledString; 488 if (U_FAILURE(status)) { 489 return &EmptyString; 490 } 491 if (s != NULL) { 492 pooledString = static_cast<UChar *>(uhash_get(fHash, s)); 493 if (pooledString == NULL) { 494 UChar *ncs = const_cast<UChar *>(s); 495 uhash_put(fHash, ncs, ncs, &status); 496 } 497 } 498 return s; 499} 500 501 502const UChar *ZNStringPool::get(const UnicodeString &s, UErrorCode &status) { 503 UnicodeString &nonConstStr = const_cast<UnicodeString &>(s); 504 return this->get(nonConstStr.getTerminatedBuffer(), status); 505} 506 507/* 508 * freeze(). Close the hash table that maps to the pooled strings. 509 * After freezing, the pool can not be searched or added to, 510 * but all existing references to pooled strings remain valid. 511 * 512 * The main purpose is to recover the storage used for the hash. 513 */ 514void ZNStringPool::freeze() { 515 uhash_close(fHash); 516 fHash = NULL; 517} 518 519 520// --------------------------------------------------- 521// ZNames - names common for time zone and meta zone 522// --------------------------------------------------- 523class ZNames : public UMemory { 524public: 525 virtual ~ZNames(); 526 527 static ZNames* createInstance(UResourceBundle* rb, const char* key); 528 virtual const UChar* getName(UTimeZoneNameType type); 529 530protected: 531 ZNames(const UChar** names); 532 static const UChar** loadData(UResourceBundle* rb, const char* key); 533 534private: 535 const UChar** fNames; 536}; 537 538ZNames::ZNames(const UChar** names) 539: fNames(names) { 540} 541 542ZNames::~ZNames() { 543 if (fNames != NULL) { 544 uprv_free(fNames); 545 } 546} 547 548ZNames* 549ZNames::createInstance(UResourceBundle* rb, const char* key) { 550 const UChar** names = loadData(rb, key); 551 if (names == NULL) { 552 // No names data available 553 return NULL; 554 } 555 return new ZNames(names); 556} 557 558const UChar* 559ZNames::getName(UTimeZoneNameType type) { 560 if (fNames == NULL) { 561 return NULL; 562 } 563 const UChar *name = NULL; 564 switch(type) { 565 case UTZNM_LONG_GENERIC: 566 name = fNames[0]; 567 break; 568 case UTZNM_LONG_STANDARD: 569 name = fNames[1]; 570 break; 571 case UTZNM_LONG_DAYLIGHT: 572 name = fNames[2]; 573 break; 574 case UTZNM_SHORT_GENERIC: 575 name = fNames[3]; 576 break; 577 case UTZNM_SHORT_STANDARD: 578 name = fNames[4]; 579 break; 580 case UTZNM_SHORT_DAYLIGHT: 581 name = fNames[5]; 582 break; 583 case UTZNM_EXEMPLAR_LOCATION: // implemeted by subclass 584 default: 585 name = NULL; 586 } 587 return name; 588} 589 590const UChar** 591ZNames::loadData(UResourceBundle* rb, const char* key) { 592 if (rb == NULL || key == NULL || *key == 0) { 593 return NULL; 594 } 595 596 UErrorCode status = U_ZERO_ERROR; 597 const UChar **names = NULL; 598 599 UResourceBundle* rbTable = NULL; 600 rbTable = ures_getByKeyWithFallback(rb, key, rbTable, &status); 601 if (U_SUCCESS(status)) { 602 names = (const UChar **)uprv_malloc(sizeof(const UChar*) * KEYS_SIZE); 603 if (names != NULL) { 604 UBool isEmpty = TRUE; 605 for (int32_t i = 0; i < KEYS_SIZE; i++) { 606 status = U_ZERO_ERROR; 607 int32_t len = 0; 608 const UChar *value = ures_getStringByKeyWithFallback(rbTable, KEYS[i], &len, &status); 609 if (U_FAILURE(status) || len == 0) { 610 names[i] = NULL; 611 } else { 612 names[i] = value; 613 isEmpty = FALSE; 614 } 615 } 616 if (isEmpty) { 617 // No need to keep the names array 618 uprv_free(names); 619 names = NULL; 620 } 621 } 622 } 623 ures_close(rbTable); 624 return names; 625} 626 627// --------------------------------------------------- 628// TZNames - names for a time zone 629// --------------------------------------------------- 630class TZNames : public ZNames { 631public: 632 virtual ~TZNames(); 633 634 static TZNames* createInstance(UResourceBundle* rb, const char* key, const UnicodeString& tzID); 635 virtual const UChar* getName(UTimeZoneNameType type); 636 637private: 638 TZNames(const UChar** names); 639 const UChar* fLocationName; 640 UChar* fLocationNameOwned; 641}; 642 643TZNames::TZNames(const UChar** names) 644: ZNames(names), fLocationName(NULL), fLocationNameOwned(NULL) { 645} 646 647TZNames::~TZNames() { 648 if (fLocationNameOwned) { 649 uprv_free(fLocationNameOwned); 650 } 651} 652 653const UChar* 654TZNames::getName(UTimeZoneNameType type) { 655 if (type == UTZNM_EXEMPLAR_LOCATION) { 656 return fLocationName; 657 } 658 return ZNames::getName(type); 659} 660 661TZNames* 662TZNames::createInstance(UResourceBundle* rb, const char* key, const UnicodeString& tzID) { 663 if (rb == NULL || key == NULL || *key == 0) { 664 return NULL; 665 } 666 667 const UChar** names = loadData(rb, key); 668 const UChar* locationName = NULL; 669 UChar* locationNameOwned = NULL; 670 671 UErrorCode status = U_ZERO_ERROR; 672 int32_t len = 0; 673 674 UResourceBundle* table = ures_getByKeyWithFallback(rb, key, NULL, &status); 675 locationName = ures_getStringByKeyWithFallback(table, gEcTag, &len, &status); 676 // ignore missing resource here 677 status = U_ZERO_ERROR; 678 679 ures_close(table); 680 681 if (locationName == NULL) { 682 UnicodeString tmpName; 683 int32_t tmpNameLen = 0; 684 TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, tmpName); 685 tmpNameLen = tmpName.length(); 686 687 if (tmpNameLen > 0) { 688 locationNameOwned = (UChar*) uprv_malloc(sizeof(UChar) * (tmpNameLen + 1)); 689 if (locationNameOwned) { 690 tmpName.extract(locationNameOwned, tmpNameLen + 1, status); 691 locationName = locationNameOwned; 692 } 693 } 694 } 695 696 TZNames* tznames = NULL; 697 if (locationName != NULL || names != NULL) { 698 tznames = new TZNames(names); 699 if (tznames == NULL) { 700 if (locationNameOwned) { 701 uprv_free(locationNameOwned); 702 } 703 } 704 tznames->fLocationName = locationName; 705 tznames->fLocationNameOwned = locationNameOwned; 706 } 707 708 return tznames; 709} 710 711// --------------------------------------------------- 712// The meta zone ID enumeration class 713// --------------------------------------------------- 714class MetaZoneIDsEnumeration : public StringEnumeration { 715public: 716 MetaZoneIDsEnumeration(); 717 MetaZoneIDsEnumeration(const UVector& mzIDs); 718 MetaZoneIDsEnumeration(UVector* mzIDs); 719 virtual ~MetaZoneIDsEnumeration(); 720 static UClassID U_EXPORT2 getStaticClassID(void); 721 virtual UClassID getDynamicClassID(void) const; 722 virtual const UnicodeString* snext(UErrorCode& status); 723 virtual void reset(UErrorCode& status); 724 virtual int32_t count(UErrorCode& status) const; 725private: 726 int32_t fLen; 727 int32_t fPos; 728 const UVector* fMetaZoneIDs; 729 UVector *fLocalVector; 730}; 731 732UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MetaZoneIDsEnumeration) 733 734MetaZoneIDsEnumeration::MetaZoneIDsEnumeration() 735: fLen(0), fPos(0), fMetaZoneIDs(NULL), fLocalVector(NULL) { 736} 737 738MetaZoneIDsEnumeration::MetaZoneIDsEnumeration(const UVector& mzIDs) 739: fPos(0), fMetaZoneIDs(&mzIDs), fLocalVector(NULL) { 740 fLen = fMetaZoneIDs->size(); 741} 742 743MetaZoneIDsEnumeration::MetaZoneIDsEnumeration(UVector *mzIDs) 744: fLen(0), fPos(0), fMetaZoneIDs(mzIDs), fLocalVector(mzIDs) { 745 if (fMetaZoneIDs) { 746 fLen = fMetaZoneIDs->size(); 747 } 748} 749 750const UnicodeString* 751MetaZoneIDsEnumeration::snext(UErrorCode& status) { 752 if (U_SUCCESS(status) && fMetaZoneIDs != NULL && fPos < fLen) { 753 unistr.setTo((const UChar*)fMetaZoneIDs->elementAt(fPos++), -1); 754 return &unistr; 755 } 756 return NULL; 757} 758 759void 760MetaZoneIDsEnumeration::reset(UErrorCode& /*status*/) { 761 fPos = 0; 762} 763 764int32_t 765MetaZoneIDsEnumeration::count(UErrorCode& /*status*/) const { 766 return fLen; 767} 768 769MetaZoneIDsEnumeration::~MetaZoneIDsEnumeration() { 770 if (fLocalVector) { 771 delete fLocalVector; 772 } 773} 774 775U_CDECL_BEGIN 776/** 777 * ZNameInfo stores zone name information in the trie 778 */ 779typedef struct ZNameInfo { 780 UTimeZoneNameType type; 781 const UChar* tzID; 782 const UChar* mzID; 783} ZNameInfo; 784 785/** 786 * ZMatchInfo stores zone name match information used by find method 787 */ 788typedef struct ZMatchInfo { 789 const ZNameInfo* znameInfo; 790 int32_t matchLength; 791} ZMatchInfo; 792U_CDECL_END 793 794 795// --------------------------------------------------- 796// ZNameSearchHandler 797// --------------------------------------------------- 798class ZNameSearchHandler : public TextTrieMapSearchResultHandler { 799public: 800 ZNameSearchHandler(uint32_t types); 801 virtual ~ZNameSearchHandler(); 802 803 UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status); 804 TimeZoneNames::MatchInfoCollection* getMatches(int32_t& maxMatchLen); 805 806private: 807 uint32_t fTypes; 808 int32_t fMaxMatchLen; 809 TimeZoneNames::MatchInfoCollection* fResults; 810}; 811 812ZNameSearchHandler::ZNameSearchHandler(uint32_t types) 813: fTypes(types), fMaxMatchLen(0), fResults(NULL) { 814} 815 816ZNameSearchHandler::~ZNameSearchHandler() { 817 if (fResults != NULL) { 818 delete fResults; 819 } 820} 821 822UBool 823ZNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) { 824 if (U_FAILURE(status)) { 825 return FALSE; 826 } 827 if (node->hasValues()) { 828 int32_t valuesCount = node->countValues(); 829 for (int32_t i = 0; i < valuesCount; i++) { 830 ZNameInfo *nameinfo = (ZNameInfo *)node->getValue(i); 831 if (nameinfo == NULL) { 832 continue; 833 } 834 if ((nameinfo->type & fTypes) != 0) { 835 // matches a requested type 836 if (fResults == NULL) { 837 fResults = new TimeZoneNames::MatchInfoCollection(); 838 if (fResults == NULL) { 839 status = U_MEMORY_ALLOCATION_ERROR; 840 } 841 } 842 if (U_SUCCESS(status)) { 843 U_ASSERT(fResults != NULL); 844 if (nameinfo->tzID) { 845 fResults->addZone(nameinfo->type, matchLength, UnicodeString(nameinfo->tzID, -1), status); 846 } else { 847 U_ASSERT(nameinfo->mzID); 848 fResults->addMetaZone(nameinfo->type, matchLength, UnicodeString(nameinfo->mzID, -1), status); 849 } 850 if (U_SUCCESS(status) && matchLength > fMaxMatchLen) { 851 fMaxMatchLen = matchLength; 852 } 853 } 854 } 855 } 856 } 857 return TRUE; 858} 859 860TimeZoneNames::MatchInfoCollection* 861ZNameSearchHandler::getMatches(int32_t& maxMatchLen) { 862 // give the ownership to the caller 863 TimeZoneNames::MatchInfoCollection* results = fResults; 864 maxMatchLen = fMaxMatchLen; 865 866 // reset 867 fResults = NULL; 868 fMaxMatchLen = 0; 869 return results; 870} 871 872// --------------------------------------------------- 873// TimeZoneNamesImpl 874// 875// TimeZoneNames implementation class. This is the main 876// part of this module. 877// --------------------------------------------------- 878 879U_CDECL_BEGIN 880/** 881 * Deleter for ZNames 882 */ 883static void U_CALLCONV 884deleteZNames(void *obj) { 885 if (obj != EMPTY) { 886 delete (ZNames *)obj; 887 } 888} 889/** 890 * Deleter for TZNames 891 */ 892static void U_CALLCONV 893deleteTZNames(void *obj) { 894 if (obj != EMPTY) { 895 delete (TZNames *)obj; 896 } 897} 898 899/** 900 * Deleter for ZNameInfo 901 */ 902static void U_CALLCONV 903deleteZNameInfo(void *obj) { 904 uprv_free(obj); 905} 906 907U_CDECL_END 908 909static UMutex gLock = U_MUTEX_INITIALIZER; 910 911TimeZoneNamesImpl::TimeZoneNamesImpl(const Locale& locale, UErrorCode& status) 912: fLocale(locale), 913 fZoneStrings(NULL), 914 fTZNamesMap(NULL), 915 fMZNamesMap(NULL), 916 fNamesTrieFullyLoaded(FALSE), 917 fNamesTrie(TRUE, deleteZNameInfo) { 918 initialize(locale, status); 919} 920 921void 922TimeZoneNamesImpl::initialize(const Locale& locale, UErrorCode& status) { 923 if (U_FAILURE(status)) { 924 return; 925 } 926 927 // Load zoneStrings bundle 928 UErrorCode tmpsts = U_ZERO_ERROR; // OK with fallback warning.. 929 fZoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts); 930 fZoneStrings = ures_getByKeyWithFallback(fZoneStrings, gZoneStrings, fZoneStrings, &tmpsts); 931 if (U_FAILURE(tmpsts)) { 932 status = tmpsts; 933 cleanup(); 934 return; 935 } 936 937 // Initialize hashtables holding time zone/meta zone names 938 fMZNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); 939 fTZNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); 940 if (U_FAILURE(status)) { 941 cleanup(); 942 return; 943 } 944 945 uhash_setValueDeleter(fMZNamesMap, deleteZNames); 946 uhash_setValueDeleter(fTZNamesMap, deleteTZNames); 947 // no key deleters for name maps 948 949 // preload zone strings for the default zone 950 TimeZone *tz = TimeZone::createDefault(); 951 const UChar *tzID = ZoneMeta::getCanonicalCLDRID(*tz); 952 if (tzID != NULL) { 953 loadStrings(UnicodeString(tzID)); 954 } 955 delete tz; 956 957 return; 958} 959 960/* 961 * This method updates the cache and must be called with a lock, 962 * except initializer. 963 */ 964void 965TimeZoneNamesImpl::loadStrings(const UnicodeString& tzCanonicalID) { 966 loadTimeZoneNames(tzCanonicalID); 967 968 UErrorCode status = U_ZERO_ERROR; 969 StringEnumeration *mzIDs = getAvailableMetaZoneIDs(tzCanonicalID, status); 970 if (U_SUCCESS(status) && mzIDs != NULL) { 971 const UnicodeString *mzID; 972 while ((mzID = mzIDs->snext(status))) { 973 if (U_FAILURE(status)) { 974 break; 975 } 976 loadMetaZoneNames(*mzID); 977 } 978 delete mzIDs; 979 } 980} 981 982TimeZoneNamesImpl::~TimeZoneNamesImpl() { 983 cleanup(); 984} 985 986void 987TimeZoneNamesImpl::cleanup() { 988 if (fZoneStrings != NULL) { 989 ures_close(fZoneStrings); 990 fZoneStrings = NULL; 991 } 992 if (fMZNamesMap != NULL) { 993 uhash_close(fMZNamesMap); 994 fMZNamesMap = NULL; 995 } 996 if (fTZNamesMap != NULL) { 997 uhash_close(fTZNamesMap); 998 fTZNamesMap = NULL; 999 } 1000} 1001 1002UBool 1003TimeZoneNamesImpl::operator==(const TimeZoneNames& other) const { 1004 if (this == &other) { 1005 return TRUE; 1006 } 1007 // No implementation for now 1008 return FALSE; 1009} 1010 1011TimeZoneNames* 1012TimeZoneNamesImpl::clone() const { 1013 UErrorCode status = U_ZERO_ERROR; 1014 return new TimeZoneNamesImpl(fLocale, status); 1015} 1016 1017StringEnumeration* 1018TimeZoneNamesImpl::getAvailableMetaZoneIDs(UErrorCode& status) const { 1019 return TimeZoneNamesImpl::_getAvailableMetaZoneIDs(status); 1020} 1021 1022// static implementation of getAvailableMetaZoneIDs(UErrorCode&) 1023StringEnumeration* 1024TimeZoneNamesImpl::_getAvailableMetaZoneIDs(UErrorCode& status) { 1025 if (U_FAILURE(status)) { 1026 return NULL; 1027 } 1028 const UVector* mzIDs = ZoneMeta::getAvailableMetazoneIDs(); 1029 if (mzIDs == NULL) { 1030 return new MetaZoneIDsEnumeration(); 1031 } 1032 return new MetaZoneIDsEnumeration(*mzIDs); 1033} 1034 1035StringEnumeration* 1036TimeZoneNamesImpl::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { 1037 return TimeZoneNamesImpl::_getAvailableMetaZoneIDs(tzID, status); 1038} 1039 1040// static implementation of getAvailableMetaZoneIDs(const UnicodeString&, UErrorCode&) 1041StringEnumeration* 1042TimeZoneNamesImpl::_getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) { 1043 if (U_FAILURE(status)) { 1044 return NULL; 1045 } 1046 const UVector* mappings = ZoneMeta::getMetazoneMappings(tzID); 1047 if (mappings == NULL) { 1048 return new MetaZoneIDsEnumeration(); 1049 } 1050 1051 MetaZoneIDsEnumeration *senum = NULL; 1052 UVector* mzIDs = new UVector(NULL, uhash_compareUChars, status); 1053 if (mzIDs == NULL) { 1054 status = U_MEMORY_ALLOCATION_ERROR; 1055 } 1056 if (U_SUCCESS(status)) { 1057 U_ASSERT(mzIDs != NULL); 1058 for (int32_t i = 0; U_SUCCESS(status) && i < mappings->size(); i++) { 1059 1060 OlsonToMetaMappingEntry *map = (OlsonToMetaMappingEntry *)mappings->elementAt(i); 1061 const UChar *mzID = map->mzid; 1062 if (!mzIDs->contains((void *)mzID)) { 1063 mzIDs->addElement((void *)mzID, status); 1064 } 1065 } 1066 if (U_SUCCESS(status)) { 1067 senum = new MetaZoneIDsEnumeration(mzIDs); 1068 } else { 1069 delete mzIDs; 1070 } 1071 } 1072 return senum; 1073} 1074 1075UnicodeString& 1076TimeZoneNamesImpl::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { 1077 return TimeZoneNamesImpl::_getMetaZoneID(tzID, date, mzID); 1078} 1079 1080// static implementation of getMetaZoneID 1081UnicodeString& 1082TimeZoneNamesImpl::_getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) { 1083 ZoneMeta::getMetazoneID(tzID, date, mzID); 1084 return mzID; 1085} 1086 1087UnicodeString& 1088TimeZoneNamesImpl::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { 1089 return TimeZoneNamesImpl::_getReferenceZoneID(mzID, region, tzID); 1090} 1091 1092// static implementaion of getReferenceZoneID 1093UnicodeString& 1094TimeZoneNamesImpl::_getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) { 1095 ZoneMeta::getZoneIdByMetazone(mzID, UnicodeString(region, -1, US_INV), tzID); 1096 return tzID; 1097} 1098 1099 1100UnicodeString& 1101TimeZoneNamesImpl::getMetaZoneDisplayName(const UnicodeString& mzID, 1102 UTimeZoneNameType type, 1103 UnicodeString& name) const { 1104 name.setToBogus(); // cleanup result. 1105 if (mzID.isEmpty()) { 1106 return name; 1107 } 1108 1109 ZNames *znames = NULL; 1110 TimeZoneNamesImpl *nonConstThis = const_cast<TimeZoneNamesImpl *>(this); 1111 1112 umtx_lock(&gLock); 1113 { 1114 znames = nonConstThis->loadMetaZoneNames(mzID); 1115 } 1116 umtx_unlock(&gLock); 1117 1118 if (znames != NULL) { 1119 const UChar* s = znames->getName(type); 1120 if (s != NULL) { 1121 name.setTo(TRUE, s, -1); 1122 } 1123 } 1124 return name; 1125} 1126 1127UnicodeString& 1128TimeZoneNamesImpl::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const { 1129 name.setToBogus(); // cleanup result. 1130 if (tzID.isEmpty()) { 1131 return name; 1132 } 1133 1134 TZNames *tznames = NULL; 1135 TimeZoneNamesImpl *nonConstThis = const_cast<TimeZoneNamesImpl *>(this); 1136 1137 umtx_lock(&gLock); 1138 { 1139 tznames = nonConstThis->loadTimeZoneNames(tzID); 1140 } 1141 umtx_unlock(&gLock); 1142 1143 if (tznames != NULL) { 1144 const UChar *s = tznames->getName(type); 1145 if (s != NULL) { 1146 name.setTo(TRUE, s, -1); 1147 } 1148 } 1149 return name; 1150} 1151 1152UnicodeString& 1153TimeZoneNamesImpl::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { 1154 name.setToBogus(); // cleanup result. 1155 const UChar* locName = NULL; 1156 TZNames *tznames = NULL; 1157 TimeZoneNamesImpl *nonConstThis = const_cast<TimeZoneNamesImpl *>(this); 1158 1159 umtx_lock(&gLock); 1160 { 1161 tznames = nonConstThis->loadTimeZoneNames(tzID); 1162 } 1163 umtx_unlock(&gLock); 1164 1165 if (tznames != NULL) { 1166 locName = tznames->getName(UTZNM_EXEMPLAR_LOCATION); 1167 } 1168 if (locName != NULL) { 1169 name.setTo(TRUE, locName, -1); 1170 } 1171 1172 return name; 1173} 1174 1175 1176// Merge the MZ_PREFIX and mzId 1177static void mergeTimeZoneKey(const UnicodeString& mzID, char* result) { 1178 if (mzID.isEmpty()) { 1179 result[0] = '\0'; 1180 return; 1181 } 1182 1183 char mzIdChar[ZID_KEY_MAX + 1]; 1184 int32_t keyLen; 1185 int32_t prefixLen = uprv_strlen(gMZPrefix); 1186 keyLen = mzID.extract(0, mzID.length(), mzIdChar, ZID_KEY_MAX + 1, US_INV); 1187 uprv_memcpy((void *)result, (void *)gMZPrefix, prefixLen); 1188 uprv_memcpy((void *)(result + prefixLen), (void *)mzIdChar, keyLen); 1189 result[keyLen + prefixLen] = '\0'; 1190} 1191 1192/* 1193 * This method updates the cache and must be called with a lock 1194 */ 1195ZNames* 1196TimeZoneNamesImpl::loadMetaZoneNames(const UnicodeString& mzID) { 1197 if (mzID.length() > (ZID_KEY_MAX - MZ_PREFIX_LEN)) { 1198 return NULL; 1199 } 1200 1201 ZNames *znames = NULL; 1202 1203 UErrorCode status = U_ZERO_ERROR; 1204 UChar mzIDKey[ZID_KEY_MAX + 1]; 1205 mzID.extract(mzIDKey, ZID_KEY_MAX + 1, status); 1206 U_ASSERT(status == U_ZERO_ERROR); // already checked length above 1207 mzIDKey[mzID.length()] = 0; 1208 1209 void *cacheVal = uhash_get(fMZNamesMap, mzIDKey); 1210 if (cacheVal == NULL) { 1211 char key[ZID_KEY_MAX + 1]; 1212 mergeTimeZoneKey(mzID, key); 1213 znames = ZNames::createInstance(fZoneStrings, key); 1214 1215 if (znames == NULL) { 1216 cacheVal = (void *)EMPTY; 1217 } else { 1218 cacheVal = znames; 1219 } 1220 // Use the persistent ID as the resource key, so we can 1221 // avoid duplications. 1222 const UChar* newKey = ZoneMeta::findMetaZoneID(mzID); 1223 if (newKey != NULL) { 1224 uhash_put(fMZNamesMap, (void *)newKey, cacheVal, &status); 1225 if (U_FAILURE(status)) { 1226 if (znames != NULL) { 1227 delete znames; 1228 znames = NULL; 1229 } 1230 } else if (znames != NULL) { 1231 // put the name info into the trie 1232 for (int32_t i = 0; ALL_NAME_TYPES[i] != UTZNM_UNKNOWN; i++) { 1233 const UChar* name = znames->getName(ALL_NAME_TYPES[i]); 1234 if (name != NULL) { 1235 ZNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(ZNameInfo)); 1236 if (nameinfo != NULL) { 1237 nameinfo->type = ALL_NAME_TYPES[i]; 1238 nameinfo->tzID = NULL; 1239 nameinfo->mzID = newKey; 1240 fNamesTrie.put(name, nameinfo, status); 1241 } 1242 } 1243 } 1244 } 1245 1246 } else { 1247 // Should never happen with a valid input 1248 if (znames != NULL) { 1249 // It's not possible that we get a valid ZNames with unknown ID. 1250 // But just in case.. 1251 delete znames; 1252 znames = NULL; 1253 } 1254 } 1255 } else if (cacheVal != EMPTY) { 1256 znames = (ZNames *)cacheVal; 1257 } 1258 1259 return znames; 1260} 1261 1262/* 1263 * This method updates the cache and must be called with a lock 1264 */ 1265TZNames* 1266TimeZoneNamesImpl::loadTimeZoneNames(const UnicodeString& tzID) { 1267 if (tzID.length() > ZID_KEY_MAX) { 1268 return NULL; 1269 } 1270 1271 TZNames *tznames = NULL; 1272 1273 UErrorCode status = U_ZERO_ERROR; 1274 UChar tzIDKey[ZID_KEY_MAX + 1]; 1275 int32_t tzIDKeyLen = tzID.extract(tzIDKey, ZID_KEY_MAX + 1, status); 1276 U_ASSERT(status == U_ZERO_ERROR); // already checked length above 1277 tzIDKey[tzIDKeyLen] = 0; 1278 1279 void *cacheVal = uhash_get(fTZNamesMap, tzIDKey); 1280 if (cacheVal == NULL) { 1281 char key[ZID_KEY_MAX + 1]; 1282 UErrorCode status = U_ZERO_ERROR; 1283 // Replace "/" with ":". 1284 UnicodeString uKey(tzID); 1285 for (int32_t i = 0; i < uKey.length(); i++) { 1286 if (uKey.charAt(i) == (UChar)0x2F) { 1287 uKey.setCharAt(i, (UChar)0x3A); 1288 } 1289 } 1290 uKey.extract(0, uKey.length(), key, sizeof(key), US_INV); 1291 tznames = TZNames::createInstance(fZoneStrings, key, tzID); 1292 1293 if (tznames == NULL) { 1294 cacheVal = (void *)EMPTY; 1295 } else { 1296 cacheVal = tznames; 1297 } 1298 // Use the persistent ID as the resource key, so we can 1299 // avoid duplications. 1300 const UChar* newKey = ZoneMeta::findTimeZoneID(tzID); 1301 if (newKey != NULL) { 1302 uhash_put(fTZNamesMap, (void *)newKey, cacheVal, &status); 1303 if (U_FAILURE(status)) { 1304 if (tznames != NULL) { 1305 delete tznames; 1306 tznames = NULL; 1307 } 1308 } else if (tznames != NULL) { 1309 // put the name info into the trie 1310 for (int32_t i = 0; ALL_NAME_TYPES[i] != UTZNM_UNKNOWN; i++) { 1311 const UChar* name = tznames->getName(ALL_NAME_TYPES[i]); 1312 if (name != NULL) { 1313 ZNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(ZNameInfo)); 1314 if (nameinfo != NULL) { 1315 nameinfo->type = ALL_NAME_TYPES[i]; 1316 nameinfo->tzID = newKey; 1317 nameinfo->mzID = NULL; 1318 fNamesTrie.put(name, nameinfo, status); 1319 } 1320 } 1321 } 1322 } 1323 } else { 1324 // Should never happen with a valid input 1325 if (tznames != NULL) { 1326 // It's not possible that we get a valid TZNames with unknown ID. 1327 // But just in case.. 1328 delete tznames; 1329 tznames = NULL; 1330 } 1331 } 1332 } else if (cacheVal != EMPTY) { 1333 tznames = (TZNames *)cacheVal; 1334 } 1335 1336 return tznames; 1337} 1338 1339TimeZoneNames::MatchInfoCollection* 1340TimeZoneNamesImpl::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { 1341 ZNameSearchHandler handler(types); 1342 1343 TimeZoneNamesImpl *nonConstThis = const_cast<TimeZoneNamesImpl *>(this); 1344 1345 umtx_lock(&gLock); 1346 { 1347 fNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); 1348 } 1349 umtx_unlock(&gLock); 1350 1351 if (U_FAILURE(status)) { 1352 return NULL; 1353 } 1354 1355 int32_t maxLen = 0; 1356 TimeZoneNames::MatchInfoCollection* matches = handler.getMatches(maxLen); 1357 if (matches != NULL && ((maxLen == (text.length() - start)) || fNamesTrieFullyLoaded)) { 1358 // perfect match 1359 return matches; 1360 } 1361 1362 delete matches; 1363 1364 // All names are not yet loaded into the trie 1365 umtx_lock(&gLock); 1366 { 1367 if (!fNamesTrieFullyLoaded) { 1368 const UnicodeString *id; 1369 1370 // load strings for all zones 1371 StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); 1372 if (U_SUCCESS(status)) { 1373 while ((id = tzIDs->snext(status))) { 1374 if (U_FAILURE(status)) { 1375 break; 1376 } 1377 // loadStrings also load related metazone strings 1378 nonConstThis->loadStrings(*id); 1379 } 1380 } 1381 if (tzIDs != NULL) { 1382 delete tzIDs; 1383 } 1384 if (U_SUCCESS(status)) { 1385 nonConstThis->fNamesTrieFullyLoaded = TRUE; 1386 } 1387 } 1388 } 1389 umtx_unlock(&gLock); 1390 1391 if (U_FAILURE(status)) { 1392 return NULL; 1393 } 1394 1395 umtx_lock(&gLock); 1396 { 1397 // now try it again 1398 fNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); 1399 } 1400 umtx_unlock(&gLock); 1401 1402 return handler.getMatches(maxLen); 1403} 1404 1405static const UChar gEtcPrefix[] = { 0x45, 0x74, 0x63, 0x2F }; // "Etc/" 1406static const int32_t gEtcPrefixLen = 4; 1407static const UChar gSystemVPrefix[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F }; // "SystemV/ 1408static const int32_t gSystemVPrefixLen = 8; 1409static const UChar gRiyadh8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38 }; // "Riyadh8" 1410static const int32_t gRiyadh8Len = 7; 1411 1412UnicodeString& U_EXPORT2 1413TimeZoneNamesImpl::getDefaultExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) { 1414 if (tzID.isEmpty() || tzID.startsWith(gEtcPrefix, gEtcPrefixLen) 1415 || tzID.startsWith(gSystemVPrefix, gSystemVPrefixLen) || tzID.indexOf(gRiyadh8, gRiyadh8Len, 0) > 0) { 1416 name.setToBogus(); 1417 return name; 1418 } 1419 1420 int32_t sep = tzID.lastIndexOf((UChar)0x2F /* '/' */); 1421 if (sep > 0 && sep + 1 < tzID.length()) { 1422 name.setTo(tzID, sep + 1); 1423 name.findAndReplace(UnicodeString((UChar)0x5f /* _ */), 1424 UnicodeString((UChar)0x20 /* space */)); 1425 } else { 1426 name.setToBogus(); 1427 } 1428 return name; 1429} 1430 1431// --------------------------------------------------- 1432// TZDBTimeZoneNames and its supporting classes 1433// 1434// TZDBTimeZoneNames is an implementation class of 1435// TimeZoneNames holding the IANA tz database abbreviations. 1436// --------------------------------------------------- 1437 1438class TZDBNames : public UMemory { 1439public: 1440 virtual ~TZDBNames(); 1441 1442 static TZDBNames* createInstance(UResourceBundle* rb, const char* key); 1443 const UChar* getName(UTimeZoneNameType type) const; 1444 const char** getParseRegions(int32_t& numRegions) const; 1445 1446protected: 1447 TZDBNames(const UChar** names, char** regions, int32_t numRegions); 1448 1449private: 1450 const UChar** fNames; 1451 char** fRegions; 1452 int32_t fNumRegions; 1453}; 1454 1455TZDBNames::TZDBNames(const UChar** names, char** regions, int32_t numRegions) 1456 : fNames(names), 1457 fRegions(regions), 1458 fNumRegions(numRegions) { 1459} 1460 1461TZDBNames::~TZDBNames() { 1462 if (fNames != NULL) { 1463 uprv_free(fNames); 1464 } 1465 if (fRegions != NULL) { 1466 char **p = fRegions; 1467 for (int32_t i = 0; i < fNumRegions; p++, i++) { 1468 uprv_free(*p); 1469 } 1470 uprv_free(fRegions); 1471 } 1472} 1473 1474TZDBNames* 1475TZDBNames::createInstance(UResourceBundle* rb, const char* key) { 1476 if (rb == NULL || key == NULL || *key == 0) { 1477 return NULL; 1478 } 1479 1480 UErrorCode status = U_ZERO_ERROR; 1481 1482 const UChar **names = NULL; 1483 char** regions = NULL; 1484 int32_t numRegions = 0; 1485 1486 int32_t len = 0; 1487 1488 UResourceBundle* rbTable = NULL; 1489 rbTable = ures_getByKey(rb, key, rbTable, &status); 1490 if (U_FAILURE(status)) { 1491 return NULL; 1492 } 1493 1494 names = (const UChar **)uprv_malloc(sizeof(const UChar*) * TZDBNAMES_KEYS_SIZE); 1495 UBool isEmpty = TRUE; 1496 if (names != NULL) { 1497 for (int32_t i = 0; i < TZDBNAMES_KEYS_SIZE; i++) { 1498 status = U_ZERO_ERROR; 1499 const UChar *value = ures_getStringByKey(rbTable, TZDBNAMES_KEYS[i], &len, &status); 1500 if (U_FAILURE(status) || len == 0) { 1501 names[i] = NULL; 1502 } else { 1503 names[i] = value; 1504 isEmpty = FALSE; 1505 } 1506 } 1507 } 1508 1509 if (isEmpty) { 1510 if (names != NULL) { 1511 uprv_free(names); 1512 } 1513 return NULL; 1514 } 1515 1516 UResourceBundle *regionsRes = ures_getByKey(rbTable, "parseRegions", NULL, &status); 1517 UBool regionError = FALSE; 1518 if (U_SUCCESS(status)) { 1519 numRegions = ures_getSize(regionsRes); 1520 if (numRegions > 0) { 1521 regions = (char**)uprv_malloc(sizeof(char*) * numRegions); 1522 if (regions != NULL) { 1523 char **pRegion = regions; 1524 for (int32_t i = 0; i < numRegions; i++, pRegion++) { 1525 *pRegion = NULL; 1526 } 1527 // filling regions 1528 pRegion = regions; 1529 for (int32_t i = 0; i < numRegions; i++, pRegion++) { 1530 status = U_ZERO_ERROR; 1531 const UChar *uregion = ures_getStringByIndex(regionsRes, i, &len, &status); 1532 if (U_FAILURE(status)) { 1533 regionError = TRUE; 1534 break; 1535 } 1536 *pRegion = (char*)uprv_malloc(sizeof(char) * (len + 1)); 1537 if (*pRegion == NULL) { 1538 regionError = TRUE; 1539 break; 1540 } 1541 u_UCharsToChars(uregion, *pRegion, len); 1542 (*pRegion)[len] = 0; 1543 } 1544 } 1545 } 1546 } 1547 ures_close(regionsRes); 1548 ures_close(rbTable); 1549 1550 if (regionError) { 1551 if (names != NULL) { 1552 uprv_free(names); 1553 } 1554 if (regions != NULL) { 1555 char **p = regions; 1556 for (int32_t i = 0; i < numRegions; p++, i++) { 1557 uprv_free(p); 1558 } 1559 uprv_free(regions); 1560 } 1561 return NULL; 1562 } 1563 1564 return new TZDBNames(names, regions, numRegions); 1565} 1566 1567const UChar* 1568TZDBNames::getName(UTimeZoneNameType type) const { 1569 if (fNames == NULL) { 1570 return NULL; 1571 } 1572 const UChar *name = NULL; 1573 switch(type) { 1574 case UTZNM_SHORT_STANDARD: 1575 name = fNames[0]; 1576 break; 1577 case UTZNM_SHORT_DAYLIGHT: 1578 name = fNames[1]; 1579 break; 1580 default: 1581 name = NULL; 1582 } 1583 return name; 1584} 1585 1586const char** 1587TZDBNames::getParseRegions(int32_t& numRegions) const { 1588 if (fRegions == NULL) { 1589 numRegions = 0; 1590 } else { 1591 numRegions = fNumRegions; 1592 } 1593 return (const char**)fRegions; 1594} 1595 1596U_CDECL_BEGIN 1597/** 1598 * TZDBNameInfo stores metazone name information for the IANA abbreviations 1599 * in the trie 1600 */ 1601typedef struct TZDBNameInfo { 1602 const UChar* mzID; 1603 UTimeZoneNameType type; 1604 UBool ambiguousType; 1605 const char** parseRegions; 1606 int32_t nRegions; 1607} TZDBNameInfo; 1608U_CDECL_END 1609 1610 1611class TZDBNameSearchHandler : public TextTrieMapSearchResultHandler { 1612public: 1613 TZDBNameSearchHandler(uint32_t types, const char* region); 1614 virtual ~TZDBNameSearchHandler(); 1615 1616 UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status); 1617 TimeZoneNames::MatchInfoCollection* getMatches(int32_t& maxMatchLen); 1618 1619private: 1620 uint32_t fTypes; 1621 int32_t fMaxMatchLen; 1622 TimeZoneNames::MatchInfoCollection* fResults; 1623 const char* fRegion; 1624}; 1625 1626TZDBNameSearchHandler::TZDBNameSearchHandler(uint32_t types, const char* region) 1627: fTypes(types), fMaxMatchLen(0), fResults(NULL), fRegion(region) { 1628} 1629 1630TZDBNameSearchHandler::~TZDBNameSearchHandler() { 1631 if (fResults != NULL) { 1632 delete fResults; 1633 } 1634} 1635 1636UBool 1637TZDBNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) { 1638 if (U_FAILURE(status)) { 1639 return FALSE; 1640 } 1641 1642 TZDBNameInfo *match = NULL; 1643 TZDBNameInfo *defaultRegionMatch = NULL; 1644 1645 if (node->hasValues()) { 1646 int32_t valuesCount = node->countValues(); 1647 for (int32_t i = 0; i < valuesCount; i++) { 1648 TZDBNameInfo *ninfo = (TZDBNameInfo *)node->getValue(i); 1649 if (ninfo == NULL) { 1650 continue; 1651 } 1652 if ((ninfo->type & fTypes) != 0) { 1653 // Some tz database abbreviations are ambiguous. For example, 1654 // CST means either Central Standard Time or China Standard Time. 1655 // Unlike CLDR time zone display names, this implementation 1656 // does not use unique names. And TimeZoneFormat does not expect 1657 // multiple results returned for the same time zone type. 1658 // For this reason, this implementation resolve one among same 1659 // zone type with a same name at this level. 1660 if (ninfo->parseRegions == NULL) { 1661 // parseRegions == null means this is the default metazone 1662 // mapping for the abbreviation. 1663 if (defaultRegionMatch == NULL) { 1664 match = defaultRegionMatch = ninfo; 1665 } 1666 } else { 1667 UBool matchRegion = FALSE; 1668 // non-default metazone mapping for an abbreviation 1669 // comes with applicable regions. For example, the default 1670 // metazone mapping for "CST" is America_Central, 1671 // but if region is one of CN/MO/TW, "CST" is parsed 1672 // as metazone China (China Standard Time). 1673 for (int32_t i = 0; i < ninfo->nRegions; i++) { 1674 const char *region = ninfo->parseRegions[i]; 1675 if (uprv_strcmp(fRegion, region) == 0) { 1676 match = ninfo; 1677 matchRegion = TRUE; 1678 break; 1679 } 1680 } 1681 if (matchRegion) { 1682 break; 1683 } 1684 if (match == NULL) { 1685 match = ninfo; 1686 } 1687 } 1688 } 1689 } 1690 1691 if (match != NULL) { 1692 UTimeZoneNameType ntype = match->type; 1693 // Note: Workaround for duplicated standard/daylight names 1694 // The tz database contains a few zones sharing a 1695 // same name for both standard time and daylight saving 1696 // time. For example, Australia/Sydney observes DST, 1697 // but "EST" is used for both standard and daylight. 1698 // When both SHORT_STANDARD and SHORT_DAYLIGHT are included 1699 // in the find operation, we cannot tell which one was 1700 // actually matched. 1701 // TimeZoneFormat#parse returns a matched name type (standard 1702 // or daylight) and DateFormat implementation uses the info to 1703 // to adjust actual time. To avoid false type information, 1704 // this implementation replaces the name type with SHORT_GENERIC. 1705 if (match->ambiguousType 1706 && (ntype == UTZNM_SHORT_STANDARD || ntype == UTZNM_SHORT_DAYLIGHT) 1707 && (fTypes & UTZNM_SHORT_STANDARD) != 0 1708 && (fTypes & UTZNM_SHORT_DAYLIGHT) != 0) { 1709 ntype = UTZNM_SHORT_GENERIC; 1710 } 1711 1712 if (fResults == NULL) { 1713 fResults = new TimeZoneNames::MatchInfoCollection(); 1714 if (fResults == NULL) { 1715 status = U_MEMORY_ALLOCATION_ERROR; 1716 } 1717 } 1718 if (U_SUCCESS(status)) { 1719 U_ASSERT(fResults != NULL); 1720 U_ASSERT(match->mzID != NULL); 1721 fResults->addMetaZone(ntype, matchLength, UnicodeString(match->mzID, -1), status); 1722 if (U_SUCCESS(status) && matchLength > fMaxMatchLen) { 1723 fMaxMatchLen = matchLength; 1724 } 1725 } 1726 } 1727 } 1728 return TRUE; 1729} 1730 1731TimeZoneNames::MatchInfoCollection* 1732TZDBNameSearchHandler::getMatches(int32_t& maxMatchLen) { 1733 // give the ownership to the caller 1734 TimeZoneNames::MatchInfoCollection* results = fResults; 1735 maxMatchLen = fMaxMatchLen; 1736 1737 // reset 1738 fResults = NULL; 1739 fMaxMatchLen = 0; 1740 return results; 1741} 1742 1743U_CDECL_BEGIN 1744/** 1745 * Deleter for TZDBNames 1746 */ 1747static void U_CALLCONV 1748deleteTZDBNames(void *obj) { 1749 if (obj != EMPTY) { 1750 delete (TZDBNames *)obj; 1751 } 1752} 1753 1754static void U_CALLCONV initTZDBNamesMap(UErrorCode &status) { 1755 gTZDBNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); 1756 if (U_FAILURE(status)) { 1757 gTZDBNamesMap = NULL; 1758 return; 1759 } 1760 // no key deleters for tzdb name maps 1761 uhash_setValueDeleter(gTZDBNamesMap, deleteTZDBNames); 1762 ucln_i18n_registerCleanup(UCLN_I18N_TZDBTIMEZONENAMES, tzdbTimeZoneNames_cleanup); 1763} 1764 1765/** 1766 * Deleter for TZDBNameInfo 1767 */ 1768static void U_CALLCONV 1769deleteTZDBNameInfo(void *obj) { 1770 if (obj != NULL) { 1771 uprv_free(obj); 1772 } 1773} 1774 1775static void U_CALLCONV prepareFind(UErrorCode &status) { 1776 if (U_FAILURE(status)) { 1777 return; 1778 } 1779 gTZDBNamesTrie = new TextTrieMap(TRUE, deleteTZDBNameInfo); 1780 if (gTZDBNamesTrie == NULL) { 1781 status = U_MEMORY_ALLOCATION_ERROR; 1782 return; 1783 } 1784 1785 const UnicodeString *mzID; 1786 StringEnumeration *mzIDs = TimeZoneNamesImpl::_getAvailableMetaZoneIDs(status); 1787 if (U_SUCCESS(status)) { 1788 while ((mzID = mzIDs->snext(status)) && U_SUCCESS(status)) { 1789 const TZDBNames *names = TZDBTimeZoneNames::getMetaZoneNames(*mzID, status); 1790 if (names == NULL) { 1791 continue; 1792 } 1793 const UChar *std = names->getName(UTZNM_SHORT_STANDARD); 1794 const UChar *dst = names->getName(UTZNM_SHORT_DAYLIGHT); 1795 if (std == NULL && dst == NULL) { 1796 continue; 1797 } 1798 int32_t numRegions = 0; 1799 const char **parseRegions = names->getParseRegions(numRegions); 1800 1801 // The tz database contains a few zones sharing a 1802 // same name for both standard time and daylight saving 1803 // time. For example, Australia/Sydney observes DST, 1804 // but "EST" is used for both standard and daylight. 1805 // we need to store the information for later processing. 1806 UBool ambiguousType = (std != NULL && dst != NULL && u_strcmp(std, dst) == 0); 1807 1808 const UChar *uMzID = ZoneMeta::findMetaZoneID(*mzID); 1809 if (std != NULL) { 1810 TZDBNameInfo *stdInf = (TZDBNameInfo *)uprv_malloc(sizeof(TZDBNameInfo)); 1811 if (stdInf == NULL) { 1812 status = U_MEMORY_ALLOCATION_ERROR; 1813 break; 1814 } 1815 stdInf->mzID = uMzID; 1816 stdInf->type = UTZNM_SHORT_STANDARD; 1817 stdInf->ambiguousType = ambiguousType; 1818 stdInf->parseRegions = parseRegions; 1819 stdInf->nRegions = numRegions; 1820 gTZDBNamesTrie->put(std, stdInf, status); 1821 } 1822 if (U_SUCCESS(status) && dst != NULL) { 1823 TZDBNameInfo *dstInf = (TZDBNameInfo *)uprv_malloc(sizeof(TZDBNameInfo)); 1824 if (dstInf == NULL) { 1825 status = U_MEMORY_ALLOCATION_ERROR; 1826 break; 1827 } 1828 dstInf->mzID = uMzID; 1829 dstInf->type = UTZNM_SHORT_DAYLIGHT; 1830 dstInf->ambiguousType = ambiguousType; 1831 dstInf->parseRegions = parseRegions; 1832 dstInf->nRegions = numRegions; 1833 gTZDBNamesTrie->put(dst, dstInf, status); 1834 } 1835 } 1836 } 1837 delete mzIDs; 1838 1839 if (U_FAILURE(status)) { 1840 delete gTZDBNamesTrie; 1841 gTZDBNamesTrie = NULL; 1842 return; 1843 } 1844 1845 ucln_i18n_registerCleanup(UCLN_I18N_TZDBTIMEZONENAMES, tzdbTimeZoneNames_cleanup); 1846} 1847 1848U_CDECL_END 1849 1850TZDBTimeZoneNames::TZDBTimeZoneNames(const Locale& locale) 1851: fLocale(locale) { 1852 UBool useWorld = TRUE; 1853 const char* region = fLocale.getCountry(); 1854 int32_t regionLen = uprv_strlen(region); 1855 if (regionLen == 0) { 1856 UErrorCode status = U_ZERO_ERROR; 1857 char loc[ULOC_FULLNAME_CAPACITY]; 1858 uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status); 1859 regionLen = uloc_getCountry(loc, fRegion, sizeof(fRegion), &status); 1860 if (U_SUCCESS(status) && regionLen < (int32_t)sizeof(fRegion)) { 1861 useWorld = FALSE; 1862 } 1863 } else if (regionLen < (int32_t)sizeof(fRegion)) { 1864 uprv_strcpy(fRegion, region); 1865 useWorld = FALSE; 1866 } 1867 if (useWorld) { 1868 uprv_strcpy(fRegion, "001"); 1869 } 1870} 1871 1872TZDBTimeZoneNames::~TZDBTimeZoneNames() { 1873} 1874 1875UBool 1876TZDBTimeZoneNames::operator==(const TimeZoneNames& other) const { 1877 if (this == &other) { 1878 return TRUE; 1879 } 1880 // No implementation for now 1881 return FALSE; 1882} 1883 1884TimeZoneNames* 1885TZDBTimeZoneNames::clone() const { 1886 return new TZDBTimeZoneNames(fLocale); 1887} 1888 1889StringEnumeration* 1890TZDBTimeZoneNames::getAvailableMetaZoneIDs(UErrorCode& status) const { 1891 return TimeZoneNamesImpl::_getAvailableMetaZoneIDs(status); 1892} 1893 1894StringEnumeration* 1895TZDBTimeZoneNames::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { 1896 return TimeZoneNamesImpl::_getAvailableMetaZoneIDs(tzID, status); 1897} 1898 1899UnicodeString& 1900TZDBTimeZoneNames::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { 1901 return TimeZoneNamesImpl::_getMetaZoneID(tzID, date, mzID); 1902} 1903 1904UnicodeString& 1905TZDBTimeZoneNames::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { 1906 return TimeZoneNamesImpl::_getReferenceZoneID(mzID, region, tzID); 1907} 1908 1909UnicodeString& 1910TZDBTimeZoneNames::getMetaZoneDisplayName(const UnicodeString& mzID, 1911 UTimeZoneNameType type, 1912 UnicodeString& name) const { 1913 name.setToBogus(); 1914 if (mzID.isEmpty()) { 1915 return name; 1916 } 1917 1918 UErrorCode status = U_ZERO_ERROR; 1919 const TZDBNames *tzdbNames = TZDBTimeZoneNames::getMetaZoneNames(mzID, status); 1920 if (U_SUCCESS(status)) { 1921 const UChar *s = tzdbNames->getName(type); 1922 if (s != NULL) { 1923 name.setTo(TRUE, s, -1); 1924 } 1925 } 1926 1927 return name; 1928} 1929 1930UnicodeString& 1931TZDBTimeZoneNames::getTimeZoneDisplayName(const UnicodeString& /* tzID */, UTimeZoneNameType /* type */, UnicodeString& name) const { 1932 // No abbreviations associated a zone directly for now. 1933 name.setToBogus(); 1934 return name; 1935} 1936 1937TZDBTimeZoneNames::MatchInfoCollection* 1938TZDBTimeZoneNames::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { 1939 umtx_initOnce(gTZDBNamesTrieInitOnce, &prepareFind, status); 1940 if (U_FAILURE(status)) { 1941 return NULL; 1942 } 1943 1944 TZDBNameSearchHandler handler(types, fRegion); 1945 gTZDBNamesTrie->search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); 1946 if (U_FAILURE(status)) { 1947 return NULL; 1948 } 1949 int32_t maxLen = 0; 1950 return handler.getMatches(maxLen); 1951} 1952 1953const TZDBNames* 1954TZDBTimeZoneNames::getMetaZoneNames(const UnicodeString& mzID, UErrorCode& status) { 1955 umtx_initOnce(gTZDBNamesMapInitOnce, &initTZDBNamesMap, status); 1956 if (U_FAILURE(status)) { 1957 return NULL; 1958 } 1959 1960 TZDBNames* tzdbNames = NULL; 1961 1962 UChar mzIDKey[ZID_KEY_MAX + 1]; 1963 mzID.extract(mzIDKey, ZID_KEY_MAX + 1, status); 1964 U_ASSERT(status == U_ZERO_ERROR); // already checked length above 1965 mzIDKey[mzID.length()] = 0; 1966 1967 umtx_lock(&gTZDBNamesMapLock); 1968 { 1969 void *cacheVal = uhash_get(gTZDBNamesMap, mzIDKey); 1970 if (cacheVal == NULL) { 1971 UResourceBundle *zoneStringsRes = ures_openDirect(U_ICUDATA_ZONE, "tzdbNames", &status); 1972 zoneStringsRes = ures_getByKey(zoneStringsRes, gZoneStrings, zoneStringsRes, &status); 1973 if (U_SUCCESS(status)) { 1974 char key[ZID_KEY_MAX + 1]; 1975 mergeTimeZoneKey(mzID, key); 1976 tzdbNames = TZDBNames::createInstance(zoneStringsRes, key); 1977 1978 if (tzdbNames == NULL) { 1979 cacheVal = (void *)EMPTY; 1980 } else { 1981 cacheVal = tzdbNames; 1982 } 1983 // Use the persistent ID as the resource key, so we can 1984 // avoid duplications. 1985 const UChar* newKey = ZoneMeta::findMetaZoneID(mzID); 1986 if (newKey != NULL) { 1987 uhash_put(gTZDBNamesMap, (void *)newKey, cacheVal, &status); 1988 if (U_FAILURE(status)) { 1989 if (tzdbNames != NULL) { 1990 delete tzdbNames; 1991 tzdbNames = NULL; 1992 } 1993 } 1994 } else { 1995 // Should never happen with a valid input 1996 if (tzdbNames != NULL) { 1997 // It's not possible that we get a valid tzdbNames with unknown ID. 1998 // But just in case.. 1999 delete tzdbNames; 2000 tzdbNames = NULL; 2001 } 2002 } 2003 } 2004 ures_close(zoneStringsRes); 2005 } else if (cacheVal != EMPTY) { 2006 tzdbNames = (TZDBNames *)cacheVal; 2007 } 2008 } 2009 umtx_unlock(&gTZDBNamesMapLock); 2010 2011 return tzdbNames; 2012} 2013 2014U_NAMESPACE_END 2015 2016 2017#endif /* #if !UCONFIG_NO_FORMATTING */ 2018 2019//eof 2020