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"
29 #include "tznames_impl.h"
35 #define ZID_KEY_MAX 128
37 static const char gZoneStrings
[] = "zoneStrings";
39 static const char gRegionFormatTag
[] = "regionFormat";
40 static const char gFallbackRegionFormatTag
[] = "fallbackRegionFormat";
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 gDefFallbackRegionPattern
[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
47 static const UChar gDefFallbackPattern
[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
49 static const double kDstCheckRange
= (double)184*U_MILLIS_PER_DAY
;
55 typedef struct PartialLocationKey
{
62 * Hash function for partial location name hash key
64 static int32_t U_CALLCONV
65 hashPartialLocationKey(const UHashTok key
) {
66 // <tzID>&<mzID>#[L|S]
67 PartialLocationKey
*p
= (PartialLocationKey
*)key
.pointer
;
68 UnicodeString
str(p
->tzID
);
69 str
.append((UChar
)0x26)
72 .append((UChar
)(p
->isLong
? 0x4C : 0x53));
73 return str
.hashCode();
77 * Comparer for partial location name hash key
79 static UBool U_CALLCONV
80 comparePartialLocationKey(const UHashTok key1
, const UHashTok key2
) {
81 PartialLocationKey
*p1
= (PartialLocationKey
*)key1
.pointer
;
82 PartialLocationKey
*p2
= (PartialLocationKey
*)key2
.pointer
;
87 if (p1
== NULL
|| p2
== NULL
) {
90 // We just check identity of tzID/mzID
91 return (p1
->tzID
== p2
->tzID
&& p1
->mzID
== p2
->mzID
&& p1
->isLong
== p2
->isLong
);
95 * Deleter for GNameInfo
97 static void U_CALLCONV
98 deleteGNameInfo(void *obj
) {
103 * GNameInfo stores zone name information in the local trie
105 typedef struct GNameInfo
{
106 UTimeZoneGenericNameType type
;
111 * GMatchInfo stores zone name match information used by find method
113 typedef struct GMatchInfo
{
114 const GNameInfo
* gnameInfo
;
116 UTimeZoneFormatTimeType timeType
;
121 // ---------------------------------------------------
122 // The class stores time zone generic name match information
123 // ---------------------------------------------------
124 class TimeZoneGenericNameMatchInfo
: public UMemory
{
126 TimeZoneGenericNameMatchInfo(UVector
* matches
);
127 ~TimeZoneGenericNameMatchInfo();
129 int32_t size() const;
130 UTimeZoneGenericNameType
getGenericNameType(int32_t index
) const;
131 int32_t getMatchLength(int32_t index
) const;
132 UnicodeString
& getTimeZoneID(int32_t index
, UnicodeString
& tzID
) const;
135 UVector
* fMatches
; // vector of MatchEntry
138 TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector
* matches
)
139 : fMatches(matches
) {
142 TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() {
143 if (fMatches
!= NULL
) {
149 TimeZoneGenericNameMatchInfo::size() const {
150 if (fMatches
== NULL
) {
153 return fMatches
->size();
156 UTimeZoneGenericNameType
157 TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index
) const {
158 GMatchInfo
*minfo
= (GMatchInfo
*)fMatches
->elementAt(index
);
160 return static_cast<UTimeZoneGenericNameType
>(minfo
->gnameInfo
->type
);
162 return UTZGNM_UNKNOWN
;
166 TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index
) const {
167 ZMatchInfo
*minfo
= (ZMatchInfo
*)fMatches
->elementAt(index
);
169 return minfo
->matchLength
;
175 TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index
, UnicodeString
& tzID
) const {
176 GMatchInfo
*minfo
= (GMatchInfo
*)fMatches
->elementAt(index
);
177 if (minfo
!= NULL
&& minfo
->gnameInfo
->tzID
!= NULL
) {
178 tzID
.setTo(TRUE
, minfo
->gnameInfo
->tzID
, -1);
185 // ---------------------------------------------------
186 // GNameSearchHandler
187 // ---------------------------------------------------
188 class GNameSearchHandler
: public TextTrieMapSearchResultHandler
{
190 GNameSearchHandler(uint32_t types
);
191 virtual ~GNameSearchHandler();
193 UBool
handleMatch(int32_t matchLength
, const CharacterNode
*node
, UErrorCode
&status
);
194 UVector
* getMatches(int32_t& maxMatchLen
);
199 int32_t fMaxMatchLen
;
202 GNameSearchHandler::GNameSearchHandler(uint32_t types
)
203 : fTypes(types
), fResults(NULL
), fMaxMatchLen(0) {
206 GNameSearchHandler::~GNameSearchHandler() {
207 if (fResults
!= NULL
) {
213 GNameSearchHandler::handleMatch(int32_t matchLength
, const CharacterNode
*node
, UErrorCode
&status
) {
214 if (U_FAILURE(status
)) {
217 if (node
->hasValues()) {
218 int32_t valuesCount
= node
->countValues();
219 for (int32_t i
= 0; i
< valuesCount
; i
++) {
220 GNameInfo
*nameinfo
= (ZNameInfo
*)node
->getValue(i
);
221 if (nameinfo
== NULL
) {
224 if ((nameinfo
->type
& fTypes
) != 0) {
225 // matches a requested type
226 if (fResults
== NULL
) {
227 fResults
= new UVector(uprv_free
, NULL
, status
);
228 if (fResults
== NULL
) {
229 status
= U_MEMORY_ALLOCATION_ERROR
;
232 if (U_SUCCESS(status
)) {
233 U_ASSERT(fResults
!= NULL
);
234 GMatchInfo
*gmatch
= (GMatchInfo
*)uprv_malloc(sizeof(GMatchInfo
));
235 if (gmatch
== NULL
) {
236 status
= U_MEMORY_ALLOCATION_ERROR
;
238 // add the match to the vector
239 gmatch
->gnameInfo
= nameinfo
;
240 gmatch
->matchLength
= matchLength
;
241 gmatch
->timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
242 fResults
->addElement(gmatch
, status
);
243 if (U_FAILURE(status
)) {
246 if (matchLength
> fMaxMatchLen
) {
247 fMaxMatchLen
= matchLength
;
259 GNameSearchHandler::getMatches(int32_t& maxMatchLen
) {
260 // give the ownership to the caller
261 UVector
*results
= fResults
;
262 maxMatchLen
= fMaxMatchLen
;
270 static UMutex gLock
= U_MUTEX_INITIALIZER
;
272 class TZGNCore
: public UMemory
{
274 TZGNCore(const Locale
& locale
, UErrorCode
& status
);
277 UnicodeString
& getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
278 UDate date
, UnicodeString
& name
) const;
280 UnicodeString
& getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const;
282 int32_t findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
283 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const;
287 const TimeZoneNames
* fTimeZoneNames
;
288 UHashtable
* fLocationNamesMap
;
289 UHashtable
* fPartialLocationNamesMap
;
291 MessageFormat
* fRegionFormat
;
292 MessageFormat
* fFallbackFormat
;
294 LocaleDisplayNames
* fLocaleDisplayNames
;
295 ZNStringPool fStringPool
;
297 TextTrieMap fGNamesTrie
;
298 UBool fGNamesTrieFullyLoaded
;
300 char fTargetRegion
[ULOC_COUNTRY_CAPACITY
];
302 void initialize(const Locale
& locale
, UErrorCode
& status
);
305 void loadStrings(const UnicodeString
& tzCanonicalID
);
307 const UChar
* getGenericLocationName(const UnicodeString
& tzCanonicalID
);
309 UnicodeString
& formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
310 UDate date
, UnicodeString
& name
) const;
312 UnicodeString
& getPartialLocationName(const UnicodeString
& tzCanonicalID
,
313 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
314 UnicodeString
& name
) const;
316 const UChar
* getPartialLocationName(const UnicodeString
& tzCanonicalID
,
317 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
);
319 TimeZoneGenericNameMatchInfo
* findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const;
321 TimeZoneNames::MatchInfoCollection
* findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const;
325 // ---------------------------------------------------
326 // TZGNCore - core implmentation of TimeZoneGenericNames
328 // TimeZoneGenericNames is parallel to TimeZoneNames,
329 // but handles run-time generated time zone names.
330 // This is the main part of this module.
331 // ---------------------------------------------------
332 TZGNCore::TZGNCore(const Locale
& locale
, UErrorCode
& status
)
334 fTimeZoneNames(NULL
),
335 fLocationNamesMap(NULL
),
336 fPartialLocationNamesMap(NULL
),
338 fFallbackFormat(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
frpat(TRUE
, gDefFallbackRegionPattern
, -1);
365 UnicodeString
fpat(TRUE
, gDefFallbackPattern
, -1);
367 UErrorCode tmpsts
= U_ZERO_ERROR
; // OK with fallback warning..
368 UResourceBundle
*zoneStrings
= ures_open(U_ICUDATA_ZONE
, locale
.getName(), &tmpsts
);
369 zoneStrings
= ures_getByKeyWithFallback(zoneStrings
, gZoneStrings
, zoneStrings
, &tmpsts
);
371 if (U_SUCCESS(tmpsts
)) {
372 const UChar
*regionPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gRegionFormatTag
, NULL
, &tmpsts
);
373 if (U_SUCCESS(tmpsts
) && u_strlen(regionPattern
) > 0) {
374 rpat
.setTo(regionPattern
, -1);
376 tmpsts
= U_ZERO_ERROR
;
377 const UChar
*fallbackRegionPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gFallbackRegionFormatTag
, NULL
, &tmpsts
);
378 if (U_SUCCESS(tmpsts
) && u_strlen(fallbackRegionPattern
) > 0) {
379 frpat
.setTo(fallbackRegionPattern
, -1);
381 tmpsts
= U_ZERO_ERROR
;
382 const UChar
*fallbackPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gFallbackFormatTag
, NULL
, &tmpsts
);
383 if (U_SUCCESS(tmpsts
) && u_strlen(fallbackPattern
) > 0) {
384 fpat
.setTo(fallbackPattern
, -1);
387 ures_close(zoneStrings
);
389 fRegionFormat
= new MessageFormat(rpat
, status
);
390 if (fRegionFormat
== NULL
) {
391 status
= U_MEMORY_ALLOCATION_ERROR
;
393 fFallbackFormat
= new MessageFormat(fpat
, status
);
394 if (fFallbackFormat
== NULL
) {
395 status
= U_MEMORY_ALLOCATION_ERROR
;
397 if (U_FAILURE(status
)) {
402 // locale display names
403 fLocaleDisplayNames
= LocaleDisplayNames::createInstance(locale
);
405 // hash table for names - no key/value deleters
406 fLocationNamesMap
= uhash_open(uhash_hashUChars
, uhash_compareUChars
, NULL
, &status
);
407 if (U_FAILURE(status
)) {
412 fPartialLocationNamesMap
= uhash_open(hashPartialLocationKey
, comparePartialLocationKey
, NULL
, &status
);
413 if (U_FAILURE(status
)) {
417 uhash_setKeyDeleter(fPartialLocationNamesMap
, uprv_free
);
421 const char* region
= fLocale
.getCountry();
422 int32_t regionLen
= uprv_strlen(region
);
423 if (regionLen
== 0) {
424 char loc
[ULOC_FULLNAME_CAPACITY
];
425 uloc_addLikelySubtags(fLocale
.getName(), loc
, sizeof(loc
), &status
);
427 regionLen
= uloc_getCountry(loc
, fTargetRegion
, sizeof(fTargetRegion
), &status
);
428 if (U_SUCCESS(status
)) {
429 fTargetRegion
[regionLen
] = 0;
434 } else if (regionLen
< (int32_t)sizeof(fTargetRegion
)) {
435 uprv_strcpy(fTargetRegion
, region
);
437 fTargetRegion
[0] = 0;
440 // preload generic names for the default zone
441 TimeZone
*tz
= TimeZone::createDefault();
442 const UChar
*tzID
= ZoneMeta::getCanonicalCLDRID(*tz
);
444 loadStrings(UnicodeString(tzID
));
450 TZGNCore::cleanup() {
451 if (fRegionFormat
!= NULL
) {
452 delete fRegionFormat
;
454 if (fFallbackFormat
!= NULL
) {
455 delete fFallbackFormat
;
457 if (fLocaleDisplayNames
!= NULL
) {
458 delete fLocaleDisplayNames
;
460 if (fTimeZoneNames
!= NULL
) {
461 delete fTimeZoneNames
;
464 uhash_close(fLocationNamesMap
);
465 uhash_close(fPartialLocationNamesMap
);
470 TZGNCore::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
473 case UTZGNM_LOCATION
:
475 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
476 if (tzCanonicalID
!= NULL
) {
477 getGenericLocationName(UnicodeString(tzCanonicalID
), name
);
483 formatGenericNonLocationName(tz
, type
, date
, name
);
484 if (name
.isEmpty()) {
485 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
486 if (tzCanonicalID
!= NULL
) {
487 getGenericLocationName(UnicodeString(tzCanonicalID
), name
);
498 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
499 if (tzCanonicalID
.isEmpty()) {
504 const UChar
*locname
= NULL
;
505 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
508 locname
= nonConstThis
->getGenericLocationName(tzCanonicalID
);
512 if (locname
== NULL
) {
515 name
.setTo(locname
, u_strlen(locname
));
522 * This method updates the cache and must be called with a lock
525 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
) {
526 U_ASSERT(!tzCanonicalID
.isEmpty());
527 if (tzCanonicalID
.length() > ZID_KEY_MAX
) {
531 UErrorCode status
= U_ZERO_ERROR
;
532 UChar tzIDKey
[ZID_KEY_MAX
+ 1];
533 int32_t tzIDKeyLen
= tzCanonicalID
.extract(tzIDKey
, ZID_KEY_MAX
+ 1, status
);
534 U_ASSERT(status
== U_ZERO_ERROR
); // already checked length above
535 tzIDKey
[tzIDKeyLen
] = 0;
537 const UChar
*locname
= (const UChar
*)uhash_get(fLocationNamesMap
, tzIDKey
);
539 if (locname
!= NULL
) {
540 // gEmpty indicate the name is not available
541 if (locname
== gEmpty
) {
547 // Construct location name
549 UnicodeString usCountryCode
;
550 UBool isPrimary
= FALSE
;
552 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
, &isPrimary
);
554 if (!usCountryCode
.isEmpty()) {
558 // If this is the primary zone in the country, use the country name.
559 char countryCode
[ULOC_COUNTRY_CAPACITY
];
560 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
561 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
562 countryCode
[ccLen
] = 0;
564 UnicodeString country
;
565 fLocaleDisplayNames
->regionDisplayName(countryCode
, country
);
567 Formattable param
[] = {
571 fRegionFormat
->format(param
, 1, name
, fpos
, status
);
573 // If this is not the primary zone in the country,
574 // use the exemplar city name.
576 // getExemplarLocationName should retur non-empty string
577 // if the time zone is associated with a region
580 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, city
);
582 Formattable param
[] = {
586 fRegionFormat
->format(param
, 1, name
, fpos
, status
);
588 if (U_FAILURE(status
)) {
593 locname
= name
.isEmpty() ? NULL
: fStringPool
.get(name
, status
);
594 if (U_SUCCESS(status
)) {
596 const UChar
* cacheID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
597 U_ASSERT(cacheID
!= NULL
);
598 if (locname
== NULL
) {
599 // gEmpty to indicate - no location name available
600 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)gEmpty
, &status
);
602 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)locname
, &status
);
603 if (U_FAILURE(status
)) {
606 // put the name info into the trie
607 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
608 if (nameinfo
!= NULL
) {
609 nameinfo
->type
= UTZGNM_LOCATION
;
610 nameinfo
->tzID
= cacheID
;
611 fGNamesTrie
.put(locname
, nameinfo
, status
);
621 TZGNCore::formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
622 U_ASSERT(type
== UTZGNM_LONG
|| type
== UTZGNM_SHORT
);
625 const UChar
* uID
= ZoneMeta::getCanonicalCLDRID(tz
);
630 UnicodeString
tzID(uID
);
632 // Try to get a name from time zone first
633 UTimeZoneNameType nameType
= (type
== UTZGNM_LONG
) ? UTZNM_LONG_GENERIC
: UTZNM_SHORT_GENERIC
;
634 fTimeZoneNames
->getTimeZoneDisplayName(tzID
, nameType
, name
);
636 if (!name
.isEmpty()) {
642 fTimeZoneNames
->getMetaZoneID(tzID
, date
, mzID
);
643 if (!mzID
.isEmpty()) {
644 UErrorCode status
= U_ZERO_ERROR
;
645 UBool useStandard
= FALSE
;
648 tz
.getOffset(date
, FALSE
, raw
, sav
, status
);
649 if (U_FAILURE(status
)) {
656 TimeZone
*tmptz
= tz
.clone();
657 // Check if the zone actually uses daylight saving time around the time
658 BasicTimeZone
*btz
= NULL
;
659 if (dynamic_cast<OlsonTimeZone
*>(tmptz
) != NULL
660 || dynamic_cast<SimpleTimeZone
*>(tmptz
) != NULL
661 || dynamic_cast<RuleBasedTimeZone
*>(tmptz
) != NULL
662 || dynamic_cast<VTimeZone
*>(tmptz
) != NULL
) {
663 btz
= (BasicTimeZone
*)tmptz
;
667 TimeZoneTransition before
;
668 UBool beforTrs
= btz
->getPreviousTransition(date
, TRUE
, before
);
670 && (date
- before
.getTime() < kDstCheckRange
)
671 && before
.getFrom()->getDSTSavings() != 0) {
674 TimeZoneTransition after
;
675 UBool afterTrs
= btz
->getNextTransition(date
, FALSE
, after
);
677 && (after
.getTime() - date
< kDstCheckRange
)
678 && after
.getTo()->getDSTSavings() != 0) {
683 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
684 // We may get a wrong answer in edge case, but it should practically work OK.
685 tmptz
->getOffset(date
- kDstCheckRange
, FALSE
, raw
, sav
, status
);
689 tmptz
->getOffset(date
+ kDstCheckRange
, FALSE
, raw
, sav
, status
);
694 if (U_FAILURE(status
)) {
702 UTimeZoneNameType stdNameType
= (nameType
== UTZNM_LONG_GENERIC
)
703 ? UTZNM_LONG_STANDARD
: UTZNM_SHORT_STANDARD
;
704 UnicodeString stdName
;
705 fTimeZoneNames
->getDisplayName(tzID
, stdNameType
, date
, stdName
);
706 if (!stdName
.isEmpty()) {
709 // TODO: revisit this issue later
710 // In CLDR, a same display name is used for both generic and standard
711 // for some meta zones in some locales. This looks like a data bugs.
712 // For now, we check if the standard name is different from its generic
714 UnicodeString mzGenericName
;
715 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzGenericName
);
716 if (stdName
.caseCompare(mzGenericName
, 0) == 0) {
721 if (name
.isEmpty()) {
722 // Get a name from meta zone
723 UnicodeString mzName
;
724 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzName
);
725 if (!mzName
.isEmpty()) {
726 // Check if we need to use a partial location format.
727 // This check is done by comparing offset with the meta zone's
728 // golden zone at the given date.
729 UnicodeString goldenID
;
730 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, goldenID
);
731 if (!goldenID
.isEmpty() && goldenID
!= tzID
) {
732 TimeZone
*goldenZone
= TimeZone::createTimeZone(goldenID
);
735 // Check offset in the golden zone with wall time.
736 // With getOffset(date, false, offsets1),
737 // you may get incorrect results because of time overlap at DST->STD
739 goldenZone
->getOffset(date
+ raw
+ sav
, TRUE
, raw1
, sav1
, status
);
741 if (U_SUCCESS(status
)) {
742 if (raw
!= raw1
|| sav
!= sav1
) {
743 // Now we need to use a partial location format
744 getPartialLocationName(tzID
, mzID
, (nameType
== UTZNM_LONG_GENERIC
), mzName
, name
);
759 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
760 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
761 UnicodeString
& name
) const {
763 if (tzCanonicalID
.isEmpty() || mzID
.isEmpty() || mzDisplayName
.isEmpty()) {
767 const UChar
*uplname
= NULL
;
768 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
771 uplname
= nonConstThis
->getPartialLocationName(tzCanonicalID
, mzID
, isLong
, mzDisplayName
);
775 if (uplname
== NULL
) {
778 name
.setTo(TRUE
, uplname
, -1);
784 * This method updates the cache and must be called with a lock
787 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
788 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
) {
789 U_ASSERT(!tzCanonicalID
.isEmpty());
790 U_ASSERT(!mzID
.isEmpty());
791 U_ASSERT(!mzDisplayName
.isEmpty());
793 PartialLocationKey key
;
794 key
.tzID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
795 key
.mzID
= ZoneMeta::findMetaZoneID(mzID
);
797 U_ASSERT(key
.tzID
!= NULL
&& key
.mzID
!= NULL
);
799 const UChar
* uplname
= (const UChar
*)uhash_get(fPartialLocationNamesMap
, (void *)&key
);
800 if (uplname
!= NULL
) {
804 UnicodeString location
;
805 UnicodeString usCountryCode
;
806 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
);
807 if (!usCountryCode
.isEmpty()) {
808 char countryCode
[ULOC_COUNTRY_CAPACITY
];
809 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
810 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
811 countryCode
[ccLen
] = 0;
813 UnicodeString regionalGolden
;
814 fTimeZoneNames
->getReferenceZoneID(mzID
, countryCode
, regionalGolden
);
815 if (tzCanonicalID
== regionalGolden
) {
817 fLocaleDisplayNames
->regionDisplayName(countryCode
, location
);
819 // Otherwise, use exemplar city name
820 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
823 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
824 if (location
.isEmpty()) {
825 // This could happen when the time zone is not associated with a country,
826 // and its ID is not hierarchical, for example, CST6CDT.
827 // We use the canonical ID itself as the location for this case.
828 location
.setTo(tzCanonicalID
);
832 UErrorCode status
= U_ZERO_ERROR
;
836 Formattable param
[] = {
837 Formattable(location
),
838 Formattable(mzDisplayName
)
840 fFallbackFormat
->format(param
, 2, name
, fpos
, status
);
841 if (U_FAILURE(status
)) {
845 uplname
= fStringPool
.get(name
, status
);
846 if (U_SUCCESS(status
)) {
847 // Add the name to cache
848 PartialLocationKey
* cacheKey
= (PartialLocationKey
*)uprv_malloc(sizeof(PartialLocationKey
));
849 if (cacheKey
!= NULL
) {
850 cacheKey
->tzID
= key
.tzID
;
851 cacheKey
->mzID
= key
.mzID
;
852 cacheKey
->isLong
= key
.isLong
;
853 uhash_put(fPartialLocationNamesMap
, (void *)cacheKey
, (void *)uplname
, &status
);
854 if (U_FAILURE(status
)) {
857 // put the name to the local trie as well
858 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
859 if (nameinfo
!= NULL
) {
860 nameinfo
->type
= isLong
? UTZGNM_LONG
: UTZGNM_SHORT
;
861 nameinfo
->tzID
= key
.tzID
;
862 fGNamesTrie
.put(uplname
, nameinfo
, status
);
871 * This method updates the cache and must be called with a lock,
872 * except initializer.
875 TZGNCore::loadStrings(const UnicodeString
& tzCanonicalID
) {
876 // load the generic location name
877 getGenericLocationName(tzCanonicalID
);
879 // partial location names
880 UErrorCode status
= U_ZERO_ERROR
;
882 const UnicodeString
*mzID
;
883 UnicodeString goldenID
;
884 UnicodeString mzGenName
;
885 UTimeZoneNameType genNonLocTypes
[] = {
886 UTZNM_LONG_GENERIC
, UTZNM_SHORT_GENERIC
,
887 UTZNM_UNKNOWN
/*terminator*/
890 StringEnumeration
*mzIDs
= fTimeZoneNames
->getAvailableMetaZoneIDs(tzCanonicalID
, status
);
891 while ((mzID
= mzIDs
->snext(status
))) {
892 if (U_FAILURE(status
)) {
895 // if this time zone is not the golden zone of the meta zone,
896 // partial location name (such as "PT (Los Angeles)") might be
898 fTimeZoneNames
->getReferenceZoneID(*mzID
, fTargetRegion
, goldenID
);
899 if (tzCanonicalID
!= goldenID
) {
900 for (int32_t i
= 0; genNonLocTypes
[i
] != UTZNM_UNKNOWN
; i
++) {
901 fTimeZoneNames
->getMetaZoneDisplayName(*mzID
, genNonLocTypes
[i
], mzGenName
);
902 if (!mzGenName
.isEmpty()) {
903 // getPartialLocationName formats a name and put it into the trie
904 getPartialLocationName(tzCanonicalID
, *mzID
,
905 (genNonLocTypes
[i
] == UTZNM_LONG_GENERIC
), mzGenName
);
916 TZGNCore::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
917 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
918 timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
921 if (U_FAILURE(status
)) {
925 // Find matches in the TimeZoneNames first
926 TimeZoneNames::MatchInfoCollection
*tznamesMatches
= findTimeZoneNames(text
, start
, types
, status
);
927 if (U_FAILURE(status
)) {
931 int32_t bestMatchLen
= 0;
932 UTimeZoneFormatTimeType bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
933 UnicodeString bestMatchTzID
;
934 // UBool isLongStandard = FALSE; // workaround - see the comments below
935 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
937 if (tznamesMatches
!= NULL
) {
939 for (int32_t i
= 0; i
< tznamesMatches
->size(); i
++) {
940 int32_t len
= tznamesMatches
->getMatchLengthAt(i
);
941 if (len
> bestMatchLen
) {
943 if (!tznamesMatches
->getTimeZoneIDAt(i
, bestMatchTzID
)) {
944 // name for a meta zone
945 if (tznamesMatches
->getMetaZoneIDAt(i
, mzID
)) {
946 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, bestMatchTzID
);
949 UTimeZoneNameType nameType
= tznamesMatches
->getNameTypeAt(i
);
950 if (U_FAILURE(status
)) {
954 case UTZNM_LONG_STANDARD
:
955 // isLongStandard = TRUE;
956 case UTZNM_SHORT_STANDARD
: // this one is never used for generic, but just in case
957 isStandard
= TRUE
; // TODO: Remove this later, see the comments above.
958 bestMatchTimeType
= UTZFMT_TIME_TYPE_STANDARD
;
960 case UTZNM_LONG_DAYLIGHT
:
961 case UTZNM_SHORT_DAYLIGHT
: // this one is never used for generic, but just in case
962 bestMatchTimeType
= UTZFMT_TIME_TYPE_DAYLIGHT
;
965 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
969 delete tznamesMatches
;
970 if (U_FAILURE(status
)) {
974 if (bestMatchLen
== (text
.length() - start
)) {
977 //tzID.setTo(bestMatchTzID);
978 //timeType = bestMatchTimeType;
979 //return bestMatchLen;
981 // TODO Some time zone uses a same name for the long standard name
982 // and the location name. When the match is a long standard name,
983 // then we need to check if the name is same with the location name.
984 // This is probably a data error or a design bug.
986 if (!isLongStandard) {
987 tzID.setTo(bestMatchTzID);
988 timeType = bestMatchTimeType;
992 // TODO The deprecation of commonlyUsed flag introduced the name
993 // conflict not only for long standard names, but short standard names too.
994 // These short names (found in zh_Hant) should be gone once we clean
995 // up CLDR time zone display name data. Once the short name conflict
996 // problem (with location name) is resolved, we should change the condition
997 // below back to the original one above. -Yoshito (2011-09-14)
999 tzID
.setTo(bestMatchTzID
);
1000 timeType
= bestMatchTimeType
;
1001 return bestMatchLen
;
1006 // Find matches in the local trie
1007 TimeZoneGenericNameMatchInfo
*localMatches
= findLocal(text
, start
, types
, status
);
1008 if (U_FAILURE(status
)) {
1011 if (localMatches
!= NULL
) {
1012 for (int32_t i
= 0; i
< localMatches
->size(); i
++) {
1013 int32_t len
= localMatches
->getMatchLength(i
);
1015 // TODO See the above TODO. We use len >= bestMatchLen
1016 // because of the long standard/location name collision
1017 // problem. If it is also a location name, carrying
1018 // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
1019 // problem in SimpleDateFormat
1020 if (len
>= bestMatchLen
) {
1021 bestMatchLen
= localMatches
->getMatchLength(i
);
1022 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
; // because generic
1023 localMatches
->getTimeZoneID(i
, bestMatchTzID
);
1026 delete localMatches
;
1029 if (bestMatchLen
> 0) {
1030 timeType
= bestMatchTimeType
;
1031 tzID
.setTo(bestMatchTzID
);
1033 return bestMatchLen
;
1036 TimeZoneGenericNameMatchInfo
*
1037 TZGNCore::findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1038 GNameSearchHandler
handler(types
);
1040 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
1044 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1046 umtx_unlock(&gLock
);
1048 if (U_FAILURE(status
)) {
1052 TimeZoneGenericNameMatchInfo
*gmatchInfo
= NULL
;
1055 UVector
*results
= handler
.getMatches(maxLen
);
1056 if (results
!= NULL
&& ((maxLen
== (text
.length() - start
)) || fGNamesTrieFullyLoaded
)) {
1058 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1059 if (gmatchInfo
== NULL
) {
1060 status
= U_MEMORY_ALLOCATION_ERROR
;
1067 if (results
!= NULL
) {
1071 // All names are not yet loaded into the local trie.
1072 // Load all available names into the trie. This could be very heavy.
1075 if (!fGNamesTrieFullyLoaded
) {
1076 StringEnumeration
*tzIDs
= TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL
, NULL
, NULL
, status
);
1077 if (U_SUCCESS(status
)) {
1078 const UnicodeString
*tzID
;
1079 while ((tzID
= tzIDs
->snext(status
))) {
1080 if (U_FAILURE(status
)) {
1083 nonConstThis
->loadStrings(*tzID
);
1086 if (tzIDs
!= NULL
) {
1090 if (U_SUCCESS(status
)) {
1091 nonConstThis
->fGNamesTrieFullyLoaded
= TRUE
;
1095 umtx_unlock(&gLock
);
1097 if (U_FAILURE(status
)) {
1104 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1106 umtx_unlock(&gLock
);
1108 results
= handler
.getMatches(maxLen
);
1109 if (results
!= NULL
&& maxLen
> 0) {
1110 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1111 if (gmatchInfo
== NULL
) {
1112 status
= U_MEMORY_ALLOCATION_ERROR
;
1121 TimeZoneNames::MatchInfoCollection
*
1122 TZGNCore::findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1123 // Check if the target name typs is really in the TimeZoneNames
1124 uint32_t nameTypes
= 0;
1125 if (types
& UTZGNM_LONG
) {
1126 nameTypes
|= (UTZNM_LONG_GENERIC
| UTZNM_LONG_STANDARD
);
1128 if (types
& UTZGNM_SHORT
) {
1129 nameTypes
|= (UTZNM_SHORT_GENERIC
| UTZNM_SHORT_STANDARD
);
1133 // Find matches in the TimeZoneNames
1134 return fTimeZoneNames
->find(text
, start
, nameTypes
, status
);
1140 typedef struct TZGNCoreRef
{
1146 // TZGNCore object cache handling
1147 static UMutex gTZGNLock
= U_MUTEX_INITIALIZER
;
1148 static UHashtable
*gTZGNCoreCache
= NULL
;
1149 static UBool gTZGNCoreCacheInitialized
= FALSE
;
1151 // Access count - incremented every time up to SWEEP_INTERVAL,
1153 static int32_t gAccessCount
= 0;
1155 // Interval for calling the cache sweep function - every 100 times
1156 #define SWEEP_INTERVAL 100
1158 // Cache expiration in millisecond. When a cached entry is no
1159 // longer referenced and exceeding this threshold since last
1160 // access time, then the cache entry will be deleted by the sweep
1161 // function. For now, 3 minutes.
1162 #define CACHE_EXPIRATION 180000.0
1166 * Cleanup callback func
1168 static UBool U_CALLCONV
tzgnCore_cleanup(void)
1170 if (gTZGNCoreCache
!= NULL
) {
1171 uhash_close(gTZGNCoreCache
);
1172 gTZGNCoreCache
= NULL
;
1174 gTZGNCoreCacheInitialized
= FALSE
;
1179 * Deleter for TZGNCoreRef
1181 static void U_CALLCONV
1182 deleteTZGNCoreRef(void *obj
) {
1183 icu::TZGNCoreRef
*entry
= (icu::TZGNCoreRef
*)obj
;
1184 delete (icu::TZGNCore
*) entry
->obj
;
1190 * Function used for removing unreferrenced cache entries exceeding
1191 * the expiration time. This function must be called with in the mutex
1194 static void sweepCache() {
1196 const UHashElement
* elem
;
1197 double now
= (double)uprv_getUTCtime();
1199 while ((elem
= uhash_nextElement(gTZGNCoreCache
, &pos
))) {
1200 TZGNCoreRef
*entry
= (TZGNCoreRef
*)elem
->value
.pointer
;
1201 if (entry
->refCount
<= 0 && (now
- entry
->lastAccess
) > CACHE_EXPIRATION
) {
1202 // delete this entry
1203 uhash_removeElement(gTZGNCoreCache
, elem
);
1208 TimeZoneGenericNames::TimeZoneGenericNames()
1212 TimeZoneGenericNames::~TimeZoneGenericNames() {
1213 umtx_lock(&gTZGNLock
);
1215 U_ASSERT(fRef
->refCount
> 0);
1216 // Just decrement the reference count
1219 umtx_unlock(&gTZGNLock
);
1222 TimeZoneGenericNames
*
1223 TimeZoneGenericNames::createInstance(const Locale
& locale
, UErrorCode
& status
) {
1224 if (U_FAILURE(status
)) {
1227 TimeZoneGenericNames
* instance
= new TimeZoneGenericNames();
1228 if (instance
== NULL
) {
1229 status
= U_MEMORY_ALLOCATION_ERROR
;
1234 UMTX_CHECK(&gTZGNLock
, gTZGNCoreCacheInitialized
, initialized
);
1236 // Create empty hashtable
1237 umtx_lock(&gTZGNLock
);
1239 if (!gTZGNCoreCacheInitialized
) {
1240 gTZGNCoreCache
= uhash_open(uhash_hashChars
, uhash_compareChars
, NULL
, &status
);
1241 if (U_SUCCESS(status
)) {
1242 uhash_setKeyDeleter(gTZGNCoreCache
, uprv_free
);
1243 uhash_setValueDeleter(gTZGNCoreCache
, deleteTZGNCoreRef
);
1244 gTZGNCoreCacheInitialized
= TRUE
;
1245 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES
, tzgnCore_cleanup
);
1249 umtx_unlock(&gTZGNLock
);
1251 if (U_FAILURE(status
)) {
1256 // Check the cache, if not available, create new one and cache
1257 TZGNCoreRef
*cacheEntry
= NULL
;
1258 umtx_lock(&gTZGNLock
);
1260 const char *key
= locale
.getName();
1261 cacheEntry
= (TZGNCoreRef
*)uhash_get(gTZGNCoreCache
, key
);
1262 if (cacheEntry
== NULL
) {
1263 TZGNCore
*tzgnCore
= NULL
;
1264 char *newKey
= NULL
;
1266 tzgnCore
= new TZGNCore(locale
, status
);
1267 if (tzgnCore
== NULL
) {
1268 status
= U_MEMORY_ALLOCATION_ERROR
;
1270 if (U_SUCCESS(status
)) {
1271 newKey
= (char *)uprv_malloc(uprv_strlen(key
) + 1);
1272 if (newKey
== NULL
) {
1273 status
= U_MEMORY_ALLOCATION_ERROR
;
1275 uprv_strcpy(newKey
, key
);
1278 if (U_SUCCESS(status
)) {
1279 cacheEntry
= (TZGNCoreRef
*)uprv_malloc(sizeof(TZGNCoreRef
));
1280 if (cacheEntry
== NULL
) {
1281 status
= U_MEMORY_ALLOCATION_ERROR
;
1283 cacheEntry
->obj
= tzgnCore
;
1284 cacheEntry
->refCount
= 1;
1285 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1287 uhash_put(gTZGNCoreCache
, newKey
, cacheEntry
, &status
);
1290 if (U_FAILURE(status
)) {
1291 if (tzgnCore
!= NULL
) {
1294 if (newKey
!= NULL
) {
1297 if (cacheEntry
!= NULL
) {
1298 uprv_free(cacheEntry
);
1303 // Update the reference count
1304 cacheEntry
->refCount
++;
1305 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1308 if (gAccessCount
>= SWEEP_INTERVAL
) {
1314 umtx_unlock(&gTZGNLock
);
1316 if (cacheEntry
== NULL
) {
1321 instance
->fRef
= cacheEntry
;
1326 TimeZoneGenericNames::operator==(const TimeZoneGenericNames
& other
) const {
1327 // Just compare if the other object also use the same
1329 return fRef
== other
.fRef
;
1332 TimeZoneGenericNames
*
1333 TimeZoneGenericNames::clone() const {
1334 TimeZoneGenericNames
* other
= new TimeZoneGenericNames();
1336 umtx_lock(&gTZGNLock
);
1338 // Just increments the reference count
1342 umtx_unlock(&gTZGNLock
);
1348 TimeZoneGenericNames::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
1349 UDate date
, UnicodeString
& name
) const {
1350 return fRef
->obj
->getDisplayName(tz
, type
, date
, name
);
1354 TimeZoneGenericNames::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
1355 return fRef
->obj
->getGenericLocationName(tzCanonicalID
, name
);
1359 TimeZoneGenericNames::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
1360 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
1361 return fRef
->obj
->findBestMatch(text
, start
, types
, tzID
, timeType
, status
);