mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 09:05:45 +00:00
013fc50cd5
Bug 924839 - Remove a patch already part of ICU 52.1. See http://bugs.icu-project.org/trac/ticket/10283 but also note the relevant code was removed completely upstream. r=glandium * * * Bug 924839 - Remove another patch already part of ICU 52.1. See http://bugs.icu-project.org/trac/ticket/10290 for that. r=gaston * * * Bug 924839 - Remove another patch already in ICU 52.1. See http://bugs.icu-project.org/trac/ticket/10045 for more. r=Norbert * * * Bug 924839 - Remove another patch already applied upstream. See http://bugs.icu-project.org/trac/changeset/32937 for more. r=gaston * * * Bug 924839 - Update the ICU update script to update to 52.1, *without* applying any of our local patches. r=glandium * * * Bug 924839 - Make the ICU update script only do updating within intl/icu/source and nowhere else. r=glandium * * * Bug 924839 - Implement the changes that would be made by |cd intl/; ./update-icu.sh http://source.icu-project.org/repos/icu/icu/tags/release-52-1/;|, run with the prior changesets' changes made (thus not applying any of our local patches). These changes don't actually work without subsequent adjustments, but this provides a codebase upon which those adjustments can be made, for the purpose of generating local patches to be kept in intl/icu-patches/. rs=the-usual-suspects * * * Bug 924839 - Update the bug 899722 local patch to make runConfigureICU not override CC/CXX on BSD systems. r=gaston * * * Bug 924839 - Update the bug 724533 patch that makes ICU builds with MozillaBuild on Windows. r=glandium * * * Bug 924839 - Import an upstream patch fixing the genrb tool to properly handle the -R (--omitCollationRules) option. See http://bugs.icu-project.org/trac/ticket/10043 for the original bug report and a link to the ultimate upstream landing. r=Norbert * * * Bug 924839 - Import the upstream fix for http://bugs.icu-project.org/trac/ticket/10486 so that ICU with -DU_USING_ICU_NAMESPACE=0 will compile on Windows. r=Norbert * * * Bug 924839 - Adjust the update script to update ICU, then to apply all local patches (rather than skipping the second step). Thus if the update script is properly run, now, the final result should be no changes at all to the tree. NOT REVIEWED YET * * * Bug 924839 - Update jstests that depend on CLDR locale data to match CLDR 24. r=Norbert
1350 lines
43 KiB
C++
1350 lines
43 KiB
C++
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 2011-2013, 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/msgfmt.h"
|
|
#include "unicode/rbtz.h"
|
|
#include "unicode/simpletz.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;
|
|
|
|
MessageFormat* fRegionFormat;
|
|
MessageFormat* 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),
|
|
fRegionFormat(NULL),
|
|
fFallbackFormat(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 = new MessageFormat(rpat, status);
|
|
if (fRegionFormat == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
fFallbackFormat = new MessageFormat(fpat, status);
|
|
if (fFallbackFormat == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
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 = 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(tzID));
|
|
}
|
|
delete tz;
|
|
}
|
|
|
|
void
|
|
TZGNCore::cleanup() {
|
|
if (fRegionFormat != NULL) {
|
|
delete fRegionFormat;
|
|
}
|
|
if (fFallbackFormat != NULL) {
|
|
delete fFallbackFormat;
|
|
}
|
|
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(tzCanonicalID), 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(tzCanonicalID), 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()) {
|
|
FieldPosition fpos;
|
|
|
|
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);
|
|
|
|
Formattable param[] = {
|
|
Formattable(country)
|
|
};
|
|
|
|
fRegionFormat->format(param, 1, name, fpos, 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);
|
|
|
|
Formattable param[] = {
|
|
Formattable(city),
|
|
};
|
|
|
|
fRegionFormat->format(param, 1, name, fpos, 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(uID);
|
|
|
|
// 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
|
|
UnicodeString mzID;
|
|
fTimeZoneNames->getMetaZoneID(tzID, date, mzID);
|
|
if (!mzID.isEmpty()) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
UBool useStandard = FALSE;
|
|
int32_t raw, sav;
|
|
|
|
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;
|
|
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.
|
|
UnicodeString mzGenericName;
|
|
fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzGenericName);
|
|
if (stdName.caseCompare(mzGenericName, 0) == 0) {
|
|
name.setToBogus();
|
|
}
|
|
}
|
|
}
|
|
if (name.isEmpty()) {
|
|
// Get a name from meta zone
|
|
UnicodeString mzName;
|
|
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.
|
|
UnicodeString goldenID;
|
|
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;
|
|
|
|
FieldPosition fpos;
|
|
Formattable param[] = {
|
|
Formattable(location),
|
|
Formattable(mzDisplayName)
|
|
};
|
|
fFallbackFormat->format(param, 2, name, fpos, 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))) {
|
|
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))) {
|
|
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 = -1;
|
|
const UHashElement* elem;
|
|
double now = (double)uprv_getUTCtime();
|
|
|
|
while ((elem = uhash_nextElement(gTZGNCoreCache, &pos))) {
|
|
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
|