2 *******************************************************************************
3 * Copyright (C) 2011-2012, 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 class TZGNCore
: public UMemory
{
272 TZGNCore(const Locale
& locale
, UErrorCode
& status
);
275 UnicodeString
& getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
276 UDate date
, UnicodeString
& name
) const;
278 UnicodeString
& getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const;
280 int32_t findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
281 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const;
286 const TimeZoneNames
* fTimeZoneNames
;
287 UHashtable
* fLocationNamesMap
;
288 UHashtable
* fPartialLocationNamesMap
;
290 MessageFormat
* fRegionFormat
;
291 MessageFormat
* fFallbackRegionFormat
;
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
)
335 fTimeZoneNames(NULL
),
336 fLocationNamesMap(NULL
),
337 fPartialLocationNamesMap(NULL
),
339 fFallbackRegionFormat(NULL
),
340 fFallbackFormat(NULL
),
341 fLocaleDisplayNames(NULL
),
343 fGNamesTrie(TRUE
, deleteGNameInfo
),
344 fGNamesTrieFullyLoaded(FALSE
) {
345 initialize(locale
, status
);
348 TZGNCore::~TZGNCore() {
350 umtx_destroy(&fLock
);
354 TZGNCore::initialize(const Locale
& locale
, UErrorCode
& status
) {
355 if (U_FAILURE(status
)) {
360 fTimeZoneNames
= TimeZoneNames::createInstance(locale
, status
);
361 if (U_FAILURE(status
)) {
365 // Initialize format patterns
366 UnicodeString
rpat(TRUE
, gDefRegionPattern
, -1);
367 UnicodeString
frpat(TRUE
, gDefFallbackRegionPattern
, -1);
368 UnicodeString
fpat(TRUE
, gDefFallbackPattern
, -1);
370 UErrorCode tmpsts
= U_ZERO_ERROR
; // OK with fallback warning..
371 UResourceBundle
*zoneStrings
= ures_open(U_ICUDATA_ZONE
, locale
.getName(), &tmpsts
);
372 zoneStrings
= ures_getByKeyWithFallback(zoneStrings
, gZoneStrings
, zoneStrings
, &tmpsts
);
374 if (U_SUCCESS(tmpsts
)) {
375 const UChar
*regionPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gRegionFormatTag
, NULL
, &tmpsts
);
376 if (U_SUCCESS(tmpsts
) && u_strlen(regionPattern
) > 0) {
377 rpat
.setTo(regionPattern
, -1);
379 tmpsts
= U_ZERO_ERROR
;
380 const UChar
*fallbackRegionPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gFallbackRegionFormatTag
, NULL
, &tmpsts
);
381 if (U_SUCCESS(tmpsts
) && u_strlen(fallbackRegionPattern
) > 0) {
382 frpat
.setTo(fallbackRegionPattern
, -1);
384 tmpsts
= U_ZERO_ERROR
;
385 const UChar
*fallbackPattern
= ures_getStringByKeyWithFallback(zoneStrings
, gFallbackFormatTag
, NULL
, &tmpsts
);
386 if (U_SUCCESS(tmpsts
) && u_strlen(fallbackPattern
) > 0) {
387 fpat
.setTo(fallbackPattern
, -1);
390 ures_close(zoneStrings
);
392 fRegionFormat
= new MessageFormat(rpat
, status
);
393 if (fRegionFormat
== NULL
) {
394 status
= U_MEMORY_ALLOCATION_ERROR
;
396 fFallbackRegionFormat
= new MessageFormat(frpat
, status
);
397 if (fFallbackRegionFormat
== NULL
) {
398 status
= U_MEMORY_ALLOCATION_ERROR
;
400 fFallbackFormat
= new MessageFormat(fpat
, status
);
401 if (fFallbackFormat
== NULL
) {
402 status
= U_MEMORY_ALLOCATION_ERROR
;
404 if (U_FAILURE(status
)) {
409 // locale display names
410 fLocaleDisplayNames
= LocaleDisplayNames::createInstance(locale
);
412 // hash table for names - no key/value deleters
413 fLocationNamesMap
= uhash_open(uhash_hashUChars
, uhash_compareUChars
, NULL
, &status
);
414 if (U_FAILURE(status
)) {
419 fPartialLocationNamesMap
= uhash_open(hashPartialLocationKey
, comparePartialLocationKey
, NULL
, &status
);
420 if (U_FAILURE(status
)) {
424 uhash_setKeyDeleter(fPartialLocationNamesMap
, uprv_free
);
428 const char* region
= fLocale
.getCountry();
429 int32_t regionLen
= uprv_strlen(region
);
430 if (regionLen
== 0) {
431 char loc
[ULOC_FULLNAME_CAPACITY
];
432 uloc_addLikelySubtags(fLocale
.getName(), loc
, sizeof(loc
), &status
);
434 regionLen
= uloc_getCountry(loc
, fTargetRegion
, sizeof(fTargetRegion
), &status
);
435 if (U_SUCCESS(status
)) {
436 fTargetRegion
[regionLen
] = 0;
441 } else if (regionLen
< (int32_t)sizeof(fTargetRegion
)) {
442 uprv_strcpy(fTargetRegion
, region
);
444 fTargetRegion
[0] = 0;
447 // preload generic names for the default zone
448 TimeZone
*tz
= TimeZone::createDefault();
449 const UChar
*tzID
= ZoneMeta::getCanonicalCLDRID(*tz
);
451 loadStrings(UnicodeString(tzID
));
457 TZGNCore::cleanup() {
458 if (fRegionFormat
!= NULL
) {
459 delete fRegionFormat
;
461 if (fFallbackRegionFormat
!= NULL
) {
462 delete fFallbackRegionFormat
;
464 if (fFallbackFormat
!= NULL
) {
465 delete fFallbackFormat
;
467 if (fLocaleDisplayNames
!= NULL
) {
468 delete fLocaleDisplayNames
;
470 if (fTimeZoneNames
!= NULL
) {
471 delete fTimeZoneNames
;
474 uhash_close(fLocationNamesMap
);
475 uhash_close(fPartialLocationNamesMap
);
480 TZGNCore::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
483 case UTZGNM_LOCATION
:
485 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
486 if (tzCanonicalID
!= NULL
) {
487 getGenericLocationName(UnicodeString(tzCanonicalID
), name
);
493 formatGenericNonLocationName(tz
, type
, date
, name
);
494 if (name
.isEmpty()) {
495 const UChar
* tzCanonicalID
= ZoneMeta::getCanonicalCLDRID(tz
);
496 if (tzCanonicalID
!= NULL
) {
497 getGenericLocationName(UnicodeString(tzCanonicalID
), name
);
508 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
509 if (tzCanonicalID
.isEmpty()) {
514 const UChar
*locname
= NULL
;
515 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
516 umtx_lock(&nonConstThis
->fLock
);
518 locname
= nonConstThis
->getGenericLocationName(tzCanonicalID
);
520 umtx_unlock(&nonConstThis
->fLock
);
522 if (locname
== NULL
) {
525 name
.setTo(locname
, u_strlen(locname
));
532 * This method updates the cache and must be called with a lock
535 TZGNCore::getGenericLocationName(const UnicodeString
& tzCanonicalID
) {
536 U_ASSERT(!tzCanonicalID
.isEmpty());
537 if (tzCanonicalID
.length() > ZID_KEY_MAX
) {
541 UErrorCode status
= U_ZERO_ERROR
;
542 UChar tzIDKey
[ZID_KEY_MAX
+ 1];
543 int32_t tzIDKeyLen
= tzCanonicalID
.extract(tzIDKey
, ZID_KEY_MAX
+ 1, status
);
544 U_ASSERT(status
== U_ZERO_ERROR
); // already checked length above
545 tzIDKey
[tzIDKeyLen
] = 0;
547 const UChar
*locname
= (const UChar
*)uhash_get(fLocationNamesMap
, tzIDKey
);
549 if (locname
!= NULL
) {
550 // gEmpty indicate the name is not available
551 if (locname
== gEmpty
) {
557 // Construct location name
559 UBool isSingleCountry
= FALSE
;
560 UnicodeString usCountryCode
;
561 ZoneMeta::getSingleCountry(tzCanonicalID
, usCountryCode
);
562 if (!usCountryCode
.isEmpty()) {
563 isSingleCountry
= TRUE
;
565 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
);
568 if (!usCountryCode
.isEmpty()) {
569 char countryCode
[ULOC_COUNTRY_CAPACITY
];
570 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
571 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
572 countryCode
[ccLen
] = 0;
574 UnicodeString country
;
575 fLocaleDisplayNames
->regionDisplayName(countryCode
, country
);
579 if (isSingleCountry
) {
580 // If the zone is only one zone in the country, do not add city
581 Formattable param
[] = {
584 fRegionFormat
->format(param
, 1, name
, fpos
, status
);
586 // getExemplarLocationName should retur non-empty string
587 // if the time zone is associated with a region
589 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, city
);
591 Formattable params
[] = {
595 fFallbackRegionFormat
->format(params
, 2, name
, fpos
, status
);
597 if (U_FAILURE(status
)) {
602 locname
= name
.isEmpty() ? NULL
: fStringPool
.get(name
, status
);
603 if (U_SUCCESS(status
)) {
605 const UChar
* cacheID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
606 U_ASSERT(cacheID
!= NULL
);
607 if (locname
== NULL
) {
608 // gEmpty to indicate - no location name available
609 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)gEmpty
, &status
);
611 uhash_put(fLocationNamesMap
, (void *)cacheID
, (void *)locname
, &status
);
612 if (U_FAILURE(status
)) {
615 // put the name info into the trie
616 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
617 if (nameinfo
!= NULL
) {
618 nameinfo
->type
= UTZGNM_LOCATION
;
619 nameinfo
->tzID
= cacheID
;
620 fGNamesTrie
.put(locname
, nameinfo
, status
);
630 TZGNCore::formatGenericNonLocationName(const TimeZone
& tz
, UTimeZoneGenericNameType type
, UDate date
, UnicodeString
& name
) const {
631 U_ASSERT(type
== UTZGNM_LONG
|| type
== UTZGNM_SHORT
);
634 const UChar
* uID
= ZoneMeta::getCanonicalCLDRID(tz
);
639 UnicodeString
tzID(uID
);
641 // Try to get a name from time zone first
642 UTimeZoneNameType nameType
= (type
== UTZGNM_LONG
) ? UTZNM_LONG_GENERIC
: UTZNM_SHORT_GENERIC
;
643 fTimeZoneNames
->getTimeZoneDisplayName(tzID
, nameType
, name
);
645 if (!name
.isEmpty()) {
651 fTimeZoneNames
->getMetaZoneID(tzID
, date
, mzID
);
652 if (!mzID
.isEmpty()) {
653 UErrorCode status
= U_ZERO_ERROR
;
654 UBool useStandard
= FALSE
;
657 tz
.getOffset(date
, FALSE
, raw
, sav
, status
);
658 if (U_FAILURE(status
)) {
665 TimeZone
*tmptz
= tz
.clone();
666 // Check if the zone actually uses daylight saving time around the time
667 BasicTimeZone
*btz
= NULL
;
668 if (dynamic_cast<OlsonTimeZone
*>(tmptz
) != NULL
669 || dynamic_cast<SimpleTimeZone
*>(tmptz
) != NULL
670 || dynamic_cast<RuleBasedTimeZone
*>(tmptz
) != NULL
671 || dynamic_cast<VTimeZone
*>(tmptz
) != NULL
) {
672 btz
= (BasicTimeZone
*)tmptz
;
676 TimeZoneTransition before
;
677 UBool beforTrs
= btz
->getPreviousTransition(date
, TRUE
, before
);
679 && (date
- before
.getTime() < kDstCheckRange
)
680 && before
.getFrom()->getDSTSavings() != 0) {
683 TimeZoneTransition after
;
684 UBool afterTrs
= btz
->getNextTransition(date
, FALSE
, after
);
686 && (after
.getTime() - date
< kDstCheckRange
)
687 && after
.getTo()->getDSTSavings() != 0) {
692 // If not BasicTimeZone... only if the instance is not an ICU's implementation.
693 // We may get a wrong answer in edge case, but it should practically work OK.
694 tmptz
->getOffset(date
- kDstCheckRange
, FALSE
, raw
, sav
, status
);
698 tmptz
->getOffset(date
+ kDstCheckRange
, FALSE
, raw
, sav
, status
);
703 if (U_FAILURE(status
)) {
711 UTimeZoneNameType stdNameType
= (nameType
== UTZNM_LONG_GENERIC
)
712 ? UTZNM_LONG_STANDARD
: UTZNM_SHORT_STANDARD
;
713 UnicodeString stdName
;
714 fTimeZoneNames
->getDisplayName(tzID
, stdNameType
, date
, stdName
);
715 if (!stdName
.isEmpty()) {
718 // TODO: revisit this issue later
719 // In CLDR, a same display name is used for both generic and standard
720 // for some meta zones in some locales. This looks like a data bugs.
721 // For now, we check if the standard name is different from its generic
723 UnicodeString mzGenericName
;
724 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzGenericName
);
725 if (stdName
.caseCompare(mzGenericName
, 0) == 0) {
730 if (name
.isEmpty()) {
731 // Get a name from meta zone
732 UnicodeString mzName
;
733 fTimeZoneNames
->getMetaZoneDisplayName(mzID
, nameType
, mzName
);
734 if (!mzName
.isEmpty()) {
735 // Check if we need to use a partial location format.
736 // This check is done by comparing offset with the meta zone's
737 // golden zone at the given date.
738 UnicodeString goldenID
;
739 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, goldenID
);
740 if (!goldenID
.isEmpty() && goldenID
!= tzID
) {
741 TimeZone
*goldenZone
= TimeZone::createTimeZone(goldenID
);
744 // Check offset in the golden zone with wall time.
745 // With getOffset(date, false, offsets1),
746 // you may get incorrect results because of time overlap at DST->STD
748 goldenZone
->getOffset(date
+ raw
+ sav
, TRUE
, raw1
, sav1
, status
);
750 if (U_SUCCESS(status
)) {
751 if (raw
!= raw1
|| sav
!= sav1
) {
752 // Now we need to use a partial location format
753 getPartialLocationName(tzID
, mzID
, (nameType
== UTZNM_LONG_GENERIC
), mzName
, name
);
768 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
769 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
,
770 UnicodeString
& name
) const {
772 if (tzCanonicalID
.isEmpty() || mzID
.isEmpty() || mzDisplayName
.isEmpty()) {
776 const UChar
*uplname
= NULL
;
777 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
778 umtx_lock(&nonConstThis
->fLock
);
780 uplname
= nonConstThis
->getPartialLocationName(tzCanonicalID
, mzID
, isLong
, mzDisplayName
);
782 umtx_unlock(&nonConstThis
->fLock
);
784 if (uplname
== NULL
) {
787 name
.setTo(TRUE
, uplname
, -1);
793 * This method updates the cache and must be called with a lock
796 TZGNCore::getPartialLocationName(const UnicodeString
& tzCanonicalID
,
797 const UnicodeString
& mzID
, UBool isLong
, const UnicodeString
& mzDisplayName
) {
798 U_ASSERT(!tzCanonicalID
.isEmpty());
799 U_ASSERT(!mzID
.isEmpty());
800 U_ASSERT(!mzDisplayName
.isEmpty());
802 PartialLocationKey key
;
803 key
.tzID
= ZoneMeta::findTimeZoneID(tzCanonicalID
);
804 key
.mzID
= ZoneMeta::findMetaZoneID(mzID
);
806 U_ASSERT(key
.tzID
!= NULL
&& key
.mzID
!= NULL
);
808 const UChar
* uplname
= (const UChar
*)uhash_get(fPartialLocationNamesMap
, (void *)&key
);
809 if (uplname
!= NULL
) {
813 UnicodeString location
;
814 UnicodeString usCountryCode
;
815 ZoneMeta::getCanonicalCountry(tzCanonicalID
, usCountryCode
);
816 if (!usCountryCode
.isEmpty()) {
817 char countryCode
[ULOC_COUNTRY_CAPACITY
];
818 U_ASSERT(usCountryCode
.length() < ULOC_COUNTRY_CAPACITY
);
819 int32_t ccLen
= usCountryCode
.extract(0, usCountryCode
.length(), countryCode
, sizeof(countryCode
), US_INV
);
820 countryCode
[ccLen
] = 0;
822 UnicodeString regionalGolden
;
823 fTimeZoneNames
->getReferenceZoneID(mzID
, countryCode
, regionalGolden
);
824 if (tzCanonicalID
== regionalGolden
) {
826 fLocaleDisplayNames
->regionDisplayName(countryCode
, location
);
828 // Otherwise, use exemplar city name
829 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
832 fTimeZoneNames
->getExemplarLocationName(tzCanonicalID
, location
);
833 if (location
.isEmpty()) {
834 // This could happen when the time zone is not associated with a country,
835 // and its ID is not hierarchical, for example, CST6CDT.
836 // We use the canonical ID itself as the location for this case.
837 location
.setTo(tzCanonicalID
);
841 UErrorCode status
= U_ZERO_ERROR
;
845 Formattable param
[] = {
846 Formattable(location
),
847 Formattable(mzDisplayName
)
849 fFallbackFormat
->format(param
, 2, name
, fpos
, status
);
850 if (U_FAILURE(status
)) {
854 uplname
= fStringPool
.get(name
, status
);
855 if (U_SUCCESS(status
)) {
856 // Add the name to cache
857 PartialLocationKey
* cacheKey
= (PartialLocationKey
*)uprv_malloc(sizeof(PartialLocationKey
));
858 if (cacheKey
!= NULL
) {
859 cacheKey
->tzID
= key
.tzID
;
860 cacheKey
->mzID
= key
.mzID
;
861 cacheKey
->isLong
= key
.isLong
;
862 uhash_put(fPartialLocationNamesMap
, (void *)cacheKey
, (void *)uplname
, &status
);
863 if (U_FAILURE(status
)) {
866 // put the name to the local trie as well
867 GNameInfo
*nameinfo
= (ZNameInfo
*)uprv_malloc(sizeof(GNameInfo
));
868 if (nameinfo
!= NULL
) {
869 nameinfo
->type
= isLong
? UTZGNM_LONG
: UTZGNM_SHORT
;
870 nameinfo
->tzID
= key
.tzID
;
871 fGNamesTrie
.put(uplname
, nameinfo
, status
);
880 * This method updates the cache and must be called with a lock,
881 * except initializer.
884 TZGNCore::loadStrings(const UnicodeString
& tzCanonicalID
) {
885 // load the generic location name
886 getGenericLocationName(tzCanonicalID
);
888 // partial location names
889 UErrorCode status
= U_ZERO_ERROR
;
891 const UnicodeString
*mzID
;
892 UnicodeString goldenID
;
893 UnicodeString mzGenName
;
894 UTimeZoneNameType genNonLocTypes
[] = {
895 UTZNM_LONG_GENERIC
, UTZNM_SHORT_GENERIC
,
896 UTZNM_UNKNOWN
/*terminator*/
899 StringEnumeration
*mzIDs
= fTimeZoneNames
->getAvailableMetaZoneIDs(tzCanonicalID
, status
);
900 while ((mzID
= mzIDs
->snext(status
))) {
901 if (U_FAILURE(status
)) {
904 // if this time zone is not the golden zone of the meta zone,
905 // partial location name (such as "PT (Los Angeles)") might be
907 fTimeZoneNames
->getReferenceZoneID(*mzID
, fTargetRegion
, goldenID
);
908 if (tzCanonicalID
!= goldenID
) {
909 for (int32_t i
= 0; genNonLocTypes
[i
] != UTZNM_UNKNOWN
; i
++) {
910 fTimeZoneNames
->getMetaZoneDisplayName(*mzID
, genNonLocTypes
[i
], mzGenName
);
911 if (!mzGenName
.isEmpty()) {
912 // getPartialLocationName formats a name and put it into the trie
913 getPartialLocationName(tzCanonicalID
, *mzID
,
914 (genNonLocTypes
[i
] == UTZNM_LONG_GENERIC
), mzGenName
);
925 TZGNCore::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
926 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
927 timeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
930 if (U_FAILURE(status
)) {
934 // Find matches in the TimeZoneNames first
935 TimeZoneNames::MatchInfoCollection
*tznamesMatches
= findTimeZoneNames(text
, start
, types
, status
);
936 if (U_FAILURE(status
)) {
940 int32_t bestMatchLen
= 0;
941 UTimeZoneFormatTimeType bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
942 UnicodeString bestMatchTzID
;
943 // UBool isLongStandard = FALSE; // workaround - see the comments below
944 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
946 if (tznamesMatches
!= NULL
) {
948 for (int32_t i
= 0; i
< tznamesMatches
->size(); i
++) {
949 int32_t len
= tznamesMatches
->getMatchLengthAt(i
);
950 if (len
> bestMatchLen
) {
952 if (!tznamesMatches
->getTimeZoneIDAt(i
, bestMatchTzID
)) {
953 // name for a meta zone
954 if (tznamesMatches
->getMetaZoneIDAt(i
, mzID
)) {
955 fTimeZoneNames
->getReferenceZoneID(mzID
, fTargetRegion
, bestMatchTzID
);
958 UTimeZoneNameType nameType
= tznamesMatches
->getNameTypeAt(i
);
959 if (U_FAILURE(status
)) {
963 case UTZNM_LONG_STANDARD
:
964 // isLongStandard = TRUE;
965 case UTZNM_SHORT_STANDARD
: // this one is never used for generic, but just in case
966 isStandard
= TRUE
; // TODO: Remove this later, see the comments above.
967 bestMatchTimeType
= UTZFMT_TIME_TYPE_STANDARD
;
969 case UTZNM_LONG_DAYLIGHT
:
970 case UTZNM_SHORT_DAYLIGHT
: // this one is never used for generic, but just in case
971 bestMatchTimeType
= UTZFMT_TIME_TYPE_DAYLIGHT
;
974 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
978 delete tznamesMatches
;
979 if (U_FAILURE(status
)) {
983 if (bestMatchLen
== (text
.length() - start
)) {
986 //tzID.setTo(bestMatchTzID);
987 //timeType = bestMatchTimeType;
988 //return bestMatchLen;
990 // TODO Some time zone uses a same name for the long standard name
991 // and the location name. When the match is a long standard name,
992 // then we need to check if the name is same with the location name.
993 // This is probably a data error or a design bug.
995 if (!isLongStandard) {
996 tzID.setTo(bestMatchTzID);
997 timeType = bestMatchTimeType;
1001 // TODO The deprecation of commonlyUsed flag introduced the name
1002 // conflict not only for long standard names, but short standard names too.
1003 // These short names (found in zh_Hant) should be gone once we clean
1004 // up CLDR time zone display name data. Once the short name conflict
1005 // problem (with location name) is resolved, we should change the condition
1006 // below back to the original one above. -Yoshito (2011-09-14)
1008 tzID
.setTo(bestMatchTzID
);
1009 timeType
= bestMatchTimeType
;
1010 return bestMatchLen
;
1015 // Find matches in the local trie
1016 TimeZoneGenericNameMatchInfo
*localMatches
= findLocal(text
, start
, types
, status
);
1017 if (U_FAILURE(status
)) {
1020 if (localMatches
!= NULL
) {
1021 for (int32_t i
= 0; i
< localMatches
->size(); i
++) {
1022 int32_t len
= localMatches
->getMatchLength(i
);
1024 // TODO See the above TODO. We use len >= bestMatchLen
1025 // because of the long standard/location name collision
1026 // problem. If it is also a location name, carrying
1027 // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
1028 // problem in SimpleDateFormat
1029 if (len
>= bestMatchLen
) {
1030 bestMatchLen
= localMatches
->getMatchLength(i
);
1031 bestMatchTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
; // because generic
1032 localMatches
->getTimeZoneID(i
, bestMatchTzID
);
1035 delete localMatches
;
1038 if (bestMatchLen
> 0) {
1039 timeType
= bestMatchTimeType
;
1040 tzID
.setTo(bestMatchTzID
);
1042 return bestMatchLen
;
1045 TimeZoneGenericNameMatchInfo
*
1046 TZGNCore::findLocal(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1047 GNameSearchHandler
handler(types
);
1049 TZGNCore
*nonConstThis
= const_cast<TZGNCore
*>(this);
1051 umtx_lock(&nonConstThis
->fLock
);
1053 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1055 umtx_unlock(&nonConstThis
->fLock
);
1057 if (U_FAILURE(status
)) {
1061 TimeZoneGenericNameMatchInfo
*gmatchInfo
= NULL
;
1064 UVector
*results
= handler
.getMatches(maxLen
);
1065 if (results
!= NULL
&& ((maxLen
== (text
.length() - start
)) || fGNamesTrieFullyLoaded
)) {
1067 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1068 if (gmatchInfo
== NULL
) {
1069 status
= U_MEMORY_ALLOCATION_ERROR
;
1076 if (results
!= NULL
) {
1080 // All names are not yet loaded into the local trie.
1081 // Load all available names into the trie. This could be very heavy.
1082 umtx_lock(&nonConstThis
->fLock
);
1084 if (!fGNamesTrieFullyLoaded
) {
1085 StringEnumeration
*tzIDs
= TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL
, NULL
, NULL
, status
);
1086 if (U_SUCCESS(status
)) {
1087 const UnicodeString
*tzID
;
1088 while ((tzID
= tzIDs
->snext(status
))) {
1089 if (U_FAILURE(status
)) {
1092 nonConstThis
->loadStrings(*tzID
);
1095 if (tzIDs
!= NULL
) {
1099 if (U_SUCCESS(status
)) {
1100 nonConstThis
->fGNamesTrieFullyLoaded
= TRUE
;
1104 umtx_unlock(&nonConstThis
->fLock
);
1106 if (U_FAILURE(status
)) {
1110 umtx_lock(&nonConstThis
->fLock
);
1113 fGNamesTrie
.search(text
, start
, (TextTrieMapSearchResultHandler
*)&handler
, status
);
1115 umtx_unlock(&nonConstThis
->fLock
);
1117 results
= handler
.getMatches(maxLen
);
1118 if (results
!= NULL
&& maxLen
> 0) {
1119 gmatchInfo
= new TimeZoneGenericNameMatchInfo(results
);
1120 if (gmatchInfo
== NULL
) {
1121 status
= U_MEMORY_ALLOCATION_ERROR
;
1130 TimeZoneNames::MatchInfoCollection
*
1131 TZGNCore::findTimeZoneNames(const UnicodeString
& text
, int32_t start
, uint32_t types
, UErrorCode
& status
) const {
1132 // Check if the target name typs is really in the TimeZoneNames
1133 uint32_t nameTypes
= 0;
1134 if (types
& UTZGNM_LONG
) {
1135 nameTypes
|= (UTZNM_LONG_GENERIC
| UTZNM_LONG_STANDARD
);
1137 if (types
& UTZGNM_SHORT
) {
1138 nameTypes
|= (UTZNM_SHORT_GENERIC
| UTZNM_SHORT_STANDARD
);
1142 // Find matches in the TimeZoneNames
1143 return fTimeZoneNames
->find(text
, start
, nameTypes
, status
);
1149 typedef struct TZGNCoreRef
{
1155 // TZGNCore object cache handling
1156 static UMTX gTZGNLock
= NULL
;
1157 static UHashtable
*gTZGNCoreCache
= NULL
;
1158 static UBool gTZGNCoreCacheInitialized
= FALSE
;
1160 // Access count - incremented every time up to SWEEP_INTERVAL,
1162 static int32_t gAccessCount
= 0;
1164 // Interval for calling the cache sweep function - every 100 times
1165 #define SWEEP_INTERVAL 100
1167 // Cache expiration in millisecond. When a cached entry is no
1168 // longer referenced and exceeding this threshold since last
1169 // access time, then the cache entry will be deleted by the sweep
1170 // function. For now, 3 minutes.
1171 #define CACHE_EXPIRATION 180000.0
1175 * Cleanup callback func
1177 static UBool U_CALLCONV
tzgnCore_cleanup(void)
1179 umtx_destroy(&gTZGNLock
);
1181 if (gTZGNCoreCache
!= NULL
) {
1182 uhash_close(gTZGNCoreCache
);
1183 gTZGNCoreCache
= NULL
;
1185 gTZGNCoreCacheInitialized
= FALSE
;
1190 * Deleter for TZGNCoreRef
1192 static void U_CALLCONV
1193 deleteTZGNCoreRef(void *obj
) {
1194 icu::TZGNCoreRef
*entry
= (icu::TZGNCoreRef
*)obj
;
1195 delete (icu::TZGNCore
*) entry
->obj
;
1201 * Function used for removing unreferrenced cache entries exceeding
1202 * the expiration time. This function must be called with in the mutex
1205 static void sweepCache() {
1207 const UHashElement
* elem
;
1208 double now
= (double)uprv_getUTCtime();
1210 while ((elem
= uhash_nextElement(gTZGNCoreCache
, &pos
))) {
1211 TZGNCoreRef
*entry
= (TZGNCoreRef
*)elem
->value
.pointer
;
1212 if (entry
->refCount
<= 0 && (now
- entry
->lastAccess
) > CACHE_EXPIRATION
) {
1213 // delete this entry
1214 uhash_removeElement(gTZGNCoreCache
, elem
);
1219 TimeZoneGenericNames::TimeZoneGenericNames()
1223 TimeZoneGenericNames::~TimeZoneGenericNames() {
1224 umtx_lock(&gTZGNLock
);
1226 U_ASSERT(fRef
->refCount
> 0);
1227 // Just decrement the reference count
1230 umtx_unlock(&gTZGNLock
);
1233 TimeZoneGenericNames
*
1234 TimeZoneGenericNames::createInstance(const Locale
& locale
, UErrorCode
& status
) {
1235 if (U_FAILURE(status
)) {
1238 TimeZoneGenericNames
* instance
= new TimeZoneGenericNames();
1239 if (instance
== NULL
) {
1240 status
= U_MEMORY_ALLOCATION_ERROR
;
1245 UMTX_CHECK(&gTZGNLock
, gTZGNCoreCacheInitialized
, initialized
);
1247 // Create empty hashtable
1248 umtx_lock(&gTZGNLock
);
1250 if (!gTZGNCoreCacheInitialized
) {
1251 gTZGNCoreCache
= uhash_open(uhash_hashChars
, uhash_compareChars
, NULL
, &status
);
1252 if (U_SUCCESS(status
)) {
1253 uhash_setKeyDeleter(gTZGNCoreCache
, uprv_free
);
1254 uhash_setValueDeleter(gTZGNCoreCache
, deleteTZGNCoreRef
);
1255 gTZGNCoreCacheInitialized
= TRUE
;
1256 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES
, tzgnCore_cleanup
);
1260 umtx_unlock(&gTZGNLock
);
1262 if (U_FAILURE(status
)) {
1267 // Check the cache, if not available, create new one and cache
1268 TZGNCoreRef
*cacheEntry
= NULL
;
1269 umtx_lock(&gTZGNLock
);
1271 const char *key
= locale
.getName();
1272 cacheEntry
= (TZGNCoreRef
*)uhash_get(gTZGNCoreCache
, key
);
1273 if (cacheEntry
== NULL
) {
1274 TZGNCore
*tzgnCore
= NULL
;
1275 char *newKey
= NULL
;
1277 tzgnCore
= new TZGNCore(locale
, status
);
1278 if (tzgnCore
== NULL
) {
1279 status
= U_MEMORY_ALLOCATION_ERROR
;
1281 if (U_SUCCESS(status
)) {
1282 newKey
= (char *)uprv_malloc(uprv_strlen(key
) + 1);
1283 if (newKey
== NULL
) {
1284 status
= U_MEMORY_ALLOCATION_ERROR
;
1286 uprv_strcpy(newKey
, key
);
1289 if (U_SUCCESS(status
)) {
1290 cacheEntry
= (TZGNCoreRef
*)uprv_malloc(sizeof(TZGNCoreRef
));
1291 if (cacheEntry
== NULL
) {
1292 status
= U_MEMORY_ALLOCATION_ERROR
;
1294 cacheEntry
->obj
= tzgnCore
;
1295 cacheEntry
->refCount
= 1;
1296 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1298 uhash_put(gTZGNCoreCache
, newKey
, cacheEntry
, &status
);
1301 if (U_FAILURE(status
)) {
1302 if (tzgnCore
!= NULL
) {
1305 if (newKey
!= NULL
) {
1308 if (cacheEntry
!= NULL
) {
1309 uprv_free(cacheEntry
);
1314 // Update the reference count
1315 cacheEntry
->refCount
++;
1316 cacheEntry
->lastAccess
= (double)uprv_getUTCtime();
1319 if (gAccessCount
>= SWEEP_INTERVAL
) {
1325 umtx_unlock(&gTZGNLock
);
1327 if (cacheEntry
== NULL
) {
1332 instance
->fRef
= cacheEntry
;
1337 TimeZoneGenericNames::operator==(const TimeZoneGenericNames
& other
) const {
1338 // Just compare if the other object also use the same
1340 return fRef
== other
.fRef
;
1343 TimeZoneGenericNames
*
1344 TimeZoneGenericNames::clone() const {
1345 TimeZoneGenericNames
* other
= new TimeZoneGenericNames();
1347 umtx_lock(&gTZGNLock
);
1349 // Just increments the reference count
1353 umtx_unlock(&gTZGNLock
);
1359 TimeZoneGenericNames::getDisplayName(const TimeZone
& tz
, UTimeZoneGenericNameType type
,
1360 UDate date
, UnicodeString
& name
) const {
1361 return fRef
->obj
->getDisplayName(tz
, type
, date
, name
);
1365 TimeZoneGenericNames::getGenericLocationName(const UnicodeString
& tzCanonicalID
, UnicodeString
& name
) const {
1366 return fRef
->obj
->getGenericLocationName(tzCanonicalID
, name
);
1370 TimeZoneGenericNames::findBestMatch(const UnicodeString
& text
, int32_t start
, uint32_t types
,
1371 UnicodeString
& tzID
, UTimeZoneFormatTimeType
& timeType
, UErrorCode
& status
) const {
1372 return fRef
->obj
->findBestMatch(text
, start
, types
, tzID
, timeType
, status
);