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