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
= U_MUTEX_INITIALIZER
;
274 class TZGNCore
: public UMemory
{
276 TZGNCore(const Locale
& locale
, UErrorCode
& status
);
279 UnicodeString
& getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
280 UDate date
, UnicodeString
& name
) const;
282 UnicodeString
& getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const;
284 int32_t findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
285 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const;
289 const TimeZoneNames
* fTimeZoneNames
;
290 UHashtable
* fLocationNamesMap
;
291 UHashtable
* fPartialLocationNamesMap
;
293 SimpleFormatter fRegionFormat
;
294 SimpleFormatter fFallbackFormat
;
296 LocaleDisplayNames
* fLocaleDisplayNames
;
297 ZNStringPool fStringPool
;
299 TextTrieMap fGNamesTrie
;
300 UBool fGNamesTrieFullyLoaded
;
302 char fTargetRegion
[ULOC_COUNTRY_CAPACITY
];
304 void initialize(const Locale
& locale
, UErrorCode
& status
);
307 void loadStrings(const UnicodeString
& tzCanonicalID
);
309 const UChar
* getGenericLocationName(const UnicodeString
& tzCanonicalID
);
311 UnicodeString
& formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
312 UDate date
, UnicodeString
& name
) const;
314 UnicodeString
& getPartialLocationName(const UnicodeString
& tzCanonicalID
,
315 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
316 UnicodeString
& name
) const;
318 const UChar
* getPartialLocationName(const UnicodeString
& tzCanonicalID
,
319 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
);
321 TimeZoneGenericNameMatchInfo
* findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const;
323 TimeZoneNames::MatchInfoCollection
* findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const;
327 // ---------------------------------------------------
328 // TZGNCore - core implmentation of TimeZoneGenericNames
330 // TimeZoneGenericNames is parallel to TimeZoneNames,
331 // but handles run-time generated time zone names.
332 // This is the main part of this module.
333 // ---------------------------------------------------
334 TZGNCore::TZGNCore(const Locale
& locale
, UErrorCode
& status
)
336 fTimeZoneNames(NULL
),
337 fLocationNamesMap(NULL
),
338 fPartialLocationNamesMap(NULL
),
339 fLocaleDisplayNames(NULL
),
341 fGNamesTrie(TRUE
, deleteGNameInfo
),
342 fGNamesTrieFullyLoaded(FALSE
) {
343 initialize(locale
, status
);
346 TZGNCore::~TZGNCore() {
351 TZGNCore::initialize(const Locale
& locale
, UErrorCode
& status
) {
352 if (U_FAILURE(status
)) {
357 fTimeZoneNames
= TimeZoneNames::createInstance(locale
, status
);
358 if (U_FAILURE(status
)) {
362 // Initialize format patterns
363 UnicodeString
rpat(TRUE
, gDefRegionPattern
, -1);
364 UnicodeString
fpat(TRUE
, gDefFallbackPattern
, -1);
366 UErrorCode tmpsts
= U_ZERO_ERROR
; // OK with fallback warning..
367 UResourceBundle
*zoneStrings
= ures_open(U_ICUDATA_ZONE
, locale
.getName(), &tmpsts
);
368 zoneStrings
= ures_getByKeyWithFallback(zoneStrings
, gZoneStrings
, zoneStrings
, &tmpsts
);
370 if (U_SUCCESS(tmpsts
)) {
371 const UChar
*regionPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gRegionFormatTag
, NULL
, &tmpsts
);
372 if (U_SUCCESS(tmpsts
) && u_strlen(regionPattern
) > 0) {
373 rpat
.setTo(regionPattern
, -1);
375 tmpsts
= U_ZERO_ERROR
;
376 const UChar
*fallbackPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gFallbackFormatTag
, NULL
, &tmpsts
);
377 if (U_SUCCESS(tmpsts
) && u_strlen(fallbackPattern
) > 0) {
378 fpat
.setTo(fallbackPattern
, -1);
381 ures_close(zoneStrings
);
383 fRegionFormat
.applyPatternMinMaxArguments(rpat
, 1, 1, status
);
384 fFallbackFormat
.applyPatternMinMaxArguments(fpat
, 2, 2, status
);
385 if (U_FAILURE(status
)) {
390 // locale display names
391 fLocaleDisplayNames
= LocaleDisplayNames::createInstance(locale
);
393 // hash table for names - no key/value deleters
394 fLocationNamesMap
= uhash_open(uhash_hashUChars
, uhash_compareUChars
, NULL
, &status
);
395 if (U_FAILURE(status
)) {
400 fPartialLocationNamesMap
= uhash_open(hashPartialLocationKey
, comparePartialLocationKey
, NULL
, &status
);
401 if (U_FAILURE(status
)) {
405 uhash_setKeyDeleter(fPartialLocationNamesMap
, uprv_free
);
409 const char* region
= fLocale
.getCountry();
410 int32_t regionLen
= uprv_strlen(region
);
411 if (regionLen
== 0) {
412 char loc
[ULOC_FULLNAME_CAPACITY
];
413 uloc_addLikelySubtags(fLocale
.getName(), loc
, sizeof(loc
), &status
);
415 regionLen
= uloc_getCountry(loc
, fTargetRegion
, sizeof(fTargetRegion
), &status
);
416 if (U_SUCCESS(status
)) {
417 fTargetRegion
[regionLen
] = 0;
422 } else if (regionLen
< (int32_t)sizeof(fTargetRegion
)) {
423 uprv_strcpy(fTargetRegion
, region
);
425 fTargetRegion
[0] = 0;
428 // preload generic names for the default zone
429 TimeZone
*tz
= TimeZone::createDefault();
430 const UChar
*tzID
= ZoneMeta::getCanonicalCLDRID(*tz
);
432 loadStrings(UnicodeString(TRUE
, tzID
, -1));
438 TZGNCore::cleanup() {
439 if (fLocaleDisplayNames
!= NULL
) {
440 delete fLocaleDisplayNames
;
442 if (fTimeZoneNames
!= NULL
) {
443 delete fTimeZoneNames
;
446 uhash_close(fLocationNamesMap
);
447 uhash_close(fPartialLocationNamesMap
);
452 TZGNCore::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
455 case UTZGNM_LOCATION
:
457 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
458 if (tzCanonicalID
!= NULL
) {
459 getGenericLocationName(UnicodeString(TRUE
, tzCanonicalID
, -1), name
);
465 formatGenericNonLocationName(tz
, type
, date
, name
);
466 if (name
.isEmpty()) {
467 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
468 if (tzCanonicalID
!= NULL
) {
469 getGenericLocationName(UnicodeString(TRUE
, tzCanonicalID
, -1), name
);
480 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
481 if (tzCanonicalID
.isEmpty()) {
486 const UChar
*locname
= NULL
;
487 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
490 locname
= nonConstThis
->getGenericLocationName(tzCanonicalID
);
494 if (locname
== NULL
) {
497 name
.setTo(locname
, u_strlen(locname
));
504 * This method updates the cache and must be called with a lock
507 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
) {
508 U_ASSERT(!tzCanonicalID
.isEmpty());
509 if (tzCanonicalID
.length() > ZID_KEY_MAX
) {
513 UErrorCode status
= U_ZERO_ERROR
;
514 UChar tzIDKey
[ZID_KEY_MAX
+ 1];
515 int32_t tzIDKeyLen
= tzCanonicalID
.extract(tzIDKey
, ZID_KEY_MAX
+ 1, status
);
516 U_ASSERT(status
== U_ZERO_ERROR
); // already checked length above
517 tzIDKey
[tzIDKeyLen
] = 0;
519 const UChar
*locname
= (const UChar
*)uhash_get(fLocationNamesMap
, tzIDKey
);
521 if (locname
!= NULL
) {
522 // gEmpty indicate the name is not available
523 if (locname
== gEmpty
) {
529 // Construct location name
531 UnicodeString usCountryCode
;
532 UBool isPrimary
= FALSE
;
534 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
, &isPrimary
);
536 if (!usCountryCode
.isEmpty()) {
538 // If this is the primary zone in the country, use the country name.
539 char countryCode
[ULOC_COUNTRY_CAPACITY
];
540 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
541 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
542 countryCode
[ccLen
] = 0;
544 UnicodeString country
;
545 fLocaleDisplayNames
->regionDisplayName(countryCode
, country
);
546 fRegionFormat
.format(country
, name
, status
);
548 // If this is not the primary zone in the country,
549 // use the exemplar city name.
551 // getExemplarLocationName should retur non-empty string
552 // if the time zone is associated with a region
555 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, city
);
556 fRegionFormat
.format(city
, name
, status
);
558 if (U_FAILURE(status
)) {
563 locname
= name
.isEmpty() ? NULL
: fStringPool
.get(name
, status
);
564 if (U_SUCCESS(status
)) {
566 const UChar
* cacheID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
567 U_ASSERT(cacheID
!= NULL
);
568 if (locname
== NULL
) {
569 // gEmpty to indicate - no location name available
570 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)gEmpty
, &status
);
572 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)locname
, &status
);
573 if (U_FAILURE(status
)) {
576 // put the name info into the trie
577 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
578 if (nameinfo
!= NULL
) {
579 nameinfo
->type
= UTZGNM_LOCATION
;
580 nameinfo
->tzID
= cacheID
;
581 fGNamesTrie
.put(locname
, nameinfo
, status
);
591 TZGNCore::formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
592 U_ASSERT(type
== UTZGNM_LONG
|| type
== UTZGNM_SHORT
);
595 const UChar
* uID
= ZoneMeta::getCanonicalCLDRID(tz
);
600 UnicodeString
tzID(TRUE
, uID
, -1);
602 // Try to get a name from time zone first
603 UTimeZoneNameType nameType
= (type
== UTZGNM_LONG
) ? UTZNM_LONG_GENERIC
: UTZNM_SHORT_GENERIC
;
604 fTimeZoneNames
->getTimeZoneDisplayName(tzID
, nameType
, name
);
606 if (!name
.isEmpty()) {
612 UnicodeString
mzID(mzIDBuf
, 0, UPRV_LENGTHOF(mzIDBuf
));
613 fTimeZoneNames
->getMetaZoneID(tzID
, date
, mzID
);
614 if (!mzID
.isEmpty()) {
615 UErrorCode status
= U_ZERO_ERROR
;
616 UBool useStandard
= FALSE
;
618 UChar tmpNameBuf
[64];
620 tz
.getOffset(date
, FALSE
, raw
, sav
, status
);
621 if (U_FAILURE(status
)) {
628 TimeZone
*tmptz
= tz
.clone();
629 // Check if the zone actually uses daylight saving time around the time
630 BasicTimeZone
*btz
= NULL
;
631 if (dynamic_cast<OlsonTimeZone
*>(tmptz
) != NULL
632 || dynamic_cast<SimpleTimeZone
*>(tmptz
) != NULL
633 || dynamic_cast<RuleBasedTimeZone
*>(tmptz
) != NULL
634 || dynamic_cast<VTimeZone
*>(tmptz
) != NULL
) {
635 btz
= (BasicTimeZone
*)tmptz
;
639 TimeZoneTransition before
;
640 UBool beforTrs
= btz
->getPreviousTransition(date
, TRUE
, before
);
642 && (date
- before
.getTime() < kDstCheckRange
)
643 && before
.getFrom()->getDSTSavings() != 0) {
646 TimeZoneTransition after
;
647 UBool afterTrs
= btz
->getNextTransition(date
, FALSE
, after
);
649 && (after
.getTime() - date
< kDstCheckRange
)
650 && after
.getTo()->getDSTSavings() != 0) {
655 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
656 // We may get a wrong answer in edge case, but it should practically work OK.
657 tmptz
->getOffset(date
- kDstCheckRange
, FALSE
, raw
, sav
, status
);
661 tmptz
->getOffset(date
+ kDstCheckRange
, FALSE
, raw
, sav
, status
);
666 if (U_FAILURE(status
)) {
674 UTimeZoneNameType stdNameType
= (nameType
== UTZNM_LONG_GENERIC
)
675 ? UTZNM_LONG_STANDARD
: UTZNM_SHORT_STANDARD
;
676 UnicodeString
stdName(tmpNameBuf
, 0, UPRV_LENGTHOF(tmpNameBuf
));
677 fTimeZoneNames
->getDisplayName(tzID
, stdNameType
, date
, stdName
);
678 if (!stdName
.isEmpty()) {
681 // TODO: revisit this issue later
682 // In CLDR, a same display name is used for both generic and standard
683 // for some meta zones in some locales. This looks like a data bugs.
684 // For now, we check if the standard name is different from its generic
686 UChar genNameBuf
[64];
687 UnicodeString
mzGenericName(genNameBuf
, 0, UPRV_LENGTHOF(genNameBuf
));
688 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzGenericName
);
689 if (stdName
.caseCompare(mzGenericName
, 0) == 0) {
694 if (name
.isEmpty()) {
695 // Get a name from meta zone
696 UnicodeString
mzName(tmpNameBuf
, 0, UPRV_LENGTHOF(tmpNameBuf
));
697 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzName
);
698 if (!mzName
.isEmpty()) {
699 // Check if we need to use a partial location format.
700 // This check is done by comparing offset with the meta zone's
701 // golden zone at the given date.
703 UnicodeString
goldenID(idBuf
, 0, UPRV_LENGTHOF(idBuf
));
704 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, goldenID
);
705 if (!goldenID
.isEmpty() && goldenID
!= tzID
) {
706 TimeZone
*goldenZone
= TimeZone::createTimeZone(goldenID
);
709 // Check offset in the golden zone with wall time.
710 // With getOffset(date, false, offsets1),
711 // you may get incorrect results because of time overlap at DST->STD
713 goldenZone
->getOffset(date
+ raw
+ sav
, TRUE
, raw1
, sav1
, status
);
715 if (U_SUCCESS(status
)) {
716 if (raw
!= raw1
|| sav
!= sav1
) {
717 // Now we need to use a partial location format
718 getPartialLocationName(tzID
, mzID
, (nameType
== UTZNM_LONG_GENERIC
), mzName
, name
);
733 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
734 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
735 UnicodeString
& name
) const {
737 if (tzCanonicalID
.isEmpty() || mzID
.isEmpty() || mzDisplayName
.isEmpty()) {
741 const UChar
*uplname
= NULL
;
742 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
745 uplname
= nonConstThis
->getPartialLocationName(tzCanonicalID
, mzID
, isLong
, mzDisplayName
);
749 if (uplname
== NULL
) {
752 name
.setTo(TRUE
, uplname
, -1);
758 * This method updates the cache and must be called with a lock
761 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
762 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
) {
763 U_ASSERT(!tzCanonicalID
.isEmpty());
764 U_ASSERT(!mzID
.isEmpty());
765 U_ASSERT(!mzDisplayName
.isEmpty());
767 PartialLocationKey key
;
768 key
.tzID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
769 key
.mzID
= ZoneMeta::findMetaZoneID(mzID
);
771 U_ASSERT(key
.tzID
!= NULL
&& key
.mzID
!= NULL
);
773 const UChar
* uplname
= (const UChar
*)uhash_get(fPartialLocationNamesMap
, (void *)&key
);
774 if (uplname
!= NULL
) {
778 UnicodeString location
;
779 UnicodeString usCountryCode
;
780 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
);
781 if (!usCountryCode
.isEmpty()) {
782 char countryCode
[ULOC_COUNTRY_CAPACITY
];
783 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
784 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
785 countryCode
[ccLen
] = 0;
787 UnicodeString regionalGolden
;
788 fTimeZoneNames
->getReferenceZoneID(mzID
, countryCode
, regionalGolden
);
789 if (tzCanonicalID
== regionalGolden
) {
791 fLocaleDisplayNames
->regionDisplayName(countryCode
, location
);
793 // Otherwise, use exemplar city name
794 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
797 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
798 if (location
.isEmpty()) {
799 // This could happen when the time zone is not associated with a country,
800 // and its ID is not hierarchical, for example, CST6CDT.
801 // We use the canonical ID itself as the location for this case.
802 location
.setTo(tzCanonicalID
);
806 UErrorCode status
= U_ZERO_ERROR
;
808 fFallbackFormat
.format(location
, mzDisplayName
, name
, status
);
809 if (U_FAILURE(status
)) {
813 uplname
= fStringPool
.get(name
, status
);
814 if (U_SUCCESS(status
)) {
815 // Add the name to cache
816 PartialLocationKey
* cacheKey
= (PartialLocationKey
*)uprv_malloc(sizeof(PartialLocationKey
));
817 if (cacheKey
!= NULL
) {
818 cacheKey
->tzID
= key
.tzID
;
819 cacheKey
->mzID
= key
.mzID
;
820 cacheKey
->isLong
= key
.isLong
;
821 uhash_put(fPartialLocationNamesMap
, (void *)cacheKey
, (void *)uplname
, &status
);
822 if (U_FAILURE(status
)) {
825 // put the name to the local trie as well
826 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
827 if (nameinfo
!= NULL
) {
828 nameinfo
->type
= isLong
? UTZGNM_LONG
: UTZGNM_SHORT
;
829 nameinfo
->tzID
= key
.tzID
;
830 fGNamesTrie
.put(uplname
, nameinfo
, status
);
839 * This method updates the cache and must be called with a lock,
840 * except initializer.
843 TZGNCore::loadStrings(const UnicodeString
& tzCanonicalID
) {
844 // load the generic location name
845 getGenericLocationName(tzCanonicalID
);
847 // partial location names
848 UErrorCode status
= U_ZERO_ERROR
;
850 const UnicodeString
*mzID
;
851 UnicodeString goldenID
;
852 UnicodeString mzGenName
;
853 UTimeZoneNameType genNonLocTypes
[] = {
854 UTZNM_LONG_GENERIC
, UTZNM_SHORT_GENERIC
,
855 UTZNM_UNKNOWN
/*terminator*/
858 StringEnumeration
*mzIDs
= fTimeZoneNames
->getAvailableMetaZoneIDs(tzCanonicalID
, status
);
859 while ((mzID
= mzIDs
->snext(status
))) {
860 if (U_FAILURE(status
)) {
863 // if this time zone is not the golden zone of the meta zone,
864 // partial location name (such as "PT (Los Angeles)") might be
866 fTimeZoneNames
->getReferenceZoneID(*mzID
, fTargetRegion
, goldenID
);
867 if (tzCanonicalID
!= goldenID
) {
868 for (int32_t i
= 0; genNonLocTypes
[i
] != UTZNM_UNKNOWN
; i
++) {
869 fTimeZoneNames
->getMetaZoneDisplayName(*mzID
, genNonLocTypes
[i
], mzGenName
);
870 if (!mzGenName
.isEmpty()) {
871 // getPartialLocationName formats a name and put it into the trie
872 getPartialLocationName(tzCanonicalID
, *mzID
,
873 (genNonLocTypes
[i
] == UTZNM_LONG_GENERIC
), mzGenName
);
884 TZGNCore::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
885 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
886 timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
889 if (U_FAILURE(status
)) {
893 // Find matches in the TimeZoneNames first
894 TimeZoneNames::MatchInfoCollection
*tznamesMatches
= findTimeZoneNames(text
, start
, types
, status
);
895 if (U_FAILURE(status
)) {
899 int32_t bestMatchLen
= 0;
900 UTimeZoneFormatTimeType bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
901 UnicodeString bestMatchTzID
;
902 // UBool isLongStandard = FALSE; // workaround - see the comments below
903 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
905 if (tznamesMatches
!= NULL
) {
907 for (int32_t i
= 0; i
< tznamesMatches
->size(); i
++) {
908 int32_t len
= tznamesMatches
->getMatchLengthAt(i
);
909 if (len
> bestMatchLen
) {
911 if (!tznamesMatches
->getTimeZoneIDAt(i
, bestMatchTzID
)) {
912 // name for a meta zone
913 if (tznamesMatches
->getMetaZoneIDAt(i
, mzID
)) {
914 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, bestMatchTzID
);
917 UTimeZoneNameType nameType
= tznamesMatches
->getNameTypeAt(i
);
918 if (U_FAILURE(status
)) {
922 case UTZNM_LONG_STANDARD
:
923 // isLongStandard = TRUE;
924 case UTZNM_SHORT_STANDARD
: // this one is never used for generic, but just in case
925 isStandard
= TRUE
; // TODO: Remove this later, see the comments above.
926 bestMatchTimeType
= UTZFMT_TIME_TYPE_STANDARD
;
928 case UTZNM_LONG_DAYLIGHT
:
929 case UTZNM_SHORT_DAYLIGHT
: // this one is never used for generic, but just in case
930 bestMatchTimeType
= UTZFMT_TIME_TYPE_DAYLIGHT
;
933 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
937 delete tznamesMatches
;
938 if (U_FAILURE(status
)) {
942 if (bestMatchLen
== (text
.length() - start
)) {
945 //tzID.setTo(bestMatchTzID);
946 //timeType = bestMatchTimeType;
947 //return bestMatchLen;
949 // TODO Some time zone uses a same name for the long standard name
950 // and the location name. When the match is a long standard name,
951 // then we need to check if the name is same with the location name.
952 // This is probably a data error or a design bug.
954 if (!isLongStandard) {
955 tzID.setTo(bestMatchTzID);
956 timeType = bestMatchTimeType;
960 // TODO The deprecation of commonlyUsed flag introduced the name
961 // conflict not only for long standard names, but short standard names too.
962 // These short names (found in zh_Hant) should be gone once we clean
963 // up CLDR time zone display name data. Once the short name conflict
964 // problem (with location name) is resolved, we should change the condition
965 // below back to the original one above. -Yoshito (2011-09-14)
967 tzID
.setTo(bestMatchTzID
);
968 timeType
= bestMatchTimeType
;
974 // Find matches in the local trie
975 TimeZoneGenericNameMatchInfo
*localMatches
= findLocal(text
, start
, types
, status
);
976 if (U_FAILURE(status
)) {
979 if (localMatches
!= NULL
) {
980 for (int32_t i
= 0; i
< localMatches
->size(); i
++) {
981 int32_t len
= localMatches
->getMatchLength(i
);
983 // TODO See the above TODO. We use len >= bestMatchLen
984 // because of the long standard/location name collision
985 // problem. If it is also a location name, carrying
986 // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
987 // problem in SimpleDateFormat
988 if (len
>= bestMatchLen
) {
989 bestMatchLen
= localMatches
->getMatchLength(i
);
990 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
; // because generic
991 localMatches
->getTimeZoneID(i
, bestMatchTzID
);
997 if (bestMatchLen
> 0) {
998 timeType
= bestMatchTimeType
;
999 tzID
.setTo(bestMatchTzID
);
1001 return bestMatchLen
;
1004 TimeZoneGenericNameMatchInfo
*
1005 TZGNCore::findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1006 GNameSearchHandler
handler(types
);
1008 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
1012 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1014 umtx_unlock(&gLock
);
1016 if (U_FAILURE(status
)) {
1020 TimeZoneGenericNameMatchInfo
*gmatchInfo
= NULL
;
1023 UVector
*results
= handler
.getMatches(maxLen
);
1024 if (results
!= NULL
&& ((maxLen
== (text
.length() - start
)) || fGNamesTrieFullyLoaded
)) {
1026 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1027 if (gmatchInfo
== NULL
) {
1028 status
= U_MEMORY_ALLOCATION_ERROR
;
1035 if (results
!= NULL
) {
1039 // All names are not yet loaded into the local trie.
1040 // Load all available names into the trie. This could be very heavy.
1043 if (!fGNamesTrieFullyLoaded
) {
1044 StringEnumeration
*tzIDs
= TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL
, NULL
, NULL
, status
);
1045 if (U_SUCCESS(status
)) {
1046 const UnicodeString
*tzID
;
1047 while ((tzID
= tzIDs
->snext(status
))) {
1048 if (U_FAILURE(status
)) {
1051 nonConstThis
->loadStrings(*tzID
);
1054 if (tzIDs
!= NULL
) {
1058 if (U_SUCCESS(status
)) {
1059 nonConstThis
->fGNamesTrieFullyLoaded
= TRUE
;
1063 umtx_unlock(&gLock
);
1065 if (U_FAILURE(status
)) {
1072 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1074 umtx_unlock(&gLock
);
1076 results
= handler
.getMatches(maxLen
);
1077 if (results
!= NULL
&& maxLen
> 0) {
1078 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1079 if (gmatchInfo
== NULL
) {
1080 status
= U_MEMORY_ALLOCATION_ERROR
;
1089 TimeZoneNames::MatchInfoCollection
*
1090 TZGNCore::findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1091 // Check if the target name typs is really in the TimeZoneNames
1092 uint32_t nameTypes
= 0;
1093 if (types
& UTZGNM_LONG
) {
1094 nameTypes
|= (UTZNM_LONG_GENERIC
| UTZNM_LONG_STANDARD
);
1096 if (types
& UTZGNM_SHORT
) {
1097 nameTypes
|= (UTZNM_SHORT_GENERIC
| UTZNM_SHORT_STANDARD
);
1101 // Find matches in the TimeZoneNames
1102 return fTimeZoneNames
->find(text
, start
, nameTypes
, status
);
1108 typedef struct TZGNCoreRef
{
1114 // TZGNCore object cache handling
1115 static UMutex gTZGNLock
= U_MUTEX_INITIALIZER
;
1116 static UHashtable
*gTZGNCoreCache
= NULL
;
1117 static UBool gTZGNCoreCacheInitialized
= FALSE
;
1119 // Access count - incremented every time up to SWEEP_INTERVAL,
1121 static int32_t gAccessCount
= 0;
1123 // Interval for calling the cache sweep function - every 100 times
1124 #define SWEEP_INTERVAL 100
1126 // Cache expiration in millisecond. When a cached entry is no
1127 // longer referenced and exceeding this threshold since last
1128 // access time, then the cache entry will be deleted by the sweep
1129 // function. For now, 3 minutes.
1130 #define CACHE_EXPIRATION 180000.0
1134 * Cleanup callback func
1136 static UBool U_CALLCONV
tzgnCore_cleanup(void)
1138 if (gTZGNCoreCache
!= NULL
) {
1139 uhash_close(gTZGNCoreCache
);
1140 gTZGNCoreCache
= NULL
;
1142 gTZGNCoreCacheInitialized
= FALSE
;
1147 * Deleter for TZGNCoreRef
1149 static void U_CALLCONV
1150 deleteTZGNCoreRef(void *obj
) {
1151 icu::TZGNCoreRef
*entry
= (icu::TZGNCoreRef
*)obj
;
1152 delete (icu::TZGNCore
*) entry
->obj
;
1158 * Function used for removing unreferrenced cache entries exceeding
1159 * the expiration time. This function must be called with in the mutex
1162 static void sweepCache() {
1163 int32_t pos
= UHASH_FIRST
;
1164 const UHashElement
* elem
;
1165 double now
= (double)uprv_getUTCtime();
1167 while ((elem
= uhash_nextElement(gTZGNCoreCache
, &pos
))) {
1168 TZGNCoreRef
*entry
= (TZGNCoreRef
*)elem
->value
.pointer
;
1169 if (entry
->refCount
<= 0 && (now
- entry
->lastAccess
) > CACHE_EXPIRATION
) {
1170 // delete this entry
1171 uhash_removeElement(gTZGNCoreCache
, elem
);
1176 TimeZoneGenericNames::TimeZoneGenericNames()
1180 TimeZoneGenericNames::~TimeZoneGenericNames() {
1181 umtx_lock(&gTZGNLock
);
1183 U_ASSERT(fRef
->refCount
> 0);
1184 // Just decrement the reference count
1187 umtx_unlock(&gTZGNLock
);
1190 TimeZoneGenericNames
*
1191 TimeZoneGenericNames::createInstance(const Locale
& locale
, UErrorCode
& status
) {
1192 if (U_FAILURE(status
)) {
1195 TimeZoneGenericNames
* instance
= new TimeZoneGenericNames();
1196 if (instance
== NULL
) {
1197 status
= U_MEMORY_ALLOCATION_ERROR
;
1201 TZGNCoreRef
*cacheEntry
= NULL
;
1203 Mutex
lock(&gTZGNLock
);
1205 if (!gTZGNCoreCacheInitialized
) {
1206 // Create empty hashtable
1207 gTZGNCoreCache
= uhash_open(uhash_hashChars
, uhash_compareChars
, NULL
, &status
);
1208 if (U_SUCCESS(status
)) {
1209 uhash_setKeyDeleter(gTZGNCoreCache
, uprv_free
);
1210 uhash_setValueDeleter(gTZGNCoreCache
, deleteTZGNCoreRef
);
1211 gTZGNCoreCacheInitialized
= TRUE
;
1212 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES
, tzgnCore_cleanup
);
1215 if (U_FAILURE(status
)) {
1219 // Check the cache, if not available, create new one and cache
1220 const char *key
= locale
.getName();
1221 cacheEntry
= (TZGNCoreRef
*)uhash_get(gTZGNCoreCache
, key
);
1222 if (cacheEntry
== NULL
) {
1223 TZGNCore
*tzgnCore
= NULL
;
1224 char *newKey
= NULL
;
1226 tzgnCore
= new TZGNCore(locale
, status
);
1227 if (tzgnCore
== NULL
) {
1228 status
= U_MEMORY_ALLOCATION_ERROR
;
1230 if (U_SUCCESS(status
)) {
1231 newKey
= (char *)uprv_malloc(uprv_strlen(key
) + 1);
1232 if (newKey
== NULL
) {
1233 status
= U_MEMORY_ALLOCATION_ERROR
;
1235 uprv_strcpy(newKey
, key
);
1238 if (U_SUCCESS(status
)) {
1239 cacheEntry
= (TZGNCoreRef
*)uprv_malloc(sizeof(TZGNCoreRef
));
1240 if (cacheEntry
== NULL
) {
1241 status
= U_MEMORY_ALLOCATION_ERROR
;
1243 cacheEntry
->obj
= tzgnCore
;
1244 cacheEntry
->refCount
= 1;
1245 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1247 uhash_put(gTZGNCoreCache
, newKey
, cacheEntry
, &status
);
1250 if (U_FAILURE(status
)) {
1251 if (tzgnCore
!= NULL
) {
1254 if (newKey
!= NULL
) {
1257 if (cacheEntry
!= NULL
) {
1258 uprv_free(cacheEntry
);
1263 // Update the reference count
1264 cacheEntry
->refCount
++;
1265 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1268 if (gAccessCount
>= SWEEP_INTERVAL
) {
1273 } // End of mutex locked block
1275 if (cacheEntry
== NULL
) {
1280 instance
->fRef
= cacheEntry
;
1285 TimeZoneGenericNames::operator==(const TimeZoneGenericNames
& other
) const {
1286 // Just compare if the other object also use the same
1288 return fRef
== other
.fRef
;
1291 TimeZoneGenericNames
*
1292 TimeZoneGenericNames::clone() const {
1293 TimeZoneGenericNames
* other
= new TimeZoneGenericNames();
1295 umtx_lock(&gTZGNLock
);
1297 // Just increments the reference count
1301 umtx_unlock(&gTZGNLock
);
1307 TimeZoneGenericNames::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
1308 UDate date
, UnicodeString
& name
) const {
1309 return fRef
->obj
->getDisplayName(tz
, type
, date
, name
);
1313 TimeZoneGenericNames::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
1314 return fRef
->obj
->getGenericLocationName(tzCanonicalID
, name
);
1318 TimeZoneGenericNames::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
1319 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
1320 return fRef
->obj
->findBestMatch(text
, start
, types
, tzID
, timeType
, status
);