1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 2011-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
10 #include "unicode/utypes.h"
12 #if !UCONFIG_NO_FORMATTING
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"
33 #include "tznames_impl.h"
39 #define ZID_KEY_MAX 128
41 static const char gZoneStrings
[] = "zoneStrings";
43 static const char gRegionFormatTag
[] = "regionFormat";
44 static const char gFallbackFormatTag
[] = "fallbackFormat";
46 static const UChar gEmpty
[] = {0x00};
48 static const UChar gDefRegionPattern
[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}"
49 static const UChar gDefFallbackPattern
[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
51 static const double kDstCheckRange
= (double)184*U_MILLIS_PER_DAY
;
57 typedef struct PartialLocationKey
{
64 * Hash function for partial location name hash key
66 static int32_t U_CALLCONV
67 hashPartialLocationKey(const UHashTok key
) {
68 // <tzID>&<mzID>#[L|S]
69 PartialLocationKey
*p
= (PartialLocationKey
*)key
.pointer
;
70 UnicodeString
str(p
->tzID
);
71 str
.append((UChar
)0x26)
74 .append((UChar
)(p
->isLong
? 0x4C : 0x53));
75 return str
.hashCode();
79 * Comparer for partial location name hash key
81 static UBool U_CALLCONV
82 comparePartialLocationKey(const UHashTok key1
, const UHashTok key2
) {
83 PartialLocationKey
*p1
= (PartialLocationKey
*)key1
.pointer
;
84 PartialLocationKey
*p2
= (PartialLocationKey
*)key2
.pointer
;
89 if (p1
== NULL
|| p2
== NULL
) {
92 // We just check identity of tzID/mzID
93 return (p1
->tzID
== p2
->tzID
&& p1
->mzID
== p2
->mzID
&& p1
->isLong
== p2
->isLong
);
97 * Deleter for GNameInfo
99 static void U_CALLCONV
100 deleteGNameInfo(void *obj
) {
105 * GNameInfo stores zone name information in the local trie
107 typedef struct GNameInfo
{
108 UTimeZoneGenericNameType type
;
113 * GMatchInfo stores zone name match information used by find method
115 typedef struct GMatchInfo
{
116 const GNameInfo
* gnameInfo
;
118 UTimeZoneFormatTimeType timeType
;
123 // ---------------------------------------------------
124 // The class stores time zone generic name match information
125 // ---------------------------------------------------
126 class TimeZoneGenericNameMatchInfo
: public UMemory
{
128 TimeZoneGenericNameMatchInfo(UVector
* matches
);
129 ~TimeZoneGenericNameMatchInfo();
131 int32_t size() const;
132 UTimeZoneGenericNameType
getGenericNameType(int32_t index
) const;
133 int32_t getMatchLength(int32_t index
) const;
134 UnicodeString
& getTimeZoneID(int32_t index
, UnicodeString
& tzID
) const;
137 UVector
* fMatches
; // vector of MatchEntry
140 TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector
* matches
)
141 : fMatches(matches
) {
144 TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() {
145 if (fMatches
!= NULL
) {
151 TimeZoneGenericNameMatchInfo::size() const {
152 if (fMatches
== NULL
) {
155 return fMatches
->size();
158 UTimeZoneGenericNameType
159 TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index
) const {
160 GMatchInfo
*minfo
= (GMatchInfo
*)fMatches
->elementAt(index
);
162 return static_cast<UTimeZoneGenericNameType
>(minfo
->gnameInfo
->type
);
164 return UTZGNM_UNKNOWN
;
168 TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index
) const {
169 ZMatchInfo
*minfo
= (ZMatchInfo
*)fMatches
->elementAt(index
);
171 return minfo
->matchLength
;
177 TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index
, UnicodeString
& tzID
) const {
178 GMatchInfo
*minfo
= (GMatchInfo
*)fMatches
->elementAt(index
);
179 if (minfo
!= NULL
&& minfo
->gnameInfo
->tzID
!= NULL
) {
180 tzID
.setTo(TRUE
, minfo
->gnameInfo
->tzID
, -1);
187 // ---------------------------------------------------
188 // GNameSearchHandler
189 // ---------------------------------------------------
190 class GNameSearchHandler
: public TextTrieMapSearchResultHandler
{
192 GNameSearchHandler(uint32_t types
);
193 virtual ~GNameSearchHandler();
195 UBool
handleMatch(int32_t matchLength
, const CharacterNode
*node
, UErrorCode
&status
);
196 UVector
* getMatches(int32_t& maxMatchLen
);
201 int32_t fMaxMatchLen
;
204 GNameSearchHandler::GNameSearchHandler(uint32_t types
)
205 : fTypes(types
), fResults(NULL
), fMaxMatchLen(0) {
208 GNameSearchHandler::~GNameSearchHandler() {
209 if (fResults
!= NULL
) {
215 GNameSearchHandler::handleMatch(int32_t matchLength
, const CharacterNode
*node
, UErrorCode
&status
) {
216 if (U_FAILURE(status
)) {
219 if (node
->hasValues()) {
220 int32_t valuesCount
= node
->countValues();
221 for (int32_t i
= 0; i
< valuesCount
; i
++) {
222 GNameInfo
*nameinfo
= (ZNameInfo
*)node
->getValue(i
);
223 if (nameinfo
== NULL
) {
226 if ((nameinfo
->type
& fTypes
) != 0) {
227 // matches a requested type
228 if (fResults
== NULL
) {
229 fResults
= new UVector(uprv_free
, NULL
, status
);
230 if (fResults
== NULL
) {
231 status
= U_MEMORY_ALLOCATION_ERROR
;
234 if (U_SUCCESS(status
)) {
235 U_ASSERT(fResults
!= NULL
);
236 GMatchInfo
*gmatch
= (GMatchInfo
*)uprv_malloc(sizeof(GMatchInfo
));
237 if (gmatch
== NULL
) {
238 status
= U_MEMORY_ALLOCATION_ERROR
;
240 // add the match to the vector
241 gmatch
->gnameInfo
= nameinfo
;
242 gmatch
->matchLength
= matchLength
;
243 gmatch
->timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
244 fResults
->addElement(gmatch
, status
);
245 if (U_FAILURE(status
)) {
248 if (matchLength
> fMaxMatchLen
) {
249 fMaxMatchLen
= matchLength
;
261 GNameSearchHandler::getMatches(int32_t& maxMatchLen
) {
262 // give the ownership to the caller
263 UVector
*results
= fResults
;
264 maxMatchLen
= fMaxMatchLen
;
272 static UMutex
*gLock() {
273 static UMutex
*m
= STATIC_NEW(UMutex
);
277 class TZGNCore
: public UMemory
{
279 TZGNCore(const Locale
& locale
, UErrorCode
& status
);
282 UnicodeString
& getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
283 UDate date
, UnicodeString
& name
) const;
285 UnicodeString
& getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const;
287 int32_t findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
288 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const;
292 const TimeZoneNames
* fTimeZoneNames
;
293 UHashtable
* fLocationNamesMap
;
294 UHashtable
* fPartialLocationNamesMap
;
296 SimpleFormatter fRegionFormat
;
297 SimpleFormatter fFallbackFormat
;
299 LocaleDisplayNames
* fLocaleDisplayNames
;
300 ZNStringPool fStringPool
;
302 TextTrieMap fGNamesTrie
;
303 UBool fGNamesTrieFullyLoaded
;
305 char fTargetRegion
[ULOC_COUNTRY_CAPACITY
];
307 void initialize(const Locale
& locale
, UErrorCode
& status
);
310 void loadStrings(const UnicodeString
& tzCanonicalID
);
312 const UChar
* getGenericLocationName(const UnicodeString
& tzCanonicalID
);
314 UnicodeString
& formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
315 UDate date
, UnicodeString
& name
) const;
317 UnicodeString
& getPartialLocationName(const UnicodeString
& tzCanonicalID
,
318 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
319 UnicodeString
& name
) const;
321 const UChar
* getPartialLocationName(const UnicodeString
& tzCanonicalID
,
322 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
);
324 TimeZoneGenericNameMatchInfo
* findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const;
326 TimeZoneNames::MatchInfoCollection
* findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const;
330 // ---------------------------------------------------
331 // TZGNCore - core implmentation of TimeZoneGenericNames
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 // ---------------------------------------------------
337 TZGNCore::TZGNCore(const Locale
& locale
, UErrorCode
& status
)
339 fTimeZoneNames(NULL
),
340 fLocationNamesMap(NULL
),
341 fPartialLocationNamesMap(NULL
),
342 fLocaleDisplayNames(NULL
),
344 fGNamesTrie(TRUE
, deleteGNameInfo
),
345 fGNamesTrieFullyLoaded(FALSE
) {
346 initialize(locale
, status
);
349 TZGNCore::~TZGNCore() {
354 TZGNCore::initialize(const Locale
& locale
, UErrorCode
& status
) {
355 if (U_FAILURE(status
)) {
360 fTimeZoneNames
= TimeZoneNames::createInstance(locale
, status
);
361 if (U_FAILURE(status
)) {
365 // Initialize format patterns
366 UnicodeString
rpat(TRUE
, gDefRegionPattern
, -1);
367 UnicodeString
fpat(TRUE
, gDefFallbackPattern
, -1);
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
);
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);
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);
384 ures_close(zoneStrings
);
386 fRegionFormat
.applyPatternMinMaxArguments(rpat
, 1, 1, status
);
387 fFallbackFormat
.applyPatternMinMaxArguments(fpat
, 2, 2, status
);
388 if (U_FAILURE(status
)) {
393 // locale display names
394 fLocaleDisplayNames
= LocaleDisplayNames::createInstance(locale
);
396 // hash table for names - no key/value deleters
397 fLocationNamesMap
= uhash_open(uhash_hashUChars
, uhash_compareUChars
, NULL
, &status
);
398 if (U_FAILURE(status
)) {
403 fPartialLocationNamesMap
= uhash_open(hashPartialLocationKey
, comparePartialLocationKey
, NULL
, &status
);
404 if (U_FAILURE(status
)) {
408 uhash_setKeyDeleter(fPartialLocationNamesMap
, uprv_free
);
412 const char* region
= fLocale
.getCountry();
413 int32_t regionLen
= static_cast<int32_t>(uprv_strlen(region
));
414 if (regionLen
== 0) {
415 char loc
[ULOC_FULLNAME_CAPACITY
];
416 uloc_addLikelySubtags(fLocale
.getName(), loc
, sizeof(loc
), &status
);
418 regionLen
= uloc_getCountry(loc
, fTargetRegion
, sizeof(fTargetRegion
), &status
);
419 if (U_SUCCESS(status
)) {
420 fTargetRegion
[regionLen
] = 0;
425 } else if (regionLen
< (int32_t)sizeof(fTargetRegion
)) {
426 uprv_strcpy(fTargetRegion
, region
);
428 fTargetRegion
[0] = 0;
431 // preload generic names for the default zone
432 TimeZone
*tz
= TimeZone::createDefault();
433 const UChar
*tzID
= ZoneMeta::getCanonicalCLDRID(*tz
);
435 loadStrings(UnicodeString(TRUE
, tzID
, -1));
441 TZGNCore::cleanup() {
442 if (fLocaleDisplayNames
!= NULL
) {
443 delete fLocaleDisplayNames
;
445 if (fTimeZoneNames
!= NULL
) {
446 delete fTimeZoneNames
;
449 uhash_close(fLocationNamesMap
);
450 uhash_close(fPartialLocationNamesMap
);
455 TZGNCore::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
458 case UTZGNM_LOCATION
:
460 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
461 if (tzCanonicalID
!= NULL
) {
462 getGenericLocationName(UnicodeString(TRUE
, tzCanonicalID
, -1), name
);
468 formatGenericNonLocationName(tz
, type
, date
, name
);
469 if (name
.isEmpty()) {
470 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
471 if (tzCanonicalID
!= NULL
) {
472 getGenericLocationName(UnicodeString(TRUE
, tzCanonicalID
, -1), name
);
483 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
484 if (tzCanonicalID
.isEmpty()) {
489 const UChar
*locname
= NULL
;
490 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
493 locname
= nonConstThis
->getGenericLocationName(tzCanonicalID
);
495 umtx_unlock(gLock());
497 if (locname
== NULL
) {
500 name
.setTo(locname
, u_strlen(locname
));
507 * This method updates the cache and must be called with a lock
510 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
) {
511 U_ASSERT(!tzCanonicalID
.isEmpty());
512 if (tzCanonicalID
.length() > ZID_KEY_MAX
) {
516 UErrorCode status
= U_ZERO_ERROR
;
517 UChar tzIDKey
[ZID_KEY_MAX
+ 1];
518 int32_t tzIDKeyLen
= tzCanonicalID
.extract(tzIDKey
, ZID_KEY_MAX
+ 1, status
);
519 U_ASSERT(status
== U_ZERO_ERROR
); // already checked length above
520 tzIDKey
[tzIDKeyLen
] = 0;
522 const UChar
*locname
= (const UChar
*)uhash_get(fLocationNamesMap
, tzIDKey
);
524 if (locname
!= NULL
) {
525 // gEmpty indicate the name is not available
526 if (locname
== gEmpty
) {
532 // Construct location name
534 UnicodeString usCountryCode
;
535 UBool isPrimary
= FALSE
;
537 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
, &isPrimary
);
539 if (!usCountryCode
.isEmpty()) {
541 // If this is the primary zone in the country, use the country name.
542 char countryCode
[ULOC_COUNTRY_CAPACITY
];
543 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
544 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
545 countryCode
[ccLen
] = 0;
547 UnicodeString country
;
548 fLocaleDisplayNames
->regionDisplayName(countryCode
, country
);
549 fRegionFormat
.format(country
, name
, status
);
551 // If this is not the primary zone in the country,
552 // use the exemplar city name.
554 // getExemplarLocationName should retur non-empty string
555 // if the time zone is associated with a region
558 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, city
);
559 fRegionFormat
.format(city
, name
, status
);
561 if (U_FAILURE(status
)) {
566 locname
= name
.isEmpty() ? NULL
: fStringPool
.get(name
, status
);
567 if (U_SUCCESS(status
)) {
569 const UChar
* cacheID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
570 U_ASSERT(cacheID
!= NULL
);
571 if (locname
== NULL
) {
572 // gEmpty to indicate - no location name available
573 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)gEmpty
, &status
);
575 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)locname
, &status
);
576 if (U_FAILURE(status
)) {
579 // put the name info into the trie
580 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
581 if (nameinfo
!= NULL
) {
582 nameinfo
->type
= UTZGNM_LOCATION
;
583 nameinfo
->tzID
= cacheID
;
584 fGNamesTrie
.put(locname
, nameinfo
, status
);
594 TZGNCore::formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
595 U_ASSERT(type
== UTZGNM_LONG
|| type
== UTZGNM_SHORT
);
598 const UChar
* uID
= ZoneMeta::getCanonicalCLDRID(tz
);
603 UnicodeString
tzID(TRUE
, uID
, -1);
605 // Try to get a name from time zone first
606 UTimeZoneNameType nameType
= (type
== UTZGNM_LONG
) ? UTZNM_LONG_GENERIC
: UTZNM_SHORT_GENERIC
;
607 fTimeZoneNames
->getTimeZoneDisplayName(tzID
, nameType
, name
);
609 if (!name
.isEmpty()) {
615 UnicodeString
mzID(mzIDBuf
, 0, UPRV_LENGTHOF(mzIDBuf
));
616 fTimeZoneNames
->getMetaZoneID(tzID
, date
, mzID
);
617 if (!mzID
.isEmpty()) {
618 UErrorCode status
= U_ZERO_ERROR
;
619 UBool useStandard
= FALSE
;
621 UChar tmpNameBuf
[ZONE_NAME_U16_MAX
];
623 tz
.getOffset(date
, FALSE
, raw
, sav
, status
);
624 if (U_FAILURE(status
)) {
631 TimeZone
*tmptz
= tz
.clone();
632 // Check if the zone actually uses daylight saving time around the time
633 BasicTimeZone
*btz
= NULL
;
634 if (dynamic_cast<OlsonTimeZone
*>(tmptz
) != NULL
635 || dynamic_cast<SimpleTimeZone
*>(tmptz
) != NULL
636 || dynamic_cast<RuleBasedTimeZone
*>(tmptz
) != NULL
637 || dynamic_cast<VTimeZone
*>(tmptz
) != NULL
) {
638 btz
= (BasicTimeZone
*)tmptz
;
642 TimeZoneTransition before
;
643 UBool beforTrs
= btz
->getPreviousTransition(date
, TRUE
, before
);
645 && (date
- before
.getTime() < kDstCheckRange
)
646 && before
.getFrom()->getDSTSavings() != 0) {
649 TimeZoneTransition after
;
650 UBool afterTrs
= btz
->getNextTransition(date
, FALSE
, after
);
652 && (after
.getTime() - date
< kDstCheckRange
)
653 && after
.getTo()->getDSTSavings() != 0) {
658 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
659 // We may get a wrong answer in edge case, but it should practically work OK.
660 tmptz
->getOffset(date
- kDstCheckRange
, FALSE
, raw
, sav
, status
);
664 tmptz
->getOffset(date
+ kDstCheckRange
, FALSE
, raw
, sav
, status
);
669 if (U_FAILURE(status
)) {
677 UTimeZoneNameType stdNameType
= (nameType
== UTZNM_LONG_GENERIC
)
678 ? UTZNM_LONG_STANDARD
: UTZNM_SHORT_STANDARD
;
679 UnicodeString
stdName(tmpNameBuf
, 0, UPRV_LENGTHOF(tmpNameBuf
));
680 fTimeZoneNames
->getDisplayName(tzID
, stdNameType
, date
, stdName
);
681 if (!stdName
.isEmpty()) {
684 // TODO: revisit this issue later
685 // In CLDR, a same display name is used for both generic and standard
686 // for some meta zones in some locales. This looks like a data bugs.
687 // For now, we check if the standard name is different from its generic
689 UChar genNameBuf
[ZONE_NAME_U16_MAX
];
690 UnicodeString
mzGenericName(genNameBuf
, 0, UPRV_LENGTHOF(genNameBuf
));
691 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzGenericName
);
692 if (stdName
.caseCompare(mzGenericName
, 0) == 0) {
697 if (name
.isEmpty()) {
698 // Get a name from meta zone
699 UnicodeString
mzName(tmpNameBuf
, 0, UPRV_LENGTHOF(tmpNameBuf
));
700 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzName
);
701 if (!mzName
.isEmpty()) {
702 // Check if we need to use a partial location format.
703 // This check is done by comparing offset with the meta zone's
704 // golden zone at the given date.
706 UnicodeString
goldenID(idBuf
, 0, UPRV_LENGTHOF(idBuf
));
707 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, goldenID
);
708 if (!goldenID
.isEmpty() && goldenID
!= tzID
) {
709 TimeZone
*goldenZone
= TimeZone::createTimeZone(goldenID
);
712 // Check offset in the golden zone with wall time.
713 // With getOffset(date, false, offsets1),
714 // you may get incorrect results because of time overlap at DST->STD
716 goldenZone
->getOffset(date
+ raw
+ sav
, TRUE
, raw1
, sav1
, status
);
718 if (U_SUCCESS(status
)) {
719 if (raw
!= raw1
|| sav
!= sav1
) {
720 // Now we need to use a partial location format
721 getPartialLocationName(tzID
, mzID
, (nameType
== UTZNM_LONG_GENERIC
), mzName
, name
);
736 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
737 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
738 UnicodeString
& name
) const {
740 if (tzCanonicalID
.isEmpty() || mzID
.isEmpty() || mzDisplayName
.isEmpty()) {
744 const UChar
*uplname
= NULL
;
745 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
748 uplname
= nonConstThis
->getPartialLocationName(tzCanonicalID
, mzID
, isLong
, mzDisplayName
);
750 umtx_unlock(gLock());
752 if (uplname
== NULL
) {
755 name
.setTo(TRUE
, uplname
, -1);
761 * This method updates the cache and must be called with a lock
764 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
765 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
) {
766 U_ASSERT(!tzCanonicalID
.isEmpty());
767 U_ASSERT(!mzID
.isEmpty());
768 U_ASSERT(!mzDisplayName
.isEmpty());
770 PartialLocationKey key
;
771 key
.tzID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
772 key
.mzID
= ZoneMeta::findMetaZoneID(mzID
);
774 U_ASSERT(key
.tzID
!= NULL
&& key
.mzID
!= NULL
);
776 const UChar
* uplname
= (const UChar
*)uhash_get(fPartialLocationNamesMap
, (void *)&key
);
777 if (uplname
!= NULL
) {
781 UnicodeString location
;
782 UnicodeString usCountryCode
;
783 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
);
784 if (!usCountryCode
.isEmpty()) {
785 char countryCode
[ULOC_COUNTRY_CAPACITY
];
786 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
787 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
788 countryCode
[ccLen
] = 0;
790 UnicodeString regionalGolden
;
791 fTimeZoneNames
->getReferenceZoneID(mzID
, countryCode
, regionalGolden
);
792 if (tzCanonicalID
== regionalGolden
) {
794 fLocaleDisplayNames
->regionDisplayName(countryCode
, location
);
796 // Otherwise, use exemplar city name
797 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
800 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
801 if (location
.isEmpty()) {
802 // This could happen when the time zone is not associated with a country,
803 // and its ID is not hierarchical, for example, CST6CDT.
804 // We use the canonical ID itself as the location for this case.
805 location
.setTo(tzCanonicalID
);
809 UErrorCode status
= U_ZERO_ERROR
;
811 fFallbackFormat
.format(location
, mzDisplayName
, name
, status
);
812 if (U_FAILURE(status
)) {
816 uplname
= fStringPool
.get(name
, status
);
817 if (U_SUCCESS(status
)) {
818 // Add the name to cache
819 PartialLocationKey
* cacheKey
= (PartialLocationKey
*)uprv_malloc(sizeof(PartialLocationKey
));
820 if (cacheKey
!= NULL
) {
821 cacheKey
->tzID
= key
.tzID
;
822 cacheKey
->mzID
= key
.mzID
;
823 cacheKey
->isLong
= key
.isLong
;
824 uhash_put(fPartialLocationNamesMap
, (void *)cacheKey
, (void *)uplname
, &status
);
825 if (U_FAILURE(status
)) {
828 // put the name to the local trie as well
829 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
830 if (nameinfo
!= NULL
) {
831 nameinfo
->type
= isLong
? UTZGNM_LONG
: UTZGNM_SHORT
;
832 nameinfo
->tzID
= key
.tzID
;
833 fGNamesTrie
.put(uplname
, nameinfo
, status
);
842 * This method updates the cache and must be called with a lock,
843 * except initializer.
846 TZGNCore::loadStrings(const UnicodeString
& tzCanonicalID
) {
847 // load the generic location name
848 getGenericLocationName(tzCanonicalID
);
850 // partial location names
851 UErrorCode status
= U_ZERO_ERROR
;
853 const UnicodeString
*mzID
;
854 UnicodeString goldenID
;
855 UnicodeString mzGenName
;
856 UTimeZoneNameType genNonLocTypes
[] = {
857 UTZNM_LONG_GENERIC
, UTZNM_SHORT_GENERIC
,
858 UTZNM_UNKNOWN
/*terminator*/
861 StringEnumeration
*mzIDs
= fTimeZoneNames
->getAvailableMetaZoneIDs(tzCanonicalID
, status
);
862 while ((mzID
= mzIDs
->snext(status
)) != NULL
) {
863 if (U_FAILURE(status
)) {
866 // if this time zone is not the golden zone of the meta zone,
867 // partial location name (such as "PT (Los Angeles)") might be
869 fTimeZoneNames
->getReferenceZoneID(*mzID
, fTargetRegion
, goldenID
);
870 if (tzCanonicalID
!= goldenID
) {
871 for (int32_t i
= 0; genNonLocTypes
[i
] != UTZNM_UNKNOWN
; i
++) {
872 fTimeZoneNames
->getMetaZoneDisplayName(*mzID
, genNonLocTypes
[i
], mzGenName
);
873 if (!mzGenName
.isEmpty()) {
874 // getPartialLocationName formats a name and put it into the trie
875 getPartialLocationName(tzCanonicalID
, *mzID
,
876 (genNonLocTypes
[i
] == UTZNM_LONG_GENERIC
), mzGenName
);
887 TZGNCore::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
888 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
889 timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
892 if (U_FAILURE(status
)) {
896 // Find matches in the TimeZoneNames first
897 TimeZoneNames::MatchInfoCollection
*tznamesMatches
= findTimeZoneNames(text
, start
, types
, status
);
898 if (U_FAILURE(status
)) {
902 int32_t bestMatchLen
= 0;
903 UTimeZoneFormatTimeType bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
904 UnicodeString bestMatchTzID
;
905 // UBool isLongStandard = FALSE; // workaround - see the comments below
906 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
908 if (tznamesMatches
!= NULL
) {
910 for (int32_t i
= 0; i
< tznamesMatches
->size(); i
++) {
911 int32_t len
= tznamesMatches
->getMatchLengthAt(i
);
912 if (len
> bestMatchLen
) {
914 if (!tznamesMatches
->getTimeZoneIDAt(i
, bestMatchTzID
)) {
915 // name for a meta zone
916 if (tznamesMatches
->getMetaZoneIDAt(i
, mzID
)) {
917 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, bestMatchTzID
);
920 UTimeZoneNameType nameType
= tznamesMatches
->getNameTypeAt(i
);
921 if (U_FAILURE(status
)) {
925 case UTZNM_LONG_STANDARD
:
926 // isLongStandard = TRUE;
927 case UTZNM_SHORT_STANDARD
: // this one is never used for generic, but just in case
928 isStandard
= TRUE
; // TODO: Remove this later, see the comments above.
929 bestMatchTimeType
= UTZFMT_TIME_TYPE_STANDARD
;
931 case UTZNM_LONG_DAYLIGHT
:
932 case UTZNM_SHORT_DAYLIGHT
: // this one is never used for generic, but just in case
933 bestMatchTimeType
= UTZFMT_TIME_TYPE_DAYLIGHT
;
936 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
940 delete tznamesMatches
;
941 if (U_FAILURE(status
)) {
945 if (bestMatchLen
== (text
.length() - start
)) {
948 //tzID.setTo(bestMatchTzID);
949 //timeType = bestMatchTimeType;
950 //return bestMatchLen;
952 // TODO Some time zone uses a same name for the long standard name
953 // and the location name. When the match is a long standard name,
954 // then we need to check if the name is same with the location name.
955 // This is probably a data error or a design bug.
957 if (!isLongStandard) {
958 tzID.setTo(bestMatchTzID);
959 timeType = bestMatchTimeType;
963 // TODO The deprecation of commonlyUsed flag introduced the name
964 // conflict not only for long standard names, but short standard names too.
965 // These short names (found in zh_Hant) should be gone once we clean
966 // up CLDR time zone display name data. Once the short name conflict
967 // problem (with location name) is resolved, we should change the condition
968 // below back to the original one above. -Yoshito (2011-09-14)
970 tzID
.setTo(bestMatchTzID
);
971 timeType
= bestMatchTimeType
;
977 // Find matches in the local trie
978 TimeZoneGenericNameMatchInfo
*localMatches
= findLocal(text
, start
, types
, status
);
979 if (U_FAILURE(status
)) {
982 if (localMatches
!= NULL
) {
983 for (int32_t i
= 0; i
< localMatches
->size(); i
++) {
984 int32_t len
= localMatches
->getMatchLength(i
);
986 // TODO See the above TODO. We use len >= bestMatchLen
987 // because of the long standard/location name collision
988 // problem. If it is also a location name, carrying
989 // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
990 // problem in SimpleDateFormat
991 if (len
>= bestMatchLen
) {
992 bestMatchLen
= localMatches
->getMatchLength(i
);
993 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
; // because generic
994 localMatches
->getTimeZoneID(i
, bestMatchTzID
);
1000 if (bestMatchLen
> 0) {
1001 timeType
= bestMatchTimeType
;
1002 tzID
.setTo(bestMatchTzID
);
1004 return bestMatchLen
;
1007 TimeZoneGenericNameMatchInfo
*
1008 TZGNCore::findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1009 GNameSearchHandler
handler(types
);
1011 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
1015 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1017 umtx_unlock(gLock());
1019 if (U_FAILURE(status
)) {
1023 TimeZoneGenericNameMatchInfo
*gmatchInfo
= NULL
;
1026 UVector
*results
= handler
.getMatches(maxLen
);
1027 if (results
!= NULL
&& ((maxLen
== (text
.length() - start
)) || fGNamesTrieFullyLoaded
)) {
1029 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1030 if (gmatchInfo
== NULL
) {
1031 status
= U_MEMORY_ALLOCATION_ERROR
;
1038 if (results
!= NULL
) {
1042 // All names are not yet loaded into the local trie.
1043 // Load all available names into the trie. This could be very heavy.
1046 if (!fGNamesTrieFullyLoaded
) {
1047 StringEnumeration
*tzIDs
= TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL
, NULL
, NULL
, status
);
1048 if (U_SUCCESS(status
)) {
1049 const UnicodeString
*tzID
;
1050 while ((tzID
= tzIDs
->snext(status
)) != NULL
) {
1051 if (U_FAILURE(status
)) {
1054 nonConstThis
->loadStrings(*tzID
);
1057 if (tzIDs
!= NULL
) {
1061 if (U_SUCCESS(status
)) {
1062 nonConstThis
->fGNamesTrieFullyLoaded
= TRUE
;
1066 umtx_unlock(gLock());
1068 if (U_FAILURE(status
)) {
1075 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1077 umtx_unlock(gLock());
1079 results
= handler
.getMatches(maxLen
);
1080 if (results
!= NULL
&& maxLen
> 0) {
1081 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1082 if (gmatchInfo
== NULL
) {
1083 status
= U_MEMORY_ALLOCATION_ERROR
;
1092 TimeZoneNames::MatchInfoCollection
*
1093 TZGNCore::findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1094 // Check if the target name typs is really in the TimeZoneNames
1095 uint32_t nameTypes
= 0;
1096 if (types
& UTZGNM_LONG
) {
1097 nameTypes
|= (UTZNM_LONG_GENERIC
| UTZNM_LONG_STANDARD
);
1099 if (types
& UTZGNM_SHORT
) {
1100 nameTypes
|= (UTZNM_SHORT_GENERIC
| UTZNM_SHORT_STANDARD
);
1104 // Find matches in the TimeZoneNames
1105 return fTimeZoneNames
->find(text
, start
, nameTypes
, status
);
1111 typedef struct TZGNCoreRef
{
1117 // TZGNCore object cache handling
1118 static UMutex
*gTZGNLock() {
1119 static UMutex
*m
= STATIC_NEW(UMutex
);
1122 static UHashtable
*gTZGNCoreCache
= NULL
;
1123 static UBool gTZGNCoreCacheInitialized
= FALSE
;
1125 // Access count - incremented every time up to SWEEP_INTERVAL,
1127 static int32_t gAccessCount
= 0;
1129 // Interval for calling the cache sweep function - every 100 times
1130 #define SWEEP_INTERVAL 100
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
1140 * Cleanup callback func
1142 static UBool U_CALLCONV
tzgnCore_cleanup(void)
1144 if (gTZGNCoreCache
!= NULL
) {
1145 uhash_close(gTZGNCoreCache
);
1146 gTZGNCoreCache
= NULL
;
1148 gTZGNCoreCacheInitialized
= FALSE
;
1153 * Deleter for TZGNCoreRef
1155 static void U_CALLCONV
1156 deleteTZGNCoreRef(void *obj
) {
1157 icu::TZGNCoreRef
*entry
= (icu::TZGNCoreRef
*)obj
;
1158 delete (icu::TZGNCore
*) entry
->obj
;
1164 * Function used for removing unreferrenced cache entries exceeding
1165 * the expiration time. This function must be called with in the mutex
1168 static void sweepCache() {
1169 int32_t pos
= UHASH_FIRST
;
1170 const UHashElement
* elem
;
1171 double now
= (double)uprv_getUTCtime();
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
);
1182 TimeZoneGenericNames::TimeZoneGenericNames()
1186 TimeZoneGenericNames::~TimeZoneGenericNames() {
1187 umtx_lock(gTZGNLock());
1189 U_ASSERT(fRef
->refCount
> 0);
1190 // Just decrement the reference count
1193 umtx_unlock(gTZGNLock());
1196 TimeZoneGenericNames
*
1197 TimeZoneGenericNames::createInstance(const Locale
& locale
, UErrorCode
& status
) {
1198 if (U_FAILURE(status
)) {
1201 TimeZoneGenericNames
* instance
= new TimeZoneGenericNames();
1202 if (instance
== NULL
) {
1203 status
= U_MEMORY_ALLOCATION_ERROR
;
1207 TZGNCoreRef
*cacheEntry
= NULL
;
1209 Mutex
lock(gTZGNLock());
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
);
1221 if (U_FAILURE(status
)) {
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
;
1232 tzgnCore
= new TZGNCore(locale
, status
);
1233 if (tzgnCore
== NULL
) {
1234 status
= U_MEMORY_ALLOCATION_ERROR
;
1236 if (U_SUCCESS(status
)) {
1237 newKey
= (char *)uprv_malloc(uprv_strlen(key
) + 1);
1238 if (newKey
== NULL
) {
1239 status
= U_MEMORY_ALLOCATION_ERROR
;
1241 uprv_strcpy(newKey
, key
);
1244 if (U_SUCCESS(status
)) {
1245 cacheEntry
= (TZGNCoreRef
*)uprv_malloc(sizeof(TZGNCoreRef
));
1246 if (cacheEntry
== NULL
) {
1247 status
= U_MEMORY_ALLOCATION_ERROR
;
1249 cacheEntry
->obj
= tzgnCore
;
1250 cacheEntry
->refCount
= 1;
1251 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1253 uhash_put(gTZGNCoreCache
, newKey
, cacheEntry
, &status
);
1256 if (U_FAILURE(status
)) {
1257 if (tzgnCore
!= NULL
) {
1260 if (newKey
!= NULL
) {
1263 if (cacheEntry
!= NULL
) {
1264 uprv_free(cacheEntry
);
1269 // Update the reference count
1270 cacheEntry
->refCount
++;
1271 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1274 if (gAccessCount
>= SWEEP_INTERVAL
) {
1279 } // End of mutex locked block
1281 if (cacheEntry
== NULL
) {
1286 instance
->fRef
= cacheEntry
;
1291 TimeZoneGenericNames::operator==(const TimeZoneGenericNames
& other
) const {
1292 // Just compare if the other object also use the same
1294 return fRef
== other
.fRef
;
1297 TimeZoneGenericNames
*
1298 TimeZoneGenericNames::clone() const {
1299 TimeZoneGenericNames
* other
= new TimeZoneGenericNames();
1301 umtx_lock(gTZGNLock());
1303 // Just increments the reference count
1307 umtx_unlock(gTZGNLock());
1313 TimeZoneGenericNames::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
1314 UDate date
, UnicodeString
& name
) const {
1315 return fRef
->obj
->getDisplayName(tz
, type
, date
, name
);
1319 TimeZoneGenericNames::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
1320 return fRef
->obj
->getGenericLocationName(tzCanonicalID
, name
);
1324 TimeZoneGenericNames::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
);