2 *******************************************************************************
3 * Copyright (C) 2011-2016, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 *******************************************************************************
8 #include "unicode/utypes.h"
10 #if !UCONFIG_NO_FORMATTING
14 #include "unicode/basictz.h"
15 #include "unicode/locdspnm.h"
16 #include "unicode/rbtz.h"
17 #include "unicode/simpleformatter.h"
18 #include "unicode/simpletz.h"
19 #include "unicode/vtzone.h"
30 #include "tznames_impl.h"
36 #define ZID_KEY_MAX 128
38 static const char gZoneStrings
[] = "zoneStrings";
40 static const char gRegionFormatTag
[] = "regionFormat";
41 static const char gFallbackFormatTag
[] = "fallbackFormat";
43 static const UChar gEmpty
[] = {0x00};
45 static const UChar gDefRegionPattern
[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}"
46 static const UChar gDefFallbackPattern
[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
48 static const double kDstCheckRange
= (double)184*U_MILLIS_PER_DAY
;
54 typedef struct PartialLocationKey
{
61 * Hash function for partial location name hash key
63 static int32_t U_CALLCONV
64 hashPartialLocationKey(const UHashTok key
) {
65 // <tzID>&<mzID>#[L|S]
66 PartialLocationKey
*p
= (PartialLocationKey
*)key
.pointer
;
67 UnicodeString
str(p
->tzID
);
68 str
.append((UChar
)0x26)
71 .append((UChar
)(p
->isLong
? 0x4C : 0x53));
72 return str
.hashCode();
76 * Comparer for partial location name hash key
78 static UBool U_CALLCONV
79 comparePartialLocationKey(const UHashTok key1
, const UHashTok key2
) {
80 PartialLocationKey
*p1
= (PartialLocationKey
*)key1
.pointer
;
81 PartialLocationKey
*p2
= (PartialLocationKey
*)key2
.pointer
;
86 if (p1
== NULL
|| p2
== NULL
) {
89 // We just check identity of tzID/mzID
90 return (p1
->tzID
== p2
->tzID
&& p1
->mzID
== p2
->mzID
&& p1
->isLong
== p2
->isLong
);
94 * Deleter for GNameInfo
96 static void U_CALLCONV
97 deleteGNameInfo(void *obj
) {
102 * GNameInfo stores zone name information in the local trie
104 typedef struct GNameInfo
{
105 UTimeZoneGenericNameType type
;
110 * GMatchInfo stores zone name match information used by find method
112 typedef struct GMatchInfo
{
113 const GNameInfo
* gnameInfo
;
115 UTimeZoneFormatTimeType timeType
;
120 // ---------------------------------------------------
121 // The class stores time zone generic name match information
122 // ---------------------------------------------------
123 class TimeZoneGenericNameMatchInfo
: public UMemory
{
125 TimeZoneGenericNameMatchInfo(UVector
* matches
);
126 ~TimeZoneGenericNameMatchInfo();
128 int32_t size() const;
129 UTimeZoneGenericNameType
getGenericNameType(int32_t index
) const;
130 int32_t getMatchLength(int32_t index
) const;
131 UnicodeString
& getTimeZoneID(int32_t index
, UnicodeString
& tzID
) const;
134 UVector
* fMatches
; // vector of MatchEntry
137 TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector
* matches
)
138 : fMatches(matches
) {
141 TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() {
142 if (fMatches
!= NULL
) {
148 TimeZoneGenericNameMatchInfo::size() const {
149 if (fMatches
== NULL
) {
152 return fMatches
->size();
155 UTimeZoneGenericNameType
156 TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index
) const {
157 GMatchInfo
*minfo
= (GMatchInfo
*)fMatches
->elementAt(index
);
159 return static_cast<UTimeZoneGenericNameType
>(minfo
->gnameInfo
->type
);
161 return UTZGNM_UNKNOWN
;
165 TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index
) const {
166 ZMatchInfo
*minfo
= (ZMatchInfo
*)fMatches
->elementAt(index
);
168 return minfo
->matchLength
;
174 TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index
, UnicodeString
& tzID
) const {
175 GMatchInfo
*minfo
= (GMatchInfo
*)fMatches
->elementAt(index
);
176 if (minfo
!= NULL
&& minfo
->gnameInfo
->tzID
!= NULL
) {
177 tzID
.setTo(TRUE
, minfo
->gnameInfo
->tzID
, -1);
184 // ---------------------------------------------------
185 // GNameSearchHandler
186 // ---------------------------------------------------
187 class GNameSearchHandler
: public TextTrieMapSearchResultHandler
{
189 GNameSearchHandler(uint32_t types
);
190 virtual ~GNameSearchHandler();
192 UBool
handleMatch(int32_t matchLength
, const CharacterNode
*node
, UErrorCode
&status
);
193 UVector
* getMatches(int32_t& maxMatchLen
);
198 int32_t fMaxMatchLen
;
201 GNameSearchHandler::GNameSearchHandler(uint32_t types
)
202 : fTypes(types
), fResults(NULL
), fMaxMatchLen(0) {
205 GNameSearchHandler::~GNameSearchHandler() {
206 if (fResults
!= NULL
) {
212 GNameSearchHandler::handleMatch(int32_t matchLength
, const CharacterNode
*node
, UErrorCode
&status
) {
213 if (U_FAILURE(status
)) {
216 if (node
->hasValues()) {
217 int32_t valuesCount
= node
->countValues();
218 for (int32_t i
= 0; i
< valuesCount
; i
++) {
219 GNameInfo
*nameinfo
= (ZNameInfo
*)node
->getValue(i
);
220 if (nameinfo
== NULL
) {
223 if ((nameinfo
->type
& fTypes
) != 0) {
224 // matches a requested type
225 if (fResults
== NULL
) {
226 fResults
= new UVector(uprv_free
, NULL
, status
);
227 if (fResults
== NULL
) {
228 status
= U_MEMORY_ALLOCATION_ERROR
;
231 if (U_SUCCESS(status
)) {
232 U_ASSERT(fResults
!= NULL
);
233 GMatchInfo
*gmatch
= (GMatchInfo
*)uprv_malloc(sizeof(GMatchInfo
));
234 if (gmatch
== NULL
) {
235 status
= U_MEMORY_ALLOCATION_ERROR
;
237 // add the match to the vector
238 gmatch
->gnameInfo
= nameinfo
;
239 gmatch
->matchLength
= matchLength
;
240 gmatch
->timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
241 fResults
->addElement(gmatch
, status
);
242 if (U_FAILURE(status
)) {
245 if (matchLength
> fMaxMatchLen
) {
246 fMaxMatchLen
= matchLength
;
258 GNameSearchHandler::getMatches(int32_t& maxMatchLen
) {
259 // give the ownership to the caller
260 UVector
*results
= fResults
;
261 maxMatchLen
= fMaxMatchLen
;
269 static UMutex gLock
= U_MUTEX_INITIALIZER
;
271 class TZGNCore
: public UMemory
{
273 TZGNCore(const Locale
& locale
, UErrorCode
& status
);
276 UnicodeString
& getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
277 UDate date
, UnicodeString
& name
) const;
279 UnicodeString
& getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const;
281 int32_t findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
282 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const;
286 const TimeZoneNames
* fTimeZoneNames
;
287 UHashtable
* fLocationNamesMap
;
288 UHashtable
* fPartialLocationNamesMap
;
290 SimpleFormatter fRegionFormat
;
291 SimpleFormatter fFallbackFormat
;
293 LocaleDisplayNames
* fLocaleDisplayNames
;
294 ZNStringPool fStringPool
;
296 TextTrieMap fGNamesTrie
;
297 UBool fGNamesTrieFullyLoaded
;
299 char fTargetRegion
[ULOC_COUNTRY_CAPACITY
];
301 void initialize(const Locale
& locale
, UErrorCode
& status
);
304 void loadStrings(const UnicodeString
& tzCanonicalID
);
306 const UChar
* getGenericLocationName(const UnicodeString
& tzCanonicalID
);
308 UnicodeString
& formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
309 UDate date
, UnicodeString
& name
) const;
311 UnicodeString
& getPartialLocationName(const UnicodeString
& tzCanonicalID
,
312 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
313 UnicodeString
& name
) const;
315 const UChar
* getPartialLocationName(const UnicodeString
& tzCanonicalID
,
316 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
);
318 TimeZoneGenericNameMatchInfo
* findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const;
320 TimeZoneNames::MatchInfoCollection
* findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const;
324 // ---------------------------------------------------
325 // TZGNCore - core implmentation of TimeZoneGenericNames
327 // TimeZoneGenericNames is parallel to TimeZoneNames,
328 // but handles run-time generated time zone names.
329 // This is the main part of this module.
330 // ---------------------------------------------------
331 TZGNCore::TZGNCore(const Locale
& locale
, UErrorCode
& status
)
333 fTimeZoneNames(NULL
),
334 fLocationNamesMap(NULL
),
335 fPartialLocationNamesMap(NULL
),
336 fLocaleDisplayNames(NULL
),
338 fGNamesTrie(TRUE
, deleteGNameInfo
),
339 fGNamesTrieFullyLoaded(FALSE
) {
340 initialize(locale
, status
);
343 TZGNCore::~TZGNCore() {
348 TZGNCore::initialize(const Locale
& locale
, UErrorCode
& status
) {
349 if (U_FAILURE(status
)) {
354 fTimeZoneNames
= TimeZoneNames::createInstance(locale
, status
);
355 if (U_FAILURE(status
)) {
359 // Initialize format patterns
360 UnicodeString
rpat(TRUE
, gDefRegionPattern
, -1);
361 UnicodeString
fpat(TRUE
, gDefFallbackPattern
, -1);
363 UErrorCode tmpsts
= U_ZERO_ERROR
; // OK with fallback warning..
364 UResourceBundle
*zoneStrings
= ures_open(U_ICUDATA_ZONE
, locale
.getName(), &tmpsts
);
365 zoneStrings
= ures_getByKeyWithFallback(zoneStrings
, gZoneStrings
, zoneStrings
, &tmpsts
);
367 if (U_SUCCESS(tmpsts
)) {
368 const UChar
*regionPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gRegionFormatTag
, NULL
, &tmpsts
);
369 if (U_SUCCESS(tmpsts
) && u_strlen(regionPattern
) > 0) {
370 rpat
.setTo(regionPattern
, -1);
372 tmpsts
= U_ZERO_ERROR
;
373 const UChar
*fallbackPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gFallbackFormatTag
, NULL
, &tmpsts
);
374 if (U_SUCCESS(tmpsts
) && u_strlen(fallbackPattern
) > 0) {
375 fpat
.setTo(fallbackPattern
, -1);
378 ures_close(zoneStrings
);
380 fRegionFormat
.applyPatternMinMaxArguments(rpat
, 1, 1, status
);
381 fFallbackFormat
.applyPatternMinMaxArguments(fpat
, 2, 2, status
);
382 if (U_FAILURE(status
)) {
387 // locale display names
388 fLocaleDisplayNames
= LocaleDisplayNames::createInstance(locale
);
390 // hash table for names - no key/value deleters
391 fLocationNamesMap
= uhash_open(uhash_hashUChars
, uhash_compareUChars
, NULL
, &status
);
392 if (U_FAILURE(status
)) {
397 fPartialLocationNamesMap
= uhash_open(hashPartialLocationKey
, comparePartialLocationKey
, NULL
, &status
);
398 if (U_FAILURE(status
)) {
402 uhash_setKeyDeleter(fPartialLocationNamesMap
, uprv_free
);
406 const char* region
= fLocale
.getCountry();
407 int32_t regionLen
= uprv_strlen(region
);
408 if (regionLen
== 0) {
409 char loc
[ULOC_FULLNAME_CAPACITY
];
410 uloc_addLikelySubtags(fLocale
.getName(), loc
, sizeof(loc
), &status
);
412 regionLen
= uloc_getCountry(loc
, fTargetRegion
, sizeof(fTargetRegion
), &status
);
413 if (U_SUCCESS(status
)) {
414 fTargetRegion
[regionLen
] = 0;
419 } else if (regionLen
< (int32_t)sizeof(fTargetRegion
)) {
420 uprv_strcpy(fTargetRegion
, region
);
422 fTargetRegion
[0] = 0;
425 // preload generic names for the default zone
426 TimeZone
*tz
= TimeZone::createDefault();
427 const UChar
*tzID
= ZoneMeta::getCanonicalCLDRID(*tz
);
429 loadStrings(UnicodeString(TRUE
, tzID
, -1));
435 TZGNCore::cleanup() {
436 if (fLocaleDisplayNames
!= NULL
) {
437 delete fLocaleDisplayNames
;
439 if (fTimeZoneNames
!= NULL
) {
440 delete fTimeZoneNames
;
443 uhash_close(fLocationNamesMap
);
444 uhash_close(fPartialLocationNamesMap
);
449 TZGNCore::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
452 case UTZGNM_LOCATION
:
454 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
455 if (tzCanonicalID
!= NULL
) {
456 getGenericLocationName(UnicodeString(TRUE
, tzCanonicalID
, -1), name
);
462 formatGenericNonLocationName(tz
, type
, date
, name
);
463 if (name
.isEmpty()) {
464 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
465 if (tzCanonicalID
!= NULL
) {
466 getGenericLocationName(UnicodeString(TRUE
, tzCanonicalID
, -1), name
);
477 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
478 if (tzCanonicalID
.isEmpty()) {
483 const UChar
*locname
= NULL
;
484 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
487 locname
= nonConstThis
->getGenericLocationName(tzCanonicalID
);
491 if (locname
== NULL
) {
494 name
.setTo(locname
, u_strlen(locname
));
501 * This method updates the cache and must be called with a lock
504 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
) {
505 U_ASSERT(!tzCanonicalID
.isEmpty());
506 if (tzCanonicalID
.length() > ZID_KEY_MAX
) {
510 UErrorCode status
= U_ZERO_ERROR
;
511 UChar tzIDKey
[ZID_KEY_MAX
+ 1];
512 int32_t tzIDKeyLen
= tzCanonicalID
.extract(tzIDKey
, ZID_KEY_MAX
+ 1, status
);
513 U_ASSERT(status
== U_ZERO_ERROR
); // already checked length above
514 tzIDKey
[tzIDKeyLen
] = 0;
516 const UChar
*locname
= (const UChar
*)uhash_get(fLocationNamesMap
, tzIDKey
);
518 if (locname
!= NULL
) {
519 // gEmpty indicate the name is not available
520 if (locname
== gEmpty
) {
526 // Construct location name
528 UnicodeString usCountryCode
;
529 UBool isPrimary
= FALSE
;
531 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
, &isPrimary
);
533 if (!usCountryCode
.isEmpty()) {
535 // If this is the primary zone in the country, use the country name.
536 char countryCode
[ULOC_COUNTRY_CAPACITY
];
537 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
538 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
539 countryCode
[ccLen
] = 0;
541 UnicodeString country
;
542 fLocaleDisplayNames
->regionDisplayName(countryCode
, country
);
543 fRegionFormat
.format(country
, name
, status
);
545 // If this is not the primary zone in the country,
546 // use the exemplar city name.
548 // getExemplarLocationName should retur non-empty string
549 // if the time zone is associated with a region
552 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, city
);
553 fRegionFormat
.format(city
, name
, status
);
555 if (U_FAILURE(status
)) {
560 locname
= name
.isEmpty() ? NULL
: fStringPool
.get(name
, status
);
561 if (U_SUCCESS(status
)) {
563 const UChar
* cacheID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
564 U_ASSERT(cacheID
!= NULL
);
565 if (locname
== NULL
) {
566 // gEmpty to indicate - no location name available
567 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)gEmpty
, &status
);
569 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)locname
, &status
);
570 if (U_FAILURE(status
)) {
573 // put the name info into the trie
574 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
575 if (nameinfo
!= NULL
) {
576 nameinfo
->type
= UTZGNM_LOCATION
;
577 nameinfo
->tzID
= cacheID
;
578 fGNamesTrie
.put(locname
, nameinfo
, status
);
588 TZGNCore::formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
589 U_ASSERT(type
== UTZGNM_LONG
|| type
== UTZGNM_SHORT
);
592 const UChar
* uID
= ZoneMeta::getCanonicalCLDRID(tz
);
597 UnicodeString
tzID(TRUE
, uID
, -1);
599 // Try to get a name from time zone first
600 UTimeZoneNameType nameType
= (type
== UTZGNM_LONG
) ? UTZNM_LONG_GENERIC
: UTZNM_SHORT_GENERIC
;
601 fTimeZoneNames
->getTimeZoneDisplayName(tzID
, nameType
, name
);
603 if (!name
.isEmpty()) {
609 UnicodeString
mzID(mzIDBuf
, 0, UPRV_LENGTHOF(mzIDBuf
));
610 fTimeZoneNames
->getMetaZoneID(tzID
, date
, mzID
);
611 if (!mzID
.isEmpty()) {
612 UErrorCode status
= U_ZERO_ERROR
;
613 UBool useStandard
= FALSE
;
615 UChar tmpNameBuf
[64];
617 tz
.getOffset(date
, FALSE
, raw
, sav
, status
);
618 if (U_FAILURE(status
)) {
625 TimeZone
*tmptz
= tz
.clone();
626 // Check if the zone actually uses daylight saving time around the time
627 BasicTimeZone
*btz
= NULL
;
628 if (dynamic_cast<OlsonTimeZone
*>(tmptz
) != NULL
629 || dynamic_cast<SimpleTimeZone
*>(tmptz
) != NULL
630 || dynamic_cast<RuleBasedTimeZone
*>(tmptz
) != NULL
631 || dynamic_cast<VTimeZone
*>(tmptz
) != NULL
) {
632 btz
= (BasicTimeZone
*)tmptz
;
636 TimeZoneTransition before
;
637 UBool beforTrs
= btz
->getPreviousTransition(date
, TRUE
, before
);
639 && (date
- before
.getTime() < kDstCheckRange
)
640 && before
.getFrom()->getDSTSavings() != 0) {
643 TimeZoneTransition after
;
644 UBool afterTrs
= btz
->getNextTransition(date
, FALSE
, after
);
646 && (after
.getTime() - date
< kDstCheckRange
)
647 && after
.getTo()->getDSTSavings() != 0) {
652 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
653 // We may get a wrong answer in edge case, but it should practically work OK.
654 tmptz
->getOffset(date
- kDstCheckRange
, FALSE
, raw
, sav
, status
);
658 tmptz
->getOffset(date
+ kDstCheckRange
, FALSE
, raw
, sav
, status
);
663 if (U_FAILURE(status
)) {
671 UTimeZoneNameType stdNameType
= (nameType
== UTZNM_LONG_GENERIC
)
672 ? UTZNM_LONG_STANDARD
: UTZNM_SHORT_STANDARD
;
673 UnicodeString
stdName(tmpNameBuf
, 0, UPRV_LENGTHOF(tmpNameBuf
));
674 fTimeZoneNames
->getDisplayName(tzID
, stdNameType
, date
, stdName
);
675 if (!stdName
.isEmpty()) {
678 // TODO: revisit this issue later
679 // In CLDR, a same display name is used for both generic and standard
680 // for some meta zones in some locales. This looks like a data bugs.
681 // For now, we check if the standard name is different from its generic
683 UChar genNameBuf
[64];
684 UnicodeString
mzGenericName(genNameBuf
, 0, UPRV_LENGTHOF(genNameBuf
));
685 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzGenericName
);
686 if (stdName
.caseCompare(mzGenericName
, 0) == 0) {
691 if (name
.isEmpty()) {
692 // Get a name from meta zone
693 UnicodeString
mzName(tmpNameBuf
, 0, UPRV_LENGTHOF(tmpNameBuf
));
694 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzName
);
695 if (!mzName
.isEmpty()) {
696 // Check if we need to use a partial location format.
697 // This check is done by comparing offset with the meta zone's
698 // golden zone at the given date.
700 UnicodeString
goldenID(idBuf
, 0, UPRV_LENGTHOF(idBuf
));
701 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, goldenID
);
702 if (!goldenID
.isEmpty() && goldenID
!= tzID
) {
703 TimeZone
*goldenZone
= TimeZone::createTimeZone(goldenID
);
706 // Check offset in the golden zone with wall time.
707 // With getOffset(date, false, offsets1),
708 // you may get incorrect results because of time overlap at DST->STD
710 goldenZone
->getOffset(date
+ raw
+ sav
, TRUE
, raw1
, sav1
, status
);
712 if (U_SUCCESS(status
)) {
713 if (raw
!= raw1
|| sav
!= sav1
) {
714 // Now we need to use a partial location format
715 getPartialLocationName(tzID
, mzID
, (nameType
== UTZNM_LONG_GENERIC
), mzName
, name
);
730 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
731 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
732 UnicodeString
& name
) const {
734 if (tzCanonicalID
.isEmpty() || mzID
.isEmpty() || mzDisplayName
.isEmpty()) {
738 const UChar
*uplname
= NULL
;
739 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
742 uplname
= nonConstThis
->getPartialLocationName(tzCanonicalID
, mzID
, isLong
, mzDisplayName
);
746 if (uplname
== NULL
) {
749 name
.setTo(TRUE
, uplname
, -1);
755 * This method updates the cache and must be called with a lock
758 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
759 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
) {
760 U_ASSERT(!tzCanonicalID
.isEmpty());
761 U_ASSERT(!mzID
.isEmpty());
762 U_ASSERT(!mzDisplayName
.isEmpty());
764 PartialLocationKey key
;
765 key
.tzID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
766 key
.mzID
= ZoneMeta::findMetaZoneID(mzID
);
768 U_ASSERT(key
.tzID
!= NULL
&& key
.mzID
!= NULL
);
770 const UChar
* uplname
= (const UChar
*)uhash_get(fPartialLocationNamesMap
, (void *)&key
);
771 if (uplname
!= NULL
) {
775 UnicodeString location
;
776 UnicodeString usCountryCode
;
777 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
);
778 if (!usCountryCode
.isEmpty()) {
779 char countryCode
[ULOC_COUNTRY_CAPACITY
];
780 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
781 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
782 countryCode
[ccLen
] = 0;
784 UnicodeString regionalGolden
;
785 fTimeZoneNames
->getReferenceZoneID(mzID
, countryCode
, regionalGolden
);
786 if (tzCanonicalID
== regionalGolden
) {
788 fLocaleDisplayNames
->regionDisplayName(countryCode
, location
);
790 // Otherwise, use exemplar city name
791 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
794 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
795 if (location
.isEmpty()) {
796 // This could happen when the time zone is not associated with a country,
797 // and its ID is not hierarchical, for example, CST6CDT.
798 // We use the canonical ID itself as the location for this case.
799 location
.setTo(tzCanonicalID
);
803 UErrorCode status
= U_ZERO_ERROR
;
805 fFallbackFormat
.format(location
, mzDisplayName
, name
, status
);
806 if (U_FAILURE(status
)) {
810 uplname
= fStringPool
.get(name
, status
);
811 if (U_SUCCESS(status
)) {
812 // Add the name to cache
813 PartialLocationKey
* cacheKey
= (PartialLocationKey
*)uprv_malloc(sizeof(PartialLocationKey
));
814 if (cacheKey
!= NULL
) {
815 cacheKey
->tzID
= key
.tzID
;
816 cacheKey
->mzID
= key
.mzID
;
817 cacheKey
->isLong
= key
.isLong
;
818 uhash_put(fPartialLocationNamesMap
, (void *)cacheKey
, (void *)uplname
, &status
);
819 if (U_FAILURE(status
)) {
822 // put the name to the local trie as well
823 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
824 if (nameinfo
!= NULL
) {
825 nameinfo
->type
= isLong
? UTZGNM_LONG
: UTZGNM_SHORT
;
826 nameinfo
->tzID
= key
.tzID
;
827 fGNamesTrie
.put(uplname
, nameinfo
, status
);
836 * This method updates the cache and must be called with a lock,
837 * except initializer.
840 TZGNCore::loadStrings(const UnicodeString
& tzCanonicalID
) {
841 // load the generic location name
842 getGenericLocationName(tzCanonicalID
);
844 // partial location names
845 UErrorCode status
= U_ZERO_ERROR
;
847 const UnicodeString
*mzID
;
848 UnicodeString goldenID
;
849 UnicodeString mzGenName
;
850 UTimeZoneNameType genNonLocTypes
[] = {
851 UTZNM_LONG_GENERIC
, UTZNM_SHORT_GENERIC
,
852 UTZNM_UNKNOWN
/*terminator*/
855 StringEnumeration
*mzIDs
= fTimeZoneNames
->getAvailableMetaZoneIDs(tzCanonicalID
, status
);
856 while ((mzID
= mzIDs
->snext(status
))) {
857 if (U_FAILURE(status
)) {
860 // if this time zone is not the golden zone of the meta zone,
861 // partial location name (such as "PT (Los Angeles)") might be
863 fTimeZoneNames
->getReferenceZoneID(*mzID
, fTargetRegion
, goldenID
);
864 if (tzCanonicalID
!= goldenID
) {
865 for (int32_t i
= 0; genNonLocTypes
[i
] != UTZNM_UNKNOWN
; i
++) {
866 fTimeZoneNames
->getMetaZoneDisplayName(*mzID
, genNonLocTypes
[i
], mzGenName
);
867 if (!mzGenName
.isEmpty()) {
868 // getPartialLocationName formats a name and put it into the trie
869 getPartialLocationName(tzCanonicalID
, *mzID
,
870 (genNonLocTypes
[i
] == UTZNM_LONG_GENERIC
), mzGenName
);
881 TZGNCore::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
882 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
883 timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
886 if (U_FAILURE(status
)) {
890 // Find matches in the TimeZoneNames first
891 TimeZoneNames::MatchInfoCollection
*tznamesMatches
= findTimeZoneNames(text
, start
, types
, status
);
892 if (U_FAILURE(status
)) {
896 int32_t bestMatchLen
= 0;
897 UTimeZoneFormatTimeType bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
898 UnicodeString bestMatchTzID
;
899 // UBool isLongStandard = FALSE; // workaround - see the comments below
900 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
902 if (tznamesMatches
!= NULL
) {
904 for (int32_t i
= 0; i
< tznamesMatches
->size(); i
++) {
905 int32_t len
= tznamesMatches
->getMatchLengthAt(i
);
906 if (len
> bestMatchLen
) {
908 if (!tznamesMatches
->getTimeZoneIDAt(i
, bestMatchTzID
)) {
909 // name for a meta zone
910 if (tznamesMatches
->getMetaZoneIDAt(i
, mzID
)) {
911 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, bestMatchTzID
);
914 UTimeZoneNameType nameType
= tznamesMatches
->getNameTypeAt(i
);
915 if (U_FAILURE(status
)) {
919 case UTZNM_LONG_STANDARD
:
920 // isLongStandard = TRUE;
921 case UTZNM_SHORT_STANDARD
: // this one is never used for generic, but just in case
922 isStandard
= TRUE
; // TODO: Remove this later, see the comments above.
923 bestMatchTimeType
= UTZFMT_TIME_TYPE_STANDARD
;
925 case UTZNM_LONG_DAYLIGHT
:
926 case UTZNM_SHORT_DAYLIGHT
: // this one is never used for generic, but just in case
927 bestMatchTimeType
= UTZFMT_TIME_TYPE_DAYLIGHT
;
930 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
934 delete tznamesMatches
;
935 if (U_FAILURE(status
)) {
939 if (bestMatchLen
== (text
.length() - start
)) {
942 //tzID.setTo(bestMatchTzID);
943 //timeType = bestMatchTimeType;
944 //return bestMatchLen;
946 // TODO Some time zone uses a same name for the long standard name
947 // and the location name. When the match is a long standard name,
948 // then we need to check if the name is same with the location name.
949 // This is probably a data error or a design bug.
951 if (!isLongStandard) {
952 tzID.setTo(bestMatchTzID);
953 timeType = bestMatchTimeType;
957 // TODO The deprecation of commonlyUsed flag introduced the name
958 // conflict not only for long standard names, but short standard names too.
959 // These short names (found in zh_Hant) should be gone once we clean
960 // up CLDR time zone display name data. Once the short name conflict
961 // problem (with location name) is resolved, we should change the condition
962 // below back to the original one above. -Yoshito (2011-09-14)
964 tzID
.setTo(bestMatchTzID
);
965 timeType
= bestMatchTimeType
;
971 // Find matches in the local trie
972 TimeZoneGenericNameMatchInfo
*localMatches
= findLocal(text
, start
, types
, status
);
973 if (U_FAILURE(status
)) {
976 if (localMatches
!= NULL
) {
977 for (int32_t i
= 0; i
< localMatches
->size(); i
++) {
978 int32_t len
= localMatches
->getMatchLength(i
);
980 // TODO See the above TODO. We use len >= bestMatchLen
981 // because of the long standard/location name collision
982 // problem. If it is also a location name, carrying
983 // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
984 // problem in SimpleDateFormat
985 if (len
>= bestMatchLen
) {
986 bestMatchLen
= localMatches
->getMatchLength(i
);
987 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
; // because generic
988 localMatches
->getTimeZoneID(i
, bestMatchTzID
);
994 if (bestMatchLen
> 0) {
995 timeType
= bestMatchTimeType
;
996 tzID
.setTo(bestMatchTzID
);
1001 TimeZoneGenericNameMatchInfo
*
1002 TZGNCore::findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1003 GNameSearchHandler
handler(types
);
1005 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
1009 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1011 umtx_unlock(&gLock
);
1013 if (U_FAILURE(status
)) {
1017 TimeZoneGenericNameMatchInfo
*gmatchInfo
= NULL
;
1020 UVector
*results
= handler
.getMatches(maxLen
);
1021 if (results
!= NULL
&& ((maxLen
== (text
.length() - start
)) || fGNamesTrieFullyLoaded
)) {
1023 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1024 if (gmatchInfo
== NULL
) {
1025 status
= U_MEMORY_ALLOCATION_ERROR
;
1032 if (results
!= NULL
) {
1036 // All names are not yet loaded into the local trie.
1037 // Load all available names into the trie. This could be very heavy.
1040 if (!fGNamesTrieFullyLoaded
) {
1041 StringEnumeration
*tzIDs
= TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL
, NULL
, NULL
, status
);
1042 if (U_SUCCESS(status
)) {
1043 const UnicodeString
*tzID
;
1044 while ((tzID
= tzIDs
->snext(status
))) {
1045 if (U_FAILURE(status
)) {
1048 nonConstThis
->loadStrings(*tzID
);
1051 if (tzIDs
!= NULL
) {
1055 if (U_SUCCESS(status
)) {
1056 nonConstThis
->fGNamesTrieFullyLoaded
= TRUE
;
1060 umtx_unlock(&gLock
);
1062 if (U_FAILURE(status
)) {
1069 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1071 umtx_unlock(&gLock
);
1073 results
= handler
.getMatches(maxLen
);
1074 if (results
!= NULL
&& maxLen
> 0) {
1075 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1076 if (gmatchInfo
== NULL
) {
1077 status
= U_MEMORY_ALLOCATION_ERROR
;
1086 TimeZoneNames::MatchInfoCollection
*
1087 TZGNCore::findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1088 // Check if the target name typs is really in the TimeZoneNames
1089 uint32_t nameTypes
= 0;
1090 if (types
& UTZGNM_LONG
) {
1091 nameTypes
|= (UTZNM_LONG_GENERIC
| UTZNM_LONG_STANDARD
);
1093 if (types
& UTZGNM_SHORT
) {
1094 nameTypes
|= (UTZNM_SHORT_GENERIC
| UTZNM_SHORT_STANDARD
);
1098 // Find matches in the TimeZoneNames
1099 return fTimeZoneNames
->find(text
, start
, nameTypes
, status
);
1105 typedef struct TZGNCoreRef
{
1111 // TZGNCore object cache handling
1112 static UMutex gTZGNLock
= U_MUTEX_INITIALIZER
;
1113 static UHashtable
*gTZGNCoreCache
= NULL
;
1114 static UBool gTZGNCoreCacheInitialized
= FALSE
;
1116 // Access count - incremented every time up to SWEEP_INTERVAL,
1118 static int32_t gAccessCount
= 0;
1120 // Interval for calling the cache sweep function - every 100 times
1121 #define SWEEP_INTERVAL 100
1123 // Cache expiration in millisecond. When a cached entry is no
1124 // longer referenced and exceeding this threshold since last
1125 // access time, then the cache entry will be deleted by the sweep
1126 // function. For now, 3 minutes.
1127 #define CACHE_EXPIRATION 180000.0
1131 * Cleanup callback func
1133 static UBool U_CALLCONV
tzgnCore_cleanup(void)
1135 if (gTZGNCoreCache
!= NULL
) {
1136 uhash_close(gTZGNCoreCache
);
1137 gTZGNCoreCache
= NULL
;
1139 gTZGNCoreCacheInitialized
= FALSE
;
1144 * Deleter for TZGNCoreRef
1146 static void U_CALLCONV
1147 deleteTZGNCoreRef(void *obj
) {
1148 icu::TZGNCoreRef
*entry
= (icu::TZGNCoreRef
*)obj
;
1149 delete (icu::TZGNCore
*) entry
->obj
;
1155 * Function used for removing unreferrenced cache entries exceeding
1156 * the expiration time. This function must be called with in the mutex
1159 static void sweepCache() {
1160 int32_t pos
= UHASH_FIRST
;
1161 const UHashElement
* elem
;
1162 double now
= (double)uprv_getUTCtime();
1164 while ((elem
= uhash_nextElement(gTZGNCoreCache
, &pos
))) {
1165 TZGNCoreRef
*entry
= (TZGNCoreRef
*)elem
->value
.pointer
;
1166 if (entry
->refCount
<= 0 && (now
- entry
->lastAccess
) > CACHE_EXPIRATION
) {
1167 // delete this entry
1168 uhash_removeElement(gTZGNCoreCache
, elem
);
1173 TimeZoneGenericNames::TimeZoneGenericNames()
1177 TimeZoneGenericNames::~TimeZoneGenericNames() {
1178 umtx_lock(&gTZGNLock
);
1180 U_ASSERT(fRef
->refCount
> 0);
1181 // Just decrement the reference count
1184 umtx_unlock(&gTZGNLock
);
1187 TimeZoneGenericNames
*
1188 TimeZoneGenericNames::createInstance(const Locale
& locale
, UErrorCode
& status
) {
1189 if (U_FAILURE(status
)) {
1192 TimeZoneGenericNames
* instance
= new TimeZoneGenericNames();
1193 if (instance
== NULL
) {
1194 status
= U_MEMORY_ALLOCATION_ERROR
;
1198 TZGNCoreRef
*cacheEntry
= NULL
;
1200 Mutex
lock(&gTZGNLock
);
1202 if (!gTZGNCoreCacheInitialized
) {
1203 // Create empty hashtable
1204 gTZGNCoreCache
= uhash_open(uhash_hashChars
, uhash_compareChars
, NULL
, &status
);
1205 if (U_SUCCESS(status
)) {
1206 uhash_setKeyDeleter(gTZGNCoreCache
, uprv_free
);
1207 uhash_setValueDeleter(gTZGNCoreCache
, deleteTZGNCoreRef
);
1208 gTZGNCoreCacheInitialized
= TRUE
;
1209 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES
, tzgnCore_cleanup
);
1212 if (U_FAILURE(status
)) {
1216 // Check the cache, if not available, create new one and cache
1217 const char *key
= locale
.getName();
1218 cacheEntry
= (TZGNCoreRef
*)uhash_get(gTZGNCoreCache
, key
);
1219 if (cacheEntry
== NULL
) {
1220 TZGNCore
*tzgnCore
= NULL
;
1221 char *newKey
= NULL
;
1223 tzgnCore
= new TZGNCore(locale
, status
);
1224 if (tzgnCore
== NULL
) {
1225 status
= U_MEMORY_ALLOCATION_ERROR
;
1227 if (U_SUCCESS(status
)) {
1228 newKey
= (char *)uprv_malloc(uprv_strlen(key
) + 1);
1229 if (newKey
== NULL
) {
1230 status
= U_MEMORY_ALLOCATION_ERROR
;
1232 uprv_strcpy(newKey
, key
);
1235 if (U_SUCCESS(status
)) {
1236 cacheEntry
= (TZGNCoreRef
*)uprv_malloc(sizeof(TZGNCoreRef
));
1237 if (cacheEntry
== NULL
) {
1238 status
= U_MEMORY_ALLOCATION_ERROR
;
1240 cacheEntry
->obj
= tzgnCore
;
1241 cacheEntry
->refCount
= 1;
1242 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1244 uhash_put(gTZGNCoreCache
, newKey
, cacheEntry
, &status
);
1247 if (U_FAILURE(status
)) {
1248 if (tzgnCore
!= NULL
) {
1251 if (newKey
!= NULL
) {
1254 if (cacheEntry
!= NULL
) {
1255 uprv_free(cacheEntry
);
1260 // Update the reference count
1261 cacheEntry
->refCount
++;
1262 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1265 if (gAccessCount
>= SWEEP_INTERVAL
) {
1270 } // End of mutex locked block
1272 if (cacheEntry
== NULL
) {
1277 instance
->fRef
= cacheEntry
;
1282 TimeZoneGenericNames::operator==(const TimeZoneGenericNames
& other
) const {
1283 // Just compare if the other object also use the same
1285 return fRef
== other
.fRef
;
1288 TimeZoneGenericNames
*
1289 TimeZoneGenericNames::clone() const {
1290 TimeZoneGenericNames
* other
= new TimeZoneGenericNames();
1292 umtx_lock(&gTZGNLock
);
1294 // Just increments the reference count
1298 umtx_unlock(&gTZGNLock
);
1304 TimeZoneGenericNames::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
1305 UDate date
, UnicodeString
& name
) const {
1306 return fRef
->obj
->getDisplayName(tz
, type
, date
, name
);
1310 TimeZoneGenericNames::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
1311 return fRef
->obj
->getGenericLocationName(tzCanonicalID
, name
);
1315 TimeZoneGenericNames::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
1316 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
1317 return fRef
->obj
->findBestMatch(text
, start
, types
, tzID
, timeType
, status
);