// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2011-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "tzgnames.h"
#include "unicode/basictz.h"
#include "unicode/locdspnm.h"
#include "unicode/rbtz.h"
#include "unicode/simpleformatter.h"
#include "unicode/simpletz.h"
#include "unicode/strenum.h"
#include "unicode/vtzone.h"
#include "cmemory.h"
#include "cstring.h"
#include "mutex.h"
#include "uhash.h"
#include "uassert.h"
#include "umutex.h"
#include "uresimp.h"
#include "ureslocs.h"
#include "zonemeta.h"
#include "tznames_impl.h"
#include "olsontz.h"
#include "ucln_in.h"
U_NAMESPACE_BEGIN
#define ZID_KEY_MAX 128
static const char gZoneStrings[] = "zoneStrings";
static const char gRegionFormatTag[] = "regionFormat";
static const char gFallbackFormatTag[] = "fallbackFormat";
static const UChar gEmpty[] = {0x00};
static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}"
static const UChar gDefFallbackPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
static const double kDstCheckRange = (double)184*U_MILLIS_PER_DAY;
U_CDECL_BEGIN
typedef struct PartialLocationKey {
const UChar* tzID;
const UChar* mzID;
UBool isLong;
} PartialLocationKey;
/**
* Hash function for partial location name hash key
*/
static int32_t U_CALLCONV
hashPartialLocationKey(const UHashTok key) {
// <tzID>&<mzID>#[L|S]
PartialLocationKey *p = (PartialLocationKey *)key.pointer;
UnicodeString str(p->tzID);
str.append((UChar)0x26)
.append(p->mzID, -1)
.append((UChar)0x23)
.append((UChar)(p->isLong ? 0x4C : 0x53));
return str.hashCode();
}
/**
* Comparer for partial location name hash key
*/
static UBool U_CALLCONV
comparePartialLocationKey(const UHashTok key1, const UHashTok key2) {
PartialLocationKey *p1 = (PartialLocationKey *)key1.pointer;
PartialLocationKey *p2 = (PartialLocationKey *)key2.pointer;
if (p1 == p2) {
return TRUE;
}
if (p1 == NULL || p2 == NULL) {
return FALSE;
}
// We just check identity of tzID/mzID
return (p1->tzID == p2->tzID && p1->mzID == p2->mzID && p1->isLong == p2->isLong);
}
/**
* Deleter for GNameInfo
*/
static void U_CALLCONV
deleteGNameInfo(void *obj) {
uprv_free(obj);
}
/**
* GNameInfo stores zone name information in the local trie
*/
typedef struct GNameInfo {
UTimeZoneGenericNameType type;
const UChar* tzID;
} ZNameInfo;
/**
* GMatchInfo stores zone name match information used by find method
*/
typedef struct GMatchInfo {
const GNameInfo* gnameInfo;
int32_t matchLength;
UTimeZoneFormatTimeType timeType;
} ZMatchInfo;
U_CDECL_END
// ---------------------------------------------------
// The class stores time zone generic name match information
// ---------------------------------------------------
class TimeZoneGenericNameMatchInfo : public UMemory {
public:
TimeZoneGenericNameMatchInfo(UVector* matches);
~TimeZoneGenericNameMatchInfo();
int32_t size() const;
UTimeZoneGenericNameType getGenericNameType(int32_t index) const;
int32_t getMatchLength(int32_t index) const;
UnicodeString& getTimeZoneID(int32_t index, UnicodeString& tzID) const;
private:
UVector* fMatches; // vector of MatchEntry
};
TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector* matches)
: fMatches(matches) {
}
TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() {
if (fMatches != NULL) {
delete fMatches;
}
}
int32_t
TimeZoneGenericNameMatchInfo::size() const {
if (fMatches == NULL) {
return 0;
}
return fMatches->size();
}
UTimeZoneGenericNameType
TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index) const {
GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index);
if (minfo != NULL) {
return static_cast<UTimeZoneGenericNameType>(minfo->gnameInfo->type);
}
return UTZGNM_UNKNOWN;
}
int32_t
TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index) const {
ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index);
if (minfo != NULL) {
return minfo->matchLength;
}
return -1;
}
UnicodeString&
TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index, UnicodeString& tzID) const {
GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index);
if (minfo != NULL && minfo->gnameInfo->tzID != NULL) {
tzID.setTo(TRUE, minfo->gnameInfo->tzID, -1);
} else {
tzID.setToBogus();
}
return tzID;
}
// ---------------------------------------------------
// GNameSearchHandler
// ---------------------------------------------------
class GNameSearchHandler : public TextTrieMapSearchResultHandler {
public:
GNameSearchHandler(uint32_t types);
virtual ~GNameSearchHandler();
UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status);
UVector* getMatches(int32_t& maxMatchLen);
private:
uint32_t fTypes;
UVector* fResults;
int32_t fMaxMatchLen;
};
GNameSearchHandler::GNameSearchHandler(uint32_t types)
: fTypes(types), fResults(NULL), fMaxMatchLen(0) {
}
GNameSearchHandler::~GNameSearchHandler() {
if (fResults != NULL) {
delete fResults;
}
}
UBool
GNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) {
if (U_FAILURE(status)) {
return FALSE;
}
if (node->hasValues()) {
int32_t valuesCount = node->countValues();
for (int32_t i = 0; i < valuesCount; i++) {
GNameInfo *nameinfo = (ZNameInfo *)node->getValue(i);
if (nameinfo == NULL) {
break;
}
if ((nameinfo->type & fTypes) != 0) {
// matches a requested type
if (fResults == NULL) {
fResults = new UVector(uprv_free, NULL, status);
if (fResults == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
}
}
if (U_SUCCESS(status)) {
U_ASSERT(fResults != NULL);
GMatchInfo *gmatch = (GMatchInfo *)uprv_malloc(sizeof(GMatchInfo));
if (gmatch == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
} else {
// add the match to the vector
gmatch->gnameInfo = nameinfo;
gmatch->matchLength = matchLength;
gmatch->timeType = UTZFMT_TIME_TYPE_UNKNOWN;
fResults->addElement(gmatch, status);
if (U_FAILURE(status)) {
uprv_free(gmatch);
} else {
if (matchLength > fMaxMatchLen) {
fMaxMatchLen = matchLength;
}
}
}
}
}
}
}
return TRUE;
}
UVector*
GNameSearchHandler::getMatches(int32_t& maxMatchLen) {
// give the ownership to the caller
UVector *results = fResults;
maxMatchLen = fMaxMatchLen;
// reset
fResults = NULL;
fMaxMatchLen = 0;
return results;
}
static UMutex gLock = U_MUTEX_INITIALIZER;
class TZGNCore : public UMemory {
public:
TZGNCore(const Locale& locale, UErrorCode& status);
virtual ~TZGNCore();
UnicodeString& getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type,
UDate date, UnicodeString& name) const;
UnicodeString& getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const;
int32_t findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const;
private:
Locale fLocale;
const TimeZoneNames* fTimeZoneNames;
UHashtable* fLocationNamesMap;
UHashtable* fPartialLocationNamesMap;
SimpleFormatter fRegionFormat;
SimpleFormatter fFallbackFormat;
LocaleDisplayNames* fLocaleDisplayNames;
ZNStringPool fStringPool;
TextTrieMap fGNamesTrie;
UBool fGNamesTrieFullyLoaded;
char fTargetRegion[ULOC_COUNTRY_CAPACITY];
void initialize(const Locale& locale, UErrorCode& status);
void cleanup();
void loadStrings(const UnicodeString& tzCanonicalID);
const UChar* getGenericLocationName(const UnicodeString& tzCanonicalID);
UnicodeString& formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type,
UDate date, UnicodeString& name) const;
UnicodeString& getPartialLocationName(const UnicodeString& tzCanonicalID,
const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName,
UnicodeString& name) const;
const UChar* getPartialLocationName(const UnicodeString& tzCanonicalID,
const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName);
TimeZoneGenericNameMatchInfo* findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
TimeZoneNames::MatchInfoCollection* findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
};
// ---------------------------------------------------
// TZGNCore - core implmentation of TimeZoneGenericNames
//
// TimeZoneGenericNames is parallel to TimeZoneNames,
// but handles run-time generated time zone names.
// This is the main part of this module.
// ---------------------------------------------------
TZGNCore::TZGNCore(const Locale& locale, UErrorCode& status)
: fLocale(locale),
fTimeZoneNames(NULL),
fLocationNamesMap(NULL),
fPartialLocationNamesMap(NULL),
fLocaleDisplayNames(NULL),
fStringPool(status),
fGNamesTrie(TRUE, deleteGNameInfo),
fGNamesTrieFullyLoaded(FALSE) {
initialize(locale, status);
}
TZGNCore::~TZGNCore() {
cleanup();
}
void
TZGNCore::initialize(const Locale& locale, UErrorCode& status) {
if (U_FAILURE(status)) {
return;
}
// TimeZoneNames
fTimeZoneNames = TimeZoneNames::createInstance(locale, status);
if (U_FAILURE(status)) {
return;
}
// Initialize format patterns
UnicodeString rpat(TRUE, gDefRegionPattern, -1);
UnicodeString fpat(TRUE, gDefFallbackPattern, -1);
UErrorCode tmpsts = U_ZERO_ERROR; // OK with fallback warning..
UResourceBundle *zoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts);
zoneStrings = ures_getByKeyWithFallback(zoneStrings, gZoneStrings, zoneStrings, &tmpsts);
if (U_SUCCESS(tmpsts)) {
const UChar *regionPattern = ures_getStringByKeyWithFallback(zoneStrings, gRegionFormatTag, NULL, &tmpsts);
if (U_SUCCESS(tmpsts) && u_strlen(regionPattern) > 0) {
rpat.setTo(regionPattern, -1);
}
tmpsts = U_ZERO_ERROR;
const UChar *fallbackPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackFormatTag, NULL, &tmpsts);
if (U_SUCCESS(tmpsts) && u_strlen(fallbackPattern) > 0) {
fpat.setTo(fallbackPattern, -1);
}
}
ures_close(zoneStrings);
fRegionFormat.applyPatternMinMaxArguments(rpat, 1, 1, status);
fFallbackFormat.applyPatternMinMaxArguments(fpat, 2, 2, status);
if (U_FAILURE(status)) {
cleanup();
return;
}
// locale display names
fLocaleDisplayNames = LocaleDisplayNames::createInstance(locale);
// hash table for names - no key/value deleters
fLocationNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status);
if (U_FAILURE(status)) {
cleanup();
return;
}
fPartialLocationNamesMap = uhash_open(hashPartialLocationKey, comparePartialLocationKey, NULL, &status);
if (U_FAILURE(status)) {
cleanup();
return;
}
uhash_setKeyDeleter(fPartialLocationNamesMap, uprv_free);
// no value deleter
// target region
const char* region = fLocale.getCountry();
int32_t regionLen = static_cast<int32_t>(uprv_strlen(region));
if (regionLen == 0) {
char loc[ULOC_FULLNAME_CAPACITY];
uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status);
regionLen = uloc_getCountry(loc, fTargetRegion, sizeof(fTargetRegion), &status);
if (U_SUCCESS(status)) {
fTargetRegion[regionLen] = 0;
} else {
cleanup();
return;
}
} else if (regionLen < (int32_t)sizeof(fTargetRegion)) {
uprv_strcpy(fTargetRegion, region);
} else {
fTargetRegion[0] = 0;
}
// preload generic names for the default zone
TimeZone *tz = TimeZone::createDefault();
const UChar *tzID = ZoneMeta::getCanonicalCLDRID(*tz);
if (tzID != NULL) {
loadStrings(UnicodeString(TRUE, tzID, -1));
}
delete tz;
}
void
TZGNCore::cleanup() {
if (fLocaleDisplayNames != NULL) {
delete fLocaleDisplayNames;
}
if (fTimeZoneNames != NULL) {
delete fTimeZoneNames;
}
uhash_close(fLocationNamesMap);
uhash_close(fPartialLocationNamesMap);
}
UnicodeString&
TZGNCore::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const {
name.setToBogus();
switch (type) {
case UTZGNM_LOCATION:
{
const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz);
if (tzCanonicalID != NULL) {
getGenericLocationName(UnicodeString(TRUE, tzCanonicalID, -1), name);
}
}
break;
case UTZGNM_LONG:
case UTZGNM_SHORT:
formatGenericNonLocationName(tz, type, date, name);
if (name.isEmpty()) {
const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz);
if (tzCanonicalID != NULL) {
getGenericLocationName(UnicodeString(TRUE, tzCanonicalID, -1), name);
}
}
break;
default:
break;
}
return name;
}
UnicodeString&
TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const {
if (tzCanonicalID.isEmpty()) {
name.setToBogus();
return name;
}
const UChar *locname = NULL;
TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
umtx_lock(&gLock);
{
locname = nonConstThis->getGenericLocationName(tzCanonicalID);
}
umtx_unlock(&gLock);
if (locname == NULL) {
name.setToBogus();
} else {
name.setTo(locname, u_strlen(locname));
}
return name;
}
/*
* This method updates the cache and must be called with a lock
*/
const UChar*
TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID) {
U_ASSERT(!tzCanonicalID.isEmpty());
if (tzCanonicalID.length() > ZID_KEY_MAX) {
return NULL;
}
UErrorCode status = U_ZERO_ERROR;
UChar tzIDKey[ZID_KEY_MAX + 1];
int32_t tzIDKeyLen = tzCanonicalID.extract(tzIDKey, ZID_KEY_MAX + 1, status);
U_ASSERT(status == U_ZERO_ERROR); // already checked length above
tzIDKey[tzIDKeyLen] = 0;
const UChar *locname = (const UChar *)uhash_get(fLocationNamesMap, tzIDKey);
if (locname != NULL) {
// gEmpty indicate the name is not available
if (locname == gEmpty) {
return NULL;
}
return locname;
}
// Construct location name
UnicodeString name;
UnicodeString usCountryCode;
UBool isPrimary = FALSE;
ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode, &isPrimary);
if (!usCountryCode.isEmpty()) {
if (isPrimary) {
// If this is the primary zone in the country, use the country name.
char countryCode[ULOC_COUNTRY_CAPACITY];
U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY);
int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV);
countryCode[ccLen] = 0;
UnicodeString country;
fLocaleDisplayNames->regionDisplayName(countryCode, country);
fRegionFormat.format(country, name, status);
} else {
// If this is not the primary zone in the country,
// use the exemplar city name.
// getExemplarLocationName should retur non-empty string
// if the time zone is associated with a region
UnicodeString city;
fTimeZoneNames->getExemplarLocationName(tzCanonicalID, city);
fRegionFormat.format(city, name, status);
}
if (U_FAILURE(status)) {
return NULL;
}
}
locname = name.isEmpty() ? NULL : fStringPool.get(name, status);
if (U_SUCCESS(status)) {
// Cache the result
const UChar* cacheID = ZoneMeta::findTimeZoneID(tzCanonicalID);
U_ASSERT(cacheID != NULL);
if (locname == NULL) {
// gEmpty to indicate - no location name available
uhash_put(fLocationNamesMap, (void *)cacheID, (void *)gEmpty, &status);
} else {
uhash_put(fLocationNamesMap, (void *)cacheID, (void *)locname, &status);
if (U_FAILURE(status)) {
locname = NULL;
} else {
// put the name info into the trie
GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo));
if (nameinfo != NULL) {
nameinfo->type = UTZGNM_LOCATION;
nameinfo->tzID = cacheID;
fGNamesTrie.put(locname, nameinfo, status);
}
}
}
}
return locname;
}
UnicodeString&
TZGNCore::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const {
U_ASSERT(type == UTZGNM_LONG || type == UTZGNM_SHORT);
name.setToBogus();
const UChar* uID = ZoneMeta::getCanonicalCLDRID(tz);
if (uID == NULL) {
return name;
}
UnicodeString tzID(TRUE, uID, -1);
// Try to get a name from time zone first
UTimeZoneNameType nameType = (type == UTZGNM_LONG) ? UTZNM_LONG_GENERIC : UTZNM_SHORT_GENERIC;
fTimeZoneNames->getTimeZoneDisplayName(tzID, nameType, name);
if (!name.isEmpty()) {
return name;
}
// Try meta zone
UChar mzIDBuf[32];
UnicodeString mzID(mzIDBuf, 0, UPRV_LENGTHOF(mzIDBuf));
fTimeZoneNames->getMetaZoneID(tzID, date, mzID);
if (!mzID.isEmpty()) {
UErrorCode status = U_ZERO_ERROR;
UBool useStandard = FALSE;
int32_t raw, sav;
UChar tmpNameBuf[ZONE_NAME_U16_MAX];
tz.getOffset(date, FALSE, raw, sav, status);
if (U_FAILURE(status)) {
return name;
}
if (sav == 0) {
useStandard = TRUE;
TimeZone *tmptz = tz.clone();
// Check if the zone actually uses daylight saving time around the time
BasicTimeZone *btz = NULL;
if (dynamic_cast<OlsonTimeZone *>(tmptz) != NULL
|| dynamic_cast<SimpleTimeZone *>(tmptz) != NULL
|| dynamic_cast<RuleBasedTimeZone *>(tmptz) != NULL
|| dynamic_cast<VTimeZone *>(tmptz) != NULL) {
btz = (BasicTimeZone*)tmptz;
}
if (btz != NULL) {
TimeZoneTransition before;
UBool beforTrs = btz->getPreviousTransition(date, TRUE, before);
if (beforTrs
&& (date - before.getTime() < kDstCheckRange)
&& before.getFrom()->getDSTSavings() != 0) {
useStandard = FALSE;
} else {
TimeZoneTransition after;
UBool afterTrs = btz->getNextTransition(date, FALSE, after);
if (afterTrs
&& (after.getTime() - date < kDstCheckRange)
&& after.getTo()->getDSTSavings() != 0) {
useStandard = FALSE;
}
}
} else {
// If not BasicTimeZone... only if the instance is not an ICU's implementation.
// We may get a wrong answer in edge case, but it should practically work OK.
tmptz->getOffset(date - kDstCheckRange, FALSE, raw, sav, status);
if (sav != 0) {
useStandard = FALSE;
} else {
tmptz->getOffset(date + kDstCheckRange, FALSE, raw, sav, status);
if (sav != 0){
useStandard = FALSE;
}
}
if (U_FAILURE(status)) {
delete tmptz;
return name;
}
}
delete tmptz;
}
if (useStandard) {
UTimeZoneNameType stdNameType = (nameType == UTZNM_LONG_GENERIC)
? UTZNM_LONG_STANDARD : UTZNM_SHORT_STANDARD;
UnicodeString stdName(tmpNameBuf, 0, UPRV_LENGTHOF(tmpNameBuf));
fTimeZoneNames->getDisplayName(tzID, stdNameType, date, stdName);
if (!stdName.isEmpty()) {
name.setTo(stdName);
// TODO: revisit this issue later
// In CLDR, a same display name is used for both generic and standard
// for some meta zones in some locales. This looks like a data bugs.
// For now, we check if the standard name is different from its generic
// name below.
UChar genNameBuf[ZONE_NAME_U16_MAX];
UnicodeString mzGenericName(genNameBuf, 0, UPRV_LENGTHOF(genNameBuf));
fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzGenericName);
if (stdName.caseCompare(mzGenericName, 0) == 0) {
name.setToBogus();
}
}
}
if (name.isEmpty()) {
// Get a name from meta zone
UnicodeString mzName(tmpNameBuf, 0, UPRV_LENGTHOF(tmpNameBuf));
fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzName);
if (!mzName.isEmpty()) {
// Check if we need to use a partial location format.
// This check is done by comparing offset with the meta zone's
// golden zone at the given date.
UChar idBuf[32];
UnicodeString goldenID(idBuf, 0, UPRV_LENGTHOF(idBuf));
fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, goldenID);
if (!goldenID.isEmpty() && goldenID != tzID) {
TimeZone *goldenZone = TimeZone::createTimeZone(goldenID);
int32_t raw1, sav1;
// Check offset in the golden zone with wall time.
// With getOffset(date, false, offsets1),
// you may get incorrect results because of time overlap at DST->STD
// transition.
goldenZone->getOffset(date + raw + sav, TRUE, raw1, sav1, status);
delete goldenZone;
if (U_SUCCESS(status)) {
if (raw != raw1 || sav != sav1) {
// Now we need to use a partial location format
getPartialLocationName(tzID, mzID, (nameType == UTZNM_LONG_GENERIC), mzName, name);
} else {
name.setTo(mzName);
}
}
} else {
name.setTo(mzName);
}
}
}
}
return name;
}
UnicodeString&
TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID,
const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName,
UnicodeString& name) const {
name.setToBogus();
if (tzCanonicalID.isEmpty() || mzID.isEmpty() || mzDisplayName.isEmpty()) {
return name;
}
const UChar *uplname = NULL;
TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
umtx_lock(&gLock);
{
uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName);
}
umtx_unlock(&gLock);
if (uplname == NULL) {
name.setToBogus();
} else {
name.setTo(TRUE, uplname, -1);
}
return name;
}
/*
* This method updates the cache and must be called with a lock
*/
const UChar*
TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID,
const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName) {
U_ASSERT(!tzCanonicalID.isEmpty());
U_ASSERT(!mzID.isEmpty());
U_ASSERT(!mzDisplayName.isEmpty());
PartialLocationKey key;
key.tzID = ZoneMeta::findTimeZoneID(tzCanonicalID);
key.mzID = ZoneMeta::findMetaZoneID(mzID);
key.isLong = isLong;
U_ASSERT(key.tzID != NULL && key.mzID != NULL);
const UChar* uplname = (const UChar*)uhash_get(fPartialLocationNamesMap, (void *)&key);
if (uplname != NULL) {
return uplname;
}
UnicodeString location;
UnicodeString usCountryCode;
ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode);
if (!usCountryCode.isEmpty()) {
char countryCode[ULOC_COUNTRY_CAPACITY];
U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY);
int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV);
countryCode[ccLen] = 0;
UnicodeString regionalGolden;
fTimeZoneNames->getReferenceZoneID(mzID, countryCode, regionalGolden);
if (tzCanonicalID == regionalGolden) {
// Use country name
fLocaleDisplayNames->regionDisplayName(countryCode, location);
} else {
// Otherwise, use exemplar city name
fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location);
}
} else {
fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location);
if (location.isEmpty()) {
// This could happen when the time zone is not associated with a country,
// and its ID is not hierarchical, for example, CST6CDT.
// We use the canonical ID itself as the location for this case.
location.setTo(tzCanonicalID);
}
}
UErrorCode status = U_ZERO_ERROR;
UnicodeString name;
fFallbackFormat.format(location, mzDisplayName, name, status);
if (U_FAILURE(status)) {
return NULL;
}
uplname = fStringPool.get(name, status);
if (U_SUCCESS(status)) {
// Add the name to cache
PartialLocationKey* cacheKey = (PartialLocationKey *)uprv_malloc(sizeof(PartialLocationKey));
if (cacheKey != NULL) {
cacheKey->tzID = key.tzID;
cacheKey->mzID = key.mzID;
cacheKey->isLong = key.isLong;
uhash_put(fPartialLocationNamesMap, (void *)cacheKey, (void *)uplname, &status);
if (U_FAILURE(status)) {
uprv_free(cacheKey);
} else {
// put the name to the local trie as well
GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo));
if (nameinfo != NULL) {
nameinfo->type = isLong ? UTZGNM_LONG : UTZGNM_SHORT;
nameinfo->tzID = key.tzID;
fGNamesTrie.put(uplname, nameinfo, status);
}
}
}
}
return uplname;
}
/*
* This method updates the cache and must be called with a lock,
* except initializer.
*/
void
TZGNCore::loadStrings(const UnicodeString& tzCanonicalID) {
// load the generic location name
getGenericLocationName(tzCanonicalID);
// partial location names
UErrorCode status = U_ZERO_ERROR;
const UnicodeString *mzID;
UnicodeString goldenID;
UnicodeString mzGenName;
UTimeZoneNameType genNonLocTypes[] = {
UTZNM_LONG_GENERIC, UTZNM_SHORT_GENERIC,
UTZNM_UNKNOWN /*terminator*/
};
StringEnumeration *mzIDs = fTimeZoneNames->getAvailableMetaZoneIDs(tzCanonicalID, status);
while ((mzID = mzIDs->snext(status)) != NULL) {
if (U_FAILURE(status)) {
break;
}
// if this time zone is not the golden zone of the meta zone,
// partial location name (such as "PT (Los Angeles)") might be
// available.
fTimeZoneNames->getReferenceZoneID(*mzID, fTargetRegion, goldenID);
if (tzCanonicalID != goldenID) {
for (int32_t i = 0; genNonLocTypes[i] != UTZNM_UNKNOWN; i++) {
fTimeZoneNames->getMetaZoneDisplayName(*mzID, genNonLocTypes[i], mzGenName);
if (!mzGenName.isEmpty()) {
// getPartialLocationName formats a name and put it into the trie
getPartialLocationName(tzCanonicalID, *mzID,
(genNonLocTypes[i] == UTZNM_LONG_GENERIC), mzGenName);
}
}
}
}
if (mzIDs != NULL) {
delete mzIDs;
}
}
int32_t
TZGNCore::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const {
timeType = UTZFMT_TIME_TYPE_UNKNOWN;
tzID.setToBogus();
if (U_FAILURE(status)) {
return 0;
}
// Find matches in the TimeZoneNames first
TimeZoneNames::MatchInfoCollection *tznamesMatches = findTimeZoneNames(text, start, types, status);
if (U_FAILURE(status)) {
return 0;
}
int32_t bestMatchLen = 0;
UTimeZoneFormatTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
UnicodeString bestMatchTzID;
// UBool isLongStandard = FALSE; // workaround - see the comments below
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
if (tznamesMatches != NULL) {
UnicodeString mzID;
for (int32_t i = 0; i < tznamesMatches->size(); i++) {
int32_t len = tznamesMatches->getMatchLengthAt(i);
if (len > bestMatchLen) {
bestMatchLen = len;
if (!tznamesMatches->getTimeZoneIDAt(i, bestMatchTzID)) {
// name for a meta zone
if (tznamesMatches->getMetaZoneIDAt(i, mzID)) {
fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, bestMatchTzID);
}
}
UTimeZoneNameType nameType = tznamesMatches->getNameTypeAt(i);
if (U_FAILURE(status)) {
break;
}
switch (nameType) {
case UTZNM_LONG_STANDARD:
// isLongStandard = TRUE;
case UTZNM_SHORT_STANDARD: // this one is never used for generic, but just in case
isStandard = TRUE; // TODO: Remove this later, see the comments above.
bestMatchTimeType = UTZFMT_TIME_TYPE_STANDARD;
break;
case UTZNM_LONG_DAYLIGHT:
case UTZNM_SHORT_DAYLIGHT: // this one is never used for generic, but just in case
bestMatchTimeType = UTZFMT_TIME_TYPE_DAYLIGHT;
break;
default:
bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
}
}
}
delete tznamesMatches;
if (U_FAILURE(status)) {
return 0;
}
if (bestMatchLen == (text.length() - start)) {
// Full match
//tzID.setTo(bestMatchTzID);
//timeType = bestMatchTimeType;
//return bestMatchLen;
// TODO Some time zone uses a same name for the long standard name
// and the location name. When the match is a long standard name,
// then we need to check if the name is same with the location name.
// This is probably a data error or a design bug.
/*
if (!isLongStandard) {
tzID.setTo(bestMatchTzID);
timeType = bestMatchTimeType;
return bestMatchLen;
}
*/
// TODO The deprecation of commonlyUsed flag introduced the name
// conflict not only for long standard names, but short standard names too.
// These short names (found in zh_Hant) should be gone once we clean
// up CLDR time zone display name data. Once the short name conflict
// problem (with location name) is resolved, we should change the condition
// below back to the original one above. -Yoshito (2011-09-14)
if (!isStandard) {
tzID.setTo(bestMatchTzID);
timeType = bestMatchTimeType;
return bestMatchLen;
}
}
}
// Find matches in the local trie
TimeZoneGenericNameMatchInfo *localMatches = findLocal(text, start, types, status);
if (U_FAILURE(status)) {
return 0;
}
if (localMatches != NULL) {
for (int32_t i = 0; i < localMatches->size(); i++) {
int32_t len = localMatches->getMatchLength(i);
// TODO See the above TODO. We use len >= bestMatchLen
// because of the long standard/location name collision
// problem. If it is also a location name, carrying
// timeType = UTZFMT_TIME_TYPE_STANDARD will cause a
// problem in SimpleDateFormat
if (len >= bestMatchLen) {
bestMatchLen = localMatches->getMatchLength(i);
bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; // because generic
localMatches->getTimeZoneID(i, bestMatchTzID);
}
}
delete localMatches;
}
if (bestMatchLen > 0) {
timeType = bestMatchTimeType;
tzID.setTo(bestMatchTzID);
}
return bestMatchLen;
}
TimeZoneGenericNameMatchInfo*
TZGNCore::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
GNameSearchHandler handler(types);
TZGNCore *nonConstThis = const_cast<TZGNCore *>(this);
umtx_lock(&gLock);
{
fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status);
}
umtx_unlock(&gLock);
if (U_FAILURE(status)) {
return NULL;
}
TimeZoneGenericNameMatchInfo *gmatchInfo = NULL;
int32_t maxLen = 0;
UVector *results = handler.getMatches(maxLen);
if (results != NULL && ((maxLen == (text.length() - start)) || fGNamesTrieFullyLoaded)) {
// perfect match
gmatchInfo = new TimeZoneGenericNameMatchInfo(results);
if (gmatchInfo == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
delete results;
return NULL;
}
return gmatchInfo;
}
if (results != NULL) {
delete results;
}
// All names are not yet loaded into the local trie.
// Load all available names into the trie. This could be very heavy.
umtx_lock(&gLock);
{
if (!fGNamesTrieFullyLoaded) {
StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
if (U_SUCCESS(status)) {
const UnicodeString *tzID;
while ((tzID = tzIDs->snext(status)) != NULL) {
if (U_FAILURE(status)) {
break;
}
nonConstThis->loadStrings(*tzID);
}
}
if (tzIDs != NULL) {
delete tzIDs;
}
if (U_SUCCESS(status)) {
nonConstThis->fGNamesTrieFullyLoaded = TRUE;
}
}
}
umtx_unlock(&gLock);
if (U_FAILURE(status)) {
return NULL;
}
umtx_lock(&gLock);
{
// now try it again
fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status);
}
umtx_unlock(&gLock);
results = handler.getMatches(maxLen);
if (results != NULL && maxLen > 0) {
gmatchInfo = new TimeZoneGenericNameMatchInfo(results);
if (gmatchInfo == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
delete results;
return NULL;
}
}
return gmatchInfo;
}
TimeZoneNames::MatchInfoCollection*
TZGNCore::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
// Check if the target name typs is really in the TimeZoneNames
uint32_t nameTypes = 0;
if (types & UTZGNM_LONG) {
nameTypes |= (UTZNM_LONG_GENERIC | UTZNM_LONG_STANDARD);
}
if (types & UTZGNM_SHORT) {
nameTypes |= (UTZNM_SHORT_GENERIC | UTZNM_SHORT_STANDARD);
}
if (types) {
// Find matches in the TimeZoneNames
return fTimeZoneNames->find(text, start, nameTypes, status);
}
return NULL;
}
typedef struct TZGNCoreRef {
TZGNCore* obj;
int32_t refCount;
double lastAccess;
} TZGNCoreRef;
// TZGNCore object cache handling
static UMutex gTZGNLock = U_MUTEX_INITIALIZER;
static UHashtable *gTZGNCoreCache = NULL;
static UBool gTZGNCoreCacheInitialized = FALSE;
// Access count - incremented every time up to SWEEP_INTERVAL,
// then reset to 0
static int32_t gAccessCount = 0;
// Interval for calling the cache sweep function - every 100 times
#define SWEEP_INTERVAL 100
// Cache expiration in millisecond. When a cached entry is no
// longer referenced and exceeding this threshold since last
// access time, then the cache entry will be deleted by the sweep
// function. For now, 3 minutes.
#define CACHE_EXPIRATION 180000.0
U_CDECL_BEGIN
/**
* Cleanup callback func
*/
static UBool U_CALLCONV tzgnCore_cleanup(void)
{
if (gTZGNCoreCache != NULL) {
uhash_close(gTZGNCoreCache);
gTZGNCoreCache = NULL;
}
gTZGNCoreCacheInitialized = FALSE;
return TRUE;
}
/**
* Deleter for TZGNCoreRef
*/
static void U_CALLCONV
deleteTZGNCoreRef(void *obj) {
icu::TZGNCoreRef *entry = (icu::TZGNCoreRef*)obj;
delete (icu::TZGNCore*) entry->obj;
uprv_free(entry);
}
U_CDECL_END
/**
* Function used for removing unreferrenced cache entries exceeding
* the expiration time. This function must be called with in the mutex
* block.
*/
static void sweepCache() {
int32_t pos = UHASH_FIRST;
const UHashElement* elem;
double now = (double)uprv_getUTCtime();
while ((elem = uhash_nextElement(gTZGNCoreCache, &pos)) != NULL) {
TZGNCoreRef *entry = (TZGNCoreRef *)elem->value.pointer;
if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
// delete this entry
uhash_removeElement(gTZGNCoreCache, elem);
}
}
}
TimeZoneGenericNames::TimeZoneGenericNames()
: fRef(0) {
}
TimeZoneGenericNames::~TimeZoneGenericNames() {
umtx_lock(&gTZGNLock);
{
U_ASSERT(fRef->refCount > 0);
// Just decrement the reference count
fRef->refCount--;
}
umtx_unlock(&gTZGNLock);
}
TimeZoneGenericNames*
TimeZoneGenericNames::createInstance(const Locale& locale, UErrorCode& status) {
if (U_FAILURE(status)) {
return NULL;
}
TimeZoneGenericNames* instance = new TimeZoneGenericNames();
if (instance == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
TZGNCoreRef *cacheEntry = NULL;
{
Mutex lock(&gTZGNLock);
if (!gTZGNCoreCacheInitialized) {
// Create empty hashtable
gTZGNCoreCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
if (U_SUCCESS(status)) {
uhash_setKeyDeleter(gTZGNCoreCache, uprv_free);
uhash_setValueDeleter(gTZGNCoreCache, deleteTZGNCoreRef);
gTZGNCoreCacheInitialized = TRUE;
ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES, tzgnCore_cleanup);
}
}
if (U_FAILURE(status)) {
return NULL;
}
// Check the cache, if not available, create new one and cache
const char *key = locale.getName();
cacheEntry = (TZGNCoreRef *)uhash_get(gTZGNCoreCache, key);
if (cacheEntry == NULL) {
TZGNCore *tzgnCore = NULL;
char *newKey = NULL;
tzgnCore = new TZGNCore(locale, status);
if (tzgnCore == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
}
if (U_SUCCESS(status)) {
newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
if (newKey == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
} else {
uprv_strcpy(newKey, key);
}
}
if (U_SUCCESS(status)) {
cacheEntry = (TZGNCoreRef *)uprv_malloc(sizeof(TZGNCoreRef));
if (cacheEntry == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
} else {
cacheEntry->obj = tzgnCore;
cacheEntry->refCount = 1;
cacheEntry->lastAccess = (double)uprv_getUTCtime();
uhash_put(gTZGNCoreCache, newKey, cacheEntry, &status);
}
}
if (U_FAILURE(status)) {
if (tzgnCore != NULL) {
delete tzgnCore;
}
if (newKey != NULL) {
uprv_free(newKey);
}
if (cacheEntry != NULL) {
uprv_free(cacheEntry);
}
cacheEntry = NULL;
}
} else {
// Update the reference count
cacheEntry->refCount++;
cacheEntry->lastAccess = (double)uprv_getUTCtime();
}
gAccessCount++;
if (gAccessCount >= SWEEP_INTERVAL) {
// sweep
sweepCache();
gAccessCount = 0;
}
} // End of mutex locked block
if (cacheEntry == NULL) {
delete instance;
return NULL;
}
instance->fRef = cacheEntry;
return instance;
}
UBool
TimeZoneGenericNames::operator==(const TimeZoneGenericNames& other) const {
// Just compare if the other object also use the same
// ref entry
return fRef == other.fRef;
}
TimeZoneGenericNames*
TimeZoneGenericNames::clone() const {
TimeZoneGenericNames* other = new TimeZoneGenericNames();
if (other) {
umtx_lock(&gTZGNLock);
{
// Just increments the reference count
fRef->refCount++;
other->fRef = fRef;
}
umtx_unlock(&gTZGNLock);
}
return other;
}
UnicodeString&
TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type,
UDate date, UnicodeString& name) const {
return fRef->obj->getDisplayName(tz, type, date, name);
}
UnicodeString&
TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const {
return fRef->obj->getGenericLocationName(tzCanonicalID, name);
}
int32_t
TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types,
UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const {
return fRef->obj->findBestMatch(text, start, types, tzID, timeType, status);
}
U_NAMESPACE_END
#endif