5a8942d749
X-SVN-Rev: 23460
1631 lines
58 KiB
C++
1631 lines
58 KiB
C++
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 2007-2008, International Business Machines Corporation and *
|
|
* others. All Rights Reserved. *
|
|
*******************************************************************************
|
|
*/
|
|
|
|
#include "unicode/utypes.h"
|
|
|
|
#if !UCONFIG_NO_FORMATTING
|
|
|
|
#include "zstrfmt.h"
|
|
|
|
#include "unicode/ustring.h"
|
|
#include "unicode/putil.h"
|
|
#include "unicode/msgfmt.h"
|
|
#include "unicode/basictz.h"
|
|
#include "unicode/simpletz.h"
|
|
#include "unicode/rbtz.h"
|
|
#include "unicode/vtzone.h"
|
|
|
|
#include "uvector.h"
|
|
#include "cstring.h"
|
|
#include "cmemory.h"
|
|
#include "uresimp.h"
|
|
#include "zonemeta.h"
|
|
#include "olsontz.h"
|
|
#include "umutex.h"
|
|
#include "ucln_in.h"
|
|
|
|
/**
|
|
* global ZoneStringFormatCache stuffs
|
|
*/
|
|
static UMTX gZSFCacheLock = NULL;
|
|
static U_NAMESPACE_QUALIFIER ZSFCache *gZoneStringFormatCache = NULL;
|
|
|
|
U_CDECL_BEGIN
|
|
/**
|
|
* ZoneStringFormatCache cleanup callback func
|
|
*/
|
|
static UBool U_CALLCONV zoneStringFormat_cleanup(void)
|
|
{
|
|
umtx_destroy(&gZSFCacheLock);
|
|
if (gZoneStringFormatCache != NULL) {
|
|
delete gZoneStringFormatCache;
|
|
gZoneStringFormatCache = NULL;
|
|
}
|
|
gZoneStringFormatCache = NULL;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Deleter for ZoneStringInfo
|
|
*/
|
|
static void U_CALLCONV
|
|
deleteZoneStringInfo(void *obj) {
|
|
delete (U_NAMESPACE_QUALIFIER ZoneStringInfo*)obj;
|
|
}
|
|
|
|
/**
|
|
* Deleter for ZoneStrings
|
|
*/
|
|
static void U_CALLCONV
|
|
deleteZoneStrings(void *obj) {
|
|
delete (U_NAMESPACE_QUALIFIER ZoneStrings*)obj;
|
|
}
|
|
U_CDECL_END
|
|
|
|
U_NAMESPACE_BEGIN
|
|
|
|
#define ZID_KEY_MAX 128
|
|
|
|
static const char gCountriesTag[] = "Countries";
|
|
static const char gZoneStringsTag[] = "zoneStrings";
|
|
static const char gShortGenericTag[] = "sg";
|
|
static const char gShortStandardTag[] = "ss";
|
|
static const char gShortDaylightTag[] = "sd";
|
|
static const char gLongGenericTag[] = "lg";
|
|
static const char gLongStandardTag[] = "ls";
|
|
static const char gLongDaylightTag[] = "ld";
|
|
static const char gExemplarCityTag[] = "ec";
|
|
static const char gCommonlyUsedTag[] = "cu";
|
|
static const char gFallbackFormatTag[] = "fallbackFormat";
|
|
static const char gRegionFormatTag[] = "regionFormat";
|
|
|
|
#define MZID_PREFIX_LEN 5
|
|
static const char gMetazoneIdPrefix[] = "meta:";
|
|
|
|
#define MAX_METAZONES_PER_ZONE 10
|
|
|
|
static const UChar gDefFallbackPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})"
|
|
static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}"
|
|
static const UChar gCommonlyUsedTrue[] = {0x31, 0x00}; // "1"
|
|
|
|
static const double kDstCheckRange = (double)184*U_MILLIS_PER_DAY;
|
|
|
|
static int32_t
|
|
getTimeZoneTranslationTypeIndex(TimeZoneTranslationType type) {
|
|
int32_t typeIdx = 0;
|
|
switch (type) {
|
|
case LOCATION:
|
|
typeIdx = ZSIDX_LOCATION;
|
|
break;
|
|
case GENERIC_LONG:
|
|
typeIdx = ZSIDX_LONG_GENERIC;
|
|
break;
|
|
case GENERIC_SHORT:
|
|
typeIdx = ZSIDX_SHORT_GENERIC;
|
|
break;
|
|
case STANDARD_LONG:
|
|
typeIdx = ZSIDX_LONG_STANDARD;
|
|
break;
|
|
case STANDARD_SHORT:
|
|
typeIdx = ZSIDX_SHORT_STANDARD;
|
|
break;
|
|
case DAYLIGHT_LONG:
|
|
typeIdx = ZSIDX_LONG_DAYLIGHT;
|
|
break;
|
|
case DAYLIGHT_SHORT:
|
|
typeIdx = ZSIDX_SHORT_DAYLIGHT;
|
|
break;
|
|
}
|
|
return typeIdx;
|
|
}
|
|
|
|
static int32_t
|
|
getTimeZoneTranslationType(TimeZoneTranslationTypeIndex typeIdx) {
|
|
int32_t type = 0;
|
|
switch (typeIdx) {
|
|
case ZSIDX_LOCATION:
|
|
type = LOCATION;
|
|
break;
|
|
case ZSIDX_LONG_GENERIC:
|
|
type = GENERIC_LONG;
|
|
break;
|
|
case ZSIDX_SHORT_GENERIC:
|
|
type = GENERIC_SHORT;
|
|
break;
|
|
case ZSIDX_LONG_STANDARD:
|
|
type = STANDARD_LONG;
|
|
break;
|
|
case ZSIDX_SHORT_STANDARD:
|
|
type = STANDARD_SHORT;
|
|
break;
|
|
case ZSIDX_LONG_DAYLIGHT:
|
|
type = DAYLIGHT_LONG;
|
|
break;
|
|
case ZSIDX_SHORT_DAYLIGHT:
|
|
type = DAYLIGHT_SHORT;
|
|
break;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
#define DEFAULT_CHARACTERNODE_CAPACITY 1
|
|
|
|
// ----------------------------------------------------------------------------
|
|
CharacterNode::CharacterNode(UChar32 c, UErrorCode &status)
|
|
: fChildren(deleteZoneStringInfo, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status),
|
|
fValues(deleteZoneStringInfo, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status),
|
|
fCharacter(c)
|
|
{
|
|
}
|
|
|
|
CharacterNode::~CharacterNode() {
|
|
while (!fChildren.isEmpty()) {
|
|
CharacterNode *node = (CharacterNode*)fChildren.orphanElementAt(0);
|
|
delete node;
|
|
}
|
|
}
|
|
|
|
void
|
|
CharacterNode::addValue(void *value, UErrorCode &status) {
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
fValues.addElement(value, status);
|
|
}
|
|
|
|
CharacterNode*
|
|
CharacterNode::addChildNode(UChar32 c, UErrorCode &status) {
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
CharacterNode *result = NULL;
|
|
int32_t childrenNum = fChildren.size();
|
|
for (int32_t i = 0; i < childrenNum; i++) {
|
|
CharacterNode *node = (CharacterNode*)fChildren.elementAt(i);
|
|
if (node->getCharacter() == c) {
|
|
result = node;
|
|
break;
|
|
}
|
|
}
|
|
if (result == NULL) {
|
|
result = new CharacterNode(c, status);
|
|
fChildren.addElement(result, status);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
CharacterNode*
|
|
CharacterNode::getChildNode(UChar32 c) const {
|
|
CharacterNode *result = NULL;
|
|
int32_t childrenNum = fChildren.size();
|
|
for (int32_t i = 0; i < childrenNum; i++) {
|
|
CharacterNode *node = (CharacterNode*)fChildren.elementAt(i);
|
|
if (node->getCharacter() == c) {
|
|
result = node;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
TextTrieMap::TextTrieMap(UBool ignoreCase)
|
|
: fIgnoreCase(ignoreCase), fRoot(NULL) {
|
|
}
|
|
|
|
TextTrieMap::~TextTrieMap() {
|
|
if (fRoot != NULL) {
|
|
delete fRoot;
|
|
}
|
|
}
|
|
|
|
void
|
|
TextTrieMap::put(const UnicodeString &key, void *value, UErrorCode &status) {
|
|
if (fRoot == NULL) {
|
|
fRoot = new CharacterNode(0, status);
|
|
}
|
|
|
|
UnicodeString keyString(key);
|
|
if (fIgnoreCase) {
|
|
keyString.foldCase();
|
|
}
|
|
|
|
CharacterNode *node = fRoot;
|
|
int32_t index = 0;
|
|
while (index < keyString.length()) {
|
|
UChar32 c = keyString.char32At(index);
|
|
node = node->addChildNode(c, status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
index = keyString.moveIndex32(index, 1);
|
|
}
|
|
node->addValue(value, status);
|
|
}
|
|
|
|
void
|
|
TextTrieMap::search(const UnicodeString &text, int32_t start,
|
|
TextTrieMapSearchResultHandler *handler, UErrorCode &status) const {
|
|
if (fRoot == NULL) {
|
|
return;
|
|
}
|
|
search(fRoot, text, start, start, handler, status);
|
|
}
|
|
|
|
void
|
|
TextTrieMap::search(CharacterNode *node, const UnicodeString &text, int32_t start,
|
|
int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const {
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
const UVector *values = node->getValues();
|
|
if (values != NULL) {
|
|
if (!handler->handleMatch(index - start, values, status)) {
|
|
return;
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
}
|
|
UChar32 c = text.char32At(index);
|
|
if (fIgnoreCase) {
|
|
// size of character may grow after fold operation
|
|
UnicodeString tmp(c);
|
|
tmp.foldCase();
|
|
int32_t tmpidx = 0;
|
|
while (tmpidx < tmp.length()) {
|
|
c = tmp.char32At(tmpidx);
|
|
node = node->getChildNode(c);
|
|
if (node == NULL) {
|
|
break;
|
|
}
|
|
tmpidx = tmp.moveIndex32(tmpidx, 1);
|
|
}
|
|
} else {
|
|
node = node->getChildNode(c);
|
|
}
|
|
if (node != NULL) {
|
|
search(node, text, start, index+1, handler, status);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
ZoneStringInfo::ZoneStringInfo(const UnicodeString &id, const UnicodeString &str,
|
|
TimeZoneTranslationType type)
|
|
: fId(id), fStr(str), fType(type) {
|
|
}
|
|
|
|
ZoneStringInfo::~ZoneStringInfo() {
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
ZoneStringSearchResultHandler::ZoneStringSearchResultHandler(UErrorCode &status)
|
|
: fResults(status)
|
|
{
|
|
clear();
|
|
}
|
|
|
|
ZoneStringSearchResultHandler::~ZoneStringSearchResultHandler() {
|
|
clear();
|
|
}
|
|
|
|
UBool
|
|
ZoneStringSearchResultHandler::handleMatch(int32_t matchLength, const UVector *values, UErrorCode &status) {
|
|
if (U_FAILURE(status)) {
|
|
return FALSE;
|
|
}
|
|
if (values != NULL) {
|
|
for (int32_t i = 0; values->size(); i++) {
|
|
ZoneStringInfo *zsinfo = (ZoneStringInfo*)values->elementAt(i);
|
|
if (zsinfo == NULL) {
|
|
break;
|
|
}
|
|
// Update the results
|
|
UBool foundType = FALSE;
|
|
for (int32_t j = 0; j < fResults.size(); j++) {
|
|
ZoneStringInfo *tmp = (ZoneStringInfo*)fResults.elementAt(j);
|
|
if (zsinfo->fType == tmp->fType) {
|
|
int32_t lenidx = getTimeZoneTranslationTypeIndex(tmp->fType);
|
|
if (matchLength > fMatchLen[lenidx]) {
|
|
// Same type, longer match
|
|
fResults.setElementAt(zsinfo, j);
|
|
fMatchLen[lenidx] = matchLength;
|
|
}
|
|
foundType = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundType) {
|
|
// not found in the current list
|
|
fResults.addElement(zsinfo, status);
|
|
fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)] = matchLength;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
int32_t
|
|
ZoneStringSearchResultHandler::countMatches(void) {
|
|
return fResults.size();
|
|
}
|
|
|
|
const ZoneStringInfo*
|
|
ZoneStringSearchResultHandler::getMatch(int32_t index, int32_t &matchLength) {
|
|
ZoneStringInfo *zsinfo = NULL;
|
|
if (index < fResults.size()) {
|
|
zsinfo = (ZoneStringInfo*)fResults.elementAt(index);
|
|
matchLength = fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)];
|
|
}
|
|
return zsinfo;
|
|
}
|
|
|
|
void
|
|
ZoneStringSearchResultHandler::clear(void) {
|
|
fResults.removeAllElements();
|
|
for (int32_t i = 0; i < (int32_t)(sizeof(fMatchLen)/sizeof(fMatchLen[0])); i++) {
|
|
fMatchLen[i] = 0;
|
|
}
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
ZoneStringFormat::ZoneStringFormat(const UnicodeString* const* strings,
|
|
int32_t rowCount, int32_t columnCount, UErrorCode &status)
|
|
: fLocale(""),
|
|
fTzidToStrings(uhash_compareUnicodeString, NULL, status),
|
|
fMzidToStrings(uhash_compareUnicodeString, NULL, status),
|
|
fZoneStringsTrie(TRUE)
|
|
{
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
fLocale.setToBogus();
|
|
if (strings == NULL || columnCount <= 0 || rowCount <= 0) {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
|
|
fTzidToStrings.setValueDeleter(deleteZoneStrings);
|
|
|
|
for (int32_t row = 0; row < rowCount; row++) {
|
|
if (strings[row][0].isEmpty()) {
|
|
continue;
|
|
}
|
|
UnicodeString *names = new UnicodeString[ZSIDX_COUNT];
|
|
for (int32_t col = 1; col < columnCount; col++) {
|
|
if (!strings[row][col].isEmpty()) {
|
|
int32_t typeIdx = -1;
|
|
switch (col) {
|
|
case 1:
|
|
typeIdx = ZSIDX_LONG_STANDARD;
|
|
break;
|
|
case 2:
|
|
typeIdx = ZSIDX_SHORT_STANDARD;
|
|
break;
|
|
case 3:
|
|
typeIdx = ZSIDX_LONG_DAYLIGHT;
|
|
break;
|
|
case 4:
|
|
typeIdx = ZSIDX_SHORT_DAYLIGHT;
|
|
break;
|
|
case 5:
|
|
typeIdx = ZSIDX_LOCATION;
|
|
break;
|
|
case 6:
|
|
typeIdx = ZSIDX_LONG_GENERIC;
|
|
break;
|
|
case 7:
|
|
typeIdx = ZSIDX_SHORT_GENERIC;
|
|
break;
|
|
}
|
|
if (typeIdx != -1) {
|
|
names[typeIdx].setTo(strings[row][col]);
|
|
|
|
// Put the name into the trie
|
|
int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeIdx);
|
|
ZoneStringInfo *zsinf = new ZoneStringInfo(strings[row][0], strings[row][col], (TimeZoneTranslationType)type);
|
|
fZoneStringsTrie.put(strings[row][col], zsinf, status);
|
|
if (U_FAILURE(status)) {
|
|
delete zsinf;
|
|
goto error_cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ZoneStrings *zstrings = new ZoneStrings(names, ZSIDX_COUNT, TRUE, NULL, 0, 0);
|
|
fTzidToStrings.put(strings[row][0], zstrings, status);
|
|
if (U_FAILURE(status)) {
|
|
delete zstrings;
|
|
goto error_cleanup;
|
|
}
|
|
}
|
|
return;
|
|
|
|
error_cleanup:
|
|
return;
|
|
}
|
|
|
|
ZoneStringFormat::ZoneStringFormat(const Locale &locale, UErrorCode &status)
|
|
: fLocale(locale),
|
|
fTzidToStrings(uhash_compareUnicodeString, NULL, status),
|
|
fMzidToStrings(uhash_compareUnicodeString, NULL, status),
|
|
fZoneStringsTrie(TRUE)
|
|
{
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
fTzidToStrings.setValueDeleter(deleteZoneStrings);
|
|
fMzidToStrings.setValueDeleter(deleteZoneStrings);
|
|
|
|
UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status);
|
|
zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status);
|
|
if (U_FAILURE(status)) {
|
|
// If no locale bundles are available, zoneStrings will be null.
|
|
// We still want to go through the rest of zone strings initialization,
|
|
// because generic location format is generated from tzid for the case.
|
|
// The rest of code should work even zoneStrings is null.
|
|
status = U_ZERO_ERROR;
|
|
ures_close(zoneStringsArray);
|
|
zoneStringsArray = NULL;
|
|
}
|
|
|
|
StringEnumeration *tzids = NULL;
|
|
MessageFormat *fallbackFmt = NULL;
|
|
MessageFormat *regionFmt = NULL;
|
|
|
|
UResourceBundle *zoneItem = NULL;
|
|
UResourceBundle *metazoneItem = NULL;
|
|
|
|
char zidkey[ZID_KEY_MAX];
|
|
const UChar *zstrarray[ZSIDX_COUNT];
|
|
const UChar *mzstrarray[ZSIDX_COUNT];
|
|
UnicodeString mzPartialLoc[MAX_METAZONES_PER_ZONE][4];
|
|
|
|
UnicodeString region;
|
|
getRegion(region);
|
|
|
|
fallbackFmt = getFallbackFormat(locale, status);
|
|
if (U_FAILURE(status)) {
|
|
goto error_cleanup;
|
|
}
|
|
regionFmt = getRegionFormat(locale, status);
|
|
if (U_FAILURE(status)) {
|
|
goto error_cleanup;
|
|
}
|
|
|
|
tzids = TimeZone::createEnumeration();
|
|
const char *tzid;
|
|
while ((tzid = tzids->next(NULL, status))) {
|
|
if (U_FAILURE(status)) {
|
|
goto error_cleanup;
|
|
}
|
|
// Skip non-canonical IDs
|
|
UnicodeString utzid(tzid, -1, US_INV);
|
|
UnicodeString canonicalID;
|
|
TimeZone::getCanonicalID(utzid, canonicalID, status);
|
|
if (U_FAILURE(status)) {
|
|
// Ignore unknown ID - we should not get here, but just in case.
|
|
status = U_ZERO_ERROR;
|
|
continue;
|
|
}
|
|
if (utzid != canonicalID) {
|
|
continue;
|
|
}
|
|
|
|
uprv_strcpy(zidkey, tzid);
|
|
|
|
// Replace '/' with ':'
|
|
char *pCity = NULL;
|
|
char *p = zidkey;
|
|
while (*p) {
|
|
if (*p == '/') {
|
|
*p = ':';
|
|
pCity = p + 1;
|
|
}
|
|
p++;
|
|
}
|
|
|
|
if (zoneStringsArray != NULL) {
|
|
zoneItem = ures_getByKeyWithFallback(zoneStringsArray, zidkey, zoneItem, &status);
|
|
if (U_FAILURE(status)) {
|
|
// If failed to open the zone item, create only location string
|
|
ures_close(zoneItem);
|
|
zoneItem = NULL;
|
|
status = U_ZERO_ERROR;
|
|
}
|
|
}
|
|
zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(zoneItem, gLongStandardTag);
|
|
zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(zoneItem, gShortStandardTag);
|
|
zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(zoneItem, gLongDaylightTag);
|
|
zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(zoneItem, gShortDaylightTag);
|
|
zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(zoneItem, gLongGenericTag);
|
|
zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(zoneItem, gShortGenericTag);
|
|
|
|
// Compose location format string
|
|
UnicodeString location;
|
|
UnicodeString country;
|
|
UnicodeString city;
|
|
UnicodeString countryCode;
|
|
ZoneMeta::getCanonicalCountry(utzid, countryCode);
|
|
if (countryCode.isEmpty()) {
|
|
zstrarray[ZSIDX_LOCATION] = NULL;
|
|
} else {
|
|
const UChar* tmpCity = getZoneStringFromBundle(zoneItem, gExemplarCityTag);
|
|
if (tmpCity != NULL) {
|
|
city.setTo(TRUE, tmpCity, -1);
|
|
} else {
|
|
city.setTo(UnicodeString(pCity, -1, US_INV));
|
|
// Replace '_' with ' '
|
|
for (int32_t i = 0; i < city.length(); i++) {
|
|
if (city.charAt(i) == (UChar)0x5F /*'_'*/) {
|
|
city.setCharAt(i, (UChar)0x20 /*' '*/);
|
|
}
|
|
}
|
|
}
|
|
getLocalizedCountry(countryCode, locale, country);
|
|
UnicodeString singleCountry;
|
|
ZoneMeta::getSingleCountry(utzid, singleCountry);
|
|
FieldPosition fpos;
|
|
if (singleCountry.isEmpty()) {
|
|
Formattable params [] = {
|
|
Formattable(city),
|
|
Formattable(country)
|
|
};
|
|
fallbackFmt->format(params, 2, location, fpos, status);
|
|
} else {
|
|
// If the zone is only one zone in the country, do not add city
|
|
Formattable params [] = {
|
|
Formattable(country)
|
|
};
|
|
regionFmt->format(params, 1, location, fpos, status);
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
goto error_cleanup;
|
|
}
|
|
|
|
// Workaround for reducing UMR warning in Purify.
|
|
// Append NULL before calling getTerminatedBuffer()
|
|
int32_t locLen = location.length();
|
|
location.append((UChar)0).truncate(locLen);
|
|
|
|
zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer();
|
|
}
|
|
|
|
UBool commonlyUsed = isCommonlyUsed(zoneItem);
|
|
|
|
// Resolve metazones used by this zone
|
|
int32_t mzPartialLocIdx = 0;
|
|
const UVector *metazoneMappings = ZoneMeta::getMetazoneMappings(utzid);
|
|
if (metazoneMappings != NULL) {
|
|
for (int32_t i = 0; i < metazoneMappings->size(); i++) {
|
|
const OlsonToMetaMappingEntry *mzmap = (const OlsonToMetaMappingEntry*)metazoneMappings->elementAt(i);
|
|
UnicodeString mzid(mzmap->mzid);
|
|
const ZoneStrings *mzStrings = (const ZoneStrings*)fMzidToStrings.get(mzid);
|
|
if (mzStrings == NULL) {
|
|
// If the metazone strings are not yet processed, do it now.
|
|
char mzidkey[ZID_KEY_MAX];
|
|
uprv_strcpy(mzidkey, gMetazoneIdPrefix);
|
|
u_UCharsToChars(mzmap->mzid, mzidkey + MZID_PREFIX_LEN, u_strlen(mzmap->mzid) + 1);
|
|
metazoneItem = ures_getByKeyWithFallback(zoneStringsArray, mzidkey, metazoneItem, &status);
|
|
if (U_FAILURE(status)) {
|
|
// No resources available for this metazone
|
|
// Resource bundle will be cleaned up after end of the loop.
|
|
status = U_ZERO_ERROR;
|
|
continue;
|
|
}
|
|
UBool mzCommonlyUsed = isCommonlyUsed(metazoneItem);
|
|
mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(metazoneItem, gLongStandardTag);
|
|
mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(metazoneItem, gShortStandardTag);
|
|
mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(metazoneItem, gLongDaylightTag);
|
|
mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(metazoneItem, gShortDaylightTag);
|
|
mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(metazoneItem, gLongGenericTag);
|
|
mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(metazoneItem, gShortGenericTag);
|
|
mzstrarray[ZSIDX_LOCATION] = NULL;
|
|
|
|
int32_t lastNonNullIdx = ZSIDX_COUNT - 1;
|
|
while (lastNonNullIdx >= 0) {
|
|
if (mzstrarray[lastNonNullIdx] != NULL) {
|
|
break;
|
|
}
|
|
lastNonNullIdx--;
|
|
}
|
|
UnicodeString *strings_mz = NULL;
|
|
ZoneStrings *tmp_mzStrings = NULL;
|
|
if (lastNonNullIdx >= 0) {
|
|
// Create UnicodeString array and put strings to the zone string trie
|
|
strings_mz = new UnicodeString[lastNonNullIdx + 1];
|
|
|
|
UnicodeString preferredIdForLocale;
|
|
ZoneMeta::getZoneIdByMetazone(mzid, region, preferredIdForLocale);
|
|
|
|
for (int32_t typeidx = 0; typeidx <= lastNonNullIdx; typeidx++) {
|
|
if (mzstrarray[typeidx] != NULL) {
|
|
strings_mz[typeidx].setTo(TRUE, mzstrarray[typeidx], -1);
|
|
|
|
// Add a metazone string to the zone string trie
|
|
int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeidx);
|
|
ZoneStringInfo *zsinfo = new ZoneStringInfo(preferredIdForLocale, strings_mz[typeidx], (TimeZoneTranslationType)type);
|
|
fZoneStringsTrie.put(strings_mz[typeidx], zsinfo, status);
|
|
if (U_FAILURE(status)) {
|
|
delete []strings_mz;
|
|
goto error_cleanup;
|
|
}
|
|
}
|
|
}
|
|
tmp_mzStrings = new ZoneStrings(strings_mz, lastNonNullIdx + 1, mzCommonlyUsed, NULL, 0, 0);
|
|
} else {
|
|
// Create ZoneStrings with empty contents
|
|
tmp_mzStrings = new ZoneStrings(NULL, 0, FALSE, NULL, 0, 0);
|
|
}
|
|
|
|
fMzidToStrings.put(mzid, tmp_mzStrings, status);
|
|
if (U_FAILURE(status)) {
|
|
goto error_cleanup;
|
|
}
|
|
|
|
mzStrings = tmp_mzStrings;
|
|
}
|
|
|
|
// Compose generic partial location format
|
|
UnicodeString lg;
|
|
UnicodeString sg;
|
|
|
|
mzStrings->getString(ZSIDX_LONG_GENERIC, lg);
|
|
mzStrings->getString(ZSIDX_SHORT_GENERIC, sg);
|
|
|
|
if (!lg.isEmpty() || !sg.isEmpty()) {
|
|
UBool addMzPartialLocationNames = TRUE;
|
|
for (int32_t j = 0; j < mzPartialLocIdx; j++) {
|
|
if (mzPartialLoc[j][0] == mzid) {
|
|
// already processed
|
|
addMzPartialLocationNames = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if (addMzPartialLocationNames) {
|
|
UnicodeString *locationPart = NULL;
|
|
// Check if the zone is the preferred zone for the territory associated with the zone
|
|
UnicodeString preferredID;
|
|
ZoneMeta::getZoneIdByMetazone(mzid, countryCode, preferredID);
|
|
if (utzid == preferredID) {
|
|
// Use country for the location
|
|
locationPart = &country;
|
|
} else {
|
|
// Use city for the location
|
|
locationPart = &city;
|
|
}
|
|
// Reset the partial location string array
|
|
mzPartialLoc[mzPartialLocIdx][0].setTo(mzid);
|
|
mzPartialLoc[mzPartialLocIdx][1].remove();
|
|
mzPartialLoc[mzPartialLocIdx][2].remove();
|
|
mzPartialLoc[mzPartialLocIdx][3].remove();
|
|
|
|
if (locationPart != NULL) {
|
|
FieldPosition fpos;
|
|
if (!lg.isEmpty()) {
|
|
Formattable params [] = {
|
|
Formattable(*locationPart),
|
|
Formattable(lg)
|
|
};
|
|
fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][1], fpos, status);
|
|
}
|
|
if (!sg.isEmpty()) {
|
|
Formattable params [] = {
|
|
Formattable(*locationPart),
|
|
Formattable(sg)
|
|
};
|
|
fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][2], fpos, status);
|
|
if (mzStrings->isShortFormatCommonlyUsed()) {
|
|
mzPartialLoc[mzPartialLocIdx][3].setTo(TRUE, gCommonlyUsedTrue, -1);
|
|
}
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
goto error_cleanup;
|
|
}
|
|
}
|
|
mzPartialLocIdx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Collected names for a zone
|
|
|
|
// Create UnicodeString array for localized zone strings
|
|
int32_t lastIdx = ZSIDX_COUNT - 1;
|
|
while (lastIdx >= 0) {
|
|
if (zstrarray[lastIdx] != NULL) {
|
|
break;
|
|
}
|
|
lastIdx--;
|
|
}
|
|
UnicodeString *strings = NULL;
|
|
int32_t stringsCount = lastIdx + 1;
|
|
|
|
if (stringsCount > 0) {
|
|
strings = new UnicodeString[stringsCount];
|
|
for (int32_t i = 0; i < stringsCount; i++) {
|
|
if (zstrarray[i] != NULL) {
|
|
strings[i].setTo(zstrarray[i], -1);
|
|
|
|
// Add names to the trie
|
|
int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)i);
|
|
ZoneStringInfo *zsinfo = new ZoneStringInfo(utzid, strings[i], (TimeZoneTranslationType)type);
|
|
fZoneStringsTrie.put(strings[i], zsinfo, status);
|
|
if (U_FAILURE(status)) {
|
|
delete zsinfo;
|
|
delete[] strings;
|
|
goto error_cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create UnicodeString array for generic partial location strings
|
|
UnicodeString **genericPartialLocationNames = NULL;
|
|
int32_t genericPartialRowCount = mzPartialLocIdx;
|
|
int32_t genericPartialColCount = 4;
|
|
|
|
if (genericPartialRowCount != 0) {
|
|
genericPartialLocationNames = (UnicodeString**)uprv_malloc(genericPartialRowCount * sizeof(UnicodeString*));
|
|
if (genericPartialLocationNames == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
delete[] strings;
|
|
goto error_cleanup;
|
|
}
|
|
for (int32_t i = 0; i < genericPartialRowCount; i++) {
|
|
genericPartialLocationNames[i] = new UnicodeString[genericPartialColCount];
|
|
for (int32_t j = 0; j < genericPartialColCount; j++) {
|
|
genericPartialLocationNames[i][j].setTo(mzPartialLoc[i][j]);
|
|
// Add names to the trie
|
|
if ((j == 1 || j == 2) &&!genericPartialLocationNames[i][j].isEmpty()) {
|
|
ZoneStringInfo *zsinfo;
|
|
TimeZoneTranslationType type = (j == 1) ? GENERIC_LONG : GENERIC_SHORT;
|
|
zsinfo = new ZoneStringInfo(utzid, genericPartialLocationNames[i][j], type);
|
|
fZoneStringsTrie.put(genericPartialLocationNames[i][j], zsinfo, status);
|
|
if (U_FAILURE(status)) {
|
|
delete[] genericPartialLocationNames[i];
|
|
uprv_free(genericPartialLocationNames);
|
|
delete[] strings;
|
|
goto error_cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally, create ZoneStrings instance and put it into the tzidToStinrgs map
|
|
ZoneStrings *zstrings = new ZoneStrings(strings, stringsCount, commonlyUsed,
|
|
genericPartialLocationNames, genericPartialRowCount, genericPartialColCount);
|
|
|
|
fTzidToStrings.put(utzid, zstrings, status);
|
|
if (U_FAILURE(status)) {
|
|
delete zstrings;
|
|
goto error_cleanup;
|
|
}
|
|
}
|
|
|
|
error_cleanup:
|
|
if (fallbackFmt != NULL) {
|
|
delete fallbackFmt;
|
|
}
|
|
if (regionFmt != NULL) {
|
|
delete regionFmt;
|
|
}
|
|
if (tzids != NULL) {
|
|
delete tzids;
|
|
}
|
|
ures_close(zoneItem);
|
|
ures_close(metazoneItem);
|
|
ures_close(zoneStringsArray);
|
|
}
|
|
|
|
ZoneStringFormat::~ZoneStringFormat() {
|
|
}
|
|
|
|
SafeZoneStringFormatPtr*
|
|
ZoneStringFormat::getZoneStringFormat(const Locale& locale, UErrorCode &status) {
|
|
umtx_lock(&gZSFCacheLock);
|
|
if (gZoneStringFormatCache == NULL) {
|
|
gZoneStringFormatCache = new ZSFCache(10 /* capacity */);
|
|
ucln_i18n_registerCleanup(UCLN_I18N_ZSFORMAT, zoneStringFormat_cleanup);
|
|
}
|
|
umtx_unlock(&gZSFCacheLock);
|
|
|
|
return gZoneStringFormatCache->get(locale, status);
|
|
}
|
|
|
|
|
|
UnicodeString**
|
|
ZoneStringFormat::createZoneStringsArray(UDate date, int32_t &rowCount, int32_t &colCount, UErrorCode &status) const {
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
UnicodeString **result = NULL;
|
|
rowCount = 0;
|
|
colCount = 0;
|
|
|
|
// Collect canonical time zone IDs
|
|
UVector canonicalIDs(uhash_deleteUnicodeString, uhash_compareUnicodeString, status);
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
StringEnumeration *tzids = TimeZone::createEnumeration();
|
|
const UChar *tzid;
|
|
while ((tzid = tzids->unext(NULL, status))) {
|
|
if (U_FAILURE(status)) {
|
|
delete tzids;
|
|
return NULL;
|
|
}
|
|
UnicodeString utzid(tzid);
|
|
UnicodeString canonicalID;
|
|
TimeZone::getCanonicalID(UnicodeString(tzid), canonicalID, status);
|
|
if (U_FAILURE(status)) {
|
|
// Ignore unknown ID - we should not get here, but just in case.
|
|
status = U_ZERO_ERROR;
|
|
continue;
|
|
}
|
|
if (utzid == canonicalID) {
|
|
canonicalIDs.addElement(new UnicodeString(utzid), status);
|
|
if (U_FAILURE(status)) {
|
|
delete tzids;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
delete tzids;
|
|
|
|
// Allocate array
|
|
result = (UnicodeString**)uprv_malloc(canonicalIDs.size() * sizeof(UnicodeString*));
|
|
if (result == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
for (int32_t i = 0; i < canonicalIDs.size(); i++) {
|
|
result[i] = new UnicodeString[8];
|
|
UnicodeString *id = (UnicodeString*)canonicalIDs.elementAt(i);
|
|
result[i][0].setTo(*id);
|
|
getLongStandard(*id, date, result[i][1]);
|
|
getShortStandard(*id, date, FALSE, result[i][2]);
|
|
getLongDaylight(*id, date, result[i][3]);
|
|
getShortDaylight(*id, date, FALSE, result[i][4]);
|
|
getGenericLocation(*id, result[i][5]);
|
|
getLongGenericNonLocation(*id, date, result[i][6]);
|
|
getShortGenericNonLocation(*id, date, FALSE, result[i][7]);
|
|
}
|
|
|
|
rowCount = canonicalIDs.size();
|
|
colCount = 8;
|
|
return result;
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getSpecificLongString(const Calendar &cal, UnicodeString &result,
|
|
UErrorCode &status) const {
|
|
result.remove();
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
UnicodeString tzid;
|
|
cal.getTimeZone().getID(tzid);
|
|
UDate date = cal.getTime(status);
|
|
if (cal.get(UCAL_DST_OFFSET, status) == 0) {
|
|
return getString(tzid, ZSIDX_LONG_STANDARD, date, FALSE /*not used*/, result);
|
|
} else {
|
|
return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, FALSE /*not used*/, result);
|
|
}
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getSpecificShortString(const Calendar &cal, UBool commonlyUsedOnly,
|
|
UnicodeString &result, UErrorCode &status) const {
|
|
result.remove();
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
UnicodeString tzid;
|
|
cal.getTimeZone().getID(tzid);
|
|
UDate date = cal.getTime(status);
|
|
if (cal.get(UCAL_DST_OFFSET, status) == 0) {
|
|
return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly, result);
|
|
} else {
|
|
return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly, result);
|
|
}
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getGenericLongString(const Calendar &cal, UnicodeString &result,
|
|
UErrorCode &status) const {
|
|
return getGenericString(cal, FALSE /*long*/, FALSE /* not used */, result, status);
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getGenericShortString(const Calendar &cal, UBool commonlyUsedOnly,
|
|
UnicodeString &result, UErrorCode &status) const {
|
|
return getGenericString(cal, TRUE /*short*/, commonlyUsedOnly, result, status);
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getGenericLocationString(const Calendar &cal, UnicodeString &result,
|
|
UErrorCode &status) const {
|
|
UnicodeString tzid;
|
|
cal.getTimeZone().getID(tzid);
|
|
UDate date = cal.getTime(status);
|
|
return getString(tzid, ZSIDX_LOCATION, date, FALSE /*not used*/, result);
|
|
}
|
|
|
|
const ZoneStringInfo*
|
|
ZoneStringFormat::findSpecificLong(const UnicodeString &text, int32_t start,
|
|
int32_t &matchLength, UErrorCode &status) const {
|
|
return find(text, start, STANDARD_LONG | DAYLIGHT_LONG, matchLength, status);
|
|
}
|
|
|
|
const ZoneStringInfo*
|
|
ZoneStringFormat::findSpecificShort(const UnicodeString &text, int32_t start,
|
|
int32_t &matchLength, UErrorCode &status) const {
|
|
return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT, matchLength, status);
|
|
}
|
|
|
|
const ZoneStringInfo*
|
|
ZoneStringFormat::findGenericLong(const UnicodeString &text, int32_t start,
|
|
int32_t &matchLength, UErrorCode &status) const {
|
|
return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION, matchLength, status);
|
|
}
|
|
|
|
const ZoneStringInfo*
|
|
ZoneStringFormat::findGenericShort(const UnicodeString &text, int32_t start,
|
|
int32_t &matchLength, UErrorCode &status) const {
|
|
return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION, matchLength, status);
|
|
}
|
|
|
|
const ZoneStringInfo*
|
|
ZoneStringFormat::findGenericLocation(const UnicodeString &text, int32_t start,
|
|
int32_t &matchLength, UErrorCode &status) const {
|
|
return find(text, start, LOCATION, matchLength, status);
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getString(const UnicodeString &tzid, TimeZoneTranslationTypeIndex typeIdx, UDate date,
|
|
UBool commonlyUsedOnly, UnicodeString& result) const {
|
|
result.remove();
|
|
|
|
// ICU's own array does not have entries for aliases
|
|
UnicodeString canonicalID;
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
TimeZone::getCanonicalID(tzid, canonicalID, status);
|
|
if (U_FAILURE(status)) {
|
|
// Unknown ID, but users might have their own data.
|
|
canonicalID.setTo(tzid);
|
|
}
|
|
|
|
if (fTzidToStrings.count() > 0) {
|
|
ZoneStrings *zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID);
|
|
if (zstrings != NULL) {
|
|
switch (typeIdx) {
|
|
case ZSIDX_LONG_STANDARD:
|
|
case ZSIDX_LONG_DAYLIGHT:
|
|
case ZSIDX_LONG_GENERIC:
|
|
case ZSIDX_LOCATION:
|
|
zstrings->getString(typeIdx, result);
|
|
break;
|
|
case ZSIDX_SHORT_STANDARD:
|
|
case ZSIDX_SHORT_DAYLIGHT:
|
|
case ZSIDX_SHORT_GENERIC:
|
|
if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) {
|
|
zstrings->getString(typeIdx, result);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (result.isEmpty() && fMzidToStrings.count() > 0 && typeIdx != ZSIDX_LOCATION) {
|
|
// Try metazone
|
|
UnicodeString mzid;
|
|
ZoneMeta::getMetazoneID(canonicalID, date, mzid);
|
|
if (!mzid.isEmpty()) {
|
|
ZoneStrings *mzstrings = (ZoneStrings*)fMzidToStrings.get(mzid);
|
|
if (mzstrings != NULL) {
|
|
switch (typeIdx) {
|
|
case ZSIDX_LONG_STANDARD:
|
|
case ZSIDX_LONG_DAYLIGHT:
|
|
case ZSIDX_LONG_GENERIC:
|
|
case ZSIDX_LOCATION:
|
|
mzstrings->getString(typeIdx, result);
|
|
break;
|
|
case ZSIDX_SHORT_STANDARD:
|
|
case ZSIDX_SHORT_DAYLIGHT:
|
|
case ZSIDX_SHORT_GENERIC:
|
|
if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) {
|
|
mzstrings->getString(typeIdx, result);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getGenericString(const Calendar &cal, UBool isShort, UBool commonlyUsedOnly,
|
|
UnicodeString &result, UErrorCode &status) const {
|
|
result.remove();
|
|
UDate time = cal.getTime(status);
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
const TimeZone &tz = cal.getTimeZone();
|
|
UnicodeString tzid;
|
|
tz.getID(tzid);
|
|
|
|
// ICU's own array does not have entries for aliases
|
|
UnicodeString canonicalID;
|
|
TimeZone::getCanonicalID(tzid, canonicalID, status);
|
|
if (U_FAILURE(status)) {
|
|
// Unknown ID, but users might have their own data.
|
|
status = U_ZERO_ERROR;
|
|
canonicalID.setTo(tzid);
|
|
}
|
|
|
|
ZoneStrings *zstrings = NULL;
|
|
if (fTzidToStrings.count() > 0) {
|
|
zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID);
|
|
if (zstrings != NULL) {
|
|
if (isShort) {
|
|
if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) {
|
|
zstrings->getString(ZSIDX_SHORT_GENERIC, result);
|
|
}
|
|
} else {
|
|
zstrings->getString(ZSIDX_LONG_GENERIC, result);
|
|
}
|
|
}
|
|
}
|
|
if (result.isEmpty() && fMzidToStrings.count() > 0) {
|
|
// try metazone
|
|
int32_t raw, sav;
|
|
UnicodeString mzid;
|
|
ZoneMeta::getMetazoneID(canonicalID, time, mzid);
|
|
if (!mzid.isEmpty()) {
|
|
UBool useStandard = FALSE;
|
|
sav = cal.get(UCAL_DST_OFFSET, status);
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
if (sav == 0) {
|
|
useStandard = TRUE;
|
|
// Check if the zone actually uses daylight saving time around the time
|
|
TimeZone *tmptz = tz.clone();
|
|
BasicTimeZone *btz = NULL;
|
|
if (tmptz->getDynamicClassID() == OlsonTimeZone::getStaticClassID()
|
|
|| tmptz->getDynamicClassID() == SimpleTimeZone::getStaticClassID()
|
|
|| tmptz->getDynamicClassID() == RuleBasedTimeZone::getStaticClassID()
|
|
|| tmptz->getDynamicClassID() == VTimeZone::getStaticClassID()) {
|
|
btz = (BasicTimeZone*)tmptz;
|
|
}
|
|
|
|
if (btz != NULL) {
|
|
TimeZoneTransition before;
|
|
UBool beforTrs = btz->getPreviousTransition(time, TRUE, before);
|
|
if (beforTrs
|
|
&& (time - before.getTime() < kDstCheckRange)
|
|
&& before.getFrom()->getDSTSavings() != 0) {
|
|
useStandard = FALSE;
|
|
} else {
|
|
TimeZoneTransition after;
|
|
UBool afterTrs = btz->getNextTransition(time, FALSE, after);
|
|
if (afterTrs
|
|
&& (after.getTime() - time < 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(time - kDstCheckRange, FALSE, raw, sav, status);
|
|
if (sav != 0) {
|
|
useStandard = FALSE;
|
|
} else {
|
|
tmptz->getOffset(time + kDstCheckRange, FALSE, raw, sav, status);
|
|
if (sav != 0){
|
|
useStandard = FALSE;
|
|
}
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
delete tmptz;
|
|
result.remove();
|
|
return result;
|
|
}
|
|
}
|
|
delete tmptz;
|
|
}
|
|
if (useStandard) {
|
|
getString(canonicalID, (isShort ? ZSIDX_SHORT_STANDARD : ZSIDX_LONG_STANDARD),
|
|
time, commonlyUsedOnly, result);
|
|
|
|
// Note:
|
|
// In CLDR 1.5.1, a same localization is used for both generic and standard
|
|
// for some metazones in some locales. This is actually data bugs and should
|
|
// be resolved in later versions of CLDR. For now, we check if the standard
|
|
// name is different from its generic name below.
|
|
if (!result.isEmpty()) {
|
|
UnicodeString genericNonLocation;
|
|
getString(canonicalID, (isShort ? ZSIDX_SHORT_GENERIC : ZSIDX_LONG_GENERIC),
|
|
time, commonlyUsedOnly, genericNonLocation);
|
|
if (!genericNonLocation.isEmpty() && result == genericNonLocation) {
|
|
result.remove();
|
|
}
|
|
}
|
|
}
|
|
if (result.isEmpty()) {
|
|
ZoneStrings *mzstrings = (ZoneStrings*)fMzidToStrings.get(mzid);
|
|
if (mzstrings != NULL) {
|
|
if (isShort) {
|
|
if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) {
|
|
mzstrings->getString(ZSIDX_SHORT_GENERIC, result);
|
|
}
|
|
} else {
|
|
mzstrings->getString(ZSIDX_LONG_GENERIC, result);
|
|
}
|
|
}
|
|
if (!result.isEmpty()) {
|
|
// Check if the offsets at the given time matches the preferred zone's offsets
|
|
UnicodeString preferredId;
|
|
UnicodeString region;
|
|
ZoneMeta::getZoneIdByMetazone(mzid, getRegion(region), preferredId);
|
|
if (canonicalID != preferredId) {
|
|
// Check if the offsets at the given time are identical with the preferred zone
|
|
raw = cal.get(UCAL_ZONE_OFFSET, status);
|
|
if (U_FAILURE(status)) {
|
|
result.remove();
|
|
return result;
|
|
}
|
|
TimeZone *preferredZone = TimeZone::createTimeZone(preferredId);
|
|
int32_t prfRaw, prfSav;
|
|
// Check offset in preferred time zone with wall time.
|
|
// With getOffset(time, false, preferredOffsets),
|
|
// you may get incorrect results because of time overlap at DST->STD
|
|
// transition.
|
|
preferredZone->getOffset(time + raw + sav, TRUE, prfRaw, prfSav, status);
|
|
delete preferredZone;
|
|
|
|
if (U_FAILURE(status)) {
|
|
result.remove();
|
|
return result;
|
|
}
|
|
if ((raw != prfRaw || sav != prfSav) && zstrings != NULL) {
|
|
// Use generic partial location string as fallback
|
|
zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (result.isEmpty()) {
|
|
// Use location format as the final fallback
|
|
getString(canonicalID, ZSIDX_LOCATION, time, FALSE /*not used*/, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getGenericPartialLocationString(const UnicodeString &tzid, UBool isShort,
|
|
UDate date, UBool commonlyUsedOnly, UnicodeString &result) const {
|
|
result.remove();
|
|
if (fTzidToStrings.count() <= 0) {
|
|
return result;
|
|
}
|
|
|
|
UnicodeString canonicalID;
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
TimeZone::getCanonicalID(tzid, canonicalID, status);
|
|
if (U_FAILURE(status)) {
|
|
// Unknown ID, so no corresponding meta data.
|
|
return result;
|
|
}
|
|
|
|
UnicodeString mzid;
|
|
ZoneMeta::getMetazoneID(canonicalID, date, mzid);
|
|
|
|
if (!mzid.isEmpty()) {
|
|
ZoneStrings *zstrings = (ZoneStrings*)fTzidToStrings.get(canonicalID);
|
|
if (zstrings != NULL) {
|
|
zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const ZoneStringInfo*
|
|
ZoneStringFormat::find(const UnicodeString &text, int32_t start, int32_t types,
|
|
int32_t &matchLength, UErrorCode &status) const {
|
|
matchLength = 0;
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
if (fZoneStringsTrie.isEmpty()) {
|
|
return NULL;
|
|
}
|
|
const ZoneStringInfo *result = NULL;
|
|
const ZoneStringInfo *fallback = NULL;
|
|
int32_t fallbackMatchLen = 0;
|
|
|
|
ZoneStringSearchResultHandler handler(status);
|
|
fZoneStringsTrie.search(text, start, (TextTrieMapSearchResultHandler*)&handler, status);
|
|
if (U_SUCCESS(status)) {
|
|
int32_t numMatches = handler.countMatches();
|
|
for (int32_t i = 0; i < numMatches; i++) {
|
|
int32_t tmpMatchLen;
|
|
const ZoneStringInfo *tmp = handler.getMatch(i, tmpMatchLen);
|
|
if ((types & tmp->fType) != 0) {
|
|
if (result == NULL || matchLength < tmpMatchLen) {
|
|
result = tmp;
|
|
matchLength = tmpMatchLen;
|
|
} else if (matchLength == tmpMatchLen) {
|
|
// Tie breaker - there are some examples that a
|
|
// long standard name is identical with a location
|
|
// name - for example, "Uruguay Time". In this case,
|
|
// we interpret it as generic, not specific.
|
|
if (tmp->isGeneric() && !result->isGeneric()) {
|
|
result = tmp;
|
|
}
|
|
}
|
|
} else if (result == NULL) {
|
|
if (fallback == NULL || fallbackMatchLen < tmpMatchLen) {
|
|
fallback = tmp;
|
|
fallbackMatchLen = tmpMatchLen;
|
|
} else if (fallbackMatchLen == tmpMatchLen) {
|
|
if (tmp->isGeneric() && !fallback->isGeneric()) {
|
|
fallback = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (result == NULL && fallback != NULL) {
|
|
result = fallback;
|
|
matchLength = fallbackMatchLen;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getRegion(UnicodeString ®ion) const {
|
|
const char* country = fLocale.getCountry();
|
|
// TODO: Utilize addLikelySubtag in Locale to resolve default region
|
|
// when the implementation is ready.
|
|
region.setTo(UnicodeString(country, -1, US_INV));
|
|
return region;
|
|
}
|
|
|
|
MessageFormat*
|
|
ZoneStringFormat::getFallbackFormat(const Locale &locale, UErrorCode &status) {
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
UnicodeString pattern(TRUE, gDefFallbackPattern, -1);
|
|
UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status);
|
|
zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status);
|
|
int32_t len;
|
|
const UChar *flbkfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gFallbackFormatTag, &len, &status);
|
|
if (U_SUCCESS(status)) {
|
|
pattern.setTo(flbkfmt);
|
|
} else {
|
|
status = U_ZERO_ERROR;
|
|
}
|
|
ures_close(zoneStringsArray);
|
|
|
|
MessageFormat *fallbackFmt = new MessageFormat(pattern, status);
|
|
return fallbackFmt;
|
|
}
|
|
|
|
MessageFormat*
|
|
ZoneStringFormat::getRegionFormat(const Locale& locale, UErrorCode &status) {
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
UnicodeString pattern(TRUE, gDefRegionPattern, -1);
|
|
UResourceBundle *zoneStringsArray = ures_open(NULL, locale.getName(), &status);
|
|
zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status);
|
|
int32_t len;
|
|
const UChar *regionfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gRegionFormatTag, &len, &status);
|
|
if (U_SUCCESS(status)) {
|
|
pattern.setTo(regionfmt);
|
|
} else {
|
|
status = U_ZERO_ERROR;
|
|
}
|
|
ures_close(zoneStringsArray);
|
|
|
|
MessageFormat *regionFmt = new MessageFormat(pattern, status);
|
|
return regionFmt;
|
|
}
|
|
|
|
const UChar*
|
|
ZoneStringFormat::getZoneStringFromBundle(const UResourceBundle *zoneitem, const char *key) {
|
|
const UChar *str = NULL;
|
|
if (zoneitem != NULL) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
int32_t len;
|
|
str = ures_getStringByKeyWithFallback(zoneitem, key, &len, &status);
|
|
if (U_FAILURE(status)) {
|
|
str = NULL;
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
UBool
|
|
ZoneStringFormat::isCommonlyUsed(const UResourceBundle *zoneitem) {
|
|
if (zoneitem == NULL) {
|
|
return TRUE;
|
|
}
|
|
|
|
UBool commonlyUsed = FALSE;
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
UResourceBundle *cuRes = ures_getByKey(zoneitem, gCommonlyUsedTag, NULL, &status);
|
|
int32_t cuValue = ures_getInt(cuRes, &status);
|
|
if (U_SUCCESS(status)) {
|
|
if (cuValue == 1) {
|
|
commonlyUsed = TRUE;
|
|
}
|
|
}
|
|
ures_close(cuRes);
|
|
return commonlyUsed;
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStringFormat::getLocalizedCountry(const UnicodeString &countryCode, const Locale &locale, UnicodeString &displayCountry) {
|
|
// We do not want to use display country names only from the target language bundle
|
|
// Note: we should do this in better way.
|
|
displayCountry.remove();
|
|
int32_t ccLen = countryCode.length();
|
|
if (ccLen > 0 && ccLen < ULOC_COUNTRY_CAPACITY) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
UResourceBundle *localeBundle = ures_open(NULL, locale.getName(), &status);
|
|
if (U_SUCCESS(status)) {
|
|
const char *bundleLocStr = ures_getLocale(localeBundle, &status);
|
|
if (U_SUCCESS(status) && uprv_strlen(bundleLocStr) > 0) {
|
|
Locale bundleLoc(bundleLocStr);
|
|
if (uprv_strcmp(bundleLocStr, "root") != 0 && uprv_strcmp(bundleLoc.getLanguage(), locale.getLanguage()) == 0) {
|
|
// Create a fake locale strings
|
|
char tmpLocStr[ULOC_COUNTRY_CAPACITY + 3];
|
|
uprv_strcpy(tmpLocStr, "xx_");
|
|
u_UCharsToChars(countryCode.getBuffer(), &tmpLocStr[3], ccLen);
|
|
tmpLocStr[3 + ccLen] = 0;
|
|
|
|
Locale tmpLoc(tmpLocStr);
|
|
tmpLoc.getDisplayCountry(locale, displayCountry);
|
|
}
|
|
}
|
|
}
|
|
ures_close(localeBundle);
|
|
}
|
|
if (displayCountry.isEmpty()) {
|
|
// Use the country code as the fallback
|
|
displayCountry.setTo(countryCode);
|
|
}
|
|
return displayCountry;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/*
|
|
* This constructor adopts the input UnicodeString arrays.
|
|
*/
|
|
ZoneStrings::ZoneStrings(UnicodeString *strings, int32_t stringsCount, UBool commonlyUsed,
|
|
UnicodeString **genericPartialLocationStrings, int32_t genericRowCount, int32_t genericColCount)
|
|
: fStrings(strings), fStringsCount(stringsCount), fIsCommonlyUsed(commonlyUsed),
|
|
fGenericPartialLocationStrings(genericPartialLocationStrings),
|
|
fGenericPartialLocationRowCount(genericRowCount), fGenericPartialLocationColCount(genericColCount) {
|
|
}
|
|
|
|
ZoneStrings::~ZoneStrings() {
|
|
if (fStrings != NULL) {
|
|
delete[] fStrings;
|
|
}
|
|
if (fGenericPartialLocationStrings != NULL) {
|
|
for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) {
|
|
delete[] fGenericPartialLocationStrings[i];
|
|
}
|
|
uprv_free(fGenericPartialLocationStrings);
|
|
}
|
|
}
|
|
|
|
|
|
UnicodeString&
|
|
ZoneStrings::getString(int32_t typeIdx, UnicodeString &result) const {
|
|
if (typeIdx >= 0 && typeIdx < fStringsCount) {
|
|
result.setTo(fStrings[typeIdx]);
|
|
} else {
|
|
result.remove();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
UnicodeString&
|
|
ZoneStrings::getGenericPartialLocationString(const UnicodeString &mzid, UBool isShort,
|
|
UBool commonlyUsedOnly, UnicodeString &result) const {
|
|
UBool isSet = FALSE;
|
|
if (fGenericPartialLocationColCount >= 2) {
|
|
for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) {
|
|
if (fGenericPartialLocationStrings[i][0] == mzid) {
|
|
if (isShort) {
|
|
if (fGenericPartialLocationColCount >= 3) {
|
|
if (!commonlyUsedOnly ||
|
|
fGenericPartialLocationColCount == 3 || fGenericPartialLocationStrings[i][3].length() != 0) {
|
|
result.setTo(fGenericPartialLocationStrings[i][2]);
|
|
isSet = TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
result.setTo(fGenericPartialLocationStrings[i][1]);
|
|
isSet = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!isSet) {
|
|
result.remove();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
SafeZoneStringFormatPtr::SafeZoneStringFormatPtr(ZSFCacheEntry *cacheEntry)
|
|
: fCacheEntry(cacheEntry) {
|
|
}
|
|
|
|
SafeZoneStringFormatPtr::~SafeZoneStringFormatPtr() {
|
|
fCacheEntry->delRef();
|
|
}
|
|
|
|
const ZoneStringFormat*
|
|
SafeZoneStringFormatPtr::get() const {
|
|
return fCacheEntry->getZoneStringFormat();
|
|
}
|
|
|
|
ZSFCacheEntry::ZSFCacheEntry(const Locale &locale, ZoneStringFormat *zsf, ZSFCacheEntry *next)
|
|
: fLocale(locale), fZoneStringFormat(zsf),
|
|
fNext(next), fRefCount(1)
|
|
{
|
|
}
|
|
|
|
ZSFCacheEntry::~ZSFCacheEntry () {
|
|
delete fZoneStringFormat;
|
|
}
|
|
|
|
const ZoneStringFormat*
|
|
ZSFCacheEntry::getZoneStringFormat(void) {
|
|
return (const ZoneStringFormat*)fZoneStringFormat;
|
|
}
|
|
|
|
void
|
|
ZSFCacheEntry::delRef(void) {
|
|
umtx_lock(&gZSFCacheLock);
|
|
--fRefCount;
|
|
umtx_unlock(&gZSFCacheLock);
|
|
}
|
|
|
|
ZSFCache::ZSFCache(int32_t capacity)
|
|
: fCapacity(capacity), fFirst(NULL) {
|
|
}
|
|
|
|
ZSFCache::~ZSFCache() {
|
|
ZSFCacheEntry *entry = fFirst;
|
|
while (entry) {
|
|
ZSFCacheEntry *next = entry->fNext;
|
|
delete entry;
|
|
entry = next;
|
|
}
|
|
}
|
|
|
|
SafeZoneStringFormatPtr*
|
|
ZSFCache::get(const Locale &locale, UErrorCode &status) {
|
|
SafeZoneStringFormatPtr *result = NULL;
|
|
|
|
// Search the cache entry list
|
|
ZSFCacheEntry *entry = NULL;
|
|
ZSFCacheEntry *next, *prev;
|
|
|
|
umtx_lock(&gZSFCacheLock);
|
|
entry = fFirst;
|
|
prev = NULL;
|
|
while (entry) {
|
|
next = entry->fNext;
|
|
if (entry->fLocale == locale) {
|
|
// Add reference count
|
|
entry->fRefCount++;
|
|
|
|
// move the entry to the top
|
|
if (entry != fFirst) {
|
|
prev->fNext = next;
|
|
entry->fNext = fFirst;
|
|
fFirst = entry;
|
|
}
|
|
break;
|
|
}
|
|
prev = entry;
|
|
entry = next;
|
|
}
|
|
umtx_unlock(&gZSFCacheLock);
|
|
|
|
// Create a new ZoneStringFormat
|
|
if (entry == NULL) {
|
|
ZoneStringFormat *zsf = new ZoneStringFormat(locale, status);
|
|
if (U_FAILURE(status)) {
|
|
delete zsf;
|
|
return NULL;
|
|
}
|
|
if (zsf == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
// Now add the new entry
|
|
umtx_lock(&gZSFCacheLock);
|
|
// Make sure no other threads already created the one for the same locale
|
|
entry = fFirst;
|
|
prev = NULL;
|
|
while (entry) {
|
|
next = entry->fNext;
|
|
if (entry->fLocale == locale) {
|
|
// Add reference count
|
|
entry->fRefCount++;
|
|
|
|
// move the entry to the top
|
|
if (entry != fFirst) {
|
|
prev->fNext = next;
|
|
entry->fNext = fFirst;
|
|
fFirst = entry;
|
|
}
|
|
break;
|
|
}
|
|
prev = entry;
|
|
entry = next;
|
|
}
|
|
if (entry == NULL) {
|
|
// Add the new one to the top
|
|
next = fFirst;
|
|
entry = new ZSFCacheEntry(locale, zsf, next);
|
|
fFirst = entry;
|
|
} else {
|
|
delete zsf;
|
|
}
|
|
umtx_unlock(&gZSFCacheLock);
|
|
}
|
|
|
|
result = new SafeZoneStringFormatPtr(entry);
|
|
|
|
// Now, delete unused cache entries beyond the capacity
|
|
umtx_lock(&gZSFCacheLock);
|
|
entry = fFirst;
|
|
prev = NULL;
|
|
int32_t idx = 1;
|
|
while (entry) {
|
|
next = entry->fNext;
|
|
if (idx >= fCapacity && entry->fRefCount == 0) {
|
|
if (entry == fFirst) {
|
|
fFirst = next;
|
|
} else {
|
|
prev->fNext = next;
|
|
}
|
|
delete entry;
|
|
} else {
|
|
prev = entry;
|
|
}
|
|
entry = next;
|
|
idx++;
|
|
}
|
|
umtx_unlock(&gZSFCacheLock);
|
|
|
|
return result;
|
|
}
|
|
|
|
U_NAMESPACE_END
|
|
|
|
#endif /* #if !UCONFIG_NO_FORMATTING */
|