intl/icu/source/i18n/tzgnames.cpp

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /*
michael@0 2 *******************************************************************************
michael@0 3 * Copyright (C) 2011-2013, International Business Machines Corporation and
michael@0 4 * others. All Rights Reserved.
michael@0 5 *******************************************************************************
michael@0 6 */
michael@0 7
michael@0 8 #include "unicode/utypes.h"
michael@0 9
michael@0 10 #if !UCONFIG_NO_FORMATTING
michael@0 11
michael@0 12 #include "tzgnames.h"
michael@0 13
michael@0 14 #include "unicode/basictz.h"
michael@0 15 #include "unicode/locdspnm.h"
michael@0 16 #include "unicode/msgfmt.h"
michael@0 17 #include "unicode/rbtz.h"
michael@0 18 #include "unicode/simpletz.h"
michael@0 19 #include "unicode/vtzone.h"
michael@0 20
michael@0 21 #include "cmemory.h"
michael@0 22 #include "cstring.h"
michael@0 23 #include "mutex.h"
michael@0 24 #include "uhash.h"
michael@0 25 #include "uassert.h"
michael@0 26 #include "umutex.h"
michael@0 27 #include "uresimp.h"
michael@0 28 #include "ureslocs.h"
michael@0 29 #include "zonemeta.h"
michael@0 30 #include "tznames_impl.h"
michael@0 31 #include "olsontz.h"
michael@0 32 #include "ucln_in.h"
michael@0 33
michael@0 34 U_NAMESPACE_BEGIN
michael@0 35
michael@0 36 #define ZID_KEY_MAX 128
michael@0 37
michael@0 38 static const char gZoneStrings[] = "zoneStrings";
michael@0 39
michael@0 40 static const char gRegionFormatTag[] = "regionFormat";
michael@0 41 static const char gFallbackFormatTag[] = "fallbackFormat";
michael@0 42
michael@0 43 static const UChar gEmpty[] = {0x00};
michael@0 44
michael@0 45 static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}"
michael@0 46 static const UChar gDefFallbackPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
michael@0 47
michael@0 48 static const double kDstCheckRange = (double)184*U_MILLIS_PER_DAY;
michael@0 49
michael@0 50
michael@0 51
michael@0 52 U_CDECL_BEGIN
michael@0 53
michael@0 54 typedef struct PartialLocationKey {
michael@0 55 const UChar* tzID;
michael@0 56 const UChar* mzID;
michael@0 57 UBool isLong;
michael@0 58 } PartialLocationKey;
michael@0 59
michael@0 60 /**
michael@0 61 * Hash function for partial location name hash key
michael@0 62 */
michael@0 63 static int32_t U_CALLCONV
michael@0 64 hashPartialLocationKey(const UHashTok key) {
michael@0 65 // <tzID>&<mzID>#[L|S]
michael@0 66 PartialLocationKey *p = (PartialLocationKey *)key.pointer;
michael@0 67 UnicodeString str(p->tzID);
michael@0 68 str.append((UChar)0x26)
michael@0 69 .append(p->mzID, -1)
michael@0 70 .append((UChar)0x23)
michael@0 71 .append((UChar)(p->isLong ? 0x4C : 0x53));
michael@0 72 return str.hashCode();
michael@0 73 }
michael@0 74
michael@0 75 /**
michael@0 76 * Comparer for partial location name hash key
michael@0 77 */
michael@0 78 static UBool U_CALLCONV
michael@0 79 comparePartialLocationKey(const UHashTok key1, const UHashTok key2) {
michael@0 80 PartialLocationKey *p1 = (PartialLocationKey *)key1.pointer;
michael@0 81 PartialLocationKey *p2 = (PartialLocationKey *)key2.pointer;
michael@0 82
michael@0 83 if (p1 == p2) {
michael@0 84 return TRUE;
michael@0 85 }
michael@0 86 if (p1 == NULL || p2 == NULL) {
michael@0 87 return FALSE;
michael@0 88 }
michael@0 89 // We just check identity of tzID/mzID
michael@0 90 return (p1->tzID == p2->tzID && p1->mzID == p2->mzID && p1->isLong == p2->isLong);
michael@0 91 }
michael@0 92
michael@0 93 /**
michael@0 94 * Deleter for GNameInfo
michael@0 95 */
michael@0 96 static void U_CALLCONV
michael@0 97 deleteGNameInfo(void *obj) {
michael@0 98 uprv_free(obj);
michael@0 99 }
michael@0 100
michael@0 101 /**
michael@0 102 * GNameInfo stores zone name information in the local trie
michael@0 103 */
michael@0 104 typedef struct GNameInfo {
michael@0 105 UTimeZoneGenericNameType type;
michael@0 106 const UChar* tzID;
michael@0 107 } ZNameInfo;
michael@0 108
michael@0 109 /**
michael@0 110 * GMatchInfo stores zone name match information used by find method
michael@0 111 */
michael@0 112 typedef struct GMatchInfo {
michael@0 113 const GNameInfo* gnameInfo;
michael@0 114 int32_t matchLength;
michael@0 115 UTimeZoneFormatTimeType timeType;
michael@0 116 } ZMatchInfo;
michael@0 117
michael@0 118 U_CDECL_END
michael@0 119
michael@0 120 // ---------------------------------------------------
michael@0 121 // The class stores time zone generic name match information
michael@0 122 // ---------------------------------------------------
michael@0 123 class TimeZoneGenericNameMatchInfo : public UMemory {
michael@0 124 public:
michael@0 125 TimeZoneGenericNameMatchInfo(UVector* matches);
michael@0 126 ~TimeZoneGenericNameMatchInfo();
michael@0 127
michael@0 128 int32_t size() const;
michael@0 129 UTimeZoneGenericNameType getGenericNameType(int32_t index) const;
michael@0 130 int32_t getMatchLength(int32_t index) const;
michael@0 131 UnicodeString& getTimeZoneID(int32_t index, UnicodeString& tzID) const;
michael@0 132
michael@0 133 private:
michael@0 134 UVector* fMatches; // vector of MatchEntry
michael@0 135 };
michael@0 136
michael@0 137 TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector* matches)
michael@0 138 : fMatches(matches) {
michael@0 139 }
michael@0 140
michael@0 141 TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() {
michael@0 142 if (fMatches != NULL) {
michael@0 143 delete fMatches;
michael@0 144 }
michael@0 145 }
michael@0 146
michael@0 147 int32_t
michael@0 148 TimeZoneGenericNameMatchInfo::size() const {
michael@0 149 if (fMatches == NULL) {
michael@0 150 return 0;
michael@0 151 }
michael@0 152 return fMatches->size();
michael@0 153 }
michael@0 154
michael@0 155 UTimeZoneGenericNameType
michael@0 156 TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index) const {
michael@0 157 GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index);
michael@0 158 if (minfo != NULL) {
michael@0 159 return static_cast<UTimeZoneGenericNameType>(minfo->gnameInfo->type);
michael@0 160 }
michael@0 161 return UTZGNM_UNKNOWN;
michael@0 162 }
michael@0 163
michael@0 164 int32_t
michael@0 165 TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index) const {
michael@0 166 ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index);
michael@0 167 if (minfo != NULL) {
michael@0 168 return minfo->matchLength;
michael@0 169 }
michael@0 170 return -1;
michael@0 171 }
michael@0 172
michael@0 173 UnicodeString&
michael@0 174 TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index, UnicodeString& tzID) const {
michael@0 175 GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index);
michael@0 176 if (minfo != NULL && minfo->gnameInfo->tzID != NULL) {
michael@0 177 tzID.setTo(TRUE, minfo->gnameInfo->tzID, -1);
michael@0 178 } else {
michael@0 179 tzID.setToBogus();
michael@0 180 }
michael@0 181 return tzID;
michael@0 182 }
michael@0 183
michael@0 184 // ---------------------------------------------------
michael@0 185 // GNameSearchHandler
michael@0 186 // ---------------------------------------------------
michael@0 187 class GNameSearchHandler : public TextTrieMapSearchResultHandler {
michael@0 188 public:
michael@0 189 GNameSearchHandler(uint32_t types);
michael@0 190 virtual ~GNameSearchHandler();
michael@0 191
michael@0 192 UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status);
michael@0 193 UVector* getMatches(int32_t& maxMatchLen);
michael@0 194
michael@0 195 private:
michael@0 196 uint32_t fTypes;
michael@0 197 UVector* fResults;
michael@0 198 int32_t fMaxMatchLen;
michael@0 199 };
michael@0 200
michael@0 201 GNameSearchHandler::GNameSearchHandler(uint32_t types)
michael@0 202 : fTypes(types), fResults(NULL), fMaxMatchLen(0) {
michael@0 203 }
michael@0 204
michael@0 205 GNameSearchHandler::~GNameSearchHandler() {
michael@0 206 if (fResults != NULL) {
michael@0 207 delete fResults;
michael@0 208 }
michael@0 209 }
michael@0 210
michael@0 211 UBool
michael@0 212 GNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) {
michael@0 213 if (U_FAILURE(status)) {
michael@0 214 return FALSE;
michael@0 215 }
michael@0 216 if (node->hasValues()) {
michael@0 217 int32_t valuesCount = node->countValues();
michael@0 218 for (int32_t i = 0; i < valuesCount; i++) {
michael@0 219 GNameInfo *nameinfo = (ZNameInfo *)node->getValue(i);
michael@0 220 if (nameinfo == NULL) {
michael@0 221 break;
michael@0 222 }
michael@0 223 if ((nameinfo->type & fTypes) != 0) {
michael@0 224 // matches a requested type
michael@0 225 if (fResults == NULL) {
michael@0 226 fResults = new UVector(uprv_free, NULL, status);
michael@0 227 if (fResults == NULL) {
michael@0 228 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 229 }
michael@0 230 }
michael@0 231 if (U_SUCCESS(status)) {
michael@0 232 U_ASSERT(fResults != NULL);
michael@0 233 GMatchInfo *gmatch = (GMatchInfo *)uprv_malloc(sizeof(GMatchInfo));
michael@0 234 if (gmatch == NULL) {
michael@0 235 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 236 } else {
michael@0 237 // add the match to the vector
michael@0 238 gmatch->gnameInfo = nameinfo;
michael@0 239 gmatch->matchLength = matchLength;
michael@0 240 gmatch->timeType = UTZFMT_TIME_TYPE_UNKNOWN;
michael@0 241 fResults->addElement(gmatch, status);
michael@0 242 if (U_FAILURE(status)) {
michael@0 243 uprv_free(gmatch);
michael@0 244 } else {
michael@0 245 if (matchLength > fMaxMatchLen) {
michael@0 246 fMaxMatchLen = matchLength;
michael@0 247 }
michael@0 248 }
michael@0 249 }
michael@0 250 }
michael@0 251 }
michael@0 252 }
michael@0 253 }
michael@0 254 return TRUE;
michael@0 255 }
michael@0 256
michael@0 257 UVector*
michael@0 258 GNameSearchHandler::getMatches(int32_t& maxMatchLen) {
michael@0 259 // give the ownership to the caller
michael@0 260 UVector *results = fResults;
michael@0 261 maxMatchLen = fMaxMatchLen;
michael@0 262
michael@0 263 // reset
michael@0 264 fResults = NULL;
michael@0 265 fMaxMatchLen = 0;
michael@0 266 return results;
michael@0 267 }
michael@0 268
michael@0 269 static UMutex gLock = U_MUTEX_INITIALIZER;
michael@0 270
michael@0 271 class TZGNCore : public UMemory {
michael@0 272 public:
michael@0 273 TZGNCore(const Locale& locale, UErrorCode& status);
michael@0 274 virtual ~TZGNCore();
michael@0 275
michael@0 276 UnicodeString& getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type,
michael@0 277 UDate date, UnicodeString& name) const;
michael@0 278
michael@0 279 UnicodeString& getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const;
michael@0 280
michael@0 281 int32_t findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
michael@0 282 UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const;
michael@0 283
michael@0 284 private:
michael@0 285 Locale fLocale;
michael@0 286 const TimeZoneNames* fTimeZoneNames;
michael@0 287 UHashtable* fLocationNamesMap;
michael@0 288 UHashtable* fPartialLocationNamesMap;
michael@0 289
michael@0 290 MessageFormat* fRegionFormat;
michael@0 291 MessageFormat* fFallbackFormat;
michael@0 292
michael@0 293 LocaleDisplayNames* fLocaleDisplayNames;
michael@0 294 ZNStringPool fStringPool;
michael@0 295
michael@0 296 TextTrieMap fGNamesTrie;
michael@0 297 UBool fGNamesTrieFullyLoaded;
michael@0 298
michael@0 299 char fTargetRegion[ULOC_COUNTRY_CAPACITY];
michael@0 300
michael@0 301 void initialize(const Locale& locale, UErrorCode& status);
michael@0 302 void cleanup();
michael@0 303
michael@0 304 void loadStrings(const UnicodeString& tzCanonicalID);
michael@0 305
michael@0 306 const UChar* getGenericLocationName(const UnicodeString& tzCanonicalID);
michael@0 307
michael@0 308 UnicodeString& formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type,
michael@0 309 UDate date, UnicodeString& name) const;
michael@0 310
michael@0 311 UnicodeString& getPartialLocationName(const UnicodeString& tzCanonicalID,
michael@0 312 const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName,
michael@0 313 UnicodeString& name) const;
michael@0 314
michael@0 315 const UChar* getPartialLocationName(const UnicodeString& tzCanonicalID,
michael@0 316 const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName);
michael@0 317
michael@0 318 TimeZoneGenericNameMatchInfo* findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
michael@0 319
michael@0 320 TimeZoneNames::MatchInfoCollection* findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
michael@0 321 };
michael@0 322
michael@0 323
michael@0 324 // ---------------------------------------------------
michael@0 325 // TZGNCore - core implmentation of TimeZoneGenericNames
michael@0 326 //
michael@0 327 // TimeZoneGenericNames is parallel to TimeZoneNames,
michael@0 328 // but handles run-time generated time zone names.
michael@0 329 // This is the main part of this module.
michael@0 330 // ---------------------------------------------------
michael@0 331 TZGNCore::TZGNCore(const Locale& locale, UErrorCode& status)
michael@0 332 : fLocale(locale),
michael@0 333 fTimeZoneNames(NULL),
michael@0 334 fLocationNamesMap(NULL),
michael@0 335 fPartialLocationNamesMap(NULL),
michael@0 336 fRegionFormat(NULL),
michael@0 337 fFallbackFormat(NULL),
michael@0 338 fLocaleDisplayNames(NULL),
michael@0 339 fStringPool(status),
michael@0 340 fGNamesTrie(TRUE, deleteGNameInfo),
michael@0 341 fGNamesTrieFullyLoaded(FALSE) {
michael@0 342 initialize(locale, status);
michael@0 343 }
michael@0 344
michael@0 345 TZGNCore::~TZGNCore() {
michael@0 346 cleanup();
michael@0 347 }
michael@0 348
michael@0 349 void
michael@0 350 TZGNCore::initialize(const Locale& locale, UErrorCode& status) {
michael@0 351 if (U_FAILURE(status)) {
michael@0 352 return;
michael@0 353 }
michael@0 354
michael@0 355 // TimeZoneNames
michael@0 356 fTimeZoneNames = TimeZoneNames::createInstance(locale, status);
michael@0 357 if (U_FAILURE(status)) {
michael@0 358 return;
michael@0 359 }
michael@0 360
michael@0 361 // Initialize format patterns
michael@0 362 UnicodeString rpat(TRUE, gDefRegionPattern, -1);
michael@0 363 UnicodeString fpat(TRUE, gDefFallbackPattern, -1);
michael@0 364
michael@0 365 UErrorCode tmpsts = U_ZERO_ERROR; // OK with fallback warning..
michael@0 366 UResourceBundle *zoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts);
michael@0 367 zoneStrings = ures_getByKeyWithFallback(zoneStrings, gZoneStrings, zoneStrings, &tmpsts);
michael@0 368
michael@0 369 if (U_SUCCESS(tmpsts)) {
michael@0 370 const UChar *regionPattern = ures_getStringByKeyWithFallback(zoneStrings, gRegionFormatTag, NULL, &tmpsts);
michael@0 371 if (U_SUCCESS(tmpsts) && u_strlen(regionPattern) > 0) {
michael@0 372 rpat.setTo(regionPattern, -1);
michael@0 373 }
michael@0 374 tmpsts = U_ZERO_ERROR;
michael@0 375 const UChar *fallbackPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackFormatTag, NULL, &tmpsts);
michael@0 376 if (U_SUCCESS(tmpsts) && u_strlen(fallbackPattern) > 0) {
michael@0 377 fpat.setTo(fallbackPattern, -1);
michael@0 378 }
michael@0 379 }
michael@0 380 ures_close(zoneStrings);
michael@0 381
michael@0 382 fRegionFormat = new MessageFormat(rpat, status);
michael@0 383 if (fRegionFormat == NULL) {
michael@0 384 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 385 }
michael@0 386 fFallbackFormat = new MessageFormat(fpat, status);
michael@0 387 if (fFallbackFormat == NULL) {
michael@0 388 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 389 }
michael@0 390 if (U_FAILURE(status)) {
michael@0 391 cleanup();
michael@0 392 return;
michael@0 393 }
michael@0 394
michael@0 395 // locale display names
michael@0 396 fLocaleDisplayNames = LocaleDisplayNames::createInstance(locale);
michael@0 397
michael@0 398 // hash table for names - no key/value deleters
michael@0 399 fLocationNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status);
michael@0 400 if (U_FAILURE(status)) {
michael@0 401 cleanup();
michael@0 402 return;
michael@0 403 }
michael@0 404
michael@0 405 fPartialLocationNamesMap = uhash_open(hashPartialLocationKey, comparePartialLocationKey, NULL, &status);
michael@0 406 if (U_FAILURE(status)) {
michael@0 407 cleanup();
michael@0 408 return;
michael@0 409 }
michael@0 410 uhash_setKeyDeleter(fPartialLocationNamesMap, uprv_free);
michael@0 411 // no value deleter
michael@0 412
michael@0 413 // target region
michael@0 414 const char* region = fLocale.getCountry();
michael@0 415 int32_t regionLen = uprv_strlen(region);
michael@0 416 if (regionLen == 0) {
michael@0 417 char loc[ULOC_FULLNAME_CAPACITY];
michael@0 418 uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status);
michael@0 419
michael@0 420 regionLen = uloc_getCountry(loc, fTargetRegion, sizeof(fTargetRegion), &status);
michael@0 421 if (U_SUCCESS(status)) {
michael@0 422 fTargetRegion[regionLen] = 0;
michael@0 423 } else {
michael@0 424 cleanup();
michael@0 425 return;
michael@0 426 }
michael@0 427 } else if (regionLen < (int32_t)sizeof(fTargetRegion)) {
michael@0 428 uprv_strcpy(fTargetRegion, region);
michael@0 429 } else {
michael@0 430 fTargetRegion[0] = 0;
michael@0 431 }
michael@0 432
michael@0 433 // preload generic names for the default zone
michael@0 434 TimeZone *tz = TimeZone::createDefault();
michael@0 435 const UChar *tzID = ZoneMeta::getCanonicalCLDRID(*tz);
michael@0 436 if (tzID != NULL) {
michael@0 437 loadStrings(UnicodeString(tzID));
michael@0 438 }
michael@0 439 delete tz;
michael@0 440 }
michael@0 441
michael@0 442 void
michael@0 443 TZGNCore::cleanup() {
michael@0 444 if (fRegionFormat != NULL) {
michael@0 445 delete fRegionFormat;
michael@0 446 }
michael@0 447 if (fFallbackFormat != NULL) {
michael@0 448 delete fFallbackFormat;
michael@0 449 }
michael@0 450 if (fLocaleDisplayNames != NULL) {
michael@0 451 delete fLocaleDisplayNames;
michael@0 452 }
michael@0 453 if (fTimeZoneNames != NULL) {
michael@0 454 delete fTimeZoneNames;
michael@0 455 }
michael@0 456
michael@0 457 uhash_close(fLocationNamesMap);
michael@0 458 uhash_close(fPartialLocationNamesMap);
michael@0 459 }
michael@0 460
michael@0 461
michael@0 462 UnicodeString&
michael@0 463 TZGNCore::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const {
michael@0 464 name.setToBogus();
michael@0 465 switch (type) {
michael@0 466 case UTZGNM_LOCATION:
michael@0 467 {
michael@0 468 const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz);
michael@0 469 if (tzCanonicalID != NULL) {
michael@0 470 getGenericLocationName(UnicodeString(tzCanonicalID), name);
michael@0 471 }
michael@0 472 }
michael@0 473 break;
michael@0 474 case UTZGNM_LONG:
michael@0 475 case UTZGNM_SHORT:
michael@0 476 formatGenericNonLocationName(tz, type, date, name);
michael@0 477 if (name.isEmpty()) {
michael@0 478 const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz);
michael@0 479 if (tzCanonicalID != NULL) {
michael@0 480 getGenericLocationName(UnicodeString(tzCanonicalID), name);
michael@0 481 }
michael@0 482 }
michael@0 483 break;
michael@0 484 default:
michael@0 485 break;
michael@0 486 }
michael@0 487 return name;
michael@0 488 }
michael@0 489
michael@0 490 UnicodeString&
michael@0 491 TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const {
michael@0 492 if (tzCanonicalID.isEmpty()) {
michael@0 493 name.setToBogus();
michael@0 494 return name;
michael@0 495 }
michael@0 496
michael@0 497 const UChar *locname = NULL;
michael@0 498 TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
michael@0 499 umtx_lock(&gLock);
michael@0 500 {
michael@0 501 locname = nonConstThis->getGenericLocationName(tzCanonicalID);
michael@0 502 }
michael@0 503 umtx_unlock(&gLock);
michael@0 504
michael@0 505 if (locname == NULL) {
michael@0 506 name.setToBogus();
michael@0 507 } else {
michael@0 508 name.setTo(locname, u_strlen(locname));
michael@0 509 }
michael@0 510
michael@0 511 return name;
michael@0 512 }
michael@0 513
michael@0 514 /*
michael@0 515 * This method updates the cache and must be called with a lock
michael@0 516 */
michael@0 517 const UChar*
michael@0 518 TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID) {
michael@0 519 U_ASSERT(!tzCanonicalID.isEmpty());
michael@0 520 if (tzCanonicalID.length() > ZID_KEY_MAX) {
michael@0 521 return NULL;
michael@0 522 }
michael@0 523
michael@0 524 UErrorCode status = U_ZERO_ERROR;
michael@0 525 UChar tzIDKey[ZID_KEY_MAX + 1];
michael@0 526 int32_t tzIDKeyLen = tzCanonicalID.extract(tzIDKey, ZID_KEY_MAX + 1, status);
michael@0 527 U_ASSERT(status == U_ZERO_ERROR); // already checked length above
michael@0 528 tzIDKey[tzIDKeyLen] = 0;
michael@0 529
michael@0 530 const UChar *locname = (const UChar *)uhash_get(fLocationNamesMap, tzIDKey);
michael@0 531
michael@0 532 if (locname != NULL) {
michael@0 533 // gEmpty indicate the name is not available
michael@0 534 if (locname == gEmpty) {
michael@0 535 return NULL;
michael@0 536 }
michael@0 537 return locname;
michael@0 538 }
michael@0 539
michael@0 540 // Construct location name
michael@0 541 UnicodeString name;
michael@0 542 UnicodeString usCountryCode;
michael@0 543 UBool isPrimary = FALSE;
michael@0 544
michael@0 545 ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode, &isPrimary);
michael@0 546
michael@0 547 if (!usCountryCode.isEmpty()) {
michael@0 548 FieldPosition fpos;
michael@0 549
michael@0 550 if (isPrimary) {
michael@0 551 // If this is the primary zone in the country, use the country name.
michael@0 552 char countryCode[ULOC_COUNTRY_CAPACITY];
michael@0 553 U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY);
michael@0 554 int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV);
michael@0 555 countryCode[ccLen] = 0;
michael@0 556
michael@0 557 UnicodeString country;
michael@0 558 fLocaleDisplayNames->regionDisplayName(countryCode, country);
michael@0 559
michael@0 560 Formattable param[] = {
michael@0 561 Formattable(country)
michael@0 562 };
michael@0 563
michael@0 564 fRegionFormat->format(param, 1, name, fpos, status);
michael@0 565 } else {
michael@0 566 // If this is not the primary zone in the country,
michael@0 567 // use the exemplar city name.
michael@0 568
michael@0 569 // getExemplarLocationName should retur non-empty string
michael@0 570 // if the time zone is associated with a region
michael@0 571
michael@0 572 UnicodeString city;
michael@0 573 fTimeZoneNames->getExemplarLocationName(tzCanonicalID, city);
michael@0 574
michael@0 575 Formattable param[] = {
michael@0 576 Formattable(city),
michael@0 577 };
michael@0 578
michael@0 579 fRegionFormat->format(param, 1, name, fpos, status);
michael@0 580 }
michael@0 581 if (U_FAILURE(status)) {
michael@0 582 return NULL;
michael@0 583 }
michael@0 584 }
michael@0 585
michael@0 586 locname = name.isEmpty() ? NULL : fStringPool.get(name, status);
michael@0 587 if (U_SUCCESS(status)) {
michael@0 588 // Cache the result
michael@0 589 const UChar* cacheID = ZoneMeta::findTimeZoneID(tzCanonicalID);
michael@0 590 U_ASSERT(cacheID != NULL);
michael@0 591 if (locname == NULL) {
michael@0 592 // gEmpty to indicate - no location name available
michael@0 593 uhash_put(fLocationNamesMap, (void *)cacheID, (void *)gEmpty, &status);
michael@0 594 } else {
michael@0 595 uhash_put(fLocationNamesMap, (void *)cacheID, (void *)locname, &status);
michael@0 596 if (U_FAILURE(status)) {
michael@0 597 locname = NULL;
michael@0 598 } else {
michael@0 599 // put the name info into the trie
michael@0 600 GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo));
michael@0 601 if (nameinfo != NULL) {
michael@0 602 nameinfo->type = UTZGNM_LOCATION;
michael@0 603 nameinfo->tzID = cacheID;
michael@0 604 fGNamesTrie.put(locname, nameinfo, status);
michael@0 605 }
michael@0 606 }
michael@0 607 }
michael@0 608 }
michael@0 609
michael@0 610 return locname;
michael@0 611 }
michael@0 612
michael@0 613 UnicodeString&
michael@0 614 TZGNCore::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const {
michael@0 615 U_ASSERT(type == UTZGNM_LONG || type == UTZGNM_SHORT);
michael@0 616 name.setToBogus();
michael@0 617
michael@0 618 const UChar* uID = ZoneMeta::getCanonicalCLDRID(tz);
michael@0 619 if (uID == NULL) {
michael@0 620 return name;
michael@0 621 }
michael@0 622
michael@0 623 UnicodeString tzID(uID);
michael@0 624
michael@0 625 // Try to get a name from time zone first
michael@0 626 UTimeZoneNameType nameType = (type == UTZGNM_LONG) ? UTZNM_LONG_GENERIC : UTZNM_SHORT_GENERIC;
michael@0 627 fTimeZoneNames->getTimeZoneDisplayName(tzID, nameType, name);
michael@0 628
michael@0 629 if (!name.isEmpty()) {
michael@0 630 return name;
michael@0 631 }
michael@0 632
michael@0 633 // Try meta zone
michael@0 634 UnicodeString mzID;
michael@0 635 fTimeZoneNames->getMetaZoneID(tzID, date, mzID);
michael@0 636 if (!mzID.isEmpty()) {
michael@0 637 UErrorCode status = U_ZERO_ERROR;
michael@0 638 UBool useStandard = FALSE;
michael@0 639 int32_t raw, sav;
michael@0 640
michael@0 641 tz.getOffset(date, FALSE, raw, sav, status);
michael@0 642 if (U_FAILURE(status)) {
michael@0 643 return name;
michael@0 644 }
michael@0 645
michael@0 646 if (sav == 0) {
michael@0 647 useStandard = TRUE;
michael@0 648
michael@0 649 TimeZone *tmptz = tz.clone();
michael@0 650 // Check if the zone actually uses daylight saving time around the time
michael@0 651 BasicTimeZone *btz = NULL;
michael@0 652 if (dynamic_cast<OlsonTimeZone *>(tmptz) != NULL
michael@0 653 || dynamic_cast<SimpleTimeZone *>(tmptz) != NULL
michael@0 654 || dynamic_cast<RuleBasedTimeZone *>(tmptz) != NULL
michael@0 655 || dynamic_cast<VTimeZone *>(tmptz) != NULL) {
michael@0 656 btz = (BasicTimeZone*)tmptz;
michael@0 657 }
michael@0 658
michael@0 659 if (btz != NULL) {
michael@0 660 TimeZoneTransition before;
michael@0 661 UBool beforTrs = btz->getPreviousTransition(date, TRUE, before);
michael@0 662 if (beforTrs
michael@0 663 && (date - before.getTime() < kDstCheckRange)
michael@0 664 && before.getFrom()->getDSTSavings() != 0) {
michael@0 665 useStandard = FALSE;
michael@0 666 } else {
michael@0 667 TimeZoneTransition after;
michael@0 668 UBool afterTrs = btz->getNextTransition(date, FALSE, after);
michael@0 669 if (afterTrs
michael@0 670 && (after.getTime() - date < kDstCheckRange)
michael@0 671 && after.getTo()->getDSTSavings() != 0) {
michael@0 672 useStandard = FALSE;
michael@0 673 }
michael@0 674 }
michael@0 675 } else {
michael@0 676 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
michael@0 677 // We may get a wrong answer in edge case, but it should practically work OK.
michael@0 678 tmptz->getOffset(date - kDstCheckRange, FALSE, raw, sav, status);
michael@0 679 if (sav != 0) {
michael@0 680 useStandard = FALSE;
michael@0 681 } else {
michael@0 682 tmptz->getOffset(date + kDstCheckRange, FALSE, raw, sav, status);
michael@0 683 if (sav != 0){
michael@0 684 useStandard = FALSE;
michael@0 685 }
michael@0 686 }
michael@0 687 if (U_FAILURE(status)) {
michael@0 688 delete tmptz;
michael@0 689 return name;
michael@0 690 }
michael@0 691 }
michael@0 692 delete tmptz;
michael@0 693 }
michael@0 694 if (useStandard) {
michael@0 695 UTimeZoneNameType stdNameType = (nameType == UTZNM_LONG_GENERIC)
michael@0 696 ? UTZNM_LONG_STANDARD : UTZNM_SHORT_STANDARD;
michael@0 697 UnicodeString stdName;
michael@0 698 fTimeZoneNames->getDisplayName(tzID, stdNameType, date, stdName);
michael@0 699 if (!stdName.isEmpty()) {
michael@0 700 name.setTo(stdName);
michael@0 701
michael@0 702 // TODO: revisit this issue later
michael@0 703 // In CLDR, a same display name is used for both generic and standard
michael@0 704 // for some meta zones in some locales. This looks like a data bugs.
michael@0 705 // For now, we check if the standard name is different from its generic
michael@0 706 // name below.
michael@0 707 UnicodeString mzGenericName;
michael@0 708 fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzGenericName);
michael@0 709 if (stdName.caseCompare(mzGenericName, 0) == 0) {
michael@0 710 name.setToBogus();
michael@0 711 }
michael@0 712 }
michael@0 713 }
michael@0 714 if (name.isEmpty()) {
michael@0 715 // Get a name from meta zone
michael@0 716 UnicodeString mzName;
michael@0 717 fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzName);
michael@0 718 if (!mzName.isEmpty()) {
michael@0 719 // Check if we need to use a partial location format.
michael@0 720 // This check is done by comparing offset with the meta zone's
michael@0 721 // golden zone at the given date.
michael@0 722 UnicodeString goldenID;
michael@0 723 fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, goldenID);
michael@0 724 if (!goldenID.isEmpty() && goldenID != tzID) {
michael@0 725 TimeZone *goldenZone = TimeZone::createTimeZone(goldenID);
michael@0 726 int32_t raw1, sav1;
michael@0 727
michael@0 728 // Check offset in the golden zone with wall time.
michael@0 729 // With getOffset(date, false, offsets1),
michael@0 730 // you may get incorrect results because of time overlap at DST->STD
michael@0 731 // transition.
michael@0 732 goldenZone->getOffset(date + raw + sav, TRUE, raw1, sav1, status);
michael@0 733 delete goldenZone;
michael@0 734 if (U_SUCCESS(status)) {
michael@0 735 if (raw != raw1 || sav != sav1) {
michael@0 736 // Now we need to use a partial location format
michael@0 737 getPartialLocationName(tzID, mzID, (nameType == UTZNM_LONG_GENERIC), mzName, name);
michael@0 738 } else {
michael@0 739 name.setTo(mzName);
michael@0 740 }
michael@0 741 }
michael@0 742 } else {
michael@0 743 name.setTo(mzName);
michael@0 744 }
michael@0 745 }
michael@0 746 }
michael@0 747 }
michael@0 748 return name;
michael@0 749 }
michael@0 750
michael@0 751 UnicodeString&
michael@0 752 TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID,
michael@0 753 const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName,
michael@0 754 UnicodeString& name) const {
michael@0 755 name.setToBogus();
michael@0 756 if (tzCanonicalID.isEmpty() || mzID.isEmpty() || mzDisplayName.isEmpty()) {
michael@0 757 return name;
michael@0 758 }
michael@0 759
michael@0 760 const UChar *uplname = NULL;
michael@0 761 TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
michael@0 762 umtx_lock(&gLock);
michael@0 763 {
michael@0 764 uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName);
michael@0 765 }
michael@0 766 umtx_unlock(&gLock);
michael@0 767
michael@0 768 if (uplname == NULL) {
michael@0 769 name.setToBogus();
michael@0 770 } else {
michael@0 771 name.setTo(TRUE, uplname, -1);
michael@0 772 }
michael@0 773 return name;
michael@0 774 }
michael@0 775
michael@0 776 /*
michael@0 777 * This method updates the cache and must be called with a lock
michael@0 778 */
michael@0 779 const UChar*
michael@0 780 TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID,
michael@0 781 const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName) {
michael@0 782 U_ASSERT(!tzCanonicalID.isEmpty());
michael@0 783 U_ASSERT(!mzID.isEmpty());
michael@0 784 U_ASSERT(!mzDisplayName.isEmpty());
michael@0 785
michael@0 786 PartialLocationKey key;
michael@0 787 key.tzID = ZoneMeta::findTimeZoneID(tzCanonicalID);
michael@0 788 key.mzID = ZoneMeta::findMetaZoneID(mzID);
michael@0 789 key.isLong = isLong;
michael@0 790 U_ASSERT(key.tzID != NULL && key.mzID != NULL);
michael@0 791
michael@0 792 const UChar* uplname = (const UChar*)uhash_get(fPartialLocationNamesMap, (void *)&key);
michael@0 793 if (uplname != NULL) {
michael@0 794 return uplname;
michael@0 795 }
michael@0 796
michael@0 797 UnicodeString location;
michael@0 798 UnicodeString usCountryCode;
michael@0 799 ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode);
michael@0 800 if (!usCountryCode.isEmpty()) {
michael@0 801 char countryCode[ULOC_COUNTRY_CAPACITY];
michael@0 802 U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY);
michael@0 803 int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV);
michael@0 804 countryCode[ccLen] = 0;
michael@0 805
michael@0 806 UnicodeString regionalGolden;
michael@0 807 fTimeZoneNames->getReferenceZoneID(mzID, countryCode, regionalGolden);
michael@0 808 if (tzCanonicalID == regionalGolden) {
michael@0 809 // Use country name
michael@0 810 fLocaleDisplayNames->regionDisplayName(countryCode, location);
michael@0 811 } else {
michael@0 812 // Otherwise, use exemplar city name
michael@0 813 fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location);
michael@0 814 }
michael@0 815 } else {
michael@0 816 fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location);
michael@0 817 if (location.isEmpty()) {
michael@0 818 // This could happen when the time zone is not associated with a country,
michael@0 819 // and its ID is not hierarchical, for example, CST6CDT.
michael@0 820 // We use the canonical ID itself as the location for this case.
michael@0 821 location.setTo(tzCanonicalID);
michael@0 822 }
michael@0 823 }
michael@0 824
michael@0 825 UErrorCode status = U_ZERO_ERROR;
michael@0 826 UnicodeString name;
michael@0 827
michael@0 828 FieldPosition fpos;
michael@0 829 Formattable param[] = {
michael@0 830 Formattable(location),
michael@0 831 Formattable(mzDisplayName)
michael@0 832 };
michael@0 833 fFallbackFormat->format(param, 2, name, fpos, status);
michael@0 834 if (U_FAILURE(status)) {
michael@0 835 return NULL;
michael@0 836 }
michael@0 837
michael@0 838 uplname = fStringPool.get(name, status);
michael@0 839 if (U_SUCCESS(status)) {
michael@0 840 // Add the name to cache
michael@0 841 PartialLocationKey* cacheKey = (PartialLocationKey *)uprv_malloc(sizeof(PartialLocationKey));
michael@0 842 if (cacheKey != NULL) {
michael@0 843 cacheKey->tzID = key.tzID;
michael@0 844 cacheKey->mzID = key.mzID;
michael@0 845 cacheKey->isLong = key.isLong;
michael@0 846 uhash_put(fPartialLocationNamesMap, (void *)cacheKey, (void *)uplname, &status);
michael@0 847 if (U_FAILURE(status)) {
michael@0 848 uprv_free(cacheKey);
michael@0 849 } else {
michael@0 850 // put the name to the local trie as well
michael@0 851 GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo));
michael@0 852 if (nameinfo != NULL) {
michael@0 853 nameinfo->type = isLong ? UTZGNM_LONG : UTZGNM_SHORT;
michael@0 854 nameinfo->tzID = key.tzID;
michael@0 855 fGNamesTrie.put(uplname, nameinfo, status);
michael@0 856 }
michael@0 857 }
michael@0 858 }
michael@0 859 }
michael@0 860 return uplname;
michael@0 861 }
michael@0 862
michael@0 863 /*
michael@0 864 * This method updates the cache and must be called with a lock,
michael@0 865 * except initializer.
michael@0 866 */
michael@0 867 void
michael@0 868 TZGNCore::loadStrings(const UnicodeString& tzCanonicalID) {
michael@0 869 // load the generic location name
michael@0 870 getGenericLocationName(tzCanonicalID);
michael@0 871
michael@0 872 // partial location names
michael@0 873 UErrorCode status = U_ZERO_ERROR;
michael@0 874
michael@0 875 const UnicodeString *mzID;
michael@0 876 UnicodeString goldenID;
michael@0 877 UnicodeString mzGenName;
michael@0 878 UTimeZoneNameType genNonLocTypes[] = {
michael@0 879 UTZNM_LONG_GENERIC, UTZNM_SHORT_GENERIC,
michael@0 880 UTZNM_UNKNOWN /*terminator*/
michael@0 881 };
michael@0 882
michael@0 883 StringEnumeration *mzIDs = fTimeZoneNames->getAvailableMetaZoneIDs(tzCanonicalID, status);
michael@0 884 while ((mzID = mzIDs->snext(status))) {
michael@0 885 if (U_FAILURE(status)) {
michael@0 886 break;
michael@0 887 }
michael@0 888 // if this time zone is not the golden zone of the meta zone,
michael@0 889 // partial location name (such as "PT (Los Angeles)") might be
michael@0 890 // available.
michael@0 891 fTimeZoneNames->getReferenceZoneID(*mzID, fTargetRegion, goldenID);
michael@0 892 if (tzCanonicalID != goldenID) {
michael@0 893 for (int32_t i = 0; genNonLocTypes[i] != UTZNM_UNKNOWN; i++) {
michael@0 894 fTimeZoneNames->getMetaZoneDisplayName(*mzID, genNonLocTypes[i], mzGenName);
michael@0 895 if (!mzGenName.isEmpty()) {
michael@0 896 // getPartialLocationName formats a name and put it into the trie
michael@0 897 getPartialLocationName(tzCanonicalID, *mzID,
michael@0 898 (genNonLocTypes[i] == UTZNM_LONG_GENERIC), mzGenName);
michael@0 899 }
michael@0 900 }
michael@0 901 }
michael@0 902 }
michael@0 903 if (mzIDs != NULL) {
michael@0 904 delete mzIDs;
michael@0 905 }
michael@0 906 }
michael@0 907
michael@0 908 int32_t
michael@0 909 TZGNCore::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
michael@0 910 UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const {
michael@0 911 timeType = UTZFMT_TIME_TYPE_UNKNOWN;
michael@0 912 tzID.setToBogus();
michael@0 913
michael@0 914 if (U_FAILURE(status)) {
michael@0 915 return 0;
michael@0 916 }
michael@0 917
michael@0 918 // Find matches in the TimeZoneNames first
michael@0 919 TimeZoneNames::MatchInfoCollection *tznamesMatches = findTimeZoneNames(text, start, types, status);
michael@0 920 if (U_FAILURE(status)) {
michael@0 921 return 0;
michael@0 922 }
michael@0 923
michael@0 924 int32_t bestMatchLen = 0;
michael@0 925 UTimeZoneFormatTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
michael@0 926 UnicodeString bestMatchTzID;
michael@0 927 // UBool isLongStandard = FALSE; // workaround - see the comments below
michael@0 928 UBool isStandard = FALSE; // TODO: Temporary hack (on hack) for short standard name/location name conflict (found in zh_Hant), should be removed after CLDR 21m1 integration
michael@0 929
michael@0 930 if (tznamesMatches != NULL) {
michael@0 931 UnicodeString mzID;
michael@0 932 for (int32_t i = 0; i < tznamesMatches->size(); i++) {
michael@0 933 int32_t len = tznamesMatches->getMatchLengthAt(i);
michael@0 934 if (len > bestMatchLen) {
michael@0 935 bestMatchLen = len;
michael@0 936 if (!tznamesMatches->getTimeZoneIDAt(i, bestMatchTzID)) {
michael@0 937 // name for a meta zone
michael@0 938 if (tznamesMatches->getMetaZoneIDAt(i, mzID)) {
michael@0 939 fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, bestMatchTzID);
michael@0 940 }
michael@0 941 }
michael@0 942 UTimeZoneNameType nameType = tznamesMatches->getNameTypeAt(i);
michael@0 943 if (U_FAILURE(status)) {
michael@0 944 break;
michael@0 945 }
michael@0 946 switch (nameType) {
michael@0 947 case UTZNM_LONG_STANDARD:
michael@0 948 // isLongStandard = TRUE;
michael@0 949 case UTZNM_SHORT_STANDARD: // this one is never used for generic, but just in case
michael@0 950 isStandard = TRUE; // TODO: Remove this later, see the comments above.
michael@0 951 bestMatchTimeType = UTZFMT_TIME_TYPE_STANDARD;
michael@0 952 break;
michael@0 953 case UTZNM_LONG_DAYLIGHT:
michael@0 954 case UTZNM_SHORT_DAYLIGHT: // this one is never used for generic, but just in case
michael@0 955 bestMatchTimeType = UTZFMT_TIME_TYPE_DAYLIGHT;
michael@0 956 break;
michael@0 957 default:
michael@0 958 bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
michael@0 959 }
michael@0 960 }
michael@0 961 }
michael@0 962 delete tznamesMatches;
michael@0 963 if (U_FAILURE(status)) {
michael@0 964 return 0;
michael@0 965 }
michael@0 966
michael@0 967 if (bestMatchLen == (text.length() - start)) {
michael@0 968 // Full match
michael@0 969
michael@0 970 //tzID.setTo(bestMatchTzID);
michael@0 971 //timeType = bestMatchTimeType;
michael@0 972 //return bestMatchLen;
michael@0 973
michael@0 974 // TODO Some time zone uses a same name for the long standard name
michael@0 975 // and the location name. When the match is a long standard name,
michael@0 976 // then we need to check if the name is same with the location name.
michael@0 977 // This is probably a data error or a design bug.
michael@0 978 /*
michael@0 979 if (!isLongStandard) {
michael@0 980 tzID.setTo(bestMatchTzID);
michael@0 981 timeType = bestMatchTimeType;
michael@0 982 return bestMatchLen;
michael@0 983 }
michael@0 984 */
michael@0 985 // TODO The deprecation of commonlyUsed flag introduced the name
michael@0 986 // conflict not only for long standard names, but short standard names too.
michael@0 987 // These short names (found in zh_Hant) should be gone once we clean
michael@0 988 // up CLDR time zone display name data. Once the short name conflict
michael@0 989 // problem (with location name) is resolved, we should change the condition
michael@0 990 // below back to the original one above. -Yoshito (2011-09-14)
michael@0 991 if (!isStandard) {
michael@0 992 tzID.setTo(bestMatchTzID);
michael@0 993 timeType = bestMatchTimeType;
michael@0 994 return bestMatchLen;
michael@0 995 }
michael@0 996 }
michael@0 997 }
michael@0 998
michael@0 999 // Find matches in the local trie
michael@0 1000 TimeZoneGenericNameMatchInfo *localMatches = findLocal(text, start, types, status);
michael@0 1001 if (U_FAILURE(status)) {
michael@0 1002 return 0;
michael@0 1003 }
michael@0 1004 if (localMatches != NULL) {
michael@0 1005 for (int32_t i = 0; i < localMatches->size(); i++) {
michael@0 1006 int32_t len = localMatches->getMatchLength(i);
michael@0 1007
michael@0 1008 // TODO See the above TODO. We use len >= bestMatchLen
michael@0 1009 // because of the long standard/location name collision
michael@0 1010 // problem. If it is also a location name, carrying
michael@0 1011 // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
michael@0 1012 // problem in SimpleDateFormat
michael@0 1013 if (len >= bestMatchLen) {
michael@0 1014 bestMatchLen = localMatches->getMatchLength(i);
michael@0 1015 bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; // because generic
michael@0 1016 localMatches->getTimeZoneID(i, bestMatchTzID);
michael@0 1017 }
michael@0 1018 }
michael@0 1019 delete localMatches;
michael@0 1020 }
michael@0 1021
michael@0 1022 if (bestMatchLen > 0) {
michael@0 1023 timeType = bestMatchTimeType;
michael@0 1024 tzID.setTo(bestMatchTzID);
michael@0 1025 }
michael@0 1026 return bestMatchLen;
michael@0 1027 }
michael@0 1028
michael@0 1029 TimeZoneGenericNameMatchInfo*
michael@0 1030 TZGNCore::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
michael@0 1031 GNameSearchHandler handler(types);
michael@0 1032
michael@0 1033 TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
michael@0 1034
michael@0 1035 umtx_lock(&gLock);
michael@0 1036 {
michael@0 1037 fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status);
michael@0 1038 }
michael@0 1039 umtx_unlock(&gLock);
michael@0 1040
michael@0 1041 if (U_FAILURE(status)) {
michael@0 1042 return NULL;
michael@0 1043 }
michael@0 1044
michael@0 1045 TimeZoneGenericNameMatchInfo *gmatchInfo = NULL;
michael@0 1046
michael@0 1047 int32_t maxLen = 0;
michael@0 1048 UVector *results = handler.getMatches(maxLen);
michael@0 1049 if (results != NULL && ((maxLen == (text.length() - start)) || fGNamesTrieFullyLoaded)) {
michael@0 1050 // perfect match
michael@0 1051 gmatchInfo = new TimeZoneGenericNameMatchInfo(results);
michael@0 1052 if (gmatchInfo == NULL) {
michael@0 1053 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 1054 delete results;
michael@0 1055 return NULL;
michael@0 1056 }
michael@0 1057 return gmatchInfo;
michael@0 1058 }
michael@0 1059
michael@0 1060 if (results != NULL) {
michael@0 1061 delete results;
michael@0 1062 }
michael@0 1063
michael@0 1064 // All names are not yet loaded into the local trie.
michael@0 1065 // Load all available names into the trie. This could be very heavy.
michael@0 1066 umtx_lock(&gLock);
michael@0 1067 {
michael@0 1068 if (!fGNamesTrieFullyLoaded) {
michael@0 1069 StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
michael@0 1070 if (U_SUCCESS(status)) {
michael@0 1071 const UnicodeString *tzID;
michael@0 1072 while ((tzID = tzIDs->snext(status))) {
michael@0 1073 if (U_FAILURE(status)) {
michael@0 1074 break;
michael@0 1075 }
michael@0 1076 nonConstThis->loadStrings(*tzID);
michael@0 1077 }
michael@0 1078 }
michael@0 1079 if (tzIDs != NULL) {
michael@0 1080 delete tzIDs;
michael@0 1081 }
michael@0 1082
michael@0 1083 if (U_SUCCESS(status)) {
michael@0 1084 nonConstThis->fGNamesTrieFullyLoaded = TRUE;
michael@0 1085 }
michael@0 1086 }
michael@0 1087 }
michael@0 1088 umtx_unlock(&gLock);
michael@0 1089
michael@0 1090 if (U_FAILURE(status)) {
michael@0 1091 return NULL;
michael@0 1092 }
michael@0 1093
michael@0 1094 umtx_lock(&gLock);
michael@0 1095 {
michael@0 1096 // now try it again
michael@0 1097 fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status);
michael@0 1098 }
michael@0 1099 umtx_unlock(&gLock);
michael@0 1100
michael@0 1101 results = handler.getMatches(maxLen);
michael@0 1102 if (results != NULL && maxLen > 0) {
michael@0 1103 gmatchInfo = new TimeZoneGenericNameMatchInfo(results);
michael@0 1104 if (gmatchInfo == NULL) {
michael@0 1105 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 1106 delete results;
michael@0 1107 return NULL;
michael@0 1108 }
michael@0 1109 }
michael@0 1110
michael@0 1111 return gmatchInfo;
michael@0 1112 }
michael@0 1113
michael@0 1114 TimeZoneNames::MatchInfoCollection*
michael@0 1115 TZGNCore::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
michael@0 1116 // Check if the target name typs is really in the TimeZoneNames
michael@0 1117 uint32_t nameTypes = 0;
michael@0 1118 if (types & UTZGNM_LONG) {
michael@0 1119 nameTypes |= (UTZNM_LONG_GENERIC | UTZNM_LONG_STANDARD);
michael@0 1120 }
michael@0 1121 if (types & UTZGNM_SHORT) {
michael@0 1122 nameTypes |= (UTZNM_SHORT_GENERIC | UTZNM_SHORT_STANDARD);
michael@0 1123 }
michael@0 1124
michael@0 1125 if (types) {
michael@0 1126 // Find matches in the TimeZoneNames
michael@0 1127 return fTimeZoneNames->find(text, start, nameTypes, status);
michael@0 1128 }
michael@0 1129
michael@0 1130 return NULL;
michael@0 1131 }
michael@0 1132
michael@0 1133 typedef struct TZGNCoreRef {
michael@0 1134 TZGNCore* obj;
michael@0 1135 int32_t refCount;
michael@0 1136 double lastAccess;
michael@0 1137 } TZGNCoreRef;
michael@0 1138
michael@0 1139 // TZGNCore object cache handling
michael@0 1140 static UMutex gTZGNLock = U_MUTEX_INITIALIZER;
michael@0 1141 static UHashtable *gTZGNCoreCache = NULL;
michael@0 1142 static UBool gTZGNCoreCacheInitialized = FALSE;
michael@0 1143
michael@0 1144 // Access count - incremented every time up to SWEEP_INTERVAL,
michael@0 1145 // then reset to 0
michael@0 1146 static int32_t gAccessCount = 0;
michael@0 1147
michael@0 1148 // Interval for calling the cache sweep function - every 100 times
michael@0 1149 #define SWEEP_INTERVAL 100
michael@0 1150
michael@0 1151 // Cache expiration in millisecond. When a cached entry is no
michael@0 1152 // longer referenced and exceeding this threshold since last
michael@0 1153 // access time, then the cache entry will be deleted by the sweep
michael@0 1154 // function. For now, 3 minutes.
michael@0 1155 #define CACHE_EXPIRATION 180000.0
michael@0 1156
michael@0 1157 U_CDECL_BEGIN
michael@0 1158 /**
michael@0 1159 * Cleanup callback func
michael@0 1160 */
michael@0 1161 static UBool U_CALLCONV tzgnCore_cleanup(void)
michael@0 1162 {
michael@0 1163 if (gTZGNCoreCache != NULL) {
michael@0 1164 uhash_close(gTZGNCoreCache);
michael@0 1165 gTZGNCoreCache = NULL;
michael@0 1166 }
michael@0 1167 gTZGNCoreCacheInitialized = FALSE;
michael@0 1168 return TRUE;
michael@0 1169 }
michael@0 1170
michael@0 1171 /**
michael@0 1172 * Deleter for TZGNCoreRef
michael@0 1173 */
michael@0 1174 static void U_CALLCONV
michael@0 1175 deleteTZGNCoreRef(void *obj) {
michael@0 1176 icu::TZGNCoreRef *entry = (icu::TZGNCoreRef*)obj;
michael@0 1177 delete (icu::TZGNCore*) entry->obj;
michael@0 1178 uprv_free(entry);
michael@0 1179 }
michael@0 1180 U_CDECL_END
michael@0 1181
michael@0 1182 /**
michael@0 1183 * Function used for removing unreferrenced cache entries exceeding
michael@0 1184 * the expiration time. This function must be called with in the mutex
michael@0 1185 * block.
michael@0 1186 */
michael@0 1187 static void sweepCache() {
michael@0 1188 int32_t pos = -1;
michael@0 1189 const UHashElement* elem;
michael@0 1190 double now = (double)uprv_getUTCtime();
michael@0 1191
michael@0 1192 while ((elem = uhash_nextElement(gTZGNCoreCache, &pos))) {
michael@0 1193 TZGNCoreRef *entry = (TZGNCoreRef *)elem->value.pointer;
michael@0 1194 if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
michael@0 1195 // delete this entry
michael@0 1196 uhash_removeElement(gTZGNCoreCache, elem);
michael@0 1197 }
michael@0 1198 }
michael@0 1199 }
michael@0 1200
michael@0 1201 TimeZoneGenericNames::TimeZoneGenericNames()
michael@0 1202 : fRef(0) {
michael@0 1203 }
michael@0 1204
michael@0 1205 TimeZoneGenericNames::~TimeZoneGenericNames() {
michael@0 1206 umtx_lock(&gTZGNLock);
michael@0 1207 {
michael@0 1208 U_ASSERT(fRef->refCount > 0);
michael@0 1209 // Just decrement the reference count
michael@0 1210 fRef->refCount--;
michael@0 1211 }
michael@0 1212 umtx_unlock(&gTZGNLock);
michael@0 1213 }
michael@0 1214
michael@0 1215 TimeZoneGenericNames*
michael@0 1216 TimeZoneGenericNames::createInstance(const Locale& locale, UErrorCode& status) {
michael@0 1217 if (U_FAILURE(status)) {
michael@0 1218 return NULL;
michael@0 1219 }
michael@0 1220 TimeZoneGenericNames* instance = new TimeZoneGenericNames();
michael@0 1221 if (instance == NULL) {
michael@0 1222 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 1223 return NULL;
michael@0 1224 }
michael@0 1225
michael@0 1226 TZGNCoreRef *cacheEntry = NULL;
michael@0 1227 {
michael@0 1228 Mutex lock(&gTZGNLock);
michael@0 1229
michael@0 1230 if (!gTZGNCoreCacheInitialized) {
michael@0 1231 // Create empty hashtable
michael@0 1232 gTZGNCoreCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
michael@0 1233 if (U_SUCCESS(status)) {
michael@0 1234 uhash_setKeyDeleter(gTZGNCoreCache, uprv_free);
michael@0 1235 uhash_setValueDeleter(gTZGNCoreCache, deleteTZGNCoreRef);
michael@0 1236 gTZGNCoreCacheInitialized = TRUE;
michael@0 1237 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES, tzgnCore_cleanup);
michael@0 1238 }
michael@0 1239 }
michael@0 1240 if (U_FAILURE(status)) {
michael@0 1241 return NULL;
michael@0 1242 }
michael@0 1243
michael@0 1244 // Check the cache, if not available, create new one and cache
michael@0 1245 const char *key = locale.getName();
michael@0 1246 cacheEntry = (TZGNCoreRef *)uhash_get(gTZGNCoreCache, key);
michael@0 1247 if (cacheEntry == NULL) {
michael@0 1248 TZGNCore *tzgnCore = NULL;
michael@0 1249 char *newKey = NULL;
michael@0 1250
michael@0 1251 tzgnCore = new TZGNCore(locale, status);
michael@0 1252 if (tzgnCore == NULL) {
michael@0 1253 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 1254 }
michael@0 1255 if (U_SUCCESS(status)) {
michael@0 1256 newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
michael@0 1257 if (newKey == NULL) {
michael@0 1258 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 1259 } else {
michael@0 1260 uprv_strcpy(newKey, key);
michael@0 1261 }
michael@0 1262 }
michael@0 1263 if (U_SUCCESS(status)) {
michael@0 1264 cacheEntry = (TZGNCoreRef *)uprv_malloc(sizeof(TZGNCoreRef));
michael@0 1265 if (cacheEntry == NULL) {
michael@0 1266 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 1267 } else {
michael@0 1268 cacheEntry->obj = tzgnCore;
michael@0 1269 cacheEntry->refCount = 1;
michael@0 1270 cacheEntry->lastAccess = (double)uprv_getUTCtime();
michael@0 1271
michael@0 1272 uhash_put(gTZGNCoreCache, newKey, cacheEntry, &status);
michael@0 1273 }
michael@0 1274 }
michael@0 1275 if (U_FAILURE(status)) {
michael@0 1276 if (tzgnCore != NULL) {
michael@0 1277 delete tzgnCore;
michael@0 1278 }
michael@0 1279 if (newKey != NULL) {
michael@0 1280 uprv_free(newKey);
michael@0 1281 }
michael@0 1282 if (cacheEntry != NULL) {
michael@0 1283 uprv_free(cacheEntry);
michael@0 1284 }
michael@0 1285 cacheEntry = NULL;
michael@0 1286 }
michael@0 1287 } else {
michael@0 1288 // Update the reference count
michael@0 1289 cacheEntry->refCount++;
michael@0 1290 cacheEntry->lastAccess = (double)uprv_getUTCtime();
michael@0 1291 }
michael@0 1292 gAccessCount++;
michael@0 1293 if (gAccessCount >= SWEEP_INTERVAL) {
michael@0 1294 // sweep
michael@0 1295 sweepCache();
michael@0 1296 gAccessCount = 0;
michael@0 1297 }
michael@0 1298 } // End of mutex locked block
michael@0 1299
michael@0 1300 if (cacheEntry == NULL) {
michael@0 1301 delete instance;
michael@0 1302 return NULL;
michael@0 1303 }
michael@0 1304
michael@0 1305 instance->fRef = cacheEntry;
michael@0 1306 return instance;
michael@0 1307 }
michael@0 1308
michael@0 1309 UBool
michael@0 1310 TimeZoneGenericNames::operator==(const TimeZoneGenericNames& other) const {
michael@0 1311 // Just compare if the other object also use the same
michael@0 1312 // ref entry
michael@0 1313 return fRef == other.fRef;
michael@0 1314 }
michael@0 1315
michael@0 1316 TimeZoneGenericNames*
michael@0 1317 TimeZoneGenericNames::clone() const {
michael@0 1318 TimeZoneGenericNames* other = new TimeZoneGenericNames();
michael@0 1319 if (other) {
michael@0 1320 umtx_lock(&gTZGNLock);
michael@0 1321 {
michael@0 1322 // Just increments the reference count
michael@0 1323 fRef->refCount++;
michael@0 1324 other->fRef = fRef;
michael@0 1325 }
michael@0 1326 umtx_unlock(&gTZGNLock);
michael@0 1327 }
michael@0 1328 return other;
michael@0 1329 }
michael@0 1330
michael@0 1331 UnicodeString&
michael@0 1332 TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type,
michael@0 1333 UDate date, UnicodeString& name) const {
michael@0 1334 return fRef->obj->getDisplayName(tz, type, date, name);
michael@0 1335 }
michael@0 1336
michael@0 1337 UnicodeString&
michael@0 1338 TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const {
michael@0 1339 return fRef->obj->getGenericLocationName(tzCanonicalID, name);
michael@0 1340 }
michael@0 1341
michael@0 1342 int32_t
michael@0 1343 TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
michael@0 1344 UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const {
michael@0 1345 return fRef->obj->findBestMatch(text, start, types, tzID, timeType, status);
michael@0 1346 }
michael@0 1347
michael@0 1348 U_NAMESPACE_END
michael@0 1349 #endif

mercurial