gecko-dev/intl/icu/source/i18n/tzgnames.cpp
Jeff Walden 013fc50cd5 Bug 924839 - Update our embedded ICU to 52.1, plus a very few local patches. r=lots of people, see subsequent lines in this commit message for the original subcomponents (merged together for landing), and the original bug for the original patch divisions
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
2013-11-12 16:23:48 -08:00

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