2 *******************************************************************************
3 * Copyright (C) 2011-2013, 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/msgfmt.h"
17 #include "unicode/rbtz.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 MessageFormat
* fRegionFormat
;
291 MessageFormat
* 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
),
337 fFallbackFormat(NULL
),
338 fLocaleDisplayNames(NULL
),
340 fGNamesTrie(TRUE
, deleteGNameInfo
),
341 fGNamesTrieFullyLoaded(FALSE
) {
342 initialize(locale
, status
);
345 TZGNCore::~TZGNCore() {
350 TZGNCore::initialize(const Locale
& locale
, UErrorCode
& status
) {
351 if (U_FAILURE(status
)) {
356 fTimeZoneNames
= TimeZoneNames::createInstance(locale
, status
);
357 if (U_FAILURE(status
)) {
361 // Initialize format patterns
362 UnicodeString
rpat(TRUE
, gDefRegionPattern
, -1);
363 UnicodeString
fpat(TRUE
, gDefFallbackPattern
, -1);
365 UErrorCode tmpsts
= U_ZERO_ERROR
; // OK with fallback warning..
366 UResourceBundle
*zoneStrings
= ures_open(U_ICUDATA_ZONE
, locale
.getName(), &tmpsts
);
367 zoneStrings
= ures_getByKeyWithFallback(zoneStrings
, gZoneStrings
, zoneStrings
, &tmpsts
);
369 if (U_SUCCESS(tmpsts
)) {
370 const UChar
*regionPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gRegionFormatTag
, NULL
, &tmpsts
);
371 if (U_SUCCESS(tmpsts
) && u_strlen(regionPattern
) > 0) {
372 rpat
.setTo(regionPattern
, -1);
374 tmpsts
= U_ZERO_ERROR
;
375 const UChar
*fallbackPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gFallbackFormatTag
, NULL
, &tmpsts
);
376 if (U_SUCCESS(tmpsts
) && u_strlen(fallbackPattern
) > 0) {
377 fpat
.setTo(fallbackPattern
, -1);
380 ures_close(zoneStrings
);
382 fRegionFormat
= new MessageFormat(rpat
, status
);
383 if (fRegionFormat
== NULL
) {
384 status
= U_MEMORY_ALLOCATION_ERROR
;
386 fFallbackFormat
= new MessageFormat(fpat
, status
);
387 if (fFallbackFormat
== NULL
) {
388 status
= U_MEMORY_ALLOCATION_ERROR
;
390 if (U_FAILURE(status
)) {
395 // locale display names
396 fLocaleDisplayNames
= LocaleDisplayNames::createInstance(locale
);
398 // hash table for names - no key/value deleters
399 fLocationNamesMap
= uhash_open(uhash_hashUChars
, uhash_compareUChars
, NULL
, &status
);
400 if (U_FAILURE(status
)) {
405 fPartialLocationNamesMap
= uhash_open(hashPartialLocationKey
, comparePartialLocationKey
, NULL
, &status
);
406 if (U_FAILURE(status
)) {
410 uhash_setKeyDeleter(fPartialLocationNamesMap
, uprv_free
);
414 const char* region
= fLocale
.getCountry();
415 int32_t regionLen
= uprv_strlen(region
);
416 if (regionLen
== 0) {
417 char loc
[ULOC_FULLNAME_CAPACITY
];
418 uloc_addLikelySubtags(fLocale
.getName(), loc
, sizeof(loc
), &status
);
420 regionLen
= uloc_getCountry(loc
, fTargetRegion
, sizeof(fTargetRegion
), &status
);
421 if (U_SUCCESS(status
)) {
422 fTargetRegion
[regionLen
] = 0;
427 } else if (regionLen
< (int32_t)sizeof(fTargetRegion
)) {
428 uprv_strcpy(fTargetRegion
, region
);
430 fTargetRegion
[0] = 0;
433 // preload generic names for the default zone
434 TimeZone
*tz
= TimeZone::createDefault();
435 const UChar
*tzID
= ZoneMeta::getCanonicalCLDRID(*tz
);
437 loadStrings(UnicodeString(tzID
));
443 TZGNCore::cleanup() {
444 if (fRegionFormat
!= NULL
) {
445 delete fRegionFormat
;
447 if (fFallbackFormat
!= NULL
) {
448 delete fFallbackFormat
;
450 if (fLocaleDisplayNames
!= NULL
) {
451 delete fLocaleDisplayNames
;
453 if (fTimeZoneNames
!= NULL
) {
454 delete fTimeZoneNames
;
457 uhash_close(fLocationNamesMap
);
458 uhash_close(fPartialLocationNamesMap
);
463 TZGNCore::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
466 case UTZGNM_LOCATION
:
468 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
469 if (tzCanonicalID
!= NULL
) {
470 getGenericLocationName(UnicodeString(tzCanonicalID
), name
);
476 formatGenericNonLocationName(tz
, type
, date
, name
);
477 if (name
.isEmpty()) {
478 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
479 if (tzCanonicalID
!= NULL
) {
480 getGenericLocationName(UnicodeString(tzCanonicalID
), name
);
491 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
492 if (tzCanonicalID
.isEmpty()) {
497 const UChar
*locname
= NULL
;
498 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
501 locname
= nonConstThis
->getGenericLocationName(tzCanonicalID
);
505 if (locname
== NULL
) {
508 name
.setTo(locname
, u_strlen(locname
));
515 * This method updates the cache and must be called with a lock
518 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
) {
519 U_ASSERT(!tzCanonicalID
.isEmpty());
520 if (tzCanonicalID
.length() > ZID_KEY_MAX
) {
524 UErrorCode status
= U_ZERO_ERROR
;
525 UChar tzIDKey
[ZID_KEY_MAX
+ 1];
526 int32_t tzIDKeyLen
= tzCanonicalID
.extract(tzIDKey
, ZID_KEY_MAX
+ 1, status
);
527 U_ASSERT(status
== U_ZERO_ERROR
); // already checked length above
528 tzIDKey
[tzIDKeyLen
] = 0;
530 const UChar
*locname
= (const UChar
*)uhash_get(fLocationNamesMap
, tzIDKey
);
532 if (locname
!= NULL
) {
533 // gEmpty indicate the name is not available
534 if (locname
== gEmpty
) {
540 // Construct location name
542 UnicodeString usCountryCode
;
543 UBool isPrimary
= FALSE
;
545 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
, &isPrimary
);
547 if (!usCountryCode
.isEmpty()) {
551 // If this is the primary zone in the country, use the country name.
552 char countryCode
[ULOC_COUNTRY_CAPACITY
];
553 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
554 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
555 countryCode
[ccLen
] = 0;
557 UnicodeString country
;
558 fLocaleDisplayNames
->regionDisplayName(countryCode
, country
);
560 Formattable param
[] = {
564 fRegionFormat
->format(param
, 1, name
, fpos
, status
);
566 // If this is not the primary zone in the country,
567 // use the exemplar city name.
569 // getExemplarLocationName should retur non-empty string
570 // if the time zone is associated with a region
573 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, city
);
575 Formattable param
[] = {
579 fRegionFormat
->format(param
, 1, name
, fpos
, status
);
581 if (U_FAILURE(status
)) {
586 locname
= name
.isEmpty() ? NULL
: fStringPool
.get(name
, status
);
587 if (U_SUCCESS(status
)) {
589 const UChar
* cacheID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
590 U_ASSERT(cacheID
!= NULL
);
591 if (locname
== NULL
) {
592 // gEmpty to indicate - no location name available
593 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)gEmpty
, &status
);
595 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)locname
, &status
);
596 if (U_FAILURE(status
)) {
599 // put the name info into the trie
600 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
601 if (nameinfo
!= NULL
) {
602 nameinfo
->type
= UTZGNM_LOCATION
;
603 nameinfo
->tzID
= cacheID
;
604 fGNamesTrie
.put(locname
, nameinfo
, status
);
614 TZGNCore::formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
615 U_ASSERT(type
== UTZGNM_LONG
|| type
== UTZGNM_SHORT
);
618 const UChar
* uID
= ZoneMeta::getCanonicalCLDRID(tz
);
623 UnicodeString
tzID(uID
);
625 // Try to get a name from time zone first
626 UTimeZoneNameType nameType
= (type
== UTZGNM_LONG
) ? UTZNM_LONG_GENERIC
: UTZNM_SHORT_GENERIC
;
627 fTimeZoneNames
->getTimeZoneDisplayName(tzID
, nameType
, name
);
629 if (!name
.isEmpty()) {
635 fTimeZoneNames
->getMetaZoneID(tzID
, date
, mzID
);
636 if (!mzID
.isEmpty()) {
637 UErrorCode status
= U_ZERO_ERROR
;
638 UBool useStandard
= FALSE
;
641 tz
.getOffset(date
, FALSE
, raw
, sav
, status
);
642 if (U_FAILURE(status
)) {
649 TimeZone
*tmptz
= tz
.clone();
650 // Check if the zone actually uses daylight saving time around the time
651 BasicTimeZone
*btz
= NULL
;
652 if (dynamic_cast<OlsonTimeZone
*>(tmptz
) != NULL
653 || dynamic_cast<SimpleTimeZone
*>(tmptz
) != NULL
654 || dynamic_cast<RuleBasedTimeZone
*>(tmptz
) != NULL
655 || dynamic_cast<VTimeZone
*>(tmptz
) != NULL
) {
656 btz
= (BasicTimeZone
*)tmptz
;
660 TimeZoneTransition before
;
661 UBool beforTrs
= btz
->getPreviousTransition(date
, TRUE
, before
);
663 && (date
- before
.getTime() < kDstCheckRange
)
664 && before
.getFrom()->getDSTSavings() != 0) {
667 TimeZoneTransition after
;
668 UBool afterTrs
= btz
->getNextTransition(date
, FALSE
, after
);
670 && (after
.getTime() - date
< kDstCheckRange
)
671 && after
.getTo()->getDSTSavings() != 0) {
676 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
677 // We may get a wrong answer in edge case, but it should practically work OK.
678 tmptz
->getOffset(date
- kDstCheckRange
, FALSE
, raw
, sav
, status
);
682 tmptz
->getOffset(date
+ kDstCheckRange
, FALSE
, raw
, sav
, status
);
687 if (U_FAILURE(status
)) {
695 UTimeZoneNameType stdNameType
= (nameType
== UTZNM_LONG_GENERIC
)
696 ? UTZNM_LONG_STANDARD
: UTZNM_SHORT_STANDARD
;
697 UnicodeString stdName
;
698 fTimeZoneNames
->getDisplayName(tzID
, stdNameType
, date
, stdName
);
699 if (!stdName
.isEmpty()) {
702 // TODO: revisit this issue later
703 // In CLDR, a same display name is used for both generic and standard
704 // for some meta zones in some locales. This looks like a data bugs.
705 // For now, we check if the standard name is different from its generic
707 UnicodeString mzGenericName
;
708 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzGenericName
);
709 if (stdName
.caseCompare(mzGenericName
, 0) == 0) {
714 if (name
.isEmpty()) {
715 // Get a name from meta zone
716 UnicodeString mzName
;
717 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzName
);
718 if (!mzName
.isEmpty()) {
719 // Check if we need to use a partial location format.
720 // This check is done by comparing offset with the meta zone's
721 // golden zone at the given date.
722 UnicodeString goldenID
;
723 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, goldenID
);
724 if (!goldenID
.isEmpty() && goldenID
!= tzID
) {
725 TimeZone
*goldenZone
= TimeZone::createTimeZone(goldenID
);
728 // Check offset in the golden zone with wall time.
729 // With getOffset(date, false, offsets1),
730 // you may get incorrect results because of time overlap at DST->STD
732 goldenZone
->getOffset(date
+ raw
+ sav
, TRUE
, raw1
, sav1
, status
);
734 if (U_SUCCESS(status
)) {
735 if (raw
!= raw1
|| sav
!= sav1
) {
736 // Now we need to use a partial location format
737 getPartialLocationName(tzID
, mzID
, (nameType
== UTZNM_LONG_GENERIC
), mzName
, name
);
752 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
753 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
754 UnicodeString
& name
) const {
756 if (tzCanonicalID
.isEmpty() || mzID
.isEmpty() || mzDisplayName
.isEmpty()) {
760 const UChar
*uplname
= NULL
;
761 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
764 uplname
= nonConstThis
->getPartialLocationName(tzCanonicalID
, mzID
, isLong
, mzDisplayName
);
768 if (uplname
== NULL
) {
771 name
.setTo(TRUE
, uplname
, -1);
777 * This method updates the cache and must be called with a lock
780 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
781 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
) {
782 U_ASSERT(!tzCanonicalID
.isEmpty());
783 U_ASSERT(!mzID
.isEmpty());
784 U_ASSERT(!mzDisplayName
.isEmpty());
786 PartialLocationKey key
;
787 key
.tzID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
788 key
.mzID
= ZoneMeta::findMetaZoneID(mzID
);
790 U_ASSERT(key
.tzID
!= NULL
&& key
.mzID
!= NULL
);
792 const UChar
* uplname
= (const UChar
*)uhash_get(fPartialLocationNamesMap
, (void *)&key
);
793 if (uplname
!= NULL
) {
797 UnicodeString location
;
798 UnicodeString usCountryCode
;
799 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
);
800 if (!usCountryCode
.isEmpty()) {
801 char countryCode
[ULOC_COUNTRY_CAPACITY
];
802 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
803 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
804 countryCode
[ccLen
] = 0;
806 UnicodeString regionalGolden
;
807 fTimeZoneNames
->getReferenceZoneID(mzID
, countryCode
, regionalGolden
);
808 if (tzCanonicalID
== regionalGolden
) {
810 fLocaleDisplayNames
->regionDisplayName(countryCode
, location
);
812 // Otherwise, use exemplar city name
813 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
816 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
817 if (location
.isEmpty()) {
818 // This could happen when the time zone is not associated with a country,
819 // and its ID is not hierarchical, for example, CST6CDT.
820 // We use the canonical ID itself as the location for this case.
821 location
.setTo(tzCanonicalID
);
825 UErrorCode status
= U_ZERO_ERROR
;
829 Formattable param
[] = {
830 Formattable(location
),
831 Formattable(mzDisplayName
)
833 fFallbackFormat
->format(param
, 2, name
, fpos
, status
);
834 if (U_FAILURE(status
)) {
838 uplname
= fStringPool
.get(name
, status
);
839 if (U_SUCCESS(status
)) {
840 // Add the name to cache
841 PartialLocationKey
* cacheKey
= (PartialLocationKey
*)uprv_malloc(sizeof(PartialLocationKey
));
842 if (cacheKey
!= NULL
) {
843 cacheKey
->tzID
= key
.tzID
;
844 cacheKey
->mzID
= key
.mzID
;
845 cacheKey
->isLong
= key
.isLong
;
846 uhash_put(fPartialLocationNamesMap
, (void *)cacheKey
, (void *)uplname
, &status
);
847 if (U_FAILURE(status
)) {
850 // put the name to the local trie as well
851 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
852 if (nameinfo
!= NULL
) {
853 nameinfo
->type
= isLong
? UTZGNM_LONG
: UTZGNM_SHORT
;
854 nameinfo
->tzID
= key
.tzID
;
855 fGNamesTrie
.put(uplname
, nameinfo
, status
);
864 * This method updates the cache and must be called with a lock,
865 * except initializer.
868 TZGNCore::loadStrings(const UnicodeString
& tzCanonicalID
) {
869 // load the generic location name
870 getGenericLocationName(tzCanonicalID
);
872 // partial location names
873 UErrorCode status
= U_ZERO_ERROR
;
875 const UnicodeString
*mzID
;
876 UnicodeString goldenID
;
877 UnicodeString mzGenName
;
878 UTimeZoneNameType genNonLocTypes
[] = {
879 UTZNM_LONG_GENERIC
, UTZNM_SHORT_GENERIC
,
880 UTZNM_UNKNOWN
/*terminator*/
883 StringEnumeration
*mzIDs
= fTimeZoneNames
->getAvailableMetaZoneIDs(tzCanonicalID
, status
);
884 while ((mzID
= mzIDs
->snext(status
))) {
885 if (U_FAILURE(status
)) {
888 // if this time zone is not the golden zone of the meta zone,
889 // partial location name (such as "PT (Los Angeles)") might be
891 fTimeZoneNames
->getReferenceZoneID(*mzID
, fTargetRegion
, goldenID
);
892 if (tzCanonicalID
!= goldenID
) {
893 for (int32_t i
= 0; genNonLocTypes
[i
] != UTZNM_UNKNOWN
; i
++) {
894 fTimeZoneNames
->getMetaZoneDisplayName(*mzID
, genNonLocTypes
[i
], mzGenName
);
895 if (!mzGenName
.isEmpty()) {
896 // getPartialLocationName formats a name and put it into the trie
897 getPartialLocationName(tzCanonicalID
, *mzID
,
898 (genNonLocTypes
[i
] == UTZNM_LONG_GENERIC
), mzGenName
);
909 TZGNCore::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
910 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
911 timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
914 if (U_FAILURE(status
)) {
918 // Find matches in the TimeZoneNames first
919 TimeZoneNames::MatchInfoCollection
*tznamesMatches
= findTimeZoneNames(text
, start
, types
, status
);
920 if (U_FAILURE(status
)) {
924 int32_t bestMatchLen
= 0;
925 UTimeZoneFormatTimeType bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
926 UnicodeString bestMatchTzID
;
927 // UBool isLongStandard = FALSE; // workaround - see the comments below
928 UBool isStandard
= FALSE
; // TODO: Temporary hack (on hack) for short standard name/location name conflict (found in zh_Hant), should be removed after CLDR 21m1 integration
930 if (tznamesMatches
!= NULL
) {
932 for (int32_t i
= 0; i
< tznamesMatches
->size(); i
++) {
933 int32_t len
= tznamesMatches
->getMatchLengthAt(i
);
934 if (len
> bestMatchLen
) {
936 if (!tznamesMatches
->getTimeZoneIDAt(i
, bestMatchTzID
)) {
937 // name for a meta zone
938 if (tznamesMatches
->getMetaZoneIDAt(i
, mzID
)) {
939 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, bestMatchTzID
);
942 UTimeZoneNameType nameType
= tznamesMatches
->getNameTypeAt(i
);
943 if (U_FAILURE(status
)) {
947 case UTZNM_LONG_STANDARD
:
948 // isLongStandard = TRUE;
949 case UTZNM_SHORT_STANDARD
: // this one is never used for generic, but just in case
950 isStandard
= TRUE
; // TODO: Remove this later, see the comments above.
951 bestMatchTimeType
= UTZFMT_TIME_TYPE_STANDARD
;
953 case UTZNM_LONG_DAYLIGHT
:
954 case UTZNM_SHORT_DAYLIGHT
: // this one is never used for generic, but just in case
955 bestMatchTimeType
= UTZFMT_TIME_TYPE_DAYLIGHT
;
958 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
962 delete tznamesMatches
;
963 if (U_FAILURE(status
)) {
967 if (bestMatchLen
== (text
.length() - start
)) {
970 //tzID.setTo(bestMatchTzID);
971 //timeType = bestMatchTimeType;
972 //return bestMatchLen;
974 // TODO Some time zone uses a same name for the long standard name
975 // and the location name. When the match is a long standard name,
976 // then we need to check if the name is same with the location name.
977 // This is probably a data error or a design bug.
979 if (!isLongStandard) {
980 tzID.setTo(bestMatchTzID);
981 timeType = bestMatchTimeType;
985 // TODO The deprecation of commonlyUsed flag introduced the name
986 // conflict not only for long standard names, but short standard names too.
987 // These short names (found in zh_Hant) should be gone once we clean
988 // up CLDR time zone display name data. Once the short name conflict
989 // problem (with location name) is resolved, we should change the condition
990 // below back to the original one above. -Yoshito (2011-09-14)
992 tzID
.setTo(bestMatchTzID
);
993 timeType
= bestMatchTimeType
;
999 // Find matches in the local trie
1000 TimeZoneGenericNameMatchInfo
*localMatches
= findLocal(text
, start
, types
, status
);
1001 if (U_FAILURE(status
)) {
1004 if (localMatches
!= NULL
) {
1005 for (int32_t i
= 0; i
< localMatches
->size(); i
++) {
1006 int32_t len
= localMatches
->getMatchLength(i
);
1008 // TODO See the above TODO. We use len >= bestMatchLen
1009 // because of the long standard/location name collision
1010 // problem. If it is also a location name, carrying
1011 // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
1012 // problem in SimpleDateFormat
1013 if (len
>= bestMatchLen
) {
1014 bestMatchLen
= localMatches
->getMatchLength(i
);
1015 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
; // because generic
1016 localMatches
->getTimeZoneID(i
, bestMatchTzID
);
1019 delete localMatches
;
1022 if (bestMatchLen
> 0) {
1023 timeType
= bestMatchTimeType
;
1024 tzID
.setTo(bestMatchTzID
);
1026 return bestMatchLen
;
1029 TimeZoneGenericNameMatchInfo
*
1030 TZGNCore::findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1031 GNameSearchHandler
handler(types
);
1033 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
1037 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1039 umtx_unlock(&gLock
);
1041 if (U_FAILURE(status
)) {
1045 TimeZoneGenericNameMatchInfo
*gmatchInfo
= NULL
;
1048 UVector
*results
= handler
.getMatches(maxLen
);
1049 if (results
!= NULL
&& ((maxLen
== (text
.length() - start
)) || fGNamesTrieFullyLoaded
)) {
1051 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1052 if (gmatchInfo
== NULL
) {
1053 status
= U_MEMORY_ALLOCATION_ERROR
;
1060 if (results
!= NULL
) {
1064 // All names are not yet loaded into the local trie.
1065 // Load all available names into the trie. This could be very heavy.
1068 if (!fGNamesTrieFullyLoaded
) {
1069 StringEnumeration
*tzIDs
= TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL
, NULL
, NULL
, status
);
1070 if (U_SUCCESS(status
)) {
1071 const UnicodeString
*tzID
;
1072 while ((tzID
= tzIDs
->snext(status
))) {
1073 if (U_FAILURE(status
)) {
1076 nonConstThis
->loadStrings(*tzID
);
1079 if (tzIDs
!= NULL
) {
1083 if (U_SUCCESS(status
)) {
1084 nonConstThis
->fGNamesTrieFullyLoaded
= TRUE
;
1088 umtx_unlock(&gLock
);
1090 if (U_FAILURE(status
)) {
1097 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1099 umtx_unlock(&gLock
);
1101 results
= handler
.getMatches(maxLen
);
1102 if (results
!= NULL
&& maxLen
> 0) {
1103 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1104 if (gmatchInfo
== NULL
) {
1105 status
= U_MEMORY_ALLOCATION_ERROR
;
1114 TimeZoneNames::MatchInfoCollection
*
1115 TZGNCore::findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1116 // Check if the target name typs is really in the TimeZoneNames
1117 uint32_t nameTypes
= 0;
1118 if (types
& UTZGNM_LONG
) {
1119 nameTypes
|= (UTZNM_LONG_GENERIC
| UTZNM_LONG_STANDARD
);
1121 if (types
& UTZGNM_SHORT
) {
1122 nameTypes
|= (UTZNM_SHORT_GENERIC
| UTZNM_SHORT_STANDARD
);
1126 // Find matches in the TimeZoneNames
1127 return fTimeZoneNames
->find(text
, start
, nameTypes
, status
);
1133 typedef struct TZGNCoreRef
{
1139 // TZGNCore object cache handling
1140 static UMutex gTZGNLock
= U_MUTEX_INITIALIZER
;
1141 static UHashtable
*gTZGNCoreCache
= NULL
;
1142 static UBool gTZGNCoreCacheInitialized
= FALSE
;
1144 // Access count - incremented every time up to SWEEP_INTERVAL,
1146 static int32_t gAccessCount
= 0;
1148 // Interval for calling the cache sweep function - every 100 times
1149 #define SWEEP_INTERVAL 100
1151 // Cache expiration in millisecond. When a cached entry is no
1152 // longer referenced and exceeding this threshold since last
1153 // access time, then the cache entry will be deleted by the sweep
1154 // function. For now, 3 minutes.
1155 #define CACHE_EXPIRATION 180000.0
1159 * Cleanup callback func
1161 static UBool U_CALLCONV
tzgnCore_cleanup(void)
1163 if (gTZGNCoreCache
!= NULL
) {
1164 uhash_close(gTZGNCoreCache
);
1165 gTZGNCoreCache
= NULL
;
1167 gTZGNCoreCacheInitialized
= FALSE
;
1172 * Deleter for TZGNCoreRef
1174 static void U_CALLCONV
1175 deleteTZGNCoreRef(void *obj
) {
1176 icu::TZGNCoreRef
*entry
= (icu::TZGNCoreRef
*)obj
;
1177 delete (icu::TZGNCore
*) entry
->obj
;
1183 * Function used for removing unreferrenced cache entries exceeding
1184 * the expiration time. This function must be called with in the mutex
1187 static void sweepCache() {
1189 const UHashElement
* elem
;
1190 double now
= (double)uprv_getUTCtime();
1192 while ((elem
= uhash_nextElement(gTZGNCoreCache
, &pos
))) {
1193 TZGNCoreRef
*entry
= (TZGNCoreRef
*)elem
->value
.pointer
;
1194 if (entry
->refCount
<= 0 && (now
- entry
->lastAccess
) > CACHE_EXPIRATION
) {
1195 // delete this entry
1196 uhash_removeElement(gTZGNCoreCache
, elem
);
1201 TimeZoneGenericNames::TimeZoneGenericNames()
1205 TimeZoneGenericNames::~TimeZoneGenericNames() {
1206 umtx_lock(&gTZGNLock
);
1208 U_ASSERT(fRef
->refCount
> 0);
1209 // Just decrement the reference count
1212 umtx_unlock(&gTZGNLock
);
1215 TimeZoneGenericNames
*
1216 TimeZoneGenericNames::createInstance(const Locale
& locale
, UErrorCode
& status
) {
1217 if (U_FAILURE(status
)) {
1220 TimeZoneGenericNames
* instance
= new TimeZoneGenericNames();
1221 if (instance
== NULL
) {
1222 status
= U_MEMORY_ALLOCATION_ERROR
;
1226 TZGNCoreRef
*cacheEntry
= NULL
;
1228 Mutex
lock(&gTZGNLock
);
1230 if (!gTZGNCoreCacheInitialized
) {
1231 // Create empty hashtable
1232 gTZGNCoreCache
= uhash_open(uhash_hashChars
, uhash_compareChars
, NULL
, &status
);
1233 if (U_SUCCESS(status
)) {
1234 uhash_setKeyDeleter(gTZGNCoreCache
, uprv_free
);
1235 uhash_setValueDeleter(gTZGNCoreCache
, deleteTZGNCoreRef
);
1236 gTZGNCoreCacheInitialized
= TRUE
;
1237 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES
, tzgnCore_cleanup
);
1240 if (U_FAILURE(status
)) {
1244 // Check the cache, if not available, create new one and cache
1245 const char *key
= locale
.getName();
1246 cacheEntry
= (TZGNCoreRef
*)uhash_get(gTZGNCoreCache
, key
);
1247 if (cacheEntry
== NULL
) {
1248 TZGNCore
*tzgnCore
= NULL
;
1249 char *newKey
= NULL
;
1251 tzgnCore
= new TZGNCore(locale
, status
);
1252 if (tzgnCore
== NULL
) {
1253 status
= U_MEMORY_ALLOCATION_ERROR
;
1255 if (U_SUCCESS(status
)) {
1256 newKey
= (char *)uprv_malloc(uprv_strlen(key
) + 1);
1257 if (newKey
== NULL
) {
1258 status
= U_MEMORY_ALLOCATION_ERROR
;
1260 uprv_strcpy(newKey
, key
);
1263 if (U_SUCCESS(status
)) {
1264 cacheEntry
= (TZGNCoreRef
*)uprv_malloc(sizeof(TZGNCoreRef
));
1265 if (cacheEntry
== NULL
) {
1266 status
= U_MEMORY_ALLOCATION_ERROR
;
1268 cacheEntry
->obj
= tzgnCore
;
1269 cacheEntry
->refCount
= 1;
1270 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1272 uhash_put(gTZGNCoreCache
, newKey
, cacheEntry
, &status
);
1275 if (U_FAILURE(status
)) {
1276 if (tzgnCore
!= NULL
) {
1279 if (newKey
!= NULL
) {
1282 if (cacheEntry
!= NULL
) {
1283 uprv_free(cacheEntry
);
1288 // Update the reference count
1289 cacheEntry
->refCount
++;
1290 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1293 if (gAccessCount
>= SWEEP_INTERVAL
) {
1298 } // End of mutex locked block
1300 if (cacheEntry
== NULL
) {
1305 instance
->fRef
= cacheEntry
;
1310 TimeZoneGenericNames::operator==(const TimeZoneGenericNames
& other
) const {
1311 // Just compare if the other object also use the same
1313 return fRef
== other
.fRef
;
1316 TimeZoneGenericNames
*
1317 TimeZoneGenericNames::clone() const {
1318 TimeZoneGenericNames
* other
= new TimeZoneGenericNames();
1320 umtx_lock(&gTZGNLock
);
1322 // Just increments the reference count
1326 umtx_unlock(&gTZGNLock
);
1332 TimeZoneGenericNames::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
1333 UDate date
, UnicodeString
& name
) const {
1334 return fRef
->obj
->getDisplayName(tz
, type
, date
, name
);
1338 TimeZoneGenericNames::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
1339 return fRef
->obj
->getGenericLocationName(tzCanonicalID
, name
);
1343 TimeZoneGenericNames::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
1344 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
1345 return fRef
->obj
->findBestMatch(text
, start
, types
, tzID
, timeType
, status
);