From 4d7569c7439de57d64021ebce28ec8720dc70fd2 Mon Sep 17 00:00:00 2001 From: Yoshito Umaoka Date: Tue, 3 May 2011 14:59:01 +0000 Subject: [PATCH] ICU-8342 Refactoring ZoneStringFormat into TimeZoneNames and TimeZoneFormat (all internal APIs for now). Also supporting localized GMT zero format and fallback region format for generic names. X-SVN-Rev: 29984 --- icu4c/source/i18n/Makefile.in | 5 +- icu4c/source/i18n/datefmt.cpp | 2 +- icu4c/source/i18n/dtfmtsym.cpp | 174 +- icu4c/source/i18n/i18n.vcxproj | 12 +- icu4c/source/i18n/i18n.vcxproj.filters | 30 +- icu4c/source/i18n/olsontz.cpp | 13 +- icu4c/source/i18n/olsontz.h | 24 +- icu4c/source/i18n/smpdtfmt.cpp | 211 ++- icu4c/source/i18n/timezone.cpp | 27 +- icu4c/source/i18n/tzfmt.cpp | 502 +++++ icu4c/source/i18n/tzfmt.h | 65 + icu4c/source/i18n/tzgnames.cpp | 1053 +++++++++++ icu4c/source/i18n/tzgnames.h | 115 ++ icu4c/source/i18n/tznames.cpp | 299 +++ icu4c/source/i18n/tznames.h | 81 + icu4c/source/i18n/tznames_impl.cpp | 1451 +++++++++++++++ icu4c/source/i18n/tznames_impl.h | 212 +++ icu4c/source/i18n/ucln_in.h | 3 +- icu4c/source/i18n/unicode/dtfmtsym.h | 27 +- icu4c/source/i18n/unicode/smpdtfmt.h | 11 + icu4c/source/i18n/unicode/timezone.h | 10 + icu4c/source/i18n/zonemeta.cpp | 350 +++- icu4c/source/i18n/zonemeta.h | 28 + icu4c/source/i18n/zstrfmt.cpp | 2228 ----------------------- icu4c/source/i18n/zstrfmt.h | 526 ------ icu4c/source/test/intltest/callimts.cpp | 4 +- icu4c/source/test/intltest/dtfmttst.cpp | 148 +- icu4c/source/test/intltest/dtptngts.cpp | 2 +- icu4c/source/test/intltest/tzfmttst.cpp | 29 +- icu4c/source/test/intltest/tztest.cpp | 4 +- 30 files changed, 4558 insertions(+), 3088 deletions(-) create mode 100644 icu4c/source/i18n/tzfmt.cpp create mode 100644 icu4c/source/i18n/tzfmt.h create mode 100644 icu4c/source/i18n/tzgnames.cpp create mode 100644 icu4c/source/i18n/tzgnames.h create mode 100644 icu4c/source/i18n/tznames.cpp create mode 100644 icu4c/source/i18n/tznames.h create mode 100644 icu4c/source/i18n/tznames_impl.cpp create mode 100644 icu4c/source/i18n/tznames_impl.h delete mode 100644 icu4c/source/i18n/zstrfmt.cpp delete mode 100644 icu4c/source/i18n/zstrfmt.h diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index a6ac3725576..e203e7635bd 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -80,11 +80,12 @@ regexcmp.o rematch.o repattrn.o regexst.o regextxt.o udatpg.o uregex.o uregexc.o ulocdata.o measfmt.o currfmt.o curramt.o currunit.o measure.o utmscale.o \ csdetect.o csmatch.o csr2022.o csrecog.o csrmbcs.o csrsbcs.o csrucode.o csrutf8.o inputext.o \ wintzimpl.o windtfmt.o winnmfmt.o basictz.o dtrule.o rbtz.o tzrule.o tztrans.o vtzone.o \ -zonemeta.o zstrfmt.o plurrule.o plurfmt.o selfmt.o dtitvfmt.o dtitvinf.o \ +zonemeta.o plurrule.o plurfmt.o selfmt.o dtitvfmt.o dtitvinf.o \ tmunit.o tmutamt.o tmutfmt.o colldata.o bmsearch.o bms.o currpinf.o \ uspoof.o uspoof_impl.o uspoof_build.o uspoof_conf.o uspoof_wsconf.o \ ztrans.o zrule.o vzone.o fphdlimp.o fpositer.o locdspnm.o \ -decNumber.o decContext.o alphaindex.o +decNumber.o decContext.o alphaindex.o tznames.o tznames_impl.o tzgnames.o \ +tzfmt.o ## Header files to install HEADERS = $(srcdir)/unicode/*.h diff --git a/icu4c/source/i18n/datefmt.cpp b/icu4c/source/i18n/datefmt.cpp index 9280d2038e6..ad3555714d1 100644 --- a/icu4c/source/i18n/datefmt.cpp +++ b/icu4c/source/i18n/datefmt.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (C) 1997-2010, International Business Machines Corporation and * + * Copyright (C) 1997-2011, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * diff --git a/icu4c/source/i18n/dtfmtsym.cpp b/icu4c/source/i18n/dtfmtsym.cpp index 78f2069a7a1..868ad9436c5 100644 --- a/icu4c/source/i18n/dtfmtsym.cpp +++ b/icu4c/source/i18n/dtfmtsym.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 1997-2009, International Business Machines Corporation and * +* Copyright (C) 1997-2011, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * @@ -35,8 +35,8 @@ #include "gregoimp.h" #include "hash.h" #include "uresimp.h" -#include "zstrfmt.h" #include "ureslocs.h" +#include "tznames.h" // ***************************************************************************** // class DateFormatSymbols @@ -120,18 +120,19 @@ static const UChar gLastResortEras[2][3] = {0x0041, 0x0044, 0x0000} /* "AD" */ }; +// Not used now +//// These are the zone strings of last resort. +//static const UChar gLastResortZoneStrings[5][4] = +//{ +// {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ +// {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ +// {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ +// {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ +// {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ +//}; -// These are the zone strings of last resort. -static const UChar gLastResortZoneStrings[7][4] = -{ - {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ - {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ - {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ - {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ - {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ - {0x0047, 0x004D, 0x0054, 0x0000}, /* "GMT" */ - {0x0047, 0x004D, 0x0054, 0x0000} /* "GMT" */ -}; +static const UChar gLastResortGmtZero[] = + {0x0047, 0x004D, 0x0054, 0x0000}; /* GMT */ static const UChar gLastResortGmtFormat[] = {0x0047, 0x004D, 0x0054, 0x007B, 0x0030, 0x007D, 0x0000}; /* GMT{0} */ @@ -189,6 +190,7 @@ static const char gAmPmMarkersTag[]="AmPmMarkers"; static const char gQuartersTag[]="quarters"; static const char gZoneStringsTag[]="zoneStrings"; +static const char gGmtZeroFormatTag[] = "gmtZeroFormat"; static const char gGmtFormatTag[]="gmtFormat"; static const char gHourFormatTag[]="hourFormat"; @@ -329,6 +331,7 @@ DateFormatSymbols::copyData(const DateFormatSymbols& other) { assignArray(fShortQuarters, fShortQuartersCount, other.fShortQuarters, other.fShortQuartersCount); assignArray(fStandaloneQuarters, fStandaloneQuartersCount, other.fStandaloneQuarters, other.fStandaloneQuartersCount); assignArray(fStandaloneShortQuarters, fStandaloneShortQuartersCount, other.fStandaloneShortQuarters, other.fStandaloneShortQuartersCount); + fGmtZero = other.fGmtZero; fGmtFormat = other.fGmtFormat; assignArray(fGmtHourFormats, fGmtHourFormatsCount, other.fGmtHourFormats, other.fGmtHourFormatsCount); @@ -344,10 +347,7 @@ DateFormatSymbols::copyData(const DateFormatSymbols& other) { } fZSFLocale = other.fZSFLocale; // Other zone strings data is created on demand - fZoneStringFormat = NULL; fLocaleZoneStrings = NULL; - fZSFCachePtr = NULL; - fZSFLocal = NULL; // fastCopyFrom() - see assignArray comments fLocalPatternChars.fastCopyFrom(other.fLocalPatternChars); @@ -410,21 +410,11 @@ void DateFormatSymbols::disposeZoneStrings() } uprv_free(fLocaleZoneStrings); } - if (fZSFLocal) { - delete fZSFLocal; - } - if (fZSFCachePtr) { - delete fZSFCachePtr; - } fZoneStrings = NULL; fLocaleZoneStrings = NULL; fZoneStringsRowCount = 0; fZoneStringsColCount = 0; - - fZoneStringFormat = NULL; - fZSFLocal = NULL; - fZSFCachePtr = NULL; } UBool @@ -469,6 +459,7 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const fStandaloneQuartersCount == other.fStandaloneQuartersCount && fStandaloneShortQuartersCount == other.fStandaloneShortQuartersCount && fGmtHourFormatsCount == other.fGmtHourFormatsCount && + fGmtZero == other.fGmtZero && fGmtFormat == other.fGmtFormat) { // Now compare the arrays themselves @@ -1033,41 +1024,6 @@ DateFormatSymbols::setAmPmStrings(const UnicodeString* amPmsArray, int32_t count fAmPmsCount = count; } -//------------------------------------------------------ -const ZoneStringFormat* -DateFormatSymbols::getZoneStringFormat(void) const { - umtx_lock(&LOCK); - if (fZoneStringFormat == NULL) { - ((DateFormatSymbols*)this)->initZoneStringFormat(); - } - umtx_unlock(&LOCK); - return fZoneStringFormat; -} - -void -DateFormatSymbols::initZoneStringFormat(void) { - if (fZoneStringFormat == NULL) { - UErrorCode status = U_ZERO_ERROR; - if (fZoneStrings) { - // Create an istance of ZoneStringFormat by the custom zone strings array - fZSFLocal = new ZoneStringFormat(fZoneStrings, fZoneStringsRowCount, - fZoneStringsColCount, status); - if (U_FAILURE(status)) { - delete fZSFLocal; - } else { - fZoneStringFormat = (const ZoneStringFormat*)fZSFLocal; - } - } else { - fZSFCachePtr = ZoneStringFormat::getZoneStringFormat(fZSFLocale, status); - if (U_FAILURE(status)) { - delete fZSFCachePtr; - } else { - fZoneStringFormat = fZSFCachePtr->get(); - } - } - } -} - const UnicodeString** DateFormatSymbols::getZoneStrings(int32_t& rowCount, int32_t& columnCount) const { @@ -1089,18 +1045,89 @@ DateFormatSymbols::getZoneStrings(int32_t& rowCount, int32_t& columnCount) const return result; } +// For now, we include all zones +#define ZONE_SET UCAL_ZONE_TYPE_ANY + +// This code must be called within a synchronized block void DateFormatSymbols::initZoneStringsArray(void) { - if (fZoneStrings == NULL && fLocaleZoneStrings == NULL) { - if (fZoneStringFormat == NULL) { - initZoneStringFormat(); + if (fZoneStrings != NULL || fLocaleZoneStrings != NULL) { + return; + } + + UErrorCode status = U_ZERO_ERROR; + + StringEnumeration *tzids = NULL; + UnicodeString ** zarray = NULL; + TimeZoneNames *tzNames = NULL; + int32_t rows = 0; + + do { // dummy do-while + + tzids = TimeZone::createTimeZoneIDEnumeration(ZONE_SET, NULL, NULL, status); + rows = tzids->count(status); + if (U_FAILURE(status)) { + break; } - if (fZoneStringFormat) { - UErrorCode status = U_ZERO_ERROR; - fLocaleZoneStrings = fZoneStringFormat->createZoneStringsArray(uprv_getUTCtime() /* use current time */, - fZoneStringsRowCount, fZoneStringsColCount, status); + + // Allocate array + int32_t size = rows * sizeof(UnicodeString*); + zarray = (UnicodeString**)uprv_malloc(size); + if (zarray == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + break; + } + uprv_memset(zarray, 0, size); + + tzNames = TimeZoneNames::createInstance(fZSFLocale, status); + + const UnicodeString *tzid; + int32_t i = 0; + UDate now = Calendar::getNow(); + UnicodeString tzDispName; + + while ((tzid = tzids->snext(status))) { + if (U_FAILURE(status)) { + break; + } + + zarray[i] = new UnicodeString[5]; + if (zarray[i] == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + break; + } + + zarray[i][0].setTo(*tzid); + zarray[i][1].setTo(tzNames->getDisplayName(*tzid, UTZNM_LONG_STANDARD, now, tzDispName)); + zarray[i][2].setTo(tzNames->getDisplayName(*tzid, UTZNM_SHORT_STANDARD, now, tzDispName)); + zarray[i][3].setTo(tzNames->getDisplayName(*tzid, UTZNM_LONG_DAYLIGHT, now, tzDispName)); + zarray[i][4].setTo(tzNames->getDisplayName(*tzid, UTZNM_SHORT_DAYLIGHT, now, tzDispName)); + i++; + } + + } while (FALSE); + + if (U_FAILURE(status)) { + if (zarray) { + for (int32_t i = 0; i < rows; i++) { + if (zarray[i]) { + delete[] zarray[i]; + } + } + uprv_free(zarray); } } + + if (tzNames) { + delete tzNames; + } + if (tzids) { + delete tzids; + } + + fLocaleZoneStrings = zarray; + fZoneStringsRowCount = rows; + fZoneStringsColCount = 5; } void @@ -1236,10 +1263,6 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError fZoneStrings = NULL; fLocaleZoneStrings = NULL; - fZoneStringFormat = NULL; - fZSFLocal = NULL; - fZSFCachePtr = NULL; - // We need to preserve the requested locale for // lazy ZoneStringFormat instantiation. ZoneStringFormat // is region sensitive, thus, bundle locale bundle's locale @@ -1318,6 +1341,7 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError initField(&fStandaloneQuarters, fStandaloneQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); initField(&fStandaloneShortQuarters, fStandaloneShortQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); initField(&fGmtHourFormats, fGmtHourFormatsCount, (const UChar *)gLastResortGmtHourFormats, kGmtHourNum, kGmtHourLen, status); + fGmtZero.setTo(TRUE, gLastResortGmtZero, -1); fGmtFormat.setTo(TRUE, gLastResortGmtFormat, -1); fLocalPatternChars.setTo(TRUE, gPatternChars, PATTERN_CHARS_LEN); } @@ -1383,6 +1407,12 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError initField(&fStandaloneShortQuarters, fStandaloneShortQuartersCount, calData.getByKey2(gQuartersTag, gNamesAbbrTag, status), status); } + // GMT zero + resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gGmtZeroFormatTag, &len, &status); + if (len > 0) { + fGmtZero.setTo(TRUE, resStr, len); + } + // GMT format patterns resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gGmtFormatTag, &len, &status); if (len > 0) { diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj index 8a034cdb654..324b9250737 100644 --- a/icu4c/source/i18n/i18n.vcxproj +++ b/icu4c/source/i18n/i18n.vcxproj @@ -255,6 +255,10 @@ + + + + @@ -356,7 +360,6 @@ - false @@ -570,6 +573,10 @@ ..\..\include\unicode\%(Filename)%(Extension);%(Outputs) + + + + @@ -668,7 +675,7 @@ - copy "%(FullPath)" ..\..\include\unicode + copy "%(FullPath)" ..\..\include\unicode ..\..\include\unicode\%(Filename)%(Extension);%(Outputs) copy "%(FullPath)" ..\..\include\unicode @@ -1328,7 +1335,6 @@ - diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters index a7ddf1fc826..99cc63c5dcb 100644 --- a/icu4c/source/i18n/i18n.vcxproj.filters +++ b/icu4c/source/i18n/i18n.vcxproj.filters @@ -312,9 +312,6 @@ formatting - - formatting - formatting @@ -471,6 +468,18 @@ collation + + formatting + + + formatting + + + formatting + + + formatting + @@ -608,9 +617,6 @@ formatting - - formatting - formatting @@ -743,6 +749,18 @@ spoof + + formatting + + + formatting + + + formatting + + + formatting + diff --git a/icu4c/source/i18n/olsontz.cpp b/icu4c/source/i18n/olsontz.cpp index b9b2e0507d8..3fe2861d55f 100644 --- a/icu4c/source/i18n/olsontz.cpp +++ b/icu4c/source/i18n/olsontz.cpp @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (c) 2003-2010, International Business Machines +* Copyright (c) 2003-2011, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Author: Alan Liu @@ -24,6 +24,7 @@ #include "uvector.h" #include // DBL_MAX #include "uresimp.h" // struct UResourceBundle +#include "zonemeta.h" #ifdef U_DEBUG_TZ # include @@ -93,6 +94,8 @@ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(OlsonTimeZone) * constructor fails so the resultant object is well-behaved. */ void OlsonTimeZone::constructEmpty() { + canonicalID = NULL; + transitionCountPre32 = transitionCount32 = transitionCountPost32 = 0; transitionTimesPre32 = transitionTimes32 = transitionTimesPost32 = NULL; @@ -113,8 +116,9 @@ void OlsonTimeZone::constructEmpty() { */ OlsonTimeZone::OlsonTimeZone(const UResourceBundle* top, const UResourceBundle* res, + const UnicodeString& tzid, UErrorCode& ec) : - finalZone(NULL), transitionRulesInitialized(FALSE) + BasicTimeZone(tzid), finalZone(NULL), transitionRulesInitialized(FALSE) { clearTransitionRules(); U_DEBUG_TZ_MSG(("OlsonTimeZone(%s)\n", ures_getKey((UResourceBundle*)res))); @@ -245,6 +249,9 @@ OlsonTimeZone::OlsonTimeZone(const UResourceBundle* top, ec = U_ZERO_ERROR; } ures_close(&r); + + // initialize canonical ID + canonicalID = ZoneMeta::getCanonicalCLDRID(tzid, ec); } if (U_FAILURE(ec)) { @@ -264,6 +271,8 @@ OlsonTimeZone::OlsonTimeZone(const OlsonTimeZone& other) : * Assignment operator */ OlsonTimeZone& OlsonTimeZone::operator=(const OlsonTimeZone& other) { + canonicalID = other.canonicalID; + transitionTimesPre32 = other.transitionTimesPre32; transitionTimes32 = other.transitionTimes32; transitionTimesPost32 = other.transitionTimesPost32; diff --git a/icu4c/source/i18n/olsontz.h b/icu4c/source/i18n/olsontz.h index d997e2665e9..b89340e3470 100644 --- a/icu4c/source/i18n/olsontz.h +++ b/icu4c/source/i18n/olsontz.h @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (c) 2003-2010, International Business Machines +* Copyright (c) 2003-2011, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Author: Alan Liu @@ -117,10 +117,13 @@ class U_I18N_API OlsonTimeZone: public BasicTimeZone { * @param top the top-level zoneinfo resource bundle. This is used * to lookup the rule that `res' may refer to, if there is one. * @param res the resource bundle of the zone to be constructed + * @param tzid the time zone ID * @param ec input-output error code */ OlsonTimeZone(const UResourceBundle* top, - const UResourceBundle* res, UErrorCode& ec); + const UResourceBundle* res, + const UnicodeString& tzid, + UErrorCode& ec); /** * Copy constructor @@ -271,6 +274,12 @@ class U_I18N_API OlsonTimeZone: public BasicTimeZone { virtual void getTimeZoneRules(const InitialTimeZoneRule*& initial, const TimeZoneRule* trsrules[], int32_t& trscount, UErrorCode& status) /*const*/; + /** + * Internal API returning the canonical ID of this zone. + * This ID won't be affected by setID(). + */ + const UChar *getCanonicalID() const; + private: /** * Default constructor. Creates a time zone with an empty ID and @@ -366,6 +375,11 @@ private: */ int32_t finalStartYear; + /* + * Canonical (CLDR) ID of this zone + */ + const UChar *canonicalID; + /* BasicTimeZone support */ void clearTransitionRules(void); void deleteTransitionRules(void); @@ -419,6 +433,12 @@ OlsonTimeZone::initialDstOffset() const { return typeOffsets[1]; } +inline const UChar* +OlsonTimeZone::getCanonicalID() const { + return canonicalID; +} + + U_NAMESPACE_END #endif // !UCONFIG_NO_FORMATTING diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index 1c360030001..03ee8c061bf 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -52,9 +52,9 @@ #include "hebrwcal.h" #include "cstring.h" #include "uassert.h" -#include "zstrfmt.h" #include "cmemory.h" #include "umutex.h" +#include "tzfmt.h" #include #if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL) @@ -177,6 +177,9 @@ SimpleDateFormat::~SimpleDateFormat() if (fNumberFormatters) { uprv_free(fNumberFormatters); } + if (fTimeZoneFormat) { + delete fTimeZoneFormat; + } while (fOverrideList) { NSOverride *cur = fOverrideList; @@ -191,6 +194,7 @@ SimpleDateFormat::~SimpleDateFormat() SimpleDateFormat::SimpleDateFormat(UErrorCode& status) : fLocale(Locale::getDefault()), fSymbols(NULL), + fTimeZoneFormat(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL) @@ -206,6 +210,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, : fPattern(pattern), fLocale(Locale::getDefault()), fSymbols(NULL), + fTimeZoneFormat(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL) @@ -225,6 +230,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, : fPattern(pattern), fLocale(Locale::getDefault()), fSymbols(NULL), + fTimeZoneFormat(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL) @@ -246,6 +252,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, UErrorCode& status) : fPattern(pattern), fLocale(locale), + fTimeZoneFormat(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL) @@ -267,6 +274,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, UErrorCode& status) : fPattern(pattern), fLocale(locale), + fTimeZoneFormat(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL) @@ -290,6 +298,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, UErrorCode& status) : fPattern(pattern), fLocale(Locale::getDefault()), + fTimeZoneFormat(NULL), fSymbols(symbolsToAdopt), fGMTFormatters(NULL), fNumberFormatters(NULL), @@ -312,6 +321,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, : fPattern(pattern), fLocale(Locale::getDefault()), fSymbols(new DateFormatSymbols(symbols)), + fTimeZoneFormat(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL) @@ -334,6 +344,7 @@ SimpleDateFormat::SimpleDateFormat(EStyle timeStyle, UErrorCode& status) : fLocale(locale), fSymbols(NULL), + fTimeZoneFormat(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL) @@ -355,6 +366,7 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, UErrorCode& status) : fPattern(gDefaultPattern), fLocale(locale), + fTimeZoneFormat(NULL), fSymbols(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), @@ -389,6 +401,7 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat& other) : DateFormat(other), fSymbols(NULL), + fTimeZoneFormat(NULL), fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL) @@ -417,6 +430,11 @@ SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other) fPattern = other.fPattern; + // TimeZoneFormat in ICU4C only deneds on a locale for now + if (fLocale != other.fLocale) { + delete fTimeZoneFormat; + } + return *this; } @@ -930,21 +948,26 @@ SimpleDateFormat::appendGMT(NumberFormat *currentNumberFormat,UnicodeString &app if (U_FAILURE(status)) { return; } - if (isDefaultGMTFormat()) { - formatGMTDefault(currentNumberFormat,appendTo, offset); + if (offset == 0) { + // use GMT zero format + appendTo += fSymbols->fGmtZero; } else { - ((SimpleDateFormat*)this)->initGMTFormatters(status); - if (U_SUCCESS(status)) { - int32_t type; - if (offset < 0) { - offset = -offset; - type = (offset % U_MILLIS_PER_MINUTE) == 0 ? kGMTNegativeHM : kGMTNegativeHMS; - } else { - type = (offset % U_MILLIS_PER_MINUTE) == 0 ? kGMTPositiveHM : kGMTPositiveHMS; + if (isDefaultGMTFormat()) { + formatGMTDefault(currentNumberFormat,appendTo, offset); + } else { + ((SimpleDateFormat*)this)->initGMTFormatters(status); + if (U_SUCCESS(status)) { + int32_t type; + if (offset < 0) { + offset = -offset; + type = (offset % U_MILLIS_PER_MINUTE) == 0 ? kGMTNegativeHM : kGMTNegativeHMS; + } else { + type = (offset % U_MILLIS_PER_MINUTE) == 0 ? kGMTPositiveHM : kGMTPositiveHMS; + } + Formattable param(offset, Formattable::kIsDate); + FieldPosition fpos(0); + fGMTFormatters[type]->format(¶m, 1, appendTo, fpos, status); } - Formattable param(offset, Formattable::kIsDate); - FieldPosition fpos(0); - fGMTFormatters[type]->format(¶m, 1, appendTo, fpos, status); } } } @@ -1648,34 +1671,32 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, case UDAT_TIMEZONE_SPECIAL_FIELD: { UnicodeString zoneString; - const ZoneStringFormat *zsf = fSymbols->getZoneStringFormat(); - if (zsf) { + const TimeZone& tz = cal.getTimeZone(); + UDate date = cal.getTime(status); + if (U_SUCCESS(status)) { if (patternCharIndex == UDAT_TIMEZONE_FIELD) { if (count < 4) { // "z", "zz", "zzz" - zsf->getSpecificShortString(cal, TRUE /*commonly used only*/, - zoneString, status); + tzFormat()->format(UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED, tz, date, zoneString); } else { // "zzzz" - zsf->getSpecificLongString(cal, zoneString, status); + tzFormat()->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, date, zoneString); } } else if (patternCharIndex == UDAT_TIMEZONE_GENERIC_FIELD) { if (count == 1) { // "v" - zsf->getGenericShortString(cal, TRUE /*commonly used only*/, - zoneString, status); + tzFormat()->format(UTZFMT_STYLE_GENERIC_SHORT, tz, date, zoneString); } else if (count == 4) { // "vvvv" - zsf->getGenericLongString(cal, zoneString, status); + tzFormat()->format(UTZFMT_STYLE_GENERIC_LONG, tz, date, zoneString); } } else { // patternCharIndex == UDAT_TIMEZONE_SPECIAL_FIELD if (count == 1) { // "V" - zsf->getSpecificShortString(cal, FALSE /*ignore commonly used*/, - zoneString, status); + tzFormat()->format(UTZFMT_STYLE_SPECIFIC_SHORT, tz, date, zoneString); } else if (count == 4) { // "VVVV" - zsf->getGenericLocationString(cal, zoneString, status); + tzFormat()->format(UTZFMT_STYLE_LOCATION, tz, date, zoneString); } } } @@ -2812,70 +2833,76 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC } // Step 3 - // At this point, check for named time zones by looking through - // the locale data from the DateFormatZoneData strings. - // Want to be able to parse both short and long forms. - // optimize for calendar's current time zone - const ZoneStringFormat *zsf = fSymbols->getZoneStringFormat(); - if (zsf) { - UErrorCode status = U_ZERO_ERROR; - const ZoneStringInfo *zsinfo = NULL; - int32_t matchLen; - - switch (patternCharIndex) { - case UDAT_TIMEZONE_FIELD: // 'z' - if (count < 4) { - zsinfo = zsf->findSpecificShort(text, start, matchLen, status); - } else { - zsinfo = zsf->findSpecificLong(text, start, matchLen, status); - } - break; - case UDAT_TIMEZONE_GENERIC_FIELD: // 'v' - if (count == 1) { - zsinfo = zsf->findGenericShort(text, start, matchLen, status); - } else if (count == 4) { - zsinfo = zsf->findGenericLong(text, start, matchLen, status); - } - break; - case UDAT_TIMEZONE_SPECIAL_FIELD: // 'V' - if (count == 1) { - zsinfo = zsf->findSpecificShort(text, start, matchLen, status); - } else if (count == 4) { - zsinfo = zsf->findGenericLocation(text, start, matchLen, status); - } - break; - default: - break; - } - - if (U_SUCCESS(status) && zsinfo != NULL) { - if (zsinfo->isStandard()) { - ((SimpleDateFormat*)this)->tztype = TZTYPE_STD; - } else if (zsinfo->isDaylight()) { - ((SimpleDateFormat*)this)->tztype = TZTYPE_DST; - } - UnicodeString tzid; - zsinfo->getID(tzid); - - UnicodeString current; - cal.getTimeZone().getID(current); - if (tzid != current) { - TimeZone *tz = TimeZone::createTimeZone(tzid); - cal.adoptTimeZone(tz); - } - return start + matchLen; - } - } - // Step 4 - // Final attempt - is this standalone GMT/UT/UTC? + // Is this standalone Localized GMT zero or GMT/UT/UTC? int32_t gmtLen = 0; - if (text.compare(start, kGmtLen, gGmt) == 0) { + if (text.compare(start, fSymbols->fGmtZero.length(), fSymbols->fGmtZero) == 0) { + gmtLen = fSymbols->fGmtZero.length(); + } else if (text.compare(start, kGmtLen, gGmt) == 0) { gmtLen = kGmtLen; } else if (text.compare(start, kUtcLen, gUtc) == 0) { gmtLen = kUtcLen; } else if (text.compare(start, kUtLen, gUt) == 0) { gmtLen = kUtLen; } + // If we parse the string to the end, we can exit here. + // If any characters follow, we still need to proceed to the + // next step. Otherwise, all time zone names starting with GMT/UT/UTC + // (for example, "UTT") will fail. + if (gmtLen > 0 && ((text.length() - start) == gmtLen)) { + TimeZone *tz = TimeZone::createTimeZone(UnicodeString("Etc/GMT")); + cal.adoptTimeZone(tz); + return start + gmtLen; + } + + // Step 4 + // At this point, check for named time zones by looking through + // the locale data. + if (patternCharIndex != UDAT_TIMEZONE_RFC_FIELD) { + UTimeZoneTimeType parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + ParsePosition tmpPos(start); + UnicodeString parsedID; + + switch (patternCharIndex) { + case UDAT_TIMEZONE_FIELD: + if (count < 4) { + tzFormat()->parse(UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED, text, tmpPos, parsedID, &parsedTimeType); + } else { + tzFormat()->parse(UTZFMT_STYLE_SPECIFIC_LONG, text, tmpPos, parsedID, &parsedTimeType); + } + break; + case UDAT_TIMEZONE_GENERIC_FIELD: + if (count < 4) { + tzFormat()->parse(UTZFMT_STYLE_GENERIC_SHORT, text, tmpPos, parsedID, &parsedTimeType); + } else { + tzFormat()->parse(UTZFMT_STYLE_GENERIC_LONG, text, tmpPos, parsedID, &parsedTimeType); + } + break; + case UDAT_TIMEZONE_SPECIAL_FIELD: + if (count < 4) { + tzFormat()->parse(UTZFMT_STYLE_SPECIFIC_SHORT, text, tmpPos, parsedID, &parsedTimeType); + } else { + tzFormat()->parse(UTZFMT_STYLE_LOCATION, text, tmpPos, parsedID, &parsedTimeType); + } + break; + } + if (tmpPos.getErrorIndex() < 0) { + if (parsedTimeType == UTZFMT_TIME_TYPE_STANDARD) { + ((SimpleDateFormat*)this)->tztype = TZTYPE_STD; + } else if (parsedTimeType == UTZFMT_TIME_TYPE_DAYLIGHT) { + ((SimpleDateFormat*)this)->tztype = TZTYPE_DST; + } + + UnicodeString current; + cal.getTimeZone().getID(current); + if (parsedID != current) { + TimeZone *tz = TimeZone::createTimeZone(parsedID); + cal.adoptTimeZone(tz); + } + return tmpPos.getIndex(); + } + } + // Step 5 + // If we saw standalone GMT zero pattern, then use GMT. if (gmtLen > 0) { TimeZone *tz = TimeZone::createTimeZone(UnicodeString("Etc/GMT")); cal.adoptTimeZone(tz); @@ -3280,6 +3307,26 @@ SimpleDateFormat::skipUWhiteSpace(const UnicodeString& text, int32_t pos) const return pos; } +//---------------------------------------------------------------------- + +// Lazy TimeZoneFormat instantiation, semantically const. +TimeZoneFormat * +SimpleDateFormat::tzFormat() const { + if (fTimeZoneFormat == NULL) { + umtx_lock(&LOCK); + { + if (fTimeZoneFormat == NULL) { + UErrorCode status = U_ZERO_ERROR; + TimeZoneFormat *tzfmt = TimeZoneFormat::createInstance(fLocale, status); + U_ASSERT(U_SUCCESS(status)); + + const_cast(this)->fTimeZoneFormat = tzfmt; + } + } + umtx_unlock(&LOCK); + } + return fTimeZoneFormat; +} U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/timezone.cpp b/icu4c/source/i18n/timezone.cpp index 58c3b47a9b3..0b11cc8ad11 100644 --- a/icu4c/source/i18n/timezone.cpp +++ b/icu4c/source/i18n/timezone.cpp @@ -417,10 +417,8 @@ TimeZone::createSystemTimeZone(const UnicodeString& id, UErrorCode& ec) { UResourceBundle *top = openOlsonResource(id, res, ec); U_DEBUG_TZ_MSG(("post-err=%s\n", u_errorName(ec))); if (U_SUCCESS(ec)) { - z = new OlsonTimeZone(top, &res, ec); - if (z) { - z->setID(id); - } else { + z = new OlsonTimeZone(top, &res, id, ec); + if (z == NULL) { U_DEBUG_TZ_MSG(("cstz: olson time zone failed to initialize - err %s\n", u_errorName(ec))); } } @@ -1062,7 +1060,26 @@ TimeZone::getEquivalentID(const UnicodeString& id, int32_t index) { // --------------------------------------- -// These two methods are used by ZoneMeta class only. +// These methods are used by ZoneMeta class only. + +const UChar* +TimeZone::findID(const UnicodeString& id) { + const UChar *result = NULL; + UErrorCode ec = U_ZERO_ERROR; + UResourceBundle *rb = ures_openDirect(NULL, kZONEINFO, &ec); + + // resolve zone index by name + UResourceBundle *names = ures_getByKey(rb, kNAMES, NULL, &ec); + int32_t idx = findInStringArray(names, id, ec); + result = ures_getStringByIndex(names, idx, NULL, &ec); + if (U_FAILURE(ec)) { + result = NULL; + } + ures_close(names); + ures_close(rb); + return result; +} + const UChar* TimeZone::dereferOlsonLink(const UnicodeString& id) { diff --git a/icu4c/source/i18n/tzfmt.cpp b/icu4c/source/i18n/tzfmt.cpp new file mode 100644 index 00000000000..ff36195bbde --- /dev/null +++ b/icu4c/source/i18n/tzfmt.cpp @@ -0,0 +1,502 @@ +/* +******************************************************************************* +* Copyright (C) 2011, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "tzfmt.h" +#include "tzgnames.h" +#include "cmemory.h" +#include "cstring.h" +#include "putilimp.h" +#include "uassert.h" +#include "ucln_in.h" +#include "uhash.h" +#include "umutex.h" +#include "zonemeta.h" + +U_NAMESPACE_BEGIN + +// --------------------------------------------------- +// TimeZoneFormatImpl - the TimeZoneFormat implementation +// --------------------------------------------------- +class TimeZoneFormatImpl : public TimeZoneFormat { +public: + TimeZoneFormatImpl(const Locale& locale, UErrorCode& status); + virtual ~TimeZoneFormatImpl(); + + const TimeZoneNames* getTimeZoneNames() const; + + UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, + UnicodeString& name, UTimeZoneTimeType* timeType = NULL) const; + + UnicodeString& parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UnicodeString& tzID, UTimeZoneTimeType* timeType = NULL) const; + +private: + Locale fLocale; + char fTargetRegion[ULOC_COUNTRY_CAPACITY]; + TimeZoneNames* fTimeZoneNames; + TimeZoneGenericNames* fTimeZoneGenericNames; + + UnicodeString& formatGeneric(const TimeZone& tz, UTimeZoneGenericNameType genType, UDate date, UnicodeString& name) const; + + UnicodeString& formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType, + UDate date, UnicodeString& name, UTimeZoneTimeType *timeType) const; +}; + +TimeZoneFormatImpl::TimeZoneFormatImpl(const Locale& locale, UErrorCode& status) +: fLocale(locale), fTimeZoneNames(NULL), fTimeZoneGenericNames(NULL) { + + 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 { + return; + } + } else if (regionLen < (int32_t)sizeof(fTargetRegion)) { + uprv_strcpy(fTargetRegion, region); + } else { + fTargetRegion[0] = 0; + } + + fTimeZoneNames = TimeZoneNames::createInstance(locale, status); + fTimeZoneGenericNames = new TimeZoneGenericNames(locale, status); + if (U_SUCCESS(status) && fTimeZoneGenericNames == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } +} + +TimeZoneFormatImpl::~TimeZoneFormatImpl() { + if (fTimeZoneNames != NULL) { + delete fTimeZoneNames; + } + if (fTimeZoneGenericNames != NULL) { + delete fTimeZoneGenericNames; + } +} + +const TimeZoneNames* +TimeZoneFormatImpl::getTimeZoneNames() const { + return fTimeZoneNames; +} + +UnicodeString& +TimeZoneFormatImpl::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, + UnicodeString& name, UTimeZoneTimeType* timeType /* = NULL */) const { + if (timeType) { + *timeType = UTZFMT_TIME_TYPE_UNKNOWN; + } + switch (style) { + case UTZFMT_STYLE_LOCATION: + formatGeneric(tz, UTZGNM_LOCATION, date, name); + break; + case UTZFMT_STYLE_GENERIC_LONG: + formatGeneric(tz, UTZGNM_LONG, date, name); + break; + case UTZFMT_STYLE_GENERIC_SHORT: + formatGeneric(tz, UTZGNM_SHORT, date, name); + break; + case UTZFMT_STYLE_SPECIFIC_LONG: + formatSpecific(tz, UTZNM_LONG_STANDARD, UTZNM_LONG_DAYLIGHT, date, name, timeType); + break; + case UTZFMT_STYLE_SPECIFIC_SHORT: + formatSpecific(tz, UTZNM_SHORT_STANDARD, UTZNM_SHORT_DAYLIGHT, date, name, timeType); + break; + case UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED: + formatSpecific(tz, UTZNM_SHORT_STANDARD_COMMONLY_USED, UTZNM_SHORT_DAYLIGHT_COMMONLY_USED, date, name, timeType); + break; + } + return name; +} + +UnicodeString& +TimeZoneFormatImpl::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UnicodeString& tzID, UTimeZoneTimeType* timeType /* = NULL */) const { + if (timeType) { + *timeType = UTZFMT_TIME_TYPE_UNKNOWN; + } + tzID.setToBogus(); + + int32_t startIdx = pos.getIndex(); + + UBool isGeneric = FALSE; + uint32_t types = 0; + + switch (style) { + case UTZFMT_STYLE_LOCATION: + isGeneric = TRUE; + types = UTZGNM_LOCATION; + break; + case UTZFMT_STYLE_GENERIC_LONG: + isGeneric = TRUE; + types = UTZGNM_LOCATION | UTZGNM_LONG; + break; + case UTZFMT_STYLE_GENERIC_SHORT: + isGeneric = TRUE; + types = UTZGNM_LOCATION | UTZGNM_SHORT; + break; + case UTZFMT_STYLE_SPECIFIC_LONG: + types = UTZNM_LONG_STANDARD | UTZNM_LONG_DAYLIGHT; + break; + case UTZFMT_STYLE_SPECIFIC_SHORT: + types = UTZNM_SHORT_STANDARD | UTZNM_SHORT_DAYLIGHT; + break; + case UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED: + types = UTZNM_SHORT_STANDARD_COMMONLY_USED | UTZNM_SHORT_DAYLIGHT_COMMONLY_USED; + break; + } + + UTimeZoneTimeType parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + UnicodeString parsedTzID; + UErrorCode status = U_ZERO_ERROR; + + if (isGeneric) { + int32_t len = fTimeZoneGenericNames->findBestMatch(text, startIdx, types, parsedTzID, parsedTimeType, status); + if (U_FAILURE(status) || len == 0) { + pos.setErrorIndex(startIdx); + return tzID; + } + pos.setIndex(startIdx + len); + } else { + TimeZoneNameMatchInfo *matchInfo = fTimeZoneNames->find(text, startIdx, types, status); + if (U_FAILURE(status) || matchInfo == NULL) { + pos.setErrorIndex(startIdx); + return tzID; + } + int32_t bestLen = 0; + int32_t bestIdx = -1; + for (int32_t i = 0; i < matchInfo->size(); i++) { + int32_t matchLen = matchInfo->getMatchLength(i); + if (matchLen > bestLen) { + bestLen = matchLen; + bestIdx = i; + } + } + if (bestIdx >= 0) { + matchInfo->getTimeZoneID(bestIdx, parsedTzID); + if (parsedTzID.isEmpty()) { + UnicodeString mzID; + matchInfo->getMetaZoneID(bestIdx, mzID); + U_ASSERT(mzID.length() > 0); + fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, parsedTzID); + } + UTimeZoneNameType nameType = matchInfo->getNameType(bestIdx); + switch (nameType) { + case UTZNM_LONG_STANDARD: + case UTZNM_SHORT_STANDARD: + case UTZNM_SHORT_STANDARD_COMMONLY_USED: + parsedTimeType = UTZFMT_TIME_TYPE_STANDARD; + break; + case UTZNM_LONG_DAYLIGHT: + case UTZNM_SHORT_DAYLIGHT: + case UTZNM_SHORT_DAYLIGHT_COMMONLY_USED: + parsedTimeType = UTZFMT_TIME_TYPE_DAYLIGHT; + break; + default: + parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + break; + } + pos.setIndex(startIdx + bestLen); + } + delete matchInfo; + } + if (timeType) { + *timeType = parsedTimeType; + } + tzID.setTo(parsedTzID); + return tzID; +} + +UnicodeString& +TimeZoneFormatImpl::formatGeneric(const TimeZone& tz, UTimeZoneGenericNameType genType, UDate date, UnicodeString& name) const { + if (fTimeZoneGenericNames == NULL) { + name.setToBogus(); + return name; + } + + if (genType == UTZGNM_LOCATION) { + const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz); + if (canonicalID == NULL) { + name.setToBogus(); + return name; + } + return fTimeZoneGenericNames->getGenericLocationName(UnicodeString(canonicalID), name); + } + return fTimeZoneGenericNames->getDisplayName(tz, genType, date, name); +} + +UnicodeString& +TimeZoneFormatImpl::formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType, + UDate date, UnicodeString& name, UTimeZoneTimeType *timeType) const { + if (fTimeZoneNames == NULL) { + name.setToBogus(); + return name; + } + + UErrorCode status = U_ZERO_ERROR; + UBool isDaylight = tz.inDaylightTime(date, status); + const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz); + + if (U_FAILURE(status) || canonicalID == NULL) { + name.setToBogus(); + return name; + } + + if (isDaylight) { + fTimeZoneNames->getDisplayName(UnicodeString(canonicalID), dstType, date, name); + } else { + fTimeZoneNames->getDisplayName(UnicodeString(canonicalID), stdType, date, name); + } + + if (timeType && !name.isEmpty()) { + *timeType = isDaylight ? UTZFMT_TIME_TYPE_DAYLIGHT : UTZFMT_TIME_TYPE_STANDARD; + } + return name; +} + + +// TimeZoneFormat object cache handling +static UMTX gTimeZoneFormatLock = NULL; +static UHashtable *gTimeZoneFormatCache = NULL; +static UBool gTimeZoneFormatCacheInitialized = 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 + +typedef struct TimeZoneFormatCacheEntry { + TimeZoneFormat* tzfmt; + int32_t refCount; + double lastAccess; +} TimeZoneNameFormatCacheEntry; + +U_CDECL_BEGIN +/** + * Cleanup callback func + */ +static UBool U_CALLCONV timeZoneFormat_cleanup(void) +{ + umtx_destroy(&gTimeZoneFormatLock); + + if (gTimeZoneFormatCache != NULL) { + uhash_close(gTimeZoneFormatCache); + gTimeZoneFormatCache = NULL; + } + gTimeZoneFormatCacheInitialized = FALSE; + return TRUE; +} + +/** + * Deleter for TimeZoneNamesCacheEntry + */ +static void U_CALLCONV +deleteTimeZoneFormatCacheEntry(void *obj) { + TimeZoneNameFormatCacheEntry *entry = (TimeZoneNameFormatCacheEntry *)obj; + delete (TimeZoneFormat *) entry->tzfmt; + uprv_free((void *)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(gTimeZoneFormatCache, &pos))) { + TimeZoneFormatCacheEntry *entry = (TimeZoneFormatCacheEntry *)elem->value.pointer; + if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) { + // delete this entry + uhash_removeElement(gTimeZoneFormatCache, elem); + } + } +} + +// --------------------------------------------------- +// TimeZoneFormatDelegate +// This class wraps a TimeZoneFormatImpl singleton +// per locale and maintain the reference count. +// --------------------------------------------------- +class TimeZoneFormatDelegate : public TimeZoneFormat { +public: + TimeZoneFormatDelegate(const Locale& locale, UErrorCode& status); + virtual ~TimeZoneFormatDelegate(); + + const TimeZoneNames* getTimeZoneNames() const; + + UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, + UnicodeString& name, UTimeZoneTimeType* timeType = NULL) const; + + UnicodeString& parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UnicodeString& tzID, UTimeZoneTimeType* timeType = NULL) const; + +private: + TimeZoneFormatCacheEntry* fTZfmtCacheEntry; +}; + +TimeZoneFormatDelegate::TimeZoneFormatDelegate(const Locale& locale, UErrorCode& status) { + UBool initialized; + UMTX_CHECK(&gTimeZoneFormatLock, gTimeZoneFormatCacheInitialized, initialized); + if (!initialized) { + // Create empty hashtable + umtx_lock(&gTimeZoneFormatLock); + { + if (!gTimeZoneFormatCacheInitialized) { + gTimeZoneFormatCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); + if (U_SUCCESS(status)) { + uhash_setKeyDeleter(gTimeZoneFormatCache, uhash_freeBlock); + uhash_setValueDeleter(gTimeZoneFormatCache, deleteTimeZoneFormatCacheEntry); + gTimeZoneFormatCacheInitialized = TRUE; + ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEFORMAT, timeZoneFormat_cleanup); + } + } + } + umtx_unlock(&gTimeZoneFormatLock); + } + + // Check the cache, if not available, create new one and cache + TimeZoneFormatCacheEntry *cacheEntry = NULL; + umtx_lock(&gTimeZoneFormatLock); + { + const char *key = locale.getName(); + cacheEntry = (TimeZoneFormatCacheEntry *)uhash_get(gTimeZoneFormatCache, key); + if (cacheEntry == NULL) { + TimeZoneFormat *tzfmt = NULL; + char *newKey = NULL; + + tzfmt = new TimeZoneFormatImpl(locale, status); + if (tzfmt == 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 = (TimeZoneFormatCacheEntry *)uprv_malloc(sizeof(TimeZoneFormatCacheEntry)); + if (cacheEntry == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + cacheEntry->tzfmt = tzfmt; + cacheEntry->refCount = 1; + cacheEntry->lastAccess = (double)uprv_getUTCtime(); + + uhash_put(gTimeZoneFormatCache, newKey, cacheEntry, &status); + } + } + if (U_FAILURE(status)) { + if (tzfmt != NULL) { + delete tzfmt; + } + if (newKey != NULL) { + uprv_free(newKey); + } + if (cacheEntry != NULL) { + uprv_free(cacheEntry); + } + return; + } + } else { + // Update the reference count + cacheEntry->refCount++; + cacheEntry->lastAccess = (double)uprv_getUTCtime(); + } + gAccessCount++; + if (gAccessCount >= SWEEP_INTERVAL) { + // sweep + sweepCache(); + gAccessCount = 0; + } + } + umtx_unlock(&gTimeZoneFormatLock); + + fTZfmtCacheEntry = cacheEntry; +} + +TimeZoneFormatDelegate::~TimeZoneFormatDelegate() { + umtx_lock(&gTimeZoneFormatLock); + { + U_ASSERT(fTZfmtCacheEntry->refCount > 0); + // Just decrement the reference count + fTZfmtCacheEntry->refCount--; + } + umtx_unlock(&gTimeZoneFormatLock); +} + +const TimeZoneNames* +TimeZoneFormatDelegate::getTimeZoneNames() const { + return fTZfmtCacheEntry->tzfmt->getTimeZoneNames(); +} + +UnicodeString& +TimeZoneFormatDelegate::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, + UnicodeString& name, UTimeZoneTimeType* timeType /* = NULL */) const { + return fTZfmtCacheEntry->tzfmt->format(style, tz, date, name, timeType); +} + +UnicodeString& +TimeZoneFormatDelegate::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UnicodeString& tzID, UTimeZoneTimeType* timeType /* = NULL */) const { + return fTZfmtCacheEntry->tzfmt->parse(style, text, pos, tzID, timeType); +} + + +// --------------------------------------------------- +// TimeZoneFormat base class +// --------------------------------------------------- +TimeZoneFormat::~TimeZoneFormat() { +} + +TimeZone* +TimeZoneFormat::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UTimeZoneTimeType* timeType /*= NULL*/) const { + UnicodeString tzID; + parse(style, text, pos, tzID, timeType); + if (pos.getErrorIndex() < 0) { + return TimeZone::createTimeZone(tzID); + } + return NULL; +} + +TimeZoneFormat* U_EXPORT2 +TimeZoneFormat::createInstance(const Locale& locale, UErrorCode& status) { + TimeZoneFormat* tzfmt = new TimeZoneFormatDelegate(locale, status); + if (U_SUCCESS(status) && tzfmt == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + return tzfmt; +} + + +U_NAMESPACE_END + +#endif diff --git a/icu4c/source/i18n/tzfmt.h b/icu4c/source/i18n/tzfmt.h new file mode 100644 index 00000000000..0222e6ae0c2 --- /dev/null +++ b/icu4c/source/i18n/tzfmt.h @@ -0,0 +1,65 @@ +/* +******************************************************************************* +* Copyright (C) 2011, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ +#ifndef __TZFMT_H +#define __TZFMT_H + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/uobject.h" +#include "unicode/uloc.h" +#include "unicode/unistr.h" +#include "unicode/format.h" +#include "unicode/timezone.h" +#include "tznames.h" + +U_CDECL_BEGIN + +typedef enum UTimeZoneFormatStyle { + UTZFMT_STYLE_LOCATION, + UTZFMT_STYLE_GENERIC_LONG, + UTZFMT_STYLE_GENERIC_SHORT, + UTZFMT_STYLE_SPECIFIC_LONG, + UTZFMT_STYLE_SPECIFIC_SHORT, + UTZFMT_STYLE_SPECIFIC_SHORT_COMMONLY_USED +} UTimeZoneFormatStyle; + +typedef enum UTimeZoneTimeType { + UTZFMT_TIME_TYPE_UNKNOWN, + UTZFMT_TIME_TYPE_STANDARD, + UTZFMT_TIME_TYPE_DAYLIGHT +} UTimeZoneTimeType; + +U_CDECL_END + +U_NAMESPACE_BEGIN + +class TimeZoneNames; + +class U_I18N_API TimeZoneFormat : public UMemory { +public: + virtual ~TimeZoneFormat(); + + static TimeZoneFormat* U_EXPORT2 createInstance(const Locale& locale, UErrorCode& status); + + virtual const TimeZoneNames* getTimeZoneNames() const = 0; + + virtual UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, + UnicodeString& name, UTimeZoneTimeType* timeType = NULL) const = 0; + + virtual UnicodeString& parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UnicodeString& tzID, UTimeZoneTimeType* timeType = NULL) const = 0; + + TimeZone* parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UTimeZoneTimeType* timeType = NULL) const; +}; + +U_NAMESPACE_END + +#endif +#endif diff --git a/icu4c/source/i18n/tzgnames.cpp b/icu4c/source/i18n/tzgnames.cpp new file mode 100644 index 00000000000..745c729aca1 --- /dev/null +++ b/icu4c/source/i18n/tzgnames.cpp @@ -0,0 +1,1053 @@ +/* +******************************************************************************* +* Copyright (C) 2011, 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 "uhash.h" +#include "uassert.h" +#include "umutex.h" +#include "uresimp.h" +#include "ureslocs.h" +#include "zonemeta.h" +#include "tznames_impl.h" +#include "olsontz.h" + +U_NAMESPACE_BEGIN + +#define ZID_KEY_MAX 128 + +static const char gZoneStrings[] = "zoneStrings"; + +static const char gRegionFormatTag[] = "regionFormat"; +static const char gFallbackRegionFormatTag[] = "fallbackRegionFormat"; +static const char gFallbackFormatTag[] = "fallbackFormat"; + +static const UChar gEmpty[] = {0x00}; + +static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}" +static const UChar gDefFallbackRegionPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({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) { + // &#[L|S] + PartialLocationKey *p = (PartialLocationKey *)key.pointer; + UnicodeString str(p->tzID); + str.append((UChar)0x26) + .append(p->mzID) + .append((UChar)0x23) + .append((UChar)(p->isLong ? 0x4C : 0x53)); + return uhash_hashUCharsN(str.getBuffer(), str.length()); +} + +/** + * 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; + UTimeZoneTimeType timeType; +} ZMatchInfo; + +U_CDECL_END + +// --------------------------------------------------- +// The class stores time zone generic name match information +// --------------------------------------------------- +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(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(uhash_freeBlock, NULL, status); + if (fResults == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + } + if (U_SUCCESS(status)) { + 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; +} + +// --------------------------------------------------- +// TimeZoneGenericNames +// +// TimeZoneGenericNames is parallel to TimeZoneNames, +// but handles run-time generated time zone names. +// This is the main part of this module. +// --------------------------------------------------- +TimeZoneGenericNames::TimeZoneGenericNames(const Locale& locale, UErrorCode& status) +: fLocale(locale), + fLock(NULL), + fTimeZoneNames(NULL), + fLocationNamesMap(NULL), + fPartialLocationNamesMap(NULL), + fRegionFormat(NULL), + fFallbackRegionFormat(NULL), + fFallbackFormat(NULL), + fLocaleDisplayNames(NULL), + fStringPool(status), + fGNamesTrie(TRUE, deleteGNameInfo), + fGNamesTrieFullyLoaded(FALSE) { + initialize(locale, status); +} + +TimeZoneGenericNames::~TimeZoneGenericNames() { + cleanup(); + umtx_destroy(&fLock); +} + +void +TimeZoneGenericNames::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 frpat(TRUE, gDefFallbackRegionPattern, -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); + } + tmpsts = U_ZERO_ERROR; + const UChar *fallbackRegionPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackRegionFormatTag, NULL, &tmpsts); + if (U_SUCCESS(tmpsts) && u_strlen(fallbackRegionPattern) > 0) { + frpat.setTo(fallbackRegionPattern); + } + tmpsts = U_ZERO_ERROR; + const UChar *fallbackPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackFormatTag, NULL, &tmpsts); + if (U_SUCCESS(tmpsts) && u_strlen(fallbackPattern) > 0) { + fpat.setTo(fallbackPattern); + } + } + ures_close(zoneStrings); + + fRegionFormat = new MessageFormat(rpat, status); + if (fRegionFormat == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + fFallbackRegionFormat = new MessageFormat(frpat, status); + if (fFallbackRegionFormat == 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, uhash_freeBlock); + // 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 +TimeZoneGenericNames::cleanup() { + if (fRegionFormat != NULL) { + delete fRegionFormat; + } + if (fFallbackRegionFormat != NULL) { + delete fFallbackRegionFormat; + } + if (fFallbackFormat != NULL) { + delete fFallbackFormat; + } + if (fLocaleDisplayNames != NULL) { + delete fLocaleDisplayNames; + } + if (fTimeZoneNames != NULL) { + delete fTimeZoneNames; + } + + uhash_close(fLocationNamesMap); + uhash_close(fPartialLocationNamesMap); +} + +UnicodeString& +TimeZoneGenericNames::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& +TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { + if (tzCanonicalID.isEmpty()) { + name.setToBogus(); + return name; + } + + const UChar *locname = NULL; + TimeZoneGenericNames *nonConstThis = const_cast(this); + umtx_lock(&nonConstThis->fLock); + { + locname = nonConstThis->getGenericLocationName(tzCanonicalID); + } + umtx_unlock(&nonConstThis->fLock); + + if (locname == NULL) { + name.setToBogus(); + } else { + name.setTo(TRUE, locname, -1); + } + + return name; +} + +/* + * This method updates the cache and must be called with a lock + */ +const UChar* +TimeZoneGenericNames::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; + UBool isSingleCountry = FALSE; + UnicodeString usCountryCode; + ZoneMeta::getSingleCountry(tzCanonicalID, usCountryCode); + if (!usCountryCode.isEmpty()) { + isSingleCountry = TRUE; + } else { + 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 country; + fLocaleDisplayNames->regionDisplayName(countryCode, country); + + // Format + FieldPosition fpos; + if (isSingleCountry) { + // If the zone is only one zone in the country, do not add city + Formattable param[] = { + Formattable(country) + }; + fRegionFormat->format(param, 1, name, fpos, status); + } else { + // getExemplarLocationName should retur non-empty string + // if the time zone is associated with a region + UnicodeString city; + fTimeZoneNames->getExemplarLocationName(tzCanonicalID, city); + + Formattable params[] = { + Formattable(city), + Formattable(country) + }; + fFallbackRegionFormat->format(params, 2, 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& +TimeZoneGenericNames::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(tmptz) != NULL + || dynamic_cast(tmptz) != NULL + || dynamic_cast(tmptz) != NULL + || dynamic_cast(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_COMMONLY_USED; + 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& +TimeZoneGenericNames::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; + TimeZoneGenericNames *nonConstThis = const_cast(this); + umtx_lock(&nonConstThis->fLock); + { + uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName); + } + umtx_unlock(&nonConstThis->fLock); + + 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* +TimeZoneGenericNames::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::getSingleCountry(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; + fLocaleDisplayNames->regionDisplayName(countryCode, 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 +TimeZoneGenericNames::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 +TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, + UnicodeString& tzID, UTimeZoneTimeType& timeType, UErrorCode& status) const { + timeType = UTZFMT_TIME_TYPE_UNKNOWN; + tzID.setToBogus(); + + if (U_FAILURE(status)) { + return 0; + } + + // Find matches in the TimeZoneNames first + TimeZoneNameMatchInfo *tznamesMatches = findTimeZoneNames(text, start, types, status); + if (U_FAILURE(status)) { + return 0; + } + + int32_t bestMatchLen = 0; + UTimeZoneTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + UnicodeString bestMatchTzID; + UBool isLongStandard = FALSE; // workaround - see the comments below + + if (tznamesMatches != NULL) { + UnicodeString mzID; + for (int32_t i = 0; i < tznamesMatches->size(); i++) { + int32_t len = tznamesMatches->getMatchLength(i); + if (len > bestMatchLen) { + bestMatchLen = len; + tznamesMatches->getTimeZoneID(i, bestMatchTzID); + if (bestMatchTzID.isEmpty()) { + // name for a meta zone + tznamesMatches->getMetaZoneID(i, mzID); + U_ASSERT(mzID.length() > 0); + fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, bestMatchTzID); + } + UTimeZoneNameType nameType = tznamesMatches->getNameType(i); + switch (nameType) { + case UTZNM_LONG_STANDARD: + isLongStandard = TRUE; + case UTZNM_SHORT_STANDARD_COMMONLY_USED: + case UTZNM_SHORT_STANDARD: // this one is never used for generic, but just in case + bestMatchTimeType = UTZFMT_TIME_TYPE_STANDARD; + break; + case UTZNM_LONG_DAYLIGHT: + case UTZNM_SHORT_DAYLIGHT_COMMONLY_USED: + 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 (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; + } + } + } + + // 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* +TimeZoneGenericNames::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { + GNameSearchHandler handler(types); + + TimeZoneGenericNames *nonConstThis = const_cast(this); + + umtx_lock(&nonConstThis->fLock); + { + fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); + } + umtx_unlock(&nonConstThis->fLock); + + 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(&nonConstThis->fLock); + { + 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(&nonConstThis->fLock); + + if (U_FAILURE(status)) { + return NULL; + } + + umtx_lock(&nonConstThis->fLock); + { + // now try it again + fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); + } + umtx_unlock(&nonConstThis->fLock); + + 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; +} + +TimeZoneNameMatchInfo* +TimeZoneGenericNames::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { + TimeZoneNameMatchInfo *matchInfo = NULL; + + // 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_COMMONLY_USED); + } + + if (types) { + // Find matches in the TimeZoneNames + matchInfo = fTimeZoneNames->find(text, start, nameTypes, status); + } + + return matchInfo; +} + +U_NAMESPACE_END +#endif diff --git a/icu4c/source/i18n/tzgnames.h b/icu4c/source/i18n/tzgnames.h new file mode 100644 index 00000000000..49a471f840c --- /dev/null +++ b/icu4c/source/i18n/tzgnames.h @@ -0,0 +1,115 @@ +/* +******************************************************************************* +* Copyright (C) 2011, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ +#ifndef __TZGNAMES_H +#define __TZGNAMES_H + +/** + * \file + * \brief C API: Time zone generic names classe + */ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/locid.h" +#include "unicode/timezone.h" +#include "unicode/unistr.h" +#include "tznames.h" +#include "tznames_impl.h" +#include "tzfmt.h" +#include "uhash.h" +#include "umutex.h" + +U_CDECL_BEGIN + +typedef enum UTimeZoneGenericNameType { + UTZGNM_UNKNOWN = 0x00, + UTZGNM_LOCATION = 0x01, + UTZGNM_LONG = 0x02, + UTZGNM_SHORT = 0x04 +} UTimeZoneGenericNameType; + +U_CDECL_END + +U_NAMESPACE_BEGIN + +class LocaleDisplayNames; +class MessageFormat; +class TimeZone; + +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 +}; + +class U_I18N_API TimeZoneGenericNames : public UMemory { +public: + TimeZoneGenericNames(const Locale& locale, UErrorCode& status); + virtual ~TimeZoneGenericNames(); + + 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, UTimeZoneTimeType& timeType, UErrorCode& status) const; + +private: + Locale fLocale; + UMTX fLock; + const TimeZoneNames* fTimeZoneNames; + UHashtable* fLocationNamesMap; + UHashtable* fPartialLocationNamesMap; + + MessageFormat* fRegionFormat; + MessageFormat* fFallbackRegionFormat; + 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; + + TimeZoneNameMatchInfo* findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; +}; + +U_NAMESPACE_END +#endif +#endif diff --git a/icu4c/source/i18n/tznames.cpp b/icu4c/source/i18n/tznames.cpp new file mode 100644 index 00000000000..276807871a7 --- /dev/null +++ b/icu4c/source/i18n/tznames.cpp @@ -0,0 +1,299 @@ +/* +******************************************************************************* +* Copyright (C) 2011, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "tznames.h" +#include "tznames_impl.h" + +#include "unicode/locid.h" +#include "unicode/uenum.h" +#include "cmemory.h" +#include "cstring.h" +#include "putilimp.h" +#include "uassert.h" +#include "ucln_in.h" +#include "uhash.h" +#include "umutex.h" + + +U_NAMESPACE_BEGIN + +static const UChar gEtcPrefix[] = { 0x45, 0x74, 0x63, 0x2F }; // "Etc/" +static const int32_t gEtcPrefixLen = 4; +static const UChar gSystemVPrefix[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F }; // "SystemV/ +static const int32_t gSystemVPrefixLen = 8; +static const UChar gRiyadh8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38 }; // "Riyadh8" +static const int32_t gRiyadh8Len = 7; + +// TimeZoneNames object cache handling +static UMTX gTimeZoneNamesLock = NULL; +static UHashtable *gTimeZoneNamesCache = NULL; +static UBool gTimeZoneNamesCacheInitialized = 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 + +typedef struct TimeZoneNamesCacheEntry { + TimeZoneNames* names; + int32_t refCount; + double lastAccess; +} TimeZoneNamesCacheEntry; + +U_CDECL_BEGIN +/** + * Cleanup callback func + */ +static UBool U_CALLCONV timeZoneNames_cleanup(void) +{ + umtx_destroy(&gTimeZoneNamesLock); + + if (gTimeZoneNamesCache != NULL) { + uhash_close(gTimeZoneNamesCache); + gTimeZoneNamesCache = NULL; + } + gTimeZoneNamesCacheInitialized = FALSE; + return TRUE; +} + +/** + * Deleter for TimeZoneNamesCacheEntry + */ +static void U_CALLCONV +deleteTimeZoneNamesCacheEntry(void *obj) { + U_NAMESPACE_QUALIFIER TimeZoneNamesCacheEntry *entry = (U_NAMESPACE_QUALIFIER TimeZoneNamesCacheEntry*)obj; + delete (U_NAMESPACE_QUALIFIER TimeZoneNamesImpl*) entry->names; + 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(gTimeZoneNamesCache, &pos))) { + TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer; + if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) { + // delete this entry + uhash_removeElement(gTimeZoneNamesCache, elem); + } + } +} + +class TimeZoneNamesDelegate : public TimeZoneNames { +public: + TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status); + virtual ~TimeZoneNamesDelegate(); + + StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const; + StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const; + UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const; + UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const; + + UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const; + UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const; + + UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; + + TimeZoneNameMatchInfo* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; +private: + TimeZoneNamesCacheEntry* fTZnamesCacheEntry; +}; + +TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) { + UBool initialized; + UMTX_CHECK(&gTimeZoneNamesLock, gTimeZoneNamesCacheInitialized, initialized); + if (!initialized) { + // Create empty hashtable + umtx_lock(&gTimeZoneNamesLock); + { + if (!gTimeZoneNamesCacheInitialized) { + gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); + if (U_SUCCESS(status)) { + uhash_setKeyDeleter(gTimeZoneNamesCache, uhash_freeBlock); + uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry); + gTimeZoneNamesCacheInitialized = TRUE; + ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup); + } + } + } + umtx_unlock(&gTimeZoneNamesLock); + + if (U_FAILURE(status)) { + return; + } + } + + // Check the cache, if not available, create new one and cache + TimeZoneNamesCacheEntry *cacheEntry = NULL; + umtx_lock(&gTimeZoneNamesLock); + { + const char *key = locale.getName(); + cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key); + if (cacheEntry == NULL) { + TimeZoneNames *tznames = NULL; + char *newKey = NULL; + + tznames = new TimeZoneNamesImpl(locale, status); + if (tznames == 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 = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry)); + if (cacheEntry == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + cacheEntry->names = tznames; + cacheEntry->refCount = 1; + cacheEntry->lastAccess = (double)uprv_getUTCtime(); + + uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status); + } + } + if (U_FAILURE(status)) { + if (tznames != NULL) { + delete tznames; + } + 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; + } + } + umtx_unlock(&gTimeZoneNamesLock); + + fTZnamesCacheEntry = cacheEntry; +} + +TimeZoneNamesDelegate::~TimeZoneNamesDelegate() { + umtx_lock(&gTimeZoneNamesLock); + { + U_ASSERT(fTZnamesCacheEntry->refCount > 0); + // Just decrement the reference count + fTZnamesCacheEntry->refCount--; + } + umtx_unlock(&gTimeZoneNamesLock); +} + +StringEnumeration* +TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const { + return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status); +} + +StringEnumeration* +TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { + return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status); +} + +UnicodeString& +TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { + return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID); +} + +UnicodeString& +TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { + return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID); +} + +UnicodeString& +TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const { + return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name); +} + +UnicodeString& +TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const { + return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name); +} + +UnicodeString& +TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { + return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name); +} + +TimeZoneNameMatchInfo* +TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { + return fTZnamesCacheEntry->names->find(text, start, types, status); +} + + + +TimeZoneNames* +TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) { + return new TimeZoneNamesDelegate(locale, status); +} + +UnicodeString& +TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { + if (tzID.isEmpty() || tzID.startsWith(gEtcPrefix, gEtcPrefixLen) + || tzID.startsWith(gSystemVPrefix, gSystemVPrefixLen) || tzID.indexOf(gRiyadh8, gRiyadh8Len, 0) > 0) { + name.setToBogus(); + return name; + } + + int32_t sep = tzID.lastIndexOf((UChar)0x2F /* '/' */); + if (sep > 0 && sep + 1 < tzID.length()) { + name.setTo(tzID, sep + 1); + name.findAndReplace("_", " "); + } else { + name.setToBogus(); + } + return name; +} + +UnicodeString& +TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const { + getTimeZoneDisplayName(tzID, type, name); + if (name.isEmpty()) { + UnicodeString mzID; + getMetaZoneID(tzID, date, mzID); + getMetaZoneDisplayName(mzID, type, name); + } + return name; +} + +U_NAMESPACE_END +#endif diff --git a/icu4c/source/i18n/tznames.h b/icu4c/source/i18n/tznames.h new file mode 100644 index 00000000000..0f281c63776 --- /dev/null +++ b/icu4c/source/i18n/tznames.h @@ -0,0 +1,81 @@ +/* +******************************************************************************* +* Copyright (C) 2011, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ +#ifndef __TZNAMES_H +#define __TZNAMES_H + +/** + * \file + * \brief C API: Time zone names class + */ +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/uloc.h" +#include "unicode/unistr.h" + +U_CDECL_BEGIN + +typedef enum UTimeZoneNameType { + UTZNM_UNKNOWN = 0x00, + UTZNM_LONG_GENERIC = 0x01, + UTZNM_LONG_STANDARD = 0x02, + UTZNM_LONG_DAYLIGHT = 0x04, + UTZNM_SHORT_GENERIC = 0x08, + UTZNM_SHORT_STANDARD = 0x10, + UTZNM_SHORT_DAYLIGHT = 0x20, + UTZNM_SHORT_STANDARD_COMMONLY_USED = 0x40, + UTZNM_SHORT_DAYLIGHT_COMMONLY_USED = 0x80 +} UTimeZoneNameType; + +U_CDECL_END + +U_NAMESPACE_BEGIN + +class U_I18N_API TimeZoneNameMatchInfo : public UMemory { +public: + virtual ~TimeZoneNameMatchInfo(); + + virtual int32_t size() const = 0; + virtual UTimeZoneNameType getNameType(int32_t index) const = 0; + virtual int32_t getMatchLength(int32_t index) const = 0; + virtual UnicodeString& getTimeZoneID(int32_t index, UnicodeString& tzID) const = 0; + virtual UnicodeString& getMetaZoneID(int32_t index, UnicodeString& mzID) const = 0; +}; + +inline +TimeZoneNameMatchInfo::~TimeZoneNameMatchInfo() { +} + +class U_I18N_API TimeZoneNames : public UMemory { +public: + virtual ~TimeZoneNames(); + + static TimeZoneNames* U_EXPORT2 createInstance(const Locale& locale, UErrorCode& status); + + virtual StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const = 0; + virtual StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const = 0; + + virtual UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const = 0; + virtual UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const = 0; + + virtual UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const = 0; + virtual UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const = 0; + + virtual UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; + virtual UnicodeString& getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const; + + virtual TimeZoneNameMatchInfo* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const = 0; +}; + +inline +TimeZoneNames::~TimeZoneNames() { +} + +U_NAMESPACE_END +#endif +#endif diff --git a/icu4c/source/i18n/tznames_impl.cpp b/icu4c/source/i18n/tznames_impl.cpp new file mode 100644 index 00000000000..d7292e4e897 --- /dev/null +++ b/icu4c/source/i18n/tznames_impl.cpp @@ -0,0 +1,1451 @@ +/* +******************************************************************************* +* Copyright (C) 2011, International Business Machines Corporation and +* others. All Rights Reserved. +******************************************************************************* +* +* File TZNAMES_IMPL.CPP +* +******************************************************************************* +*/ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/ustring.h" +#include "unicode/timezone.h" + +#include "tznames_impl.h" +#include "cmemory.h" +#include "cstring.h" +#include "uassert.h" +#include "uresimp.h" +#include "ureslocs.h" +#include "zonemeta.h" +#include "ucln_in.h" +#include "uvector.h" +#include "olsontz.h" + + +U_NAMESPACE_BEGIN + +#define ZID_KEY_MAX 128 +#define MZ_PREFIX_LEN 5 + +static const char gZoneStrings[] = "zoneStrings"; +static const char gMZPrefix[] = "meta:"; + +static const char* KEYS[] = {"lg", "ls", "ld", "sg", "ss", "sd"}; +static const int32_t KEYS_SIZE = (sizeof KEYS / sizeof KEYS[0]); + +static const char gCuTag[] = "cu"; +static const char gEcTag[] = "ec"; + +static const char EMPTY[] = ""; // place holder for empty ZNames/TZNames + +static const UTimeZoneNameType ALL_NAME_TYPES[] = { + UTZNM_LONG_GENERIC, UTZNM_LONG_STANDARD, UTZNM_LONG_DAYLIGHT, + UTZNM_SHORT_GENERIC, UTZNM_SHORT_STANDARD, UTZNM_SHORT_DAYLIGHT, + UTZNM_SHORT_STANDARD_COMMONLY_USED, UTZNM_SHORT_DAYLIGHT_COMMONLY_USED, + UTZNM_UNKNOWN // unknown as the last one +}; + +#define DEFAULT_CHARACTERNODE_CAPACITY 1 + +// --------------------------------------------------- +// CaracterNode class implementation +// --------------------------------------------------- +void CharacterNode::clear() { + uprv_memset(this, 0, sizeof(*this)); +} + +void CharacterNode::deleteValues(UObjectDeleter *valueDeleter) { + if (fValues == NULL) { + // Do nothing. + } else if (!fHasValuesVector) { + if (valueDeleter) { + valueDeleter(fValues); + } + } else { + delete (UVector *)fValues; + } +} + +void +CharacterNode::addValue(void *value, UObjectDeleter *valueDeleter, UErrorCode &status) { + if (U_FAILURE(status)) { + if (valueDeleter) { + valueDeleter(value); + } + return; + } + if (fValues == NULL) { + fValues = value; + } else { + // At least one value already. + if (!fHasValuesVector) { + // There is only one value so far, and not in a vector yet. + // Create a vector and add the old value. + UVector *values = new UVector(valueDeleter, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status); + if (U_FAILURE(status)) { + if (valueDeleter) { + valueDeleter(value); + } + return; + } + values->addElement(fValues, status); + fValues = values; + fHasValuesVector = TRUE; + } + // Add the new value. + ((UVector *)fValues)->addElement(value, status); + } +} + +// --------------------------------------------------- +// TextTrieMapSearchResultHandler class implementation +// --------------------------------------------------- +TextTrieMapSearchResultHandler::~TextTrieMapSearchResultHandler(){ +} + +// --------------------------------------------------- +// TextTrieMap class implementation +// --------------------------------------------------- +TextTrieMap::TextTrieMap(UBool ignoreCase, UObjectDeleter *valueDeleter) +: fIgnoreCase(ignoreCase), fNodes(NULL), fNodesCapacity(0), fNodesCount(0), + fLazyContents(NULL), fIsEmpty(TRUE), fValueDeleter(valueDeleter) { +} + +TextTrieMap::~TextTrieMap() { + int32_t index; + for (index = 0; index < fNodesCount; ++index) { + fNodes[index].deleteValues(fValueDeleter); + } + uprv_free(fNodes); + if (fLazyContents != NULL) { + for (int32_t i=0; isize(); i+=2) { + if (fValueDeleter) { + fValueDeleter(fLazyContents->elementAt(i+1)); + } + } + delete fLazyContents; + } +} + +int32_t TextTrieMap::isEmpty() const { + // Use a separate field for fIsEmpty because it will remain unchanged once the + // Trie is built, while fNodes and fLazyContents change with the lazy init + // of the nodes structure. Trying to test the changing fields has + // thread safety complications. + return fIsEmpty; +} + + +// We defer actually building the TextTrieMap node structure until the first time a +// search is performed. put() simply saves the parameters in case we do +// eventually need to build it. +// +void +TextTrieMap::put(const UnicodeString &key, void *value, ZNStringPool &sp, UErrorCode &status) { + const UChar *s = sp.get(key, status); + put(s, value, status); +} + +// This method is for designed for a persistent key, such as string key stored in +// resource bundle. +void +TextTrieMap::put(const UChar *key, void *value, UErrorCode &status) { + fIsEmpty = FALSE; + if (fLazyContents == NULL) { + fLazyContents = new UVector(status); + if (fLazyContents == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + } + if (U_FAILURE(status)) { + return; + } + UChar *s = const_cast(key); + fLazyContents->addElement(s, status); + fLazyContents->addElement(value, status); +} + +void +TextTrieMap::putImpl(const UnicodeString &key, void *value, UErrorCode &status) { + if (fNodes == NULL) { + fNodesCapacity = 512; + fNodes = (CharacterNode *)uprv_malloc(fNodesCapacity * sizeof(CharacterNode)); + fNodes[0].clear(); // Init root node. + fNodesCount = 1; + } + + UnicodeString foldedKey; + const UChar *keyBuffer; + int32_t keyLength; + if (fIgnoreCase) { + // Ok to use fastCopyFrom() because we discard the copy when we return. + foldedKey.fastCopyFrom(key).foldCase(); + keyBuffer = foldedKey.getBuffer(); + keyLength = foldedKey.length(); + } else { + keyBuffer = key.getBuffer(); + keyLength = key.length(); + } + + CharacterNode *node = fNodes; + int32_t index; + for (index = 0; index < keyLength; ++index) { + node = addChildNode(node, keyBuffer[index], status); + } + node->addValue(value, fValueDeleter, status); +} + +UBool +TextTrieMap::growNodes() { + if (fNodesCapacity == 0xffff) { + return FALSE; // We use 16-bit node indexes. + } + int32_t newCapacity = fNodesCapacity + 1000; + if (newCapacity > 0xffff) { + newCapacity = 0xffff; + } + CharacterNode *newNodes = (CharacterNode *)uprv_malloc(newCapacity * sizeof(CharacterNode)); + if (newNodes == NULL) { + return FALSE; + } + uprv_memcpy(newNodes, fNodes, fNodesCount * sizeof(CharacterNode)); + uprv_free(fNodes); + fNodes = newNodes; + fNodesCapacity = newCapacity; + return TRUE; +} + +CharacterNode* +TextTrieMap::addChildNode(CharacterNode *parent, UChar c, UErrorCode &status) { + if (U_FAILURE(status)) { + return NULL; + } + // Linear search of the sorted list of children. + uint16_t prevIndex = 0; + uint16_t nodeIndex = parent->fFirstChild; + while (nodeIndex > 0) { + CharacterNode *current = fNodes + nodeIndex; + UChar childCharacter = current->fCharacter; + if (childCharacter == c) { + return current; + } else if (childCharacter > c) { + break; + } + prevIndex = nodeIndex; + nodeIndex = current->fNextSibling; + } + + // Ensure capacity. Grow fNodes[] if needed. + if (fNodesCount == fNodesCapacity) { + int32_t parentIndex = (int32_t)(parent - fNodes); + if (!growNodes()) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + parent = fNodes + parentIndex; + } + + // Insert a new child node with c in sorted order. + CharacterNode *node = fNodes + fNodesCount; + node->clear(); + node->fCharacter = c; + node->fNextSibling = nodeIndex; + if (prevIndex == 0) { + parent->fFirstChild = (uint16_t)fNodesCount; + } else { + fNodes[prevIndex].fNextSibling = (uint16_t)fNodesCount; + } + ++fNodesCount; + return node; +} + +CharacterNode* +TextTrieMap::getChildNode(CharacterNode *parent, UChar c) const { + // Linear search of the sorted list of children. + uint16_t nodeIndex = parent->fFirstChild; + while (nodeIndex > 0) { + CharacterNode *current = fNodes + nodeIndex; + UChar childCharacter = current->fCharacter; + if (childCharacter == c) { + return current; + } else if (childCharacter > c) { + break; + } + nodeIndex = current->fNextSibling; + } + return NULL; +} + +// Mutex for protecting the lazy creation of the Trie node structure on the first call to search(). +static UMTX TextTrieMutex; + +// buildTrie() - The Trie node structure is needed. Create it from the data that was +// saved at the time the ZoneStringFormatter was created. The Trie is only +// needed for parsing operations, which are less common than formatting, +// and the Trie is big, which is why its creation is deferred until first use. +void TextTrieMap::buildTrie(UErrorCode &status) { + umtx_lock(&TextTrieMutex); + if (fLazyContents != NULL) { + for (int32_t i=0; isize(); i+=2) { + const UChar *key = (UChar *)fLazyContents->elementAt(i); + void *val = fLazyContents->elementAt(i+1); + UnicodeString keyString(TRUE, key, -1); // Aliasing UnicodeString constructor. + putImpl(keyString, val, status); + } + delete fLazyContents; + fLazyContents = NULL; + } + umtx_unlock(&TextTrieMutex); +} + +void +TextTrieMap::search(const UnicodeString &text, int32_t start, + TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { + UBool trieNeedsInitialization = FALSE; + UMTX_CHECK(&TextTrieMutex, fLazyContents != NULL, trieNeedsInitialization); + if (trieNeedsInitialization) { + TextTrieMap *nonConstThis = const_cast(this); + nonConstThis->buildTrie(status); + } + if (fNodes == NULL) { + return; + } + search(fNodes, 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; + } + if (node->hasValues()) { + if (!handler->handleMatch(index - start, node, 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 = getChildNode(node, c); + if (node == NULL) { + break; + } + tmpidx = tmp.moveIndex32(tmpidx, 1); + } + } else { + node = getChildNode(node, c); + } + if (node != NULL) { + search(node, text, start, index+1, handler, status); + } +} + +// --------------------------------------------------- +// ZNStringPool class implementation +// --------------------------------------------------- +static const int32_t POOL_CHUNK_SIZE = 2000; +struct ZNStringPoolChunk: public UMemory { + ZNStringPoolChunk *fNext; // Ptr to next pool chunk + int32_t fLimit; // Index to start of unused area at end of fStrings + UChar fStrings[POOL_CHUNK_SIZE]; // Strings array + ZNStringPoolChunk(); +}; + +ZNStringPoolChunk::ZNStringPoolChunk() { + fNext = NULL; + fLimit = 0; +} + +ZNStringPool::ZNStringPool(UErrorCode &status) { + fChunks = NULL; + fHash = NULL; + if (U_FAILURE(status)) { + return; + } + fChunks = new ZNStringPoolChunk; + if (fChunks == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + + fHash = uhash_open(uhash_hashUChars /* keyHash */, + uhash_compareUChars /* keyComp */, + uhash_compareUChars /* valueComp */, + &status); + if (U_FAILURE(status)) { + return; + } +} + +ZNStringPool::~ZNStringPool() { + if (fHash != NULL) { + uhash_close(fHash); + fHash = NULL; + } + + while (fChunks != NULL) { + ZNStringPoolChunk *nextChunk = fChunks->fNext; + delete fChunks; + fChunks = nextChunk; + } +} + +static const UChar EmptyString = 0; + +const UChar *ZNStringPool::get(const UChar *s, UErrorCode &status) { + const UChar *pooledString; + if (U_FAILURE(status)) { + return &EmptyString; + } + + pooledString = static_cast(uhash_get(fHash, s)); + if (pooledString != NULL) { + return pooledString; + } + + int32_t length = u_strlen(s); + int32_t remainingLength = POOL_CHUNK_SIZE - fChunks->fLimit; + if (remainingLength <= length) { + U_ASSERT(length < POOL_CHUNK_SIZE); + if (length >= POOL_CHUNK_SIZE) { + status = U_INTERNAL_PROGRAM_ERROR; + return &EmptyString; + } + ZNStringPoolChunk *oldChunk = fChunks; + fChunks = new ZNStringPoolChunk; + if (fChunks == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return &EmptyString; + } + fChunks->fNext = oldChunk; + } + + UChar *destString = &fChunks->fStrings[fChunks->fLimit]; + u_strcpy(destString, s); + fChunks->fLimit += (length + 1); + uhash_put(fHash, destString, destString, &status); + return destString; +} + + +// +// ZNStringPool::adopt() Put a string into the hash, but do not copy the string data +// into the pool's storage. Used for strings from resource bundles, +// which will perisist for the life of the zone string formatter, and +// therefore can be used directly without copying. +const UChar *ZNStringPool::adopt(const UChar * s, UErrorCode &status) { + const UChar *pooledString; + if (U_FAILURE(status)) { + return &EmptyString; + } + if (s != NULL) { + pooledString = static_cast(uhash_get(fHash, s)); + if (pooledString == NULL) { + UChar *ncs = const_cast(s); + uhash_put(fHash, ncs, ncs, &status); + } + } + return s; +} + + +const UChar *ZNStringPool::get(const UnicodeString &s, UErrorCode &status) { + UnicodeString &nonConstStr = const_cast(s); + return this->get(nonConstStr.getTerminatedBuffer(), status); +} + +/* + * freeze(). Close the hash table that maps to the pooled strings. + * After freezing, the pool can not be searched or added to, + * but all existing references to pooled strings remain valid. + * + * The main purpose is to recover the storage used for the hash. + */ +void ZNStringPool::freeze() { + uhash_close(fHash); + fHash = NULL; +} + + +// --------------------------------------------------- +// ZNames - names common for time zone and meta zone +// --------------------------------------------------- +class ZNames : public UMemory { +public: + virtual ~ZNames(); + + static ZNames* createInstance(UResourceBundle* rb, const char* key); + const UChar* getName(UTimeZoneNameType type); + +protected: + ZNames(const UChar** names, UBool shortCommonlyUsed); + static const UChar** loadData(UResourceBundle* rb, const char* key, UBool& shortCommonlyUsed); + +private: + const UChar** fNames; + UBool fShortCommonlyUsed; +}; + +ZNames::ZNames(const UChar** names, UBool shortCommonlyUsed) +: fNames(names), fShortCommonlyUsed(shortCommonlyUsed) { +} + +ZNames::~ZNames() { + if (fNames != NULL) { + uprv_free(fNames); + } +} + +ZNames* +ZNames::createInstance(UResourceBundle* rb, const char* key) { + UBool shortCommonlyUsed = FALSE; + const UChar** names = loadData(rb, key, shortCommonlyUsed); + if (names == NULL) { + // No names data available + return NULL; + } + return new ZNames(names, shortCommonlyUsed); +} + +const UChar* +ZNames::getName(UTimeZoneNameType type) { + if (fNames == NULL) { + return NULL; + } + const UChar *name = NULL; + switch(type) { + case UTZNM_LONG_GENERIC: + name = fNames[0]; + break; + case UTZNM_LONG_STANDARD: + name = fNames[1]; + break; + case UTZNM_LONG_DAYLIGHT: + name = fNames[2]; + break; + case UTZNM_SHORT_GENERIC: + if (fShortCommonlyUsed) { + name = fNames[3]; + } + break; + case UTZNM_SHORT_STANDARD: + name = fNames[4]; + break; + case UTZNM_SHORT_DAYLIGHT: + name = fNames[5]; + break; + case UTZNM_SHORT_STANDARD_COMMONLY_USED: + if (fShortCommonlyUsed) { + name = fNames[4]; + } + break; + case UTZNM_SHORT_DAYLIGHT_COMMONLY_USED: + if (fShortCommonlyUsed) { + name = fNames[5]; + } + break; + default: + name = NULL; + } + return name; +} + +const UChar** +ZNames::loadData(UResourceBundle* rb, const char* key, UBool& shortCommonlyUsed) { + if (rb == NULL || key == NULL || *key == 0) { + return NULL; + } + + UErrorCode status = U_ZERO_ERROR; + const UChar **names = NULL; + + UResourceBundle* rbTable = NULL; + rbTable = ures_getByKeyWithFallback(rb, key, rbTable, &status); + if (U_SUCCESS(status)) { + names = (const UChar **)uprv_malloc(sizeof(const UChar*) * KEYS_SIZE); + if (names != NULL) { + UBool isEmpty = TRUE; + for (int32_t i = 0; i < KEYS_SIZE; i++) { + status = U_ZERO_ERROR; + int32_t len = 0; + const UChar *value = ures_getStringByKeyWithFallback(rbTable, KEYS[i], &len, &status); + if (U_FAILURE(status) || len == 0) { + names[i] = NULL; + } else { + names[i] = value; + isEmpty = FALSE; + } + } + if (isEmpty) { + // No need to keep the names array + uprv_free(names); + names = NULL; + } + } + + if (names != NULL) { + status = U_ZERO_ERROR; + UResourceBundle* cuRes = ures_getByKeyWithFallback(rbTable, gCuTag, NULL, &status); + int32_t cu = ures_getInt(cuRes, &status); + if (U_SUCCESS(status)) { + shortCommonlyUsed = (cu != 0); + } + ures_close(cuRes); + } + } + ures_close(rbTable); + return names; +} + +// --------------------------------------------------- +// TZNames - names for a time zone +// --------------------------------------------------- +class TZNames : public ZNames { +public: + virtual ~TZNames(); + + static TZNames* createInstance(UResourceBundle* rb, const char* key); + const UChar* getLocationName(void); + +private: + TZNames(const UChar** names, UBool shortCommonlyUsed, const UChar* locationName); + const UChar* fLocationName; +}; + +TZNames::TZNames(const UChar** names, UBool shortCommonlyUsed, const UChar* locationName) +: ZNames(names, shortCommonlyUsed), fLocationName(locationName) { +} + +TZNames::~TZNames() { +} + +const UChar* +TZNames::getLocationName() { + return fLocationName; +} + +TZNames* +TZNames::createInstance(UResourceBundle* rb, const char* key) { + if (rb == NULL || key == NULL || *key == 0) { + return NULL; + } + TZNames* tznames = NULL; + UErrorCode status = U_ZERO_ERROR; + UResourceBundle* rbTable = ures_getByKeyWithFallback(rb, key, NULL, &status); + if (U_SUCCESS(status)) { + int32_t len = 0; + const UChar* locationName = ures_getStringByKeyWithFallback(rbTable, gEcTag, &len, &status); + if (U_FAILURE(status) || len == 0) { + locationName = NULL; + } + + UBool shortCommonlyUsed = FALSE; + const UChar** names = loadData(rb, key, shortCommonlyUsed); + + if (locationName != NULL || names != NULL) { + tznames = new TZNames(names, shortCommonlyUsed, locationName); + } + } + ures_close(rbTable); + return tznames; +} + +// --------------------------------------------------- +// The meta zone ID enumeration class +// --------------------------------------------------- +class MetaZoneIDsEnumeration : public StringEnumeration { +public: + MetaZoneIDsEnumeration(); + MetaZoneIDsEnumeration(const UVector& mzIDs); + MetaZoneIDsEnumeration(UVector* mzIDs); + virtual ~MetaZoneIDsEnumeration(); + static UClassID U_EXPORT2 getStaticClassID(void); + virtual UClassID getDynamicClassID(void) const; + virtual const UnicodeString* snext(UErrorCode& status); + virtual void reset(UErrorCode& status); + virtual int32_t count(UErrorCode& status) const; +private: + int32_t fLen; + int32_t fPos; + const UVector* fMetaZoneIDs; + UVector *fLocalVector; +}; + +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MetaZoneIDsEnumeration) + +MetaZoneIDsEnumeration::MetaZoneIDsEnumeration() +: fLen(0), fPos(0), fMetaZoneIDs(NULL), fLocalVector(NULL) { +} + +MetaZoneIDsEnumeration::MetaZoneIDsEnumeration(const UVector& mzIDs) +: fPos(0), fMetaZoneIDs(&mzIDs), fLocalVector(NULL) { + fLen = fMetaZoneIDs->size(); +} + +MetaZoneIDsEnumeration::MetaZoneIDsEnumeration(UVector *mzIDs) +: fLen(0), fPos(0), fMetaZoneIDs(mzIDs), fLocalVector(mzIDs) { + if (fMetaZoneIDs) { + fLen = fMetaZoneIDs->size(); + } +} + +const UnicodeString* +MetaZoneIDsEnumeration::snext(UErrorCode& status) { + if (U_SUCCESS(status) && fMetaZoneIDs != NULL && fPos < fLen) { + unistr.setTo((const UChar*)fMetaZoneIDs->elementAt(fPos++)); + return &unistr; + } + return NULL; +} + +void +MetaZoneIDsEnumeration::reset(UErrorCode& /*status*/) { + fPos = 0; +} + +int32_t +MetaZoneIDsEnumeration::count(UErrorCode& /*status*/) const { + return fLen; +} + +MetaZoneIDsEnumeration::~MetaZoneIDsEnumeration() { + if (fLocalVector) { + delete fLocalVector; + } +} + +U_CDECL_BEGIN +/** + * ZNameInfo stores zone name information in the trie + */ +typedef struct ZNameInfo { + UTimeZoneNameType type; + const UChar* tzID; + const UChar* mzID; +} ZNameInfo; + +/** + * ZMatchInfo stores zone name match information used by find method + */ +typedef struct ZMatchInfo { + const ZNameInfo* znameInfo; + int32_t matchLength; +} ZMatchInfo; +U_CDECL_END + +// --------------------------------------------------- +// The class stores time zone name match information +// --------------------------------------------------- +class TimeZoneNameMatchInfoImpl : public TimeZoneNameMatchInfo { +public: + TimeZoneNameMatchInfoImpl(UVector* matches); + ~TimeZoneNameMatchInfoImpl(); + + int32_t size() const; + UTimeZoneNameType getNameType(int32_t index) const; + int32_t getMatchLength(int32_t index) const; + UnicodeString& getTimeZoneID(int32_t index, UnicodeString& tzID) const; + UnicodeString& getMetaZoneID(int32_t index, UnicodeString& mzID) const; + +private: + UVector* fMatches; // vector of MatchEntry +}; + +TimeZoneNameMatchInfoImpl::TimeZoneNameMatchInfoImpl(UVector* matches) +: fMatches(matches) { +} + +TimeZoneNameMatchInfoImpl::~TimeZoneNameMatchInfoImpl() { + if (fMatches != NULL) { + delete fMatches; + } +} + +int32_t +TimeZoneNameMatchInfoImpl::size() const { + if (fMatches == NULL) { + return 0; + } + return fMatches->size(); +} + +UTimeZoneNameType +TimeZoneNameMatchInfoImpl::getNameType(int32_t index) const { + ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index); + if (minfo != NULL) { + return minfo->znameInfo->type; + } + return UTZNM_UNKNOWN; +} + +int32_t +TimeZoneNameMatchInfoImpl::getMatchLength(int32_t index) const { + ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index); + if (minfo != NULL) { + return minfo->matchLength; + } + return -1; +} + +UnicodeString& +TimeZoneNameMatchInfoImpl::getTimeZoneID(int32_t index, UnicodeString& tzID) const { + ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index); + if (minfo != NULL && minfo->znameInfo->tzID != NULL) { + tzID.setTo(TRUE, minfo->znameInfo->tzID, -1); + } else { + tzID.setToBogus(); + } + return tzID; +} + +UnicodeString& +TimeZoneNameMatchInfoImpl::getMetaZoneID(int32_t index, UnicodeString& mzID) const { + ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index); + if (minfo != NULL && minfo->znameInfo->mzID != NULL) { + mzID.setTo(TRUE, minfo->znameInfo->mzID, -1); + } else { + mzID.setToBogus(); + } + return mzID; +} + +// --------------------------------------------------- +// ZNameSearchHandler +// --------------------------------------------------- +class ZNameSearchHandler : public TextTrieMapSearchResultHandler { +public: + ZNameSearchHandler(uint32_t types); + virtual ~ZNameSearchHandler(); + + UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status); + UVector* getMatches(int32_t& maxMatchLen); + +private: + uint32_t fTypes; + UVector* fResults; + int32_t fMaxMatchLen; +}; + +ZNameSearchHandler::ZNameSearchHandler(uint32_t types) +: fTypes(types), fResults(NULL), fMaxMatchLen(0) { +} + +ZNameSearchHandler::~ZNameSearchHandler() { + if (fResults != NULL) { + delete fResults; + } +} + +UBool +ZNameSearchHandler::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++) { + ZNameInfo *nameinfo = (ZNameInfo *)node->getValue(i); + if (nameinfo == NULL) { + break; + } + if ((nameinfo->type & fTypes) != 0) { + // matches a requested type + if (fResults == NULL) { + fResults = new UVector(uhash_freeBlock, NULL, status); + if (fResults == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + } + if (U_SUCCESS(status)) { + ZMatchInfo *zmatch = (ZMatchInfo *)uprv_malloc(sizeof(ZMatchInfo)); + if (zmatch == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + // add the match to the vector + zmatch->znameInfo = nameinfo; + zmatch->matchLength = matchLength; + fResults->addElement(zmatch, status); + if (U_FAILURE(status)) { + uprv_free(zmatch); + } else { + if (matchLength > fMaxMatchLen) { + fMaxMatchLen = matchLength; + } + } + } + } + } + } + } + return TRUE; +} + +UVector* +ZNameSearchHandler::getMatches(int32_t& maxMatchLen) { + // give the ownership to the caller + UVector *results = fResults; + maxMatchLen = fMaxMatchLen; + + // reset + fResults = NULL; + fMaxMatchLen = 0; + return results; +} + +// --------------------------------------------------- +// TimeZoneNamesImpl +// +// TimeZoneNames implementation class. This is the main +// part of this module. +// --------------------------------------------------- + +U_CDECL_BEGIN +/** + * Deleter for ZNames + */ +static void U_CALLCONV +deleteZNames(void *obj) { + if (obj != EMPTY) { + delete (ZNames *)obj; + } +} +/** + * Deleter for TZNames + */ +static void U_CALLCONV +deleteTZNames(void *obj) { + if (obj != EMPTY) { + delete (TZNames *)obj; + } +} + +/** + * Deleter for ZNameInfo + */ +static void U_CALLCONV +deleteZNameInfo(void *obj) { + uprv_free(obj); +} + +U_CDECL_END + +TimeZoneNamesImpl::TimeZoneNamesImpl(const Locale& locale, UErrorCode& status) +: fLocale(locale), + fLock(NULL), + fZoneStrings(NULL), + fTZNamesMap(NULL), + fMZNamesMap(NULL), + fNamesTrieFullyLoaded(FALSE), + fNamesTrie(TRUE, deleteZNameInfo) { + initialize(locale, status); +} + +void +TimeZoneNamesImpl::initialize(const Locale& locale, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + + // Load zoneStrings bundle + UErrorCode tmpsts = U_ZERO_ERROR; // OK with fallback warning.. + fZoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts); + fZoneStrings = ures_getByKeyWithFallback(fZoneStrings, gZoneStrings, fZoneStrings, &tmpsts); + if (U_FAILURE(tmpsts)) { + status = tmpsts; + cleanup(); + return; + } + + // Initialize hashtables holding time zone/meta zone names + fMZNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); + fTZNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); + if (U_FAILURE(status)) { + cleanup(); + return; + } + + uhash_setValueDeleter(fMZNamesMap, deleteZNames); + uhash_setValueDeleter(fTZNamesMap, deleteTZNames); + // no key deleters for name maps + + // preload zone strings for the default zone + TimeZone *tz = TimeZone::createDefault(); + const UChar *tzID = ZoneMeta::getCanonicalCLDRID(*tz); + if (tzID != NULL) { + loadStrings(UnicodeString(tzID)); + } + delete tz; + + return; +} + +/* + * This method updates the cache and must be called with a lock, + * except initializer. + */ +void +TimeZoneNamesImpl::loadStrings(const UnicodeString& tzCanonicalID) { + loadTimeZoneNames(tzCanonicalID); + + UErrorCode status = U_ZERO_ERROR; + StringEnumeration *mzIDs = getAvailableMetaZoneIDs(tzCanonicalID, status); + if (U_SUCCESS(status) && mzIDs != NULL) { + const UnicodeString *mzID; + while ((mzID = mzIDs->snext(status))) { + if (U_FAILURE(status)) { + break; + } + loadMetaZoneNames(*mzID); + } + delete mzIDs; + } +} + +TimeZoneNamesImpl::~TimeZoneNamesImpl() { + cleanup(); + umtx_destroy(&fLock); +} + +void +TimeZoneNamesImpl::cleanup() { + if (fZoneStrings != NULL) { + ures_close(fZoneStrings); + fZoneStrings = NULL; + } + if (fMZNamesMap != NULL) { + uhash_close(fMZNamesMap); + fMZNamesMap = NULL; + } + if (fTZNamesMap != NULL) { + uhash_close(fTZNamesMap); + fTZNamesMap = NULL; + } +} + +StringEnumeration* +TimeZoneNamesImpl::getAvailableMetaZoneIDs(UErrorCode& status) const { + if (U_FAILURE(status)) { + return NULL; + } + const UVector* mzIDs = ZoneMeta::getAvailableMetazoneIDs(); + if (mzIDs == NULL) { + return new MetaZoneIDsEnumeration(); + } + return new MetaZoneIDsEnumeration(*mzIDs); +} + +StringEnumeration* +TimeZoneNamesImpl::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { + if (U_FAILURE(status)) { + return NULL; + } + const UVector* mappings = ZoneMeta::getMetazoneMappings(tzID); + if (mappings == NULL) { + return new MetaZoneIDsEnumeration(); + } + + MetaZoneIDsEnumeration *senum = NULL; + UVector* mzIDs = new UVector(NULL, uhash_compareUChars, status); + if (mzIDs == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + if (U_SUCCESS(status)) { + for (int32_t i = 0; U_SUCCESS(status) && i < mappings->size(); i++) { + + OlsonToMetaMappingEntry *map = (OlsonToMetaMappingEntry *)mappings->elementAt(i); + const UChar *mzID = map->mzid; + if (!mzIDs->contains((void *)mzID)) { + mzIDs->addElement((void *)mzID, status); + } + } + if (U_SUCCESS(status)) { + senum = new MetaZoneIDsEnumeration(mzIDs); + } else { + delete mzIDs; + } + } + return senum; +} + +UnicodeString& +TimeZoneNamesImpl::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { + ZoneMeta::getMetazoneID(tzID, date, mzID); + return mzID; +} + +UnicodeString& +TimeZoneNamesImpl::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { + ZoneMeta::getZoneIdByMetazone(mzID, UnicodeString(region), tzID); + return tzID; +} + +UnicodeString& +TimeZoneNamesImpl::getMetaZoneDisplayName(const UnicodeString& mzID, + UTimeZoneNameType type, + UnicodeString& name) const { + name.setToBogus(); // cleanup result. + if (mzID.isEmpty()) { + return name; + } + + ZNames *znames = NULL; + TimeZoneNamesImpl *nonConstThis = const_cast(this); + + umtx_lock(&nonConstThis->fLock); + { + znames = nonConstThis->loadMetaZoneNames(mzID); + } + umtx_unlock(&nonConstThis->fLock); + + if (znames != NULL) { + const UChar* s = znames->getName(type); + if (s != NULL) { + name.setTo(TRUE, s, -1); + } + } + return name; +} + +UnicodeString& +TimeZoneNamesImpl::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const { + name.setToBogus(); // cleanup result. + if (tzID.isEmpty()) { + return name; + } + + TZNames *tznames = NULL; + TimeZoneNamesImpl *nonConstThis = const_cast(this); + + umtx_lock(&nonConstThis->fLock); + { + tznames = nonConstThis->loadTimeZoneNames(tzID); + } + umtx_unlock(&nonConstThis->fLock); + + if (tznames != NULL) { + const UChar *s = tznames->getName(type); + if (s != NULL) { + name.setTo(TRUE, s, -1); + } + } + return name; +} + +UnicodeString& +TimeZoneNamesImpl::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { + const UChar* locName = NULL; + TZNames *tznames = NULL; + TimeZoneNamesImpl *nonConstThis = const_cast(this); + + umtx_lock(&nonConstThis->fLock); + { + tznames = nonConstThis->loadTimeZoneNames(tzID); + } + umtx_unlock(&nonConstThis->fLock); + + if (tznames != NULL) { + locName = tznames->getLocationName(); + } + if (locName != NULL) { + name.setTo(TRUE, locName, -1); + return name; + } + + return TimeZoneNames::getExemplarLocationName(tzID, name); +} + + +// Merge the MZ_PREFIX and mzId +static void mergeTimeZoneKey(const UnicodeString& mzID, char* result) { + if (mzID.isEmpty()) { + result[0] = '\0'; + return; + } + + char mzIdChar[ZID_KEY_MAX + 1]; + int32_t keyLen; + int32_t prefixLen = uprv_strlen(gMZPrefix); + keyLen = mzID.extract(0, mzID.length(), mzIdChar, ZID_KEY_MAX + 1, US_INV); + uprv_memcpy((void *)result, (void *)gMZPrefix, prefixLen); + uprv_memcpy((void *)(result + prefixLen), (void *)mzIdChar, keyLen); + result[keyLen + prefixLen] = '\0'; +} + +/* + * This method updates the cache and must be called with a lock + */ +ZNames* +TimeZoneNamesImpl::loadMetaZoneNames(const UnicodeString& mzID) { + if (mzID.length() > (ZID_KEY_MAX - MZ_PREFIX_LEN)) { + return NULL; + } + + ZNames *znames = NULL; + + UErrorCode status = U_ZERO_ERROR; + UChar mzIDKey[ZID_KEY_MAX + 1]; + mzID.extract(mzIDKey, ZID_KEY_MAX + 1, status); + U_ASSERT(status == U_ZERO_ERROR); // already checked length above + mzIDKey[mzID.length()] = 0; + + void *cacheVal = uhash_get(fMZNamesMap, mzIDKey); + if (cacheVal == NULL) { + char key[ZID_KEY_MAX + 1]; + mergeTimeZoneKey(mzID, key); + znames = ZNames::createInstance(fZoneStrings, key); + + if (znames == NULL) { + cacheVal = (void *)EMPTY; + } else { + cacheVal = znames; + } + // Use the persistent ID as the resource key, so we can + // avoid duplications. + const UChar* newKey = ZoneMeta::findMetaZoneID(mzID); + if (newKey != NULL) { + uhash_put(fMZNamesMap, (void *)newKey, cacheVal, &status); + if (U_FAILURE(status)) { + if (znames != NULL) { + delete znames; + } + } else if (znames != NULL) { + // put the name info into the trie + for (int32_t i = 0; ALL_NAME_TYPES[i] != UTZNM_UNKNOWN; i++) { + const UChar* name = znames->getName(ALL_NAME_TYPES[i]); + if (name != NULL) { + ZNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(ZNameInfo)); + if (nameinfo != NULL) { + nameinfo->type = ALL_NAME_TYPES[i]; + nameinfo->tzID = NULL; + nameinfo->mzID = newKey; + fNamesTrie.put(name, nameinfo, status); + } + } + } + } + + } else { + // Should never happen with a valid input + if (znames != NULL) { + // It's not possible that we get a valid ZNames with unknown ID. + // But just in case.. + delete znames; + znames = NULL; + } + } + } else if (cacheVal != EMPTY) { + znames = (ZNames *)cacheVal; + } + + return znames; +} + +static void convertTzToCLDRFomat(const UnicodeString& tzID, char* result) { + int32_t len = tzID.length(); + for (int32_t i = 0; i < len; i++) { + char c = (char)tzID.charAt(i); + if (c == '/') c = ':'; // Reaplce '/' with ':' + result[i] = c; + } + result[len] = '\0'; +} + +/* + * This method updates the cache and must be called with a lock + */ +TZNames* +TimeZoneNamesImpl::loadTimeZoneNames(const UnicodeString& tzID) { + if (tzID.length() > ZID_KEY_MAX) { + return NULL; + } + + TZNames *tznames = NULL; + + UErrorCode status = U_ZERO_ERROR; + UChar tzIDKey[ZID_KEY_MAX + 1]; + int32_t tzIDKeyLen = tzID.extract(tzIDKey, ZID_KEY_MAX + 1, status); + U_ASSERT(status == U_ZERO_ERROR); // already checked length above + tzIDKey[tzIDKeyLen] = 0; + + void *cacheVal = uhash_get(fTZNamesMap, tzIDKey); + if (cacheVal == NULL) { + char key[ZID_KEY_MAX + 1]; + UErrorCode status = U_ZERO_ERROR; + // Replace "/" with ":". + convertTzToCLDRFomat(tzID, key); + tznames = TZNames::createInstance(fZoneStrings, key); + + if (tznames == NULL) { + cacheVal = (void *)EMPTY; + } else { + cacheVal = tznames; + } + // Use the persistent ID as the resource key, so we can + // avoid duplications. + const UChar* newKey = ZoneMeta::findTimeZoneID(tzID); + if (newKey != NULL) { + uhash_put(fTZNamesMap, (void *)newKey, cacheVal, &status); + if (U_FAILURE(status)) { + if (tznames != NULL) { + delete tznames; + } + } else if (tznames != NULL) { + // put the name info into the trie + for (int32_t i = 0; ALL_NAME_TYPES[i] != UTZNM_UNKNOWN; i++) { + const UChar* name = tznames->getName(ALL_NAME_TYPES[i]); + if (name != NULL) { + ZNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(ZNameInfo)); + if (nameinfo != NULL) { + nameinfo->type = ALL_NAME_TYPES[i]; + nameinfo->tzID = newKey; + nameinfo->mzID = NULL; + fNamesTrie.put(name, nameinfo, status); + } + } + } + } + } else { + // Should never happen with a valid input + if (tznames != NULL) { + // It's not possible that we get a valid TZNames with unknown ID. + // But just in case.. + delete tznames; + tznames = NULL; + } + } + } else if (cacheVal != EMPTY) { + tznames = (TZNames *)cacheVal; + } + + return tznames; +} + +TimeZoneNameMatchInfo* +TimeZoneNamesImpl::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { + ZNameSearchHandler handler(types); + + TimeZoneNamesImpl *nonConstThis = const_cast(this); + + umtx_lock(&nonConstThis->fLock); + { + fNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); + } + umtx_unlock(&nonConstThis->fLock); + + if (U_FAILURE(status)) { + return NULL; + } + + TimeZoneNameMatchInfoImpl *matchInfo = NULL; + + int32_t maxLen = 0; + UVector *results = handler.getMatches(maxLen); + if ((results != NULL && (maxLen == (text.length() - start))) || fNamesTrieFullyLoaded) { + // perfect match + matchInfo = new TimeZoneNameMatchInfoImpl(results); + if (matchInfo == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + delete results; + return NULL; + } + return matchInfo; + } + + if (results != NULL) { + delete results; + } + + // All names are not yet loaded into the trie + umtx_lock(&nonConstThis->fLock); + { + if (!fNamesTrieFullyLoaded) { + const UnicodeString *id; + + // load strings for all zones + StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); + if (U_SUCCESS(status)) { + while ((id = tzIDs->snext(status))) { + if (U_FAILURE(status)) { + break; + } + // loadStrings also load related metazone strings + nonConstThis->loadStrings(*id); + } + } + if (tzIDs != NULL) { + delete tzIDs; + } + if (U_SUCCESS(status)) { + nonConstThis->fNamesTrieFullyLoaded = TRUE; + } + } + } + umtx_unlock(&nonConstThis->fLock); + + if (U_FAILURE(status)) { + return NULL; + } + + umtx_lock(&nonConstThis->fLock); + { + // now try it again + fNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); + } + umtx_unlock(&nonConstThis->fLock); + + results = handler.getMatches(maxLen); + if (results != NULL && maxLen > 0) { + matchInfo = new TimeZoneNameMatchInfoImpl(results); + if (matchInfo == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + delete results; + return NULL; + } + } + + return matchInfo; +} + +UnicodeString& getTZCanonicalID(const TimeZone& tz, UnicodeString& canonicalID) { + if (dynamic_cast(&tz) != NULL) { + // short cut for OlsonTimeZone + const OlsonTimeZone *otz = (const OlsonTimeZone*)&tz; + const UChar* uID = otz->getCanonicalID(); + if (uID != NULL) { + canonicalID.setTo(TRUE, uID, -1); + } else { + canonicalID.setToBogus(); + } + } else { + UErrorCode status = U_ZERO_ERROR; + UnicodeString tzID; + ZoneMeta::getCanonicalCLDRID(tz.getID(tzID), canonicalID, status); + if (U_FAILURE(status)) { + canonicalID.setToBogus(); + } + } + return canonicalID; +} + + +U_NAMESPACE_END + + +#endif /* #if !UCONFIG_NO_FORMATTING */ + +//eof diff --git a/icu4c/source/i18n/tznames_impl.h b/icu4c/source/i18n/tznames_impl.h new file mode 100644 index 00000000000..909a2926d58 --- /dev/null +++ b/icu4c/source/i18n/tznames_impl.h @@ -0,0 +1,212 @@ +/* + ******************************************************************************* + * Copyright (C) 2011, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ + +#ifndef __TZNAMES_IMPL_H__ +#define __TZNAMES_IMPL_H__ + + +/** + * \file + * \brief C++ API: TimeZoneNames object + */ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "tznames.h" +#include "unicode/ures.h" +#include "unicode/locid.h" +#include "uhash.h" +#include "uvector.h" +#include "umutex.h" + +U_NAMESPACE_BEGIN + +/* + * ZNStringPool Pool of (UChar *) strings. Provides for sharing of repeated + * zone strings. + */ +struct ZNStringPoolChunk; +class U_I18N_API ZNStringPool: public UMemory { + public: + ZNStringPool(UErrorCode &status); + ~ZNStringPool(); + + /* Get the pooled string that is equal to the supplied string s. + * Copy the string into the pool if it is not already present. + * + * Life time of the returned string is that of the pool. + */ + const UChar *get(const UChar *s, UErrorCode &status); + + /* Get the pooled string that is equal to the supplied string s. + * Copy the string into the pool if it is not already present. + */ + const UChar *get(const UnicodeString &s, UErrorCode &status); + + /* Adopt a string into the pool, without copying it. + * Used for strings from resource bundles, which will persist without copying. + */ + const UChar *adopt(const UChar *s, UErrorCode &status); + + /* Freeze the string pool. Discards the hash table that is used + * for looking up a string. All pointers to pooled strings remain valid. + */ + void freeze(); + + private: + ZNStringPoolChunk *fChunks; + UHashtable *fHash; +}; + +/* + * Character node used by TextTrieMap + */ +struct CharacterNode { + // No constructor or destructor. + // We malloc and free an uninitalized array of CharacterNode objects + // and clear and delete them ourselves. + + void clear(); + void deleteValues(UObjectDeleter *valueDeleter); + + void addValue(void *value, UObjectDeleter *valueDeleter, UErrorCode &status); + inline UBool hasValues() const; + inline int32_t countValues() const; + inline const void *getValue(int32_t index) const; + + void *fValues; // Union of one single value vs. UVector of values. + UChar fCharacter; // UTF-16 code unit. + uint16_t fFirstChild; // 0 if no children. + uint16_t fNextSibling; // 0 terminates the list. + UBool fHasValuesVector; + UBool fPadding; + + // No value: fValues == NULL and fHasValuesVector == FALSE + // One value: fValues == value and fHasValuesVector == FALSE + // >=2 values: fValues == UVector of values and fHasValuesVector == TRUE +}; + +inline UBool CharacterNode::hasValues() const { + return (UBool)(fValues != NULL); +} + +inline int32_t CharacterNode::countValues() const { + return + fValues == NULL ? 0 : + !fHasValuesVector ? 1 : + ((const UVector *)fValues)->size(); +} + +inline const void *CharacterNode::getValue(int32_t index) const { + if (!fHasValuesVector) { + return fValues; // Assume index == 0. + } else { + return ((const UVector *)fValues)->elementAt(index); + } +} + +/* + * Search result handler callback interface used by TextTrieMap search. + */ +class TextTrieMapSearchResultHandler : public UMemory { +public: + virtual UBool handleMatch(int32_t matchLength, + const CharacterNode *node, UErrorCode& status) = 0; + virtual ~TextTrieMapSearchResultHandler(); //added to avoid warning +}; + +/** + * TextTrieMap is a trie implementation for supporting + * fast prefix match for the string key. + */ +class U_I18N_API TextTrieMap : public UMemory { +public: + TextTrieMap(UBool ignoreCase, UObjectDeleter *valeDeleter); + virtual ~TextTrieMap(); + + void put(const UnicodeString &key, void *value, ZNStringPool &sp, UErrorCode &status); + void put(const UChar*, void *value, UErrorCode &status); + void search(const UnicodeString &text, int32_t start, + TextTrieMapSearchResultHandler *handler, UErrorCode& status) const; + int32_t isEmpty() const; + +private: + UBool fIgnoreCase; + CharacterNode *fNodes; + int32_t fNodesCapacity; + int32_t fNodesCount; + + UVector *fLazyContents; + UBool fIsEmpty; + UObjectDeleter *fValueDeleter; + + UBool growNodes(); + CharacterNode* addChildNode(CharacterNode *parent, UChar c, UErrorCode &status); + CharacterNode* getChildNode(CharacterNode *parent, UChar c) const; + + void putImpl(const UnicodeString &key, void *value, UErrorCode &status); + void buildTrie(UErrorCode &status); + void search(CharacterNode *node, const UnicodeString &text, int32_t start, + int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const; +}; + + + +class ZNames; +class TZNames; +class TextTrieMap; + +class TimeZoneNamesImpl : public TimeZoneNames { +public: + TimeZoneNamesImpl(const Locale& locale, UErrorCode& status); + + virtual ~TimeZoneNamesImpl(); + + StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const; + StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const; + + UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const; + UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const; + + UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const; + UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const; + + UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; + + TimeZoneNameMatchInfo* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; + +private: + + Locale fLocale; + UMTX fLock; + + UResourceBundle* fZoneStrings; + + UHashtable* fTZNamesMap; + UHashtable* fMZNamesMap; + + UBool fNamesTrieFullyLoaded; + TextTrieMap fNamesTrie; + + void initialize(const Locale& locale, UErrorCode& status); + void cleanup(); + + void loadStrings(const UnicodeString& tzCanonicalID); + + ZNames* loadMetaZoneNames(const UnicodeString& mzId); + TZNames* loadTimeZoneNames(const UnicodeString& mzId); +}; + +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ + +#endif // __TZNAMES_IMPL_H__ +//eof +// diff --git a/icu4c/source/i18n/ucln_in.h b/icu4c/source/i18n/ucln_in.h index b128bebeb4c..72db0bdab3e 100644 --- a/icu4c/source/i18n/ucln_in.h +++ b/icu4c/source/i18n/ucln_in.h @@ -33,8 +33,9 @@ typedef enum ECleanupI18NType { UCLN_I18N_HEBREW_CALENDAR, UCLN_I18N_ASTRO_CALENDAR, UCLN_I18N_CALENDAR, + UCLN_I18N_TIMEZONEFORMAT, + UCLN_I18N_TIMEZONENAMES, UCLN_I18N_ZONEMETA, - UCLN_I18N_ZSFORMAT, UCLN_I18N_TIMEZONE, UCLN_I18N_PLURAL_RULE, UCLN_I18N_CURRENCY, diff --git a/icu4c/source/i18n/unicode/dtfmtsym.h b/icu4c/source/i18n/unicode/dtfmtsym.h index d858ba9aca1..f552b0e6f0a 100644 --- a/icu4c/source/i18n/unicode/dtfmtsym.h +++ b/icu4c/source/i18n/unicode/dtfmtsym.h @@ -1,6 +1,6 @@ /* ******************************************************************************** -* Copyright (C) 1997-2010, International Business Machines +* Copyright (C) 1997-2011, International Business Machines * Corporation and others. All Rights Reserved. ******************************************************************************** * @@ -37,8 +37,6 @@ U_NAMESPACE_BEGIN /* forward declaration */ class SimpleDateFormat; class Hashtable; -class ZoneStringFormat; -class SafeZoneStringFormatPtr; /** * DateFormatSymbols is a public class for encapsulating localizable date-time @@ -409,6 +407,10 @@ public: /** * Sets timezone strings. These strings are stored in a 2-dimensional array. + *

Note: SimpleDateFormat no longer use the zone strings stored in + * a DateFormatSymbols. Therefore, the time zone strings set by this mthod + * have no effects in an instance of SimpleDateFormat for formatting time + * zones. * @param strings The timezone strings as a 2-d array to be copied. (not adopted; caller retains ownership) * @param rowCount The number of rows (count of first index). * @param columnCount The number of columns (count of second index). @@ -631,11 +633,13 @@ private: int32_t fZoneStringsRowCount; int32_t fZoneStringsColCount; - const ZoneStringFormat *fZoneStringFormat; - ZoneStringFormat *fZSFLocal; // Local ZoneStringFormat instance - SafeZoneStringFormatPtr *fZSFCachePtr; // Cached ZoneStringFormat Locale fZSFLocale; // Locale used for getting ZoneStringFormat + /** + * String used for localized GMT. For example, "GMT" + */ + UnicodeString fGmtZero; + /** * Pattern string used for localized time zone GMT format. For example, "GMT{0}" */ @@ -725,17 +729,6 @@ private: */ void copyData(const DateFormatSymbols& other); - - /** - * Returns a ZoneStringFormat, used only by SimpleDateFormat for now. - */ - const ZoneStringFormat* getZoneStringFormat(void) const; - - /** - * Create a ZoneStringFormat by locale if not yet availble - */ - void initZoneStringFormat(void); - /** * Create zone strings array by locale if not yet available */ diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h index 79745e18231..7c0e0a21f1a 100644 --- a/icu4c/source/i18n/unicode/smpdtfmt.h +++ b/icu4c/source/i18n/unicode/smpdtfmt.h @@ -41,6 +41,7 @@ class DateFormatSymbols; class DateFormat; class MessageFormat; class FieldPositionHandler; +class TimeZoneFormat; /** * @@ -1038,6 +1039,11 @@ private: */ static const UDateFormatField fgPatternIndexToDateFormatField[]; + /** + * Lazy TimeZoneFormat instantiation, semantically const + */ + TimeZoneFormat *tzFormat() const; + /** * Used to map Calendar field to field level. * The larger the level, the smaller the field unit. @@ -1074,6 +1080,11 @@ private: */ DateFormatSymbols* fSymbols; // Owned + /** + * The time zone formatter + */ + TimeZoneFormat* fTimeZoneFormat; + /** * If dates have ambiguous years, we map them into the century starting * at defaultCenturyStart, which may be any date. If defaultCenturyStart is diff --git a/icu4c/source/i18n/unicode/timezone.h b/icu4c/source/i18n/unicode/timezone.h index 0be92d2848f..8a999926278 100644 --- a/icu4c/source/i18n/unicode/timezone.h +++ b/icu4c/source/i18n/unicode/timezone.h @@ -730,6 +730,16 @@ private: static TimeZone* createCustomTimeZone(const UnicodeString&); // Creates a time zone based on the string. + /** + * Finds the given ID in the Olson tzdata. If the given ID is found in the tzdata, + * returns the pointer to the ID resource. This method is exposed through ZoneMeta class + * for ICU internal implementation and useful for building hashtable using a time zone + * ID as a key. + * @param id zone id string + * @return the pointer of the ID resource, or NULL. + */ + static const UChar* findID(const UnicodeString& id); + /** * Resolve a link in Olson tzdata. When the given id is known and it's not a link, * the id itself is returned. When the given id is known and it is a link, then diff --git a/icu4c/source/i18n/zonemeta.cpp b/icu4c/source/i18n/zonemeta.cpp index 3aafdd14248..01de17cf062 100644 --- a/icu4c/source/i18n/zonemeta.cpp +++ b/icu4c/source/i18n/zonemeta.cpp @@ -22,13 +22,25 @@ #include "cstring.h" #include "ucln_in.h" #include "uassert.h" +#include "uresimp.h" +#include "uhash.h" +#include "olsontz.h" static UMTX gZoneMetaLock = NULL; +// CLDR Canonical ID mapping table +static UHashtable *gCanonicalIDCache = NULL; +static UBool gCanonicalIDCacheInitialized = FALSE; + // Metazone mapping table static UHashtable *gOlsonToMeta = NULL; static UBool gOlsonToMetaInitialized = FALSE; +// Available metazone IDs vector and table +static U_NAMESPACE_QUALIFIER UVector *gMetaZoneIDs = NULL; +static UHashtable *gMetaZoneIDTable = NULL; +static UBool gMetaZoneIDsInitialized = FALSE; + // Country info vectors static U_NAMESPACE_QUALIFIER UVector *gSingleZoneCountries = NULL; static U_NAMESPACE_QUALIFIER UVector *gMultiZonesCountries = NULL; @@ -36,13 +48,18 @@ static UBool gCountryInfoVectorsInitialized = FALSE; U_CDECL_BEGIN - /** * Cleanup callback func */ static UBool U_CALLCONV zoneMeta_cleanup(void) { - umtx_destroy(&gZoneMetaLock); + umtx_destroy(&gZoneMetaLock); + + if (gCanonicalIDCache != NULL) { + uhash_close(gCanonicalIDCache); + gCanonicalIDCache = NULL; + } + gCanonicalIDCacheInitialized = FALSE; if (gOlsonToMeta != NULL) { uhash_close(gOlsonToMeta); @@ -50,6 +67,14 @@ static UBool U_CALLCONV zoneMeta_cleanup(void) } gOlsonToMetaInitialized = FALSE; + if (gMetaZoneIDTable != NULL) { + uhash_close(gMetaZoneIDTable); + } + // delete after closing gMetaZoneIDTable, because it holds + // value objects held by the hashtable + delete gMetaZoneIDs; + gMetaZoneIDsInitialized = FALSE; + delete gSingleZoneCountries; delete gMultiZonesCountries; gCountryInfoVectorsInitialized = FALSE; @@ -184,16 +209,63 @@ parseDate (const UChar *text, UErrorCode &status) { return 0; } -UnicodeString& U_EXPORT2 -ZoneMeta::getCanonicalCLDRID(const UnicodeString &tzid, UnicodeString &systemID, UErrorCode& status) { - int32_t len = tzid.length(); - if ( len >= ZID_KEY_MAX ) { - status = U_ILLEGAL_ARGUMENT_ERROR; - systemID.remove(); - return systemID; +const UChar* U_EXPORT2 +ZoneMeta::getCanonicalCLDRID(const UnicodeString &tzid, UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; } - char id[ZID_KEY_MAX]; + int32_t len = tzid.length(); + if (len > ZID_KEY_MAX) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return NULL; + } + + // Checking the cached results + UBool initialized; + UMTX_CHECK(&gZoneMetaLock, gCanonicalIDCacheInitialized, initialized); + if (!initialized) { + // Create empty hashtable + umtx_lock(&gZoneMetaLock); + { + if (!gCanonicalIDCacheInitialized) { + gCanonicalIDCache = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); + if (gCanonicalIDCache == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + if (U_FAILURE(status)) { + gCanonicalIDCache = NULL; + return NULL; + } + // No key/value deleters - keys/values are from a resource bundle + gCanonicalIDCacheInitialized = TRUE; + ucln_i18n_registerCleanup(UCLN_I18N_ZONEMETA, zoneMeta_cleanup); + } + } + umtx_unlock(&gZoneMetaLock); + } + + const UChar *canonicalID = NULL; + + UErrorCode tmpStatus = U_ZERO_ERROR; + UChar utzid[ZID_KEY_MAX + 1]; + tzid.extract(utzid, ZID_KEY_MAX + 1, tmpStatus); + U_ASSERT(tmpStatus == U_ZERO_ERROR); // we checked the length of tzid already + + // Check if it was already cached + umtx_lock(&gZoneMetaLock); + { + canonicalID = (const UChar *)uhash_get(gCanonicalIDCache, utzid); + } + umtx_unlock(&gZoneMetaLock); + + if (canonicalID != NULL) { + return canonicalID; + } + + // If not, resolve CLDR canonical ID with resource data + UBool isInputCanonical = FALSE; + char id[ZID_KEY_MAX + 1]; const UChar* idChars = tzid.getBuffer(); u_UCharsToChars(idChars,id,len); @@ -207,77 +279,126 @@ ZoneMeta::getCanonicalCLDRID(const UnicodeString &tzid, UnicodeString &systemID, } } - - UErrorCode tmpStatus = U_ZERO_ERROR; UResourceBundle *top = ures_openDirect(NULL, gTimeZoneTypes, &tmpStatus); UResourceBundle *rb = ures_getByKey(top, gTypeMapTag, NULL, &tmpStatus); ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus); ures_getByKey(rb, id, rb, &tmpStatus); if (U_SUCCESS(tmpStatus)) { - // direct map found - systemID.setTo(tzid); - ures_close(rb); - ures_close(top); - return systemID; + // type entry (canonical) found + // the input is the canonical ID. resolve to const UChar* + canonicalID = TimeZone::findID(tzid); + isInputCanonical = TRUE; } - // If a map element not found, then look for an alias - tmpStatus = U_ZERO_ERROR; - ures_getByKey(top, gTypeAliasTag, rb, &tmpStatus); - ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus); - const UChar *alias = ures_getStringByKey(rb,id,NULL,&tmpStatus); - if (U_SUCCESS(tmpStatus)) { - // alias found - ures_close(rb); - ures_close(top); - systemID.setTo(alias); - return systemID; - } + if (canonicalID == NULL) { + // If a map element not found, then look for an alias + tmpStatus = U_ZERO_ERROR; + ures_getByKey(top, gTypeAliasTag, rb, &tmpStatus); + ures_getByKey(rb, gTimezoneTag, rb, &tmpStatus); + const UChar *canonical = ures_getStringByKey(rb,id,NULL,&tmpStatus); + if (U_SUCCESS(tmpStatus)) { + // canonical map found + canonicalID = canonical; + } - // Dereference the input ID using the tz data - const UChar *derefer = TimeZone::dereferOlsonLink(tzid); - if (derefer == NULL) { - systemID.remove(); - status = U_ILLEGAL_ARGUMENT_ERROR; - } else { + if (canonicalID == NULL) { + // Dereference the input ID using the tz data + const UChar *derefer = TimeZone::dereferOlsonLink(tzid); + if (derefer == NULL) { + status = U_ILLEGAL_ARGUMENT_ERROR; + } else { + len = u_strlen(derefer); + u_UCharsToChars(derefer,id,len); + id[len] = (char) 0; // Make sure it is null terminated. - len = u_strlen(derefer); - u_UCharsToChars(derefer,id,len); - id[len] = (char) 0; // Make sure it is null terminated. + // replace '/' with ':' + char *p = id; + while (*p++) { + if (*p == '/') { + *p = ':'; + } + } - // replace '/' with ':' - char *p = id; - while (*p++) { - if (*p == '/') { - *p = ':'; + // If a dereference turned something up then look for an alias. + // rb still points to the alias table, so we don't have to go looking + // for it. + tmpStatus = U_ZERO_ERROR; + canonical = ures_getStringByKey(rb,id,NULL,&tmpStatus); + if (U_SUCCESS(tmpStatus)) { + // canonical map for the dereferenced ID found + canonicalID = canonical; + } else { + canonicalID = derefer; + isInputCanonical = TRUE; + } } } + } + ures_close(rb); + ures_close(top); - // If a dereference turned something up then look for an alias. - // rb still points to the alias table, so we don't have to go looking - // for it. - tmpStatus = U_ZERO_ERROR; - const UChar *alias = ures_getStringByKey(rb,id,NULL,&tmpStatus); - if (U_SUCCESS(tmpStatus)) { - // alias found - systemID.setTo(alias); - } else { - systemID.setTo(derefer); + if (U_SUCCESS(status)) { + U_ASSERT(canonicalID != NULL); // canocanilD must be non-NULL here + + // Put the resolved canonical ID to the cache + umtx_lock(&gZoneMetaLock); + { + const UChar* idInCache = (const UChar *)uhash_get(gCanonicalIDCache, utzid); + if (idInCache == NULL) { + const UChar* key = ZoneMeta::findTimeZoneID(tzid); + U_ASSERT(key != NULL); + if (key != NULL) { + idInCache = (const UChar *)uhash_put(gCanonicalIDCache, (void *)key, (void *)canonicalID, &status); + U_ASSERT(idInCache == NULL); + } + } + if (U_SUCCESS(status) && isInputCanonical) { + // Also put canonical ID itself into the cache if not exist + const UChar *canonicalInCache = (const UChar*)uhash_get(gCanonicalIDCache, canonicalID); + if (canonicalInCache == NULL) { + canonicalInCache = (const UChar *)uhash_put(gCanonicalIDCache, (void *)canonicalID, (void *)canonicalID, &status); + U_ASSERT(canonicalInCache == NULL); + } + } } + umtx_unlock(&gZoneMetaLock); } - ures_close(rb); - ures_close(top); - return systemID; + return canonicalID; } +UnicodeString& U_EXPORT2 +ZoneMeta::getCanonicalCLDRID(const UnicodeString &tzid, UnicodeString &systemID, UErrorCode& status) { + const UChar *canonicalID = getCanonicalCLDRID(tzid, status); + if (U_FAILURE(status) || canonicalID == NULL) { + systemID.setToBogus(); + return systemID; + } + systemID.setTo(TRUE, canonicalID, -1); + return systemID; +} + +const UChar* U_EXPORT2 +ZoneMeta::getCanonicalCLDRID(const TimeZone& tz) { + if (dynamic_cast(&tz) != NULL) { + // short cut for OlsonTimeZone + const OlsonTimeZone *otz = (const OlsonTimeZone*)&tz; + return otz->getCanonicalID(); + } + UErrorCode status = U_ZERO_ERROR; + UnicodeString tzID; + return getCanonicalCLDRID(tz.getID(tzID), status); +} + + + UnicodeString& U_EXPORT2 ZoneMeta::getCanonicalCountry(const UnicodeString &tzid, UnicodeString &canonicalCountry) { const UChar *region = TimeZone::getRegion(tzid); if (region != NULL && u_strcmp(gWorld, region) != 0) { canonicalCountry.setTo(region, -1); } else { - canonicalCountry.remove(); + canonicalCountry.setToBogus(); } return canonicalCountry; } @@ -288,7 +409,7 @@ ZoneMeta::getSingleCountry(const UnicodeString &tzid, UnicodeString &country) { const UChar *region = TimeZone::getRegion(tzid); if (region == NULL || u_strcmp(gWorld, region) == 0) { // special case - unknown or "001" - country.remove(); + country.setToBogus(); return country; } @@ -322,7 +443,7 @@ ZoneMeta::getSingleCountry(const UnicodeString &tzid, UnicodeString &country) { umtx_unlock(&gZoneMetaLock); if (U_FAILURE(status)) { - country.remove(); + country.setToBogus(); return country; } } @@ -378,7 +499,7 @@ ZoneMeta::getSingleCountry(const UnicodeString &tzid, UnicodeString &country) { } if (multiZones) { - country.remove(); + country.setToBogus(); } else { country.setTo(region, -1); } @@ -400,7 +521,7 @@ ZoneMeta::getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &re } } if (!isSet) { - result.remove(); + result.setToBogus(); } return result; } @@ -408,8 +529,8 @@ ZoneMeta::getMetazoneID(const UnicodeString &tzid, UDate date, UnicodeString &re const UVector* U_EXPORT2 ZoneMeta::getMetazoneMappings(const UnicodeString &tzid) { UErrorCode status = U_ZERO_ERROR; - UChar tzidUChars[ZID_KEY_MAX]; - tzid.extract(tzidUChars, ZID_KEY_MAX, status); + UChar tzidUChars[ZID_KEY_MAX + 1]; + tzid.extract(tzidUChars, ZID_KEY_MAX + 1, status); if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) { return NULL; } @@ -506,8 +627,9 @@ ZoneMeta::createMetazoneMappings(const UnicodeString &tzid) { getCanonicalCLDRID(tzid, canonicalID, status); if (U_SUCCESS(status)) { - char tzKey[ZID_KEY_MAX]; - canonicalID.extract(0, canonicalID.length(), tzKey, sizeof(tzKey), US_INV); + char tzKey[ZID_KEY_MAX + 1]; + int32_t tzKeyLen = canonicalID.extract(0, canonicalID.length(), tzKey, sizeof(tzKey), US_INV); + tzKey[tzKeyLen] = 0; // tzid keys are using ':' as separators char *p = tzKey; @@ -593,12 +715,13 @@ ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString &re char keyBuf[ZID_KEY_MAX + 1]; int32_t keyLen = 0; - if (mzid.length() >= ZID_KEY_MAX) { - result.remove(); + if (mzid.length() > ZID_KEY_MAX) { + result.setToBogus(); return result; } - keyLen = mzid.extract(0, mzid.length(), keyBuf, ZID_KEY_MAX, US_INV); + keyLen = mzid.extract(0, mzid.length(), keyBuf, ZID_KEY_MAX + 1, US_INV); + keyBuf[keyLen] = 0; UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status); ures_getByKey(rb, gMapTimezonesTag, rb, &status); @@ -607,7 +730,8 @@ ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString &re if (U_SUCCESS(status)) { // check region mapping if (region.length() == 2 || region.length() == 3) { - region.extract(0, region.length(), keyBuf, ZID_KEY_MAX, US_INV); + keyLen = region.extract(0, region.length(), keyBuf, ZID_KEY_MAX + 1, US_INV); + keyBuf[keyLen] = 0; tzid = ures_getStringByKey(rb, keyBuf, &tzidLen, &status); if (status == U_MISSING_RESOURCE_ERROR) { status = U_ZERO_ERROR; @@ -621,7 +745,7 @@ ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString &re ures_close(rb); if (tzid == NULL) { - result.remove(); + result.setToBogus(); } else { result.setTo(tzid, tzidLen); } @@ -629,6 +753,92 @@ ZoneMeta::getZoneIdByMetazone(const UnicodeString &mzid, const UnicodeString &re return result; } +void +ZoneMeta::initAvailableMetaZoneIDs () { + UBool initialized; + UMTX_CHECK(&gZoneMetaLock, gMetaZoneIDsInitialized, initialized); + if (!initialized) { + umtx_lock(&gZoneMetaLock); + { + if (!gMetaZoneIDsInitialized) { + UErrorCode status = U_ZERO_ERROR; + UHashtable *metaZoneIDTable = uhash_open(uhash_hashUnicodeString, uhash_compareUnicodeString, NULL, &status); + uhash_setKeyDeleter(metaZoneIDTable, uhash_deleteUnicodeString); + // No valueDeleter, because the vector maintain the value objects + UVector *metaZoneIDs = NULL; + if (U_SUCCESS(status)) { + metaZoneIDs = new UVector(NULL, uhash_compareUChars, status); + if (metaZoneIDs == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + } else { + uhash_close(metaZoneIDTable); + } + if (U_SUCCESS(status)) { + metaZoneIDs->setDeleter(uhash_freeBlock); + + UResourceBundle *rb = ures_openDirect(NULL, gMetaZones, &status); + UResourceBundle *bundle = ures_getByKey(rb, gMapTimezonesTag, NULL, &status); + UResourceBundle res; + ures_initStackObject(&res); + while (U_SUCCESS(status) && ures_hasNext(bundle)) { + ures_getNextResource(bundle, &res, &status); + if (U_FAILURE(status)) { + break; + } + const char *mzID = ures_getKey(&res); + int32_t len = uprv_strlen(mzID); + UChar *uMzID = (UChar*)uprv_malloc(sizeof(UChar) * (len + 1)); + if (uMzID == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + break; + } + u_charsToUChars(mzID, uMzID, len); + uMzID[len] = 0; + UnicodeString *usMzID = new UnicodeString(uMzID); + if (uhash_get(metaZoneIDTable, usMzID) == NULL) { + metaZoneIDs->addElement((void *)uMzID, status); + uhash_put(metaZoneIDTable, (void *)usMzID, (void *)uMzID, &status); + } else { + uprv_free(uMzID); + delete usMzID; + } + } + if (U_SUCCESS(status)) { + gMetaZoneIDs = metaZoneIDs; + gMetaZoneIDTable = metaZoneIDTable; + gMetaZoneIDsInitialized = TRUE; + } else { + uhash_close(metaZoneIDTable); + delete metaZoneIDs; + } + ures_close(&res); + ures_close(bundle); + ures_close(rb); + } + } + } + umtx_unlock(&gZoneMetaLock); + } +} + +const UVector* +ZoneMeta::getAvailableMetazoneIDs() { + initAvailableMetaZoneIDs(); + return gMetaZoneIDs; +} + +const UChar* +ZoneMeta::findMetaZoneID(const UnicodeString& mzid) { + initAvailableMetaZoneIDs(); + return (const UChar*)uhash_get(gMetaZoneIDTable, &mzid); +} + +const UChar* +ZoneMeta::findTimeZoneID(const UnicodeString& tzid) { + return TimeZone::findID(tzid); +} + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/zonemeta.h b/icu4c/source/i18n/zonemeta.h index fa677c8a65f..085c2af023d 100644 --- a/icu4c/source/i18n/zonemeta.h +++ b/icu4c/source/i18n/zonemeta.h @@ -23,6 +23,7 @@ typedef struct OlsonToMetaMappingEntry { } OlsonToMetaMappingEntry; class UVector; +class TimeZone; class U_I18N_API ZoneMeta { public: @@ -35,6 +36,18 @@ public: */ static UnicodeString& U_EXPORT2 getCanonicalCLDRID(const UnicodeString &tzid, UnicodeString &systemID, UErrorCode& status); + /** + * Return the canonical id for this tzid defined by CLDR, which might be the id itself. + * This overload method returns a persistent const UChar*, which is guranteed to persist + * (a pointer to a resource). + */ + static const UChar* U_EXPORT2 getCanonicalCLDRID(const UnicodeString &tzid, UErrorCode& status); + + /* + * Conveninent method returning CLDR canonical ID for the given time zone + */ + static const UChar* U_EXPORT2 getCanonicalCLDRID(const TimeZone& tz); + /** * Return the canonical country code for this tzid. If we have none, or if the time zone * is not associated with a country, return null. @@ -60,9 +73,24 @@ public: static const UVector* U_EXPORT2 getMetazoneMappings(const UnicodeString &tzid); + static const UVector* U_EXPORT2 getAvailableMetazoneIDs(); + + /** + * Returns the pointer to the persistent time zone ID string, or NULL if the given tzid is not in the + * tz database. This method is useful when you maintain persistent zone IDs without duplication. + */ + static const UChar* U_EXPORT2 findTimeZoneID(const UnicodeString& tzid); + + /** + * Returns the pointer to the persistent meta zone ID string, or NULL if the given mzid is not available. + * This method is useful when you maintain persistent meta zone IDs without duplication. + */ + static const UChar* U_EXPORT2 findMetaZoneID(const UnicodeString& mzid); + private: ZoneMeta(); // Prevent construction. static UVector* createMetazoneMappings(const UnicodeString &tzid); + static void initAvailableMetaZoneIDs(); }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/zstrfmt.cpp b/icu4c/source/i18n/zstrfmt.cpp deleted file mode 100644 index d4b06c7f2b1..00000000000 --- a/icu4c/source/i18n/zstrfmt.cpp +++ /dev/null @@ -1,2228 +0,0 @@ -/* -******************************************************************************* -* Copyright (C) 2007-2011, 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" -#include "uassert.h" -#include "ureslocs.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_COUNT: - case ZSIDX_SHORT_DAYLIGHT: - type = DAYLIGHT_SHORT; - break; - default: - break; - } - return type; -} - -#define DEFAULT_CHARACTERNODE_CAPACITY 1 - -// ---------------------------------------------------------------------------- -void CharacterNode::clear() { - uprv_memset(this, 0, sizeof(*this)); -} - -void CharacterNode::deleteValues() { - if (fValues == NULL) { - // Do nothing. - } else if (!fHasValuesVector) { - deleteZoneStringInfo(fValues); - } else { - delete (UVector *)fValues; - } -} - -void -CharacterNode::addValue(void *value, UErrorCode &status) { - if (U_FAILURE(status)) { - deleteZoneStringInfo(value); - return; - } - if (fValues == NULL) { - fValues = value; - } else { - // At least one value already. - if (!fHasValuesVector) { - // There is only one value so far, and not in a vector yet. - // Create a vector and add the old value. - UVector *values = new UVector(deleteZoneStringInfo, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status); - if (U_FAILURE(status)) { - deleteZoneStringInfo(value); - return; - } - values->addElement(fValues, status); - fValues = values; - fHasValuesVector = TRUE; - } - // Add the new value. - ((UVector *)fValues)->addElement(value, status); - } -} - -//---------------------------------------------------------------------------- -// Virtual destructor to avoid warning -TextTrieMapSearchResultHandler::~TextTrieMapSearchResultHandler(){ -} - -// ---------------------------------------------------------------------------- -TextTrieMap::TextTrieMap(UBool ignoreCase) -: fIgnoreCase(ignoreCase), fNodes(NULL), fNodesCapacity(0), fNodesCount(0), - fLazyContents(NULL), fIsEmpty(TRUE) { -} - -TextTrieMap::~TextTrieMap() { - int32_t index; - for (index = 0; index < fNodesCount; ++index) { - fNodes[index].deleteValues(); - } - uprv_free(fNodes); - if (fLazyContents != NULL) { - for (int32_t i=0; isize(); i+=2) { - ZoneStringInfo *zsinf = (ZoneStringInfo *)fLazyContents->elementAt(i+1); - delete zsinf; - } - delete fLazyContents; - } -} - -int32_t TextTrieMap::isEmpty() const { - // Use a separate field for fIsEmpty because it will remain unchanged once the - // Trie is built, while fNodes and fLazyContents change with the lazy init - // of the nodes structure. Trying to test the changing fields has - // thread safety complications. - return fIsEmpty; -} - - -// We defer actually building the TextTrieMap node structure until the first time a -// search is performed. put() simply saves the parameters in case we do -// eventually need to build it. -// -void -TextTrieMap::put(const UnicodeString &key, void *value, ZSFStringPool &sp, UErrorCode &status) { - fIsEmpty = FALSE; - if (fLazyContents == NULL) { - fLazyContents = new UVector(status); - if (fLazyContents == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - } - } - if (U_FAILURE(status)) { - return; - } - UChar *s = const_cast(sp.get(key, status)); - fLazyContents->addElement(s, status); - fLazyContents->addElement(value, status); -} - - -void -TextTrieMap::putImpl(const UnicodeString &key, void *value, UErrorCode &status) { - if (fNodes == NULL) { - fNodesCapacity = 512; - fNodes = (CharacterNode *)uprv_malloc(fNodesCapacity * sizeof(CharacterNode)); - fNodes[0].clear(); // Init root node. - fNodesCount = 1; - } - - UnicodeString foldedKey; - const UChar *keyBuffer; - int32_t keyLength; - if (fIgnoreCase) { - // Ok to use fastCopyFrom() because we discard the copy when we return. - foldedKey.fastCopyFrom(key).foldCase(); - keyBuffer = foldedKey.getBuffer(); - keyLength = foldedKey.length(); - } else { - keyBuffer = key.getBuffer(); - keyLength = key.length(); - } - - CharacterNode *node = fNodes; - int32_t index; - for (index = 0; index < keyLength; ++index) { - node = addChildNode(node, keyBuffer[index], status); - } - node->addValue(value, status); -} - -UBool -TextTrieMap::growNodes() { - if (fNodesCapacity == 0xffff) { - return FALSE; // We use 16-bit node indexes. - } - int32_t newCapacity = fNodesCapacity + 1000; - if (newCapacity > 0xffff) { - newCapacity = 0xffff; - } - CharacterNode *newNodes = (CharacterNode *)uprv_malloc(newCapacity * sizeof(CharacterNode)); - if (newNodes == NULL) { - return FALSE; - } - uprv_memcpy(newNodes, fNodes, fNodesCount * sizeof(CharacterNode)); - uprv_free(fNodes); - fNodes = newNodes; - fNodesCapacity = newCapacity; - return TRUE; -} - -CharacterNode* -TextTrieMap::addChildNode(CharacterNode *parent, UChar c, UErrorCode &status) { - if (U_FAILURE(status)) { - return NULL; - } - // Linear search of the sorted list of children. - uint16_t prevIndex = 0; - uint16_t nodeIndex = parent->fFirstChild; - while (nodeIndex > 0) { - CharacterNode *current = fNodes + nodeIndex; - UChar childCharacter = current->fCharacter; - if (childCharacter == c) { - return current; - } else if (childCharacter > c) { - break; - } - prevIndex = nodeIndex; - nodeIndex = current->fNextSibling; - } - - // Ensure capacity. Grow fNodes[] if needed. - if (fNodesCount == fNodesCapacity) { - int32_t parentIndex = (int32_t)(parent - fNodes); - if (!growNodes()) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - parent = fNodes + parentIndex; - } - - // Insert a new child node with c in sorted order. - CharacterNode *node = fNodes + fNodesCount; - node->clear(); - node->fCharacter = c; - node->fNextSibling = nodeIndex; - if (prevIndex == 0) { - parent->fFirstChild = (uint16_t)fNodesCount; - } else { - fNodes[prevIndex].fNextSibling = (uint16_t)fNodesCount; - } - ++fNodesCount; - return node; -} - -CharacterNode* -TextTrieMap::getChildNode(CharacterNode *parent, UChar c) const { - // Linear search of the sorted list of children. - uint16_t nodeIndex = parent->fFirstChild; - while (nodeIndex > 0) { - CharacterNode *current = fNodes + nodeIndex; - UChar childCharacter = current->fCharacter; - if (childCharacter == c) { - return current; - } else if (childCharacter > c) { - break; - } - nodeIndex = current->fNextSibling; - } - return NULL; -} - -// Mutex for protecting the lazy creation of the Trie node structure on the first call to search(). -static UMTX TextTrieMutex; - -// buildTrie() - The Trie node structure is needed. Create it from the data that was -// saved at the time the ZoneStringFormatter was created. The Trie is only -// needed for parsing operations, which are less common than formatting, -// and the Trie is big, which is why its creation is deferred until first use. -void TextTrieMap::buildTrie(UErrorCode &status) { - umtx_lock(&TextTrieMutex); - if (fLazyContents != NULL) { - for (int32_t i=0; isize(); i+=2) { - const UChar *key = (UChar *)fLazyContents->elementAt(i); - void *val = fLazyContents->elementAt(i+1); - UnicodeString keyString(TRUE, key, -1); // Aliasing UnicodeString constructor. - putImpl(keyString, val, status); - } - delete fLazyContents; - fLazyContents = NULL; - } - umtx_unlock(&TextTrieMutex); -} - - -void -TextTrieMap::search(const UnicodeString &text, int32_t start, - TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { - UBool trieNeedsInitialization = FALSE; - UMTX_CHECK(&TextTrieMutex, fLazyContents != NULL, trieNeedsInitialization); - if (trieNeedsInitialization) { - TextTrieMap *nonConstThis = const_cast(this); - nonConstThis->buildTrie(status); - } - if (fNodes == NULL) { - return; - } - search(fNodes, 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; - } - if (node->hasValues()) { - if (!handler->handleMatch(index - start, node, 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 = getChildNode(node, c); - if (node == NULL) { - break; - } - tmpidx = tmp.moveIndex32(tmpidx, 1); - } - } else { - node = getChildNode(node, c); - } - if (node != NULL) { - search(node, text, start, index+1, handler, status); - } -} - -// ---------------------------------------------------------------------------- -ZoneStringInfo::ZoneStringInfo(const UnicodeString &id, const UnicodeString &str, - TimeZoneTranslationType type, ZSFStringPool &sp, UErrorCode &status) -: fType(type) { - fId = sp.get(id, status); - fStr = sp.get(str, status); -} - -ZoneStringInfo::~ZoneStringInfo() { -} - - -// ---------------------------------------------------------------------------- -ZoneStringSearchResultHandler::ZoneStringSearchResultHandler(UErrorCode &status) -: fResults(status) -{ - clear(); -} - -ZoneStringSearchResultHandler::~ZoneStringSearchResultHandler() { - clear(); -} - -UBool -ZoneStringSearchResultHandler::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++) { - ZoneStringInfo *zsinfo = (ZoneStringInfo*)node->getValue(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; - } -} - -// Mutex for protecting the lazy load of a zone ID (or a full load) to ZoneStringFormat structures. -static UMTX ZoneStringFormatMutex; - - -// ---------------------------------------------------------------------------- -ZoneStringFormat::ZoneStringFormat(const UnicodeString* const* strings, - int32_t rowCount, int32_t columnCount, UErrorCode &status) -: fLocale(""), - fTzidToStrings(NULL), - fMzidToStrings(NULL), - fZoneStringsTrie(TRUE), - fStringPool(status), - fZoneStringsArray(NULL), - fMetazoneItem(NULL), - fZoneItem(NULL), - fIsFullyLoaded(FALSE) -{ - if (U_FAILURE(status)) { - return; - } - fLocale.setToBogus(); - if (strings == NULL || columnCount <= 0 || rowCount <= 0) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return; - } - fTzidToStrings = uhash_open(uhash_hashUChars, // key hash function - uhash_compareUChars, // key comparison function - NULL, // Value comparison function - &status); - fMzidToStrings = uhash_open(uhash_hashUChars, - uhash_compareUChars, - NULL, - &status); - - uhash_setValueDeleter(fTzidToStrings, deleteZoneStrings); - uhash_setValueDeleter(fMzidToStrings, 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, - fStringPool, - status); - fZoneStringsTrie.put(strings[row][col], zsinf, fStringPool, status); - if (U_FAILURE(status)) { - delete zsinf; - goto error_cleanup; - } - } - } - } - // Note: ZoneStrings constructor adopts and delete the names array. - ZoneStrings *zstrings = new ZoneStrings(names, ZSIDX_COUNT, TRUE, NULL, 0, 0, - fStringPool, status); - UChar *utzid = const_cast(fStringPool.get(strings[row][0], status)); - uhash_put(fTzidToStrings, utzid, zstrings, &status); - if (U_FAILURE(status)) { - delete zstrings; - goto error_cleanup; - } - } - fStringPool.freeze(); - fIsFullyLoaded = TRUE; - return; - -error_cleanup: - return; -} - -ZoneStringFormat::ZoneStringFormat(const Locale &locale, UErrorCode &status) -: fLocale(locale), - fTzidToStrings(NULL), - fMzidToStrings(NULL), - fZoneStringsTrie(TRUE), - fStringPool(status), - fZoneStringsArray(NULL), - fMetazoneItem(NULL), - fZoneItem(NULL), - fIsFullyLoaded(FALSE) -{ - if (U_FAILURE(status)) { - return; - } - fTzidToStrings = uhash_open(uhash_hashUChars, // key hash function - uhash_compareUChars, // key comparison function - NULL, // Value comparison function - &status); - fMzidToStrings = uhash_open(uhash_hashUChars, // key hash function - uhash_compareUChars, // key comparison function - NULL, // Value comparison function - &status); - uhash_setValueDeleter(fTzidToStrings, deleteZoneStrings); - uhash_setValueDeleter(fMzidToStrings, deleteZoneStrings); -} - -// Load only a single zone -void -ZoneStringFormat::loadZone(const UnicodeString &utzid, UErrorCode &status) -{ - if (fIsFullyLoaded) { - return; - } - - if (U_FAILURE(status)) { - return; - } - - umtx_lock(&ZoneStringFormatMutex); - - if (fZoneStringsArray == NULL) { - fZoneStringsArray = ures_open(U_ICUDATA_ZONE, fLocale.getName(), &status); - fZoneStringsArray = ures_getByKeyWithFallback(fZoneStringsArray, gZoneStringsTag, fZoneStringsArray, &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(fZoneStringsArray); - fZoneStringsArray = NULL; - } - } - - // Skip non-canonical IDs - UnicodeString canonicalID; - ZoneMeta::getCanonicalCLDRID(utzid, canonicalID, status); - if (U_FAILURE(status)) { - // Ignore unknown ID - we should not get here, but just in case. - // status = U_ZERO_ERROR; - umtx_unlock(&ZoneStringFormatMutex); - return; - } - - if (U_SUCCESS(status)) { - if (uhash_count(fTzidToStrings) > 0) { - ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); - if (zstrings != NULL) { - umtx_unlock(&ZoneStringFormatMutex); - return; // We already about this one - } - } - } - - addSingleZone(canonicalID, status); - - umtx_unlock(&ZoneStringFormatMutex); -} - -// Load only a single zone -void -ZoneStringFormat::addSingleZone(UnicodeString &utzid, UErrorCode &status) -{ - if (U_FAILURE(status)) { - return; - } - - if (uhash_count(fTzidToStrings) > 0) { - ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, utzid.getTerminatedBuffer()); - if (zstrings != NULL) { - return; // We already about this one - } - } - - MessageFormat *fallbackFmt = NULL; - MessageFormat *regionFmt = NULL; - - fallbackFmt = getFallbackFormat(fLocale, status); - if (U_FAILURE(status)) { - goto error_cleanup; - } - regionFmt = getRegionFormat(fLocale, status); - if (U_FAILURE(status)) { - goto error_cleanup; - } - - - { - char zidkey[ZID_KEY_MAX+1]; - char tzid[ZID_KEY_MAX+1]; - utzid.extract(0, utzid.length(), zidkey, ZID_KEY_MAX, US_INV); - utzid.extract(0, utzid.length(), tzid, ZID_KEY_MAX, US_INV); - - const UChar *zstrarray[ZSIDX_COUNT]; - const UChar *mzstrarray[ZSIDX_COUNT]; - UnicodeString mzPartialLoc[MAX_METAZONES_PER_ZONE][4]; - - // Replace '/' with ':' - char *pCity = NULL; - char *p = zidkey; - while (*p) { - if (*p == '/') { - *p = ':'; - pCity = p + 1; - } - p++; - } - - if (fZoneStringsArray != NULL) { - fZoneItem = ures_getByKeyWithFallback(fZoneStringsArray, zidkey, fZoneItem, &status); - if (U_FAILURE(status)) { - // If failed to open the zone item, create only location string - ures_close(fZoneItem); - fZoneItem = NULL; - status = U_ZERO_ERROR; - } - } - - UnicodeString region; - getRegion(region); - - zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(fZoneItem, gLongStandardTag); - zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(fZoneItem, gShortStandardTag); - zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(fZoneItem, gLongDaylightTag); - zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(fZoneItem, gShortDaylightTag); - zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(fZoneItem, gLongGenericTag); - zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(fZoneItem, gShortGenericTag); - - // Compose location format string - UnicodeString location; - UnicodeString country; - UnicodeString city; - UnicodeString countryCode; - ZoneMeta::getCanonicalCountry(utzid, countryCode); - if (!countryCode.isEmpty()) { - const UChar* tmpCity = getZoneStringFromBundle(fZoneItem, 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, fLocale, 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; - } - - zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer(); - } else { - if (uprv_strlen(tzid) > 4 && uprv_strncmp(tzid, "Etc/", 4) == 0) { - // "Etc/xxx" is not associated with a specific location, so localized - // GMT format is always used as generic location format. - zstrarray[ZSIDX_LOCATION] = NULL; - } else { - // When a new time zone ID, which is actually associated with a specific - // location, is added in tzdata, but the current CLDR data does not have - // the information yet, ICU creates a generic location string based on - // the ID. This implementation supports canonical time zone round trip - // with format pattern "VVVV". See #6602 for the details. - UnicodeString loc(utzid); - int32_t slashIdx = loc.lastIndexOf((UChar)0x2f); - if (slashIdx == -1) { - // A time zone ID without slash in the tz database is not - // associated with a specific location. For instances, - // MET, CET, EET and WET fall into this category. - // In this case, we still use GMT format as fallback. - zstrarray[ZSIDX_LOCATION] = NULL; - } else { - FieldPosition fpos; - Formattable params[] = { - Formattable(loc) - }; - regionFmt->format(params, 1, location, fpos, status); - if (U_FAILURE(status)) { - goto error_cleanup; - } - zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer(); - } - } - } - - UBool commonlyUsed = isCommonlyUsed(fZoneItem); - - // 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*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); - 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); - fMetazoneItem = ures_getByKeyWithFallback(fZoneStringsArray, mzidkey, fMetazoneItem, &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(fMetazoneItem); - mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(fMetazoneItem, gLongStandardTag); - mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(fMetazoneItem, gShortStandardTag); - mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(fMetazoneItem, gLongDaylightTag); - mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(fMetazoneItem, gShortDaylightTag); - mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(fMetazoneItem, gLongGenericTag); - mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(fMetazoneItem, 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, - fStringPool, - status); - fZoneStringsTrie.put(strings_mz[typeidx], zsinfo, fStringPool, status); - if (U_FAILURE(status)) { - delete []strings_mz; - goto error_cleanup; - } - } - } - // Note: ZoneStrings constructor adopts and deletes the strings_mz array. - tmp_mzStrings = new ZoneStrings(strings_mz, lastNonNullIdx + 1, - mzCommonlyUsed, NULL, 0, 0, fStringPool, status); - } else { - // Create ZoneStrings with empty contents - tmp_mzStrings = new ZoneStrings(NULL, 0, FALSE, NULL, 0, 0, fStringPool, status); - } - - UChar *umzid = const_cast(fStringPool.get(mzid, status)); - uhash_put(fMzidToStrings, umzid, 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->length() != 0) { - 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, - fStringPool, - status); - fZoneStringsTrie.put(strings[i], zsinfo, fStringPool, 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, - fStringPool, status); - fZoneStringsTrie.put(genericPartialLocationNames[i][j], zsinfo, fStringPool, 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, fStringPool, status); - - UChar *uutzid = const_cast(fStringPool.get(utzid, status)); - uhash_put(fTzidToStrings, uutzid, zstrings, &status); - if (U_FAILURE(status)) { - delete zstrings; - goto error_cleanup; - } - } - -error_cleanup: - if (fallbackFmt != NULL) { - delete fallbackFmt; - } - if (regionFmt != NULL) { - delete regionFmt; - } - // fStringPool.freeze(); -} - -void -ZoneStringFormat::loadFull(UErrorCode &status) -{ - if (U_FAILURE(status)) { - return; - } - if (fIsFullyLoaded) { - return; - } - - umtx_lock(&ZoneStringFormatMutex); - - if (fZoneStringsArray == NULL) { - fZoneStringsArray = ures_open(U_ICUDATA_ZONE, fLocale.getName(), &status); - fZoneStringsArray = ures_getByKeyWithFallback(fZoneStringsArray, gZoneStringsTag, fZoneStringsArray, &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(fZoneStringsArray); - fZoneStringsArray = NULL; - } - } - - StringEnumeration *tzids = NULL; - - 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; - ZoneMeta::getCanonicalCLDRID(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 (U_SUCCESS(status)) { - if (uhash_count(fTzidToStrings) > 0) { - ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); - if (zstrings != NULL) { - continue; // We already about this one - } - } - } - - addSingleZone(canonicalID, status); - - if (U_FAILURE(status)) { - goto error_cleanup; - } - } - - fIsFullyLoaded = TRUE; - -error_cleanup: - if (tzids != NULL) { - delete tzids; - } - fStringPool.freeze(); - - umtx_unlock(&ZoneStringFormatMutex); -} - - -ZoneStringFormat::~ZoneStringFormat() { - uhash_close(fTzidToStrings); - uhash_close(fMzidToStrings); - ures_close(fZoneItem); - ures_close(fMetazoneItem); - ures_close(fZoneStringsArray); -} - -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; - } - ZoneStringFormat *nonConstThis = const_cast(this); - nonConstThis->loadFull(status); - - 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; - ZoneMeta::getCanonicalCLDRID(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 { - UErrorCode status = U_ZERO_ERROR; - result.remove(); - if (!fIsFullyLoaded) { - // Lazy loading - ZoneStringFormat *nonConstThis = const_cast(this); - nonConstThis->loadZone(tzid, status); - } - - // ICU's own array does not have entries for aliases - UnicodeString canonicalID; - ZoneMeta::getCanonicalCLDRID(tzid, canonicalID, status); - if (U_FAILURE(status)) { - // Unknown ID, but users might have their own data. - canonicalID.setTo(tzid); - } - - if (uhash_count(fTzidToStrings) > 0) { - ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); - 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_COUNT: //added to avoid warning - case ZSIDX_SHORT_GENERIC: - if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) { - zstrings->getString(typeIdx, result); - } - break; - default: - break; - } - } - } - if (result.isEmpty() && uhash_count(fMzidToStrings) > 0 && typeIdx != ZSIDX_LOCATION) { - // Try metazone - UnicodeString mzid; - ZoneMeta::getMetazoneID(canonicalID, date, mzid); - if (!mzid.isEmpty()) { - ZoneStrings *mzstrings = (ZoneStrings*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); - 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_COUNT: //added to avoid warning - case ZSIDX_SHORT_GENERIC: - if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) { - mzstrings->getString(typeIdx, result); - } - break; - default: - 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); - - if (!fIsFullyLoaded) { - // Lazy loading - ZoneStringFormat *nonConstThis = const_cast(this); - nonConstThis->loadZone(tzid, status); - } - - // ICU's own array does not have entries for aliases - UnicodeString canonicalID; - ZoneMeta::getCanonicalCLDRID(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 (uhash_count(fTzidToStrings) > 0) { - zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); - 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() && uhash_count(fMzidToStrings) > 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 (dynamic_cast(tmptz) != NULL - || dynamic_cast(tmptz) != NULL - || dynamic_cast(tmptz) != NULL - || dynamic_cast(tmptz) != NULL) { - 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*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); - 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 { - UErrorCode status = U_ZERO_ERROR; - result.remove(); - if (!fIsFullyLoaded) { - // Lazy loading - ZoneStringFormat *nonConstThis = const_cast(this); - nonConstThis->loadZone(tzid, status); - } - - if (uhash_count(fTzidToStrings) <= 0) { - return result; - } - - UnicodeString canonicalID; - ZoneMeta::getCanonicalCLDRID(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*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); - if (zstrings != NULL) { - zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result); - } - } - return result; -} - -// This method does lazy zone string loading -const ZoneStringInfo* -ZoneStringFormat::find(const UnicodeString &text, int32_t start, int32_t types, - int32_t &matchLength, UErrorCode &status) const { - - if (U_FAILURE(status)) { - return NULL; - } - - const ZoneStringInfo * result = subFind(text, start, types, matchLength, status); - if (fIsFullyLoaded) { - return result; - } - // When zone string data is partially loaded, - // this method return the result only when - // the input text is fully consumed. - if (result != NULL) { - UnicodeString tmpString; - matchLength = (result->getString(tmpString)).length(); - if (text.length() - start == matchLength) { - return result; - } - } - - // Now load all zone strings - ZoneStringFormat *nonConstThis = const_cast(this); - nonConstThis->loadFull(status); - - return subFind(text, start, types, matchLength, status); -} - - -/* - * Find a prefix matching time zone for the given zone string types. - * @param text The text contains a time zone string - * @param start The start index within the text - * @param types The bit mask representing a set of requested types - * @return If any zone string matched for the requested types, returns a - * ZoneStringInfo for the longest match. If no matches are found for - * the requested types, returns a ZoneStringInfo for the longest match - * for any other types. If nothing matches at all, returns null. - */ -const ZoneStringInfo* -ZoneStringFormat::subFind(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 = 0; // init. output only param to silence gcc - 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(U_ICUDATA_ZONE, 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(U_ICUDATA_ZONE, 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); - str = fStringPool.adopt(str, 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; -} - -// ---------------------------------------------------------------------------- -/* - * ZoneStrings constructor adopts (and promptly copies and deletes) - * the input UnicodeString arrays. - */ -ZoneStrings::ZoneStrings(UnicodeString *strings, - int32_t stringsCount, - UBool commonlyUsed, - UnicodeString **genericPartialLocationStrings, - int32_t genericRowCount, - int32_t genericColCount, - ZSFStringPool &sp, - UErrorCode &status) -: fStrings(NULL), - fStringsCount(stringsCount), - fIsCommonlyUsed(commonlyUsed), - fGenericPartialLocationStrings(NULL), - fGenericPartialLocationRowCount(genericRowCount), - fGenericPartialLocationColCount(genericColCount) -{ - if (U_FAILURE(status)) { - return; - } - int32_t i, j; - if (strings != NULL) { - fStrings = (const UChar **)uprv_malloc(sizeof(const UChar **) * stringsCount); - if (fStrings == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - for (i=0; i= 0 && typeIdx < fStringsCount) { - result.setTo(fStrings[typeIdx], -1); - } 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 (mzid.compare(fGenericPartialLocationStrings[i][0], -1) == 0) { - if (isShort) { - if (fGenericPartialLocationColCount >= 3) { - if (!commonlyUsedOnly || - fGenericPartialLocationColCount == 3 || - fGenericPartialLocationStrings[i][3][0] != 0) { - result.setTo(fGenericPartialLocationStrings[i][2], -1); - isSet = TRUE; - } - } - } else { - result.setTo(fGenericPartialLocationStrings[i][1], -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; -} - - -/* - * Zone String Formatter String Pool Implementation - * - * String pool for (UChar *) strings. Avoids having repeated copies of the same string. - */ - -static const int32_t POOL_CHUNK_SIZE = 2000; -struct ZSFStringPoolChunk: public UMemory { - ZSFStringPoolChunk *fNext; // Ptr to next pool chunk - int32_t fLimit; // Index to start of unused area at end of fStrings - UChar fStrings[POOL_CHUNK_SIZE]; // Strings array - ZSFStringPoolChunk(); -}; - -ZSFStringPoolChunk::ZSFStringPoolChunk() { - fNext = NULL; - fLimit = 0; -} - -ZSFStringPool::ZSFStringPool(UErrorCode &status) { - fChunks = NULL; - fHash = NULL; - if (U_FAILURE(status)) { - return; - } - fChunks = new ZSFStringPoolChunk; - if (fChunks == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - - fHash = uhash_open(uhash_hashUChars /* keyHash */, - uhash_compareUChars /* keyComp */, - uhash_compareUChars /* valueComp */, - &status); - if (U_FAILURE(status)) { - return; - } -} - - -ZSFStringPool::~ZSFStringPool() { - if (fHash != NULL) { - uhash_close(fHash); - fHash = NULL; - } - - while (fChunks != NULL) { - ZSFStringPoolChunk *nextChunk = fChunks->fNext; - delete fChunks; - fChunks = nextChunk; - } -} - -static const UChar EmptyString = 0; - -const UChar *ZSFStringPool::get(const UChar *s, UErrorCode &status) { - const UChar *pooledString; - if (U_FAILURE(status)) { - return &EmptyString; - } - - pooledString = static_cast(uhash_get(fHash, s)); - if (pooledString != NULL) { - return pooledString; - } - - int32_t length = u_strlen(s); - int32_t remainingLength = POOL_CHUNK_SIZE - fChunks->fLimit; - if (remainingLength <= length) { - U_ASSERT(length < POOL_CHUNK_SIZE); - if (length >= POOL_CHUNK_SIZE) { - status = U_INTERNAL_PROGRAM_ERROR; - return &EmptyString; - } - ZSFStringPoolChunk *oldChunk = fChunks; - fChunks = new ZSFStringPoolChunk; - if (fChunks == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return &EmptyString; - } - fChunks->fNext = oldChunk; - } - - UChar *destString = &fChunks->fStrings[fChunks->fLimit]; - u_strcpy(destString, s); - fChunks->fLimit += (length + 1); - uhash_put(fHash, destString, destString, &status); - return destString; -} - - -// -// ZSFStringPool::adopt() Put a string into the hash, but do not copy the string data -// into the pool's storage. Used for strings from resource bundles, -// which will perisist for the life of the zone string formatter, and -// therefore can be used directly without copying. -const UChar *ZSFStringPool::adopt(const UChar * s, UErrorCode &status) { - const UChar *pooledString; - if (U_FAILURE(status)) { - return &EmptyString; - } - if (s != NULL) { - pooledString = static_cast(uhash_get(fHash, s)); - if (pooledString == NULL) { - UChar *ncs = const_cast(s); - uhash_put(fHash, ncs, ncs, &status); - } - } - return s; -} - - -const UChar *ZSFStringPool::get(const UnicodeString &s, UErrorCode &status) { - UnicodeString &nonConstStr = const_cast(s); - return this->get(nonConstStr.getTerminatedBuffer(), status); -} - -/* - * freeze(). Close the hash table that maps to the pooled strings. - * After freezing, the pool can not be searched or added to, - * but all existing references to pooled strings remain valid. - * - * The main purpose is to recover the storage used for the hash. - */ -void ZSFStringPool::freeze() { - uhash_close(fHash); - fHash = NULL; -} - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/zstrfmt.h b/icu4c/source/i18n/zstrfmt.h deleted file mode 100644 index 53f744458ed..00000000000 --- a/icu4c/source/i18n/zstrfmt.h +++ /dev/null @@ -1,526 +0,0 @@ -/* -******************************************************************************* -* Copyright (C) 2007-2011, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -*/ -#ifndef ZSTRFMT_H -#define ZSTRFMT_H - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unistr.h" -#include "unicode/calendar.h" -#include "uhash.h" -#include "uvector.h" - -U_NAMESPACE_BEGIN - -/* - * Character node used by TextTrieMap - */ -struct CharacterNode { - // No constructor or destructor. - // We malloc and free an uninitalized array of CharacterNode objects - // and clear and delete them ourselves. - - void clear(); - void deleteValues(); - - void addValue(void *value, UErrorCode &status); - inline UBool hasValues() const; - inline int32_t countValues() const; - inline const void *getValue(int32_t index) const; - - void *fValues; // Union of one single value vs. UVector of values. - UChar fCharacter; // UTF-16 code unit. - uint16_t fFirstChild; // 0 if no children. - uint16_t fNextSibling; // 0 terminates the list. - UBool fHasValuesVector; - UBool fPadding; - - // No value: fValues == NULL and fHasValuesVector == FALSE - // One value: fValues == value and fHasValuesVector == FALSE - // >=2 values: fValues == UVector of values and fHasValuesVector == TRUE -}; - -inline UBool CharacterNode::hasValues() const { - return (UBool)(fValues != NULL); -} - -inline int32_t CharacterNode::countValues() const { - return - fValues == NULL ? 0 : - !fHasValuesVector ? 1 : - ((const UVector *)fValues)->size(); -} - -inline const void *CharacterNode::getValue(int32_t index) const { - if (!fHasValuesVector) { - return fValues; // Assume index == 0. - } else { - return ((const UVector *)fValues)->elementAt(index); - } -} - -/* - * Search result handler callback interface used by TextTrieMap search. - */ -class TextTrieMapSearchResultHandler : public UMemory { -public: - virtual UBool handleMatch(int32_t matchLength, - const CharacterNode *node, UErrorCode& status) = 0; - virtual ~TextTrieMapSearchResultHandler(); //added to avoid warning -}; - - -/* - * ZSFStringPool Pool of (UChar *) strings. Provides for sharing of repeated - * strings within ZoneStringFormats. - */ -struct ZSFStringPoolChunk; -class ZSFStringPool: public UMemory { - public: - ZSFStringPool(UErrorCode &status); - ~ZSFStringPool(); - - /* Get the pooled string that is equal to the supplied string s. - * Copy the string into the pool if it is not already present. - * - * Life time of the returned string is that of the pool. - */ - const UChar *get(const UChar *s, UErrorCode &status); - - /* Get the pooled string that is equal to the supplied string s. - * Copy the string into the pool if it is not already present. - */ - const UChar *get(const UnicodeString &s, UErrorCode &status); - - /* Adopt a string into the pool, without copying it. - * Used for strings from resource bundles, which will persist without copying. - */ - const UChar *adopt(const UChar *s, UErrorCode &status); - - /* Freeze the string pool. Discards the hash table that is used - * for looking up a string. All pointers to pooled strings remain valid. - */ - void freeze(); - - private: - ZSFStringPoolChunk *fChunks; - UHashtable *fHash; -}; - - -/** - * TextTrieMap is a trie implementation for supporting - * fast prefix match for the string key. - */ -class TextTrieMap : public UMemory { -public: - TextTrieMap(UBool ignoreCase); - virtual ~TextTrieMap(); - - void put(const UnicodeString &key, void *value, ZSFStringPool &sp, UErrorCode &status); - void search(const UnicodeString &text, int32_t start, - TextTrieMapSearchResultHandler *handler, UErrorCode& status) const; - int32_t isEmpty() const; - -private: - UBool fIgnoreCase; - CharacterNode *fNodes; - int32_t fNodesCapacity; - int32_t fNodesCount; - - UVector *fLazyContents; - UBool fIsEmpty; - - UBool growNodes(); - CharacterNode* addChildNode(CharacterNode *parent, UChar c, UErrorCode &status); - CharacterNode* getChildNode(CharacterNode *parent, UChar c) const; - - void putImpl(const UnicodeString &key, void *value, UErrorCode &status); - void buildTrie(UErrorCode &status); - void search(CharacterNode *node, const UnicodeString &text, int32_t start, - int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const; -}; - - -// Name types, these bit flag are used for zone string lookup -enum TimeZoneTranslationType { - LOCATION = 0x0001, - GENERIC_LONG = 0x0002, - GENERIC_SHORT = 0x0004, - STANDARD_LONG = 0x0008, - STANDARD_SHORT = 0x0010, - DAYLIGHT_LONG = 0x0020, - DAYLIGHT_SHORT = 0x0040 -}; - -// Name type index, these constants are used for index in the zone strings array. -enum TimeZoneTranslationTypeIndex { - ZSIDX_LOCATION = 0, - ZSIDX_LONG_STANDARD, - ZSIDX_SHORT_STANDARD, - ZSIDX_LONG_DAYLIGHT, - ZSIDX_SHORT_DAYLIGHT, - ZSIDX_LONG_GENERIC, - ZSIDX_SHORT_GENERIC, - - ZSIDX_COUNT -}; - -class MessageFormat; - - -/* - * ZoneStringInfo is a class holding a localized zone string - * information. - */ -class ZoneStringInfo : public UMemory { -public: - virtual ~ZoneStringInfo(); - - inline UnicodeString& getID(UnicodeString &result) const; - inline UnicodeString& getString(UnicodeString &result) const; - inline UBool isStandard(void) const; - inline UBool isDaylight(void) const; - inline UBool isGeneric(void) const; - -private: - friend class ZoneStringFormat; - friend class ZoneStringSearchResultHandler; - - ZoneStringInfo(const UnicodeString &id, const UnicodeString &str, - TimeZoneTranslationType type, ZSFStringPool &sp, UErrorCode &status); - - const UChar *fId; - const UChar *fStr; - TimeZoneTranslationType fType; -}; - -inline UnicodeString& ZoneStringInfo::getID(UnicodeString &result) const { - return result.setTo(fId, -1); -} - -inline UnicodeString& ZoneStringInfo::getString(UnicodeString &result) const { - return result.setTo(fStr, -1); -} - -inline UBool ZoneStringInfo::isStandard(void) const { - return (fType == STANDARD_LONG || fType == STANDARD_SHORT); -} - -inline UBool ZoneStringInfo::isDaylight(void) const { - return (fType == DAYLIGHT_LONG || fType == DAYLIGHT_SHORT); -} - -inline UBool ZoneStringInfo::isGeneric(void) const { - return (fType == LOCATION || fType == GENERIC_LONG || fType == GENERIC_SHORT); -} - -class SafeZoneStringFormatPtr; - -class ZoneStringFormat : public UMemory { -public: - ZoneStringFormat(const UnicodeString* const* strings, int32_t rowCount, int32_t columnCount, UErrorCode &status); - ZoneStringFormat(const Locale& locale, UErrorCode &status); - virtual ~ZoneStringFormat(); - - /* Gets zone string format from cache if available, create it if not cached. */ - static SafeZoneStringFormatPtr* getZoneStringFormat(const Locale& locale, UErrorCode &status); - - /* - * Create a snapshot of old zone strings array for the given date - */ - UnicodeString** createZoneStringsArray(UDate date, int32_t &rowCount, int32_t &colCount, UErrorCode &status) const; - - /* TODO: There is no implementation for this function. Delete declaration? */ - const UnicodeString** getZoneStrings(int32_t &rowCount, int32_t &columnCount) const; - - UnicodeString& getSpecificLongString(const Calendar &cal, - UnicodeString &result, UErrorCode &status) const; - - UnicodeString& getSpecificShortString(const Calendar &cal, - UBool commonlyUsedOnly, UnicodeString &result, UErrorCode &status) const; - - UnicodeString& getGenericLongString(const Calendar &cal, - UnicodeString &result, UErrorCode &status) const; - - UnicodeString& getGenericShortString(const Calendar &cal, - UBool commonlyUsedOnly, UnicodeString &result, UErrorCode &status) const; - - UnicodeString& getGenericLocationString(const Calendar &cal, - UnicodeString &result, UErrorCode &status) const; - - const ZoneStringInfo* findSpecificLong(const UnicodeString &text, int32_t start, - int32_t &matchLength, UErrorCode &status) const; - const ZoneStringInfo* findSpecificShort(const UnicodeString &text, int32_t start, - int32_t &matchLength, UErrorCode &status) const; - const ZoneStringInfo* findGenericLong(const UnicodeString &text, int32_t start, - int32_t &matchLength, UErrorCode &status) const; - const ZoneStringInfo* findGenericShort(const UnicodeString &text, int32_t start, - int32_t &matchLength, UErrorCode &status) const; - const ZoneStringInfo* findGenericLocation(const UnicodeString &text, int32_t start, - int32_t &matchLength, UErrorCode &status) const; - - // Following APIs are not used by SimpleDateFormat, but public for testing purpose - inline UnicodeString& getLongStandard(const UnicodeString &tzid, UDate date, - UnicodeString &result) const; - inline UnicodeString& getLongDaylight(const UnicodeString &tzid, UDate date, - UnicodeString &result) const; - inline UnicodeString& getLongGenericNonLocation(const UnicodeString &tzid, UDate date, - UnicodeString &result) const; - inline UnicodeString& getLongGenericPartialLocation(const UnicodeString &tzid, UDate date, - UnicodeString &result) const; - inline UnicodeString& getShortStandard(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, - UnicodeString &result) const; - inline UnicodeString& getShortDaylight(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, - UnicodeString &result) const; - inline UnicodeString& getShortGenericNonLocation(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, - UnicodeString &result) const; - inline UnicodeString& getShortGenericPartialLocation(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, - UnicodeString &result) const; - inline UnicodeString& getGenericLocation(const UnicodeString &tzid, UnicodeString &result) const; - -private: - Locale fLocale; - UHashtable *fTzidToStrings; - UHashtable *fMzidToStrings; - - TextTrieMap fZoneStringsTrie; - ZSFStringPool fStringPool; - - UResourceBundle *fZoneStringsArray; - UResourceBundle *fMetazoneItem; - UResourceBundle *fZoneItem; - - UBool fIsFullyLoaded; - - void loadZone(const UnicodeString &utzid, UErrorCode &status); - void addSingleZone(UnicodeString &utzid, UErrorCode &status); - void loadFull(UErrorCode &status); - - - /* - * Private method to get a zone string except generic partial location types. - */ - UnicodeString& getString(const UnicodeString &tzid, TimeZoneTranslationTypeIndex typeIdx, UDate date, - UBool commonlyUsedOnly, UnicodeString& result) const; - - /* - * Private method to get a generic string, with fallback logic involved, - * that is, - * - * 1. If a generic non-location string is avaiable for the zone, return it. - * 2. If a generic non-location string is associated with a metazone and - * the zone never use daylight time around the given date, use the standard - * string (if available). - * - * Note: In CLDR1.5.1, the same localization is used for generic and standard. - * In this case, we do not use the standard string and do the rest. - * - * 3. If a generic non-location string is associated with a metazone and - * the offset at the given time is different from the preferred zone for the - * current locale, then return the generic partial location string (if avaiable) - * 4. If a generic non-location string is not available, use generic location - * string. - */ - UnicodeString& getGenericString(const Calendar &cal, UBool isShort, UBool commonlyUsedOnly, - UnicodeString &result, UErrorCode &status) const; - - /* - * Private method to get a generic partial location string - */ - UnicodeString& getGenericPartialLocationString(const UnicodeString &tzid, UBool isShort, - UDate date, UBool commonlyUsedOnly, UnicodeString &result) const; - - /* - * Find a prefix matching time zone for the given zone string types. - * @param text The text contains a time zone string - * @param start The start index within the text - * @param types The bit mask representing a set of requested types - * @param matchLength Receives the match length - * @param status - * @return If any zone string matched for the requested types, returns a - * ZoneStringInfo for the longest match. If no matches are found for - * the requested types, returns a ZoneStringInfo for the longest match - * for any other types. If nothing matches at all, returns null. - */ - const ZoneStringInfo* find(const UnicodeString &text, int32_t start, int32_t types, - int32_t &matchLength, UErrorCode &status) const; - const ZoneStringInfo* subFind(const UnicodeString &text, int32_t start, int32_t types, - int32_t &matchLength, UErrorCode &status) const; - - UnicodeString& getRegion(UnicodeString ®ion) const; - - static MessageFormat* getFallbackFormat(const Locale &locale, UErrorCode &status); - static MessageFormat* getRegionFormat(const Locale &locale, UErrorCode &status); - const UChar* getZoneStringFromBundle(const UResourceBundle *zoneitem, const char *key); - static UBool isCommonlyUsed(const UResourceBundle *zoneitem); - static UnicodeString& getLocalizedCountry(const UnicodeString &countryCode, const Locale &locale, - UnicodeString &displayCountry); -}; - -inline UnicodeString& -ZoneStringFormat::getLongStandard(const UnicodeString &tzid, UDate date, - UnicodeString &result) const { - return getString(tzid, ZSIDX_LONG_STANDARD, date, FALSE /* not used */, result); -} - -inline UnicodeString& -ZoneStringFormat::getLongDaylight(const UnicodeString &tzid, UDate date, - UnicodeString &result) const { - return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, FALSE /* not used */, result); -} - -inline UnicodeString& -ZoneStringFormat::getLongGenericNonLocation(const UnicodeString &tzid, UDate date, - UnicodeString &result) const { - return getString(tzid, ZSIDX_LONG_GENERIC, date, FALSE /* not used */, result); -} - -inline UnicodeString& -ZoneStringFormat::getLongGenericPartialLocation(const UnicodeString &tzid, UDate date, - UnicodeString &result) const { - return getGenericPartialLocationString(tzid, FALSE, date, FALSE /* not used */, result); -} - -inline UnicodeString& -ZoneStringFormat::getShortStandard(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, - UnicodeString &result) const { - return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly, result); -} - -inline UnicodeString& -ZoneStringFormat::getShortDaylight(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, - UnicodeString &result) const { - return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly, result); -} - -inline UnicodeString& -ZoneStringFormat::getShortGenericNonLocation(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, - UnicodeString &result) const { - return getString(tzid, ZSIDX_SHORT_GENERIC, date, commonlyUsedOnly, result); -} - -inline UnicodeString& -ZoneStringFormat::getShortGenericPartialLocation(const UnicodeString &tzid, UDate date, UBool commonlyUsedOnly, - UnicodeString &result) const { - return getGenericPartialLocationString(tzid, TRUE, date, commonlyUsedOnly, result); -} - -inline UnicodeString& -ZoneStringFormat::getGenericLocation(const UnicodeString &tzid, UnicodeString &result) const { - return getString(tzid, ZSIDX_LOCATION, 0 /*not used*/, FALSE /*not used*/, result); -} - - -/* - * ZoneStrings is a container of localized zone strings used by ZoneStringFormat - */ -class ZoneStrings : public UMemory { -public: - ZoneStrings(UnicodeString *strings, - int32_t stringsCount, - UBool commonlyUsed, - UnicodeString **genericPartialLocationStrings, - int32_t genericRowCount, - int32_t genericColCount, - ZSFStringPool &sp, - UErrorCode &status); - virtual ~ZoneStrings(); - - UnicodeString& getString(int32_t typeIdx, UnicodeString &result) const; - inline UBool isShortFormatCommonlyUsed(void) const; - UnicodeString& getGenericPartialLocationString(const UnicodeString &mzid, UBool isShort, - UBool commonlyUsedOnly, UnicodeString &result) const; - -private: - const UChar **fStrings; - int32_t fStringsCount; - UBool fIsCommonlyUsed; - const UChar * **fGenericPartialLocationStrings; - int32_t fGenericPartialLocationRowCount; - int32_t fGenericPartialLocationColCount; -}; - -inline UBool -ZoneStrings::isShortFormatCommonlyUsed(void) const { - return fIsCommonlyUsed; -} - -/* - * ZoneStringSearchResultHandler is an implementation of - * TextTrieMapSearchHandler. This class is used by ZoneStringFormat - * for collecting search results for localized zone strings. - */ -class ZoneStringSearchResultHandler : public TextTrieMapSearchResultHandler { -public: - ZoneStringSearchResultHandler(UErrorCode &status); - virtual ~ZoneStringSearchResultHandler(); - - virtual UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status); - int32_t countMatches(void); - const ZoneStringInfo* getMatch(int32_t index, int32_t &matchLength); - void clear(void); - -private: - UVector fResults; - int32_t fMatchLen[ZSIDX_COUNT]; -}; - - -/* - * ZoneStringFormat cache implementation - */ -class ZSFCacheEntry : public UMemory { -public: - ~ZSFCacheEntry(); - - void delRef(void); - const ZoneStringFormat* getZoneStringFormat(void); - -private: - friend class ZSFCache; - - ZSFCacheEntry(const Locale &locale, ZoneStringFormat *zsf, ZSFCacheEntry *next); - - Locale fLocale; - ZoneStringFormat *fZoneStringFormat; - ZSFCacheEntry *fNext; - int32_t fRefCount; -}; - -class SafeZoneStringFormatPtr : public UMemory { -public: - ~SafeZoneStringFormatPtr(); - const ZoneStringFormat* get() const; - -private: - friend class ZSFCache; - - SafeZoneStringFormatPtr(ZSFCacheEntry *cacheEntry); - - ZSFCacheEntry *fCacheEntry; -}; - -class ZSFCache : public UMemory { -public: - ZSFCache(int32_t capacity); - ~ZSFCache(); - - SafeZoneStringFormatPtr* get(const Locale &locale, UErrorCode &status); - -private: - int32_t fCapacity; - ZSFCacheEntry *fFirst; -}; - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ - -#endif // ZSTRFMT_H diff --git a/icu4c/source/test/intltest/callimts.cpp b/icu4c/source/test/intltest/callimts.cpp index ed583ef07db..42869cdac19 100644 --- a/icu4c/source/test/intltest/callimts.cpp +++ b/icu4c/source/test/intltest/callimts.cpp @@ -1,6 +1,6 @@ /*********************************************************************** * COPYRIGHT: - * Copyright (c) 1997-2010, International Business Machines Corporation + * Copyright (c) 1997-2011, International Business Machines Corporation * and others. All Rights Reserved. ***********************************************************************/ @@ -105,7 +105,7 @@ CalendarLimitTest::TestCalendarExtremeLimit() return; } fmt->adoptCalendar(cal); - ((SimpleDateFormat*) fmt)->applyPattern("HH:mm:ss.SSS zzz, EEEE, MMMM d, yyyy G"); + ((SimpleDateFormat*) fmt)->applyPattern("HH:mm:ss.SSS Z, EEEE, MMMM d, yyyy G"); // This test used to test the algorithmic limits of the dates that diff --git a/icu4c/source/test/intltest/dtfmttst.cpp b/icu4c/source/test/intltest/dtfmttst.cpp index e3398ff59eb..1aa8ef706ae 100644 --- a/icu4c/source/test/intltest/dtfmttst.cpp +++ b/icu4c/source/test/intltest/dtfmttst.cpp @@ -166,7 +166,7 @@ void DateFormatTest::TestWallyWedel() sdf->format(today,fmtOffset, pos); // UnicodeString fmtOffset = tzS.toString(); UnicodeString *fmtDstOffset = 0; - if (fmtOffset.startsWith("GMT")) + if (fmtOffset.startsWith("GMT") && fmtOffset.length() != 3) { //fmtDstOffset = fmtOffset->substring(3); fmtDstOffset = new UnicodeString(); @@ -397,7 +397,7 @@ void DateFormatTest::TestFieldPosition() { "Anno Domini", "1997", "August", "0013", "0014", "0014", "0034", "0012", "5130", "Wednesday", "0225", "0002", "0033", "0002", "PM", "0002", "0002", "Pacific Daylight Time", "1997", "Wednesday", "1997", "2450674", "52452513", "GMT-07:00", - "Pacific Time", "Wednesday", "August", "3rd quarter", "3rd quarter", "United States (Los Angeles)" + "Pacific Time", "Wednesday", "August", "3rd quarter", "3rd quarter", "United States Time (Los Angeles)" }; const int32_t EXPECTED_LENGTH = sizeof(EXPECTED)/sizeof(EXPECTED[0]); @@ -1066,11 +1066,11 @@ DateFormatTest::TestDateFormatZone146() UDate greenwichdate = greenwichcalendar->getTime(status); // format every way UnicodeString DATA [] = { - UnicodeString("simple format: "), UnicodeString("04/04/97 23:00 GMT+00:00"), + UnicodeString("simple format: "), UnicodeString("04/04/97 23:00 GMT"), UnicodeString("MM/dd/yy HH:mm z"), - UnicodeString("full format: "), UnicodeString("Friday, April 4, 1997 11:00:00 o'clock PM GMT+00:00"), + UnicodeString("full format: "), UnicodeString("Friday, April 4, 1997 11:00:00 o'clock PM GMT"), UnicodeString("EEEE, MMMM d, yyyy h:mm:ss 'o''clock' a z"), - UnicodeString("long format: "), UnicodeString("April 4, 1997 11:00:00 PM GMT+00:00"), + UnicodeString("long format: "), UnicodeString("April 4, 1997 11:00:00 PM GMT"), UnicodeString("MMMM d, yyyy h:mm:ss a z"), UnicodeString("default format: "), UnicodeString("04-Apr-97 11:00:00 PM"), UnicodeString("dd-MMM-yy h:mm:ss a"), @@ -1821,8 +1821,9 @@ void DateFormatTest::expectFormat(const char** data, int32_t data_length, } void DateFormatTest::TestGenericTime() { - // any zone pattern should parse any zone const Locale en("en"); + // Note: We no longer parse strings in different styles. +/* const char* ZDATA[] = { "yyyy MM dd HH:mm zzz", // round trip @@ -1862,12 +1863,44 @@ void DateFormatTest::TestGenericTime() { "y/M/d H:mm v", "pf", "2004/10/31 1:30 PDT", "2004 10 31 01:30 PDT", "2004/10/31 1:30 PT", "y/M/d H:mm", "pf", "2004/10/31 1:30", "2004 10 31 01:30 PST", "2004/10/31 1:30", }; +*/ + const char* ZDATA[] = { + "yyyy MM dd HH:mm zzz", + // round trip + "y/M/d H:mm zzzz", "F", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Standard Time", + "y/M/d H:mm zzz", "F", "2004 01 01 01:00 PST", "2004/1/1 1:00 PST", + "y/M/d H:mm vvvv", "F", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Time", + "y/M/d H:mm v", "F", "2004 01 01 01:00 PST", "2004/1/1 1:00 PT", + // non-generic timezone string influences dst offset even if wrong for date/time + "y/M/d H:mm zzz", "pf", "2004/1/1 1:00 PDT", "2004 01 01 01:00 PDT", "2004/1/1 0:00 PST", + "y/M/d H:mm zzz", "pf", "2004/7/1 1:00 PST", "2004 07 01 02:00 PDT", "2004/7/1 2:00 PDT", + // generic timezone generates dst offset appropriate for local time + "y/M/d H:mm zzz", "pf", "2004/1/1 1:00 PST", "2004 01 01 01:00 PST", "2004/1/1 1:00 PST", + "y/M/d H:mm vvvv", "pf", "2004/1/1 1:00 Pacific Time", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Time", + "y/M/d H:mm zzz", "pf", "2004/7/1 1:00 PDT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 PDT", + "y/M/d H:mm vvvv", "pf", "2004/7/1 1:00 Pacific Time", "2004 07 01 01:00 PDT", "2004/7/1 1:00 Pacific Time", + // daylight savings time transition edge cases. + // time to parse does not really exist, PT interpreted as earlier time + "y/M/d H:mm zzz", "pf", "2005/4/3 2:30 PST", "2005 04 03 03:30 PDT", "2005/4/3 3:30 PDT", + "y/M/d H:mm zzz", "pf", "2005/4/3 2:30 PDT", "2005 04 03 01:30 PST", "2005/4/3 1:30 PST", + "y/M/d H:mm v", "pf", "2005/4/3 2:30 PT", "2005 04 03 03:30 PDT", "2005/4/3 3:30 PT", + "y/M/d H:mm", "pf", "2005/4/3 2:30", "2005 04 03 03:30 PDT", "2005/4/3 3:30", + // time to parse is ambiguous, PT interpreted as later time + "y/M/d H:mm v", "pf", "2005/10/30 1:30 PT", "2005 10 30 01:30 PST", "2005/10/30 1:30 PT", + "y/M/d H:mm", "pf", "2005/10/30 1:30 PT", "2005 10 30 01:30 PST", "2005/10/30 1:30", + + "y/M/d H:mm zzz", "pf", "2004/10/31 1:30 PST", "2004 10 31 01:30 PST", "2004/10/31 1:30 PST", + "y/M/d H:mm zzz", "pf", "2004/10/31 1:30 PDT", "2004 10 31 01:30 PDT", "2004/10/31 1:30 PDT", + "y/M/d H:mm v", "pf", "2004/10/31 1:30 PT", "2004 10 31 01:30 PST", "2004/10/31 1:30 PT", + "y/M/d H:mm", "pf", "2004/10/31 1:30", "2004 10 31 01:30 PST", "2004/10/31 1:30", + }; + const int32_t ZDATA_length = sizeof(ZDATA)/ sizeof(ZDATA[0]); expect(ZDATA, ZDATA_length, en); UErrorCode status = U_ZERO_ERROR; - logln("cross format/parse tests"); + logln("cross format/parse tests"); // Note: We no longer support cross format/parse UnicodeString basepat("yy/MM/dd H:mm "); SimpleDateFormat formats[] = { SimpleDateFormat(basepat + "vvv", en, status), @@ -1919,29 +1952,52 @@ void DateFormatTest::TestGenericTime() { void DateFormatTest::TestGenericTimeZoneOrder() { // generic times should parse the same no matter what the placement of the time zone string - // should work for standard and daylight times + // Note: We no longer support cross style format/parse + + //const char* XDATA[] = { + // "yyyy MM dd HH:mm zzz", + // // standard time, explicit daylight/standard + // "y/M/d H:mm zzz", "pf", "2004/1/1 1:00 PT", "2004 01 01 01:00 PST", "2004/1/1 1:00 PST", + // "y/M/d zzz H:mm", "pf", "2004/1/1 PT 1:00", "2004 01 01 01:00 PST", "2004/1/1 PST 1:00", + // "zzz y/M/d H:mm", "pf", "PT 2004/1/1 1:00", "2004 01 01 01:00 PST", "PST 2004/1/1 1:00", + + // // standard time, generic + // "y/M/d H:mm vvvv", "pf", "2004/1/1 1:00 PT", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Time", + // "y/M/d vvvv H:mm", "pf", "2004/1/1 PT 1:00", "2004 01 01 01:00 PST", "2004/1/1 Pacific Time 1:00", + // "vvvv y/M/d H:mm", "pf", "PT 2004/1/1 1:00", "2004 01 01 01:00 PST", "Pacific Time 2004/1/1 1:00", + + // // dahylight time, explicit daylight/standard + // "y/M/d H:mm zzz", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 PDT", + // "y/M/d zzz H:mm", "pf", "2004/7/1 PT 1:00", "2004 07 01 01:00 PDT", "2004/7/1 PDT 1:00", + // "zzz y/M/d H:mm", "pf", "PT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "PDT 2004/7/1 1:00", + + // // daylight time, generic + // "y/M/d H:mm vvvv", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 Pacific Time", + // "y/M/d vvvv H:mm", "pf", "2004/7/1 PT 1:00", "2004 07 01 01:00 PDT", "2004/7/1 Pacific Time 1:00", + // "vvvv y/M/d H:mm", "pf", "PT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "Pacific Time 2004/7/1 1:00", + //}; const char* XDATA[] = { "yyyy MM dd HH:mm zzz", // standard time, explicit daylight/standard - "y/M/d H:mm zzz", "pf", "2004/1/1 1:00 PT", "2004 01 01 01:00 PST", "2004/1/1 1:00 PST", - "y/M/d zzz H:mm", "pf", "2004/1/1 PT 1:00", "2004 01 01 01:00 PST", "2004/1/1 PST 1:00", - "zzz y/M/d H:mm", "pf", "PT 2004/1/1 1:00", "2004 01 01 01:00 PST", "PST 2004/1/1 1:00", + "y/M/d H:mm zzz", "pf", "2004/1/1 1:00 PST", "2004 01 01 01:00 PST", "2004/1/1 1:00 PST", + "y/M/d zzz H:mm", "pf", "2004/1/1 PST 1:00", "2004 01 01 01:00 PST", "2004/1/1 PST 1:00", + "zzz y/M/d H:mm", "pf", "PST 2004/1/1 1:00", "2004 01 01 01:00 PST", "PST 2004/1/1 1:00", // standard time, generic - "y/M/d H:mm vvvv", "pf", "2004/1/1 1:00 PT", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Time", - "y/M/d vvvv H:mm", "pf", "2004/1/1 PT 1:00", "2004 01 01 01:00 PST", "2004/1/1 Pacific Time 1:00", - "vvvv y/M/d H:mm", "pf", "PT 2004/1/1 1:00", "2004 01 01 01:00 PST", "Pacific Time 2004/1/1 1:00", + "y/M/d H:mm vvvv", "pf", "2004/1/1 1:00 Pacific Time", "2004 01 01 01:00 PST", "2004/1/1 1:00 Pacific Time", + "y/M/d vvvv H:mm", "pf", "2004/1/1 Pacific Time 1:00", "2004 01 01 01:00 PST", "2004/1/1 Pacific Time 1:00", + "vvvv y/M/d H:mm", "pf", "Pacific Time 2004/1/1 1:00", "2004 01 01 01:00 PST", "Pacific Time 2004/1/1 1:00", // dahylight time, explicit daylight/standard - "y/M/d H:mm zzz", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 PDT", - "y/M/d zzz H:mm", "pf", "2004/7/1 PT 1:00", "2004 07 01 01:00 PDT", "2004/7/1 PDT 1:00", - "zzz y/M/d H:mm", "pf", "PT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "PDT 2004/7/1 1:00", + "y/M/d H:mm zzz", "pf", "2004/7/1 1:00 PDT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 PDT", + "y/M/d zzz H:mm", "pf", "2004/7/1 PDT 1:00", "2004 07 01 01:00 PDT", "2004/7/1 PDT 1:00", + "zzz y/M/d H:mm", "pf", "PDT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "PDT 2004/7/1 1:00", // daylight time, generic - "y/M/d H:mm vvvv", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 Pacific Time", - "y/M/d vvvv H:mm", "pf", "2004/7/1 PT 1:00", "2004 07 01 01:00 PDT", "2004/7/1 Pacific Time 1:00", - "vvvv y/M/d H:mm", "pf", "PT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "Pacific Time 2004/7/1 1:00", + "y/M/d H:mm v", "pf", "2004/7/1 1:00 PT", "2004 07 01 01:00 PDT", "2004/7/1 1:00 PT", + "y/M/d v H:mm", "pf", "2004/7/1 PT 1:00", "2004 07 01 01:00 PDT", "2004/7/1 PT 1:00", + "v y/M/d H:mm", "pf", "PT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "PT 2004/7/1 1:00", }; const int32_t XDATA_length = sizeof(XDATA)/sizeof(XDATA[0]); Locale en("en"); @@ -2187,7 +2243,7 @@ void DateFormatTest::TestTimeZoneDisplayName() { "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "zzzz", "Pacific Daylight Time", "America/Los_Angeles" }, { "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "v", "PT", "America/Los_Angeles" }, { "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "vvvv", "Pacific Time", "America/Los_Angeles" }, - { "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "VVVV", "United States (Los Angeles)", "America/Los_Angeles" }, + { "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "VVVV", "United States Time (Los Angeles)", "America/Los_Angeles" }, { "en_GB", "America/Los_Angeles", "2004-01-15T12:00:00Z", "z", "PST", "America/Los_Angeles" }, { "en", "America/Phoenix", "2004-01-15T00:00:00Z", "Z", "-0700", "-7:00" }, { "en", "America/Phoenix", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-07:00", "-7:00" }, @@ -2201,7 +2257,7 @@ void DateFormatTest::TestTimeZoneDisplayName() { "en", "America/Phoenix", "2004-07-15T00:00:00Z", "zzzz", "Mountain Standard Time", "America/Phoenix" }, { "en", "America/Phoenix", "2004-07-15T00:00:00Z", "v", "MST", "America/Phoenix" }, { "en", "America/Phoenix", "2004-07-15T00:00:00Z", "vvvv", "Mountain Standard Time", "America/Phoenix" }, - { "en", "America/Phoenix", "2004-07-15T00:00:00Z", "VVVV", "United States (Phoenix)", "America/Phoenix" }, + { "en", "America/Phoenix", "2004-07-15T00:00:00Z", "VVVV", "United States Time (Phoenix)", "America/Phoenix" }, { "en", "America/Argentina/Buenos_Aires", "2004-01-15T00:00:00Z", "Z", "-0300", "-3:00" }, { "en", "America/Argentina/Buenos_Aires", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-03:00", "-3:00" }, @@ -2213,9 +2269,9 @@ void DateFormatTest::TestTimeZoneDisplayName() { "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "z", "GMT-03:00", "-3:00" }, { "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "V", "ART", "-3:00" }, { "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "zzzz", "Argentina Time", "-3:00" }, - { "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "v", "Argentina (Buenos Aires)", "America/Buenos_Aires" }, + { "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "v", "Argentina Time (Buenos Aires)", "America/Buenos_Aires" }, { "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "vvvv", "Argentina Time", "America/Buenos_Aires" }, - { "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "VVVV", "Argentina (Buenos Aires)", "America/Buenos_Aires" }, + { "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "VVVV", "Argentina Time (Buenos Aires)", "America/Buenos_Aires" }, { "en", "America/Buenos_Aires", "2004-01-15T00:00:00Z", "Z", "-0300", "-3:00" }, { "en", "America/Buenos_Aires", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-03:00", "-3:00" }, @@ -2227,9 +2283,9 @@ void DateFormatTest::TestTimeZoneDisplayName() { "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "z", "GMT-03:00", "-3:00" }, { "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "V", "ART", "-3:00" }, { "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "zzzz", "Argentina Time", "-3:00" }, - { "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "v", "Argentina (Buenos Aires)", "America/Buenos_Aires" }, + { "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "v", "Argentina Time (Buenos Aires)", "America/Buenos_Aires" }, { "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "vvvv", "Argentina Time", "America/Buenos_Aires" }, - { "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "VVVV", "Argentina (Buenos Aires)", "America/Buenos_Aires" }, + { "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "VVVV", "Argentina Time (Buenos Aires)", "America/Buenos_Aires" }, { "en", "America/Havana", "2004-01-15T00:00:00Z", "Z", "-0500", "-5:00" }, { "en", "America/Havana", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-05:00", "-5:00" }, @@ -2255,9 +2311,9 @@ void DateFormatTest::TestTimeZoneDisplayName() { "en", "Australia/ACT", "2004-07-15T00:00:00Z", "z", "GMT+10:00", "+10:00" }, { "en", "Australia/ACT", "2004-07-15T00:00:00Z", "V", "AEST", "+10:00" }, { "en", "Australia/ACT", "2004-07-15T00:00:00Z", "zzzz", "Australian Eastern Standard Time", "+10:00" }, - { "en", "Australia/ACT", "2004-07-15T00:00:00Z", "v", "Australia (Sydney)", "Australia/Sydney" }, + { "en", "Australia/ACT", "2004-07-15T00:00:00Z", "v", "Australia Time (Sydney)", "Australia/Sydney" }, { "en", "Australia/ACT", "2004-07-15T00:00:00Z", "vvvv", "Eastern Australia Time", "Australia/Sydney" }, - { "en", "Australia/ACT", "2004-07-15T00:00:00Z", "VVVV", "Australia (Sydney)", "Australia/Sydney" }, + { "en", "Australia/ACT", "2004-07-15T00:00:00Z", "VVVV", "Australia Time (Sydney)", "Australia/Sydney" }, { "en", "Australia/Sydney", "2004-01-15T00:00:00Z", "Z", "+1100", "+11:00" }, { "en", "Australia/Sydney", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+11:00", "+11:00" }, @@ -2269,12 +2325,12 @@ void DateFormatTest::TestTimeZoneDisplayName() { "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "z", "GMT+10:00", "+10:00" }, { "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "V", "AEST", "+10:00" }, { "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "zzzz", "Australian Eastern Standard Time", "+10:00" }, - { "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "v", "Australia (Sydney)", "Australia/Sydney" }, + { "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "v", "Australia Time (Sydney)", "Australia/Sydney" }, { "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "Eastern Australia Time", "Australia/Sydney" }, - { "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "VVVV", "Australia (Sydney)", "Australia/Sydney" }, + { "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "VVVV", "Australia Time (Sydney)", "Australia/Sydney" }, { "en", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" }, - { "en", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+00:00", "+0:00" }, + { "en", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" }, { "en", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" }, { "en", "Europe/London", "2004-01-15T00:00:00Z", "V", "GMT", "+0:00" }, { "en", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "Greenwich Mean Time", "+0:00" }, @@ -2385,9 +2441,9 @@ void DateFormatTest::TestTimeZoneDisplayName() { "de", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "Australien (Sydney)", "Australia/Sydney" }, { "de", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" }, - { "de", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+00:00", "+0:00" }, - { "de", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT+00:00", "+0:00" }, - { "de", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT+00:00", "+0:00" }, + { "de", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" }, + { "de", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" }, + { "de", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT", "+0:00" }, { "de", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" }, { "de", "Europe/London", "2004-07-15T00:00:00Z", "ZZZZ", "GMT+01:00", "+1:00" }, { "de", "Europe/London", "2004-07-15T00:00:00Z", "z", "GMT+01:00", "+1:00" }, @@ -2489,8 +2545,8 @@ void DateFormatTest::TestTimeZoneDisplayName() { "zh", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "\\u6fb3\\u5927\\u5229\\u4e9a\\u4e1c\\u90e8\\u65f6\\u95f4", "Australia/Sydney" }, { "zh", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" }, - { "zh", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "\\u683c\\u6797\\u5c3c\\u6cbb\\u6807\\u51c6\\u65f6\\u95f4+0000", "+0:00" }, - { "zh", "Europe/London", "2004-01-15T00:00:00Z", "z", "\\u683c\\u6797\\u5c3c\\u6cbb\\u6807\\u51c6\\u65f6\\u95f4+0000", "+0:00" }, + { "zh", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "\\u683c\\u6797\\u5c3c\\u6cbb\\u6807\\u51c6\\u65f6\\u95f4", "+0:00" }, + { "zh", "Europe/London", "2004-01-15T00:00:00Z", "z", "\\u683c\\u6797\\u5c3c\\u6cbb\\u6807\\u51c6\\u65f6\\u95f4", "+0:00" }, { "zh", "Europe/London", "2004-01-15T00:00:00Z", "V", "GMT", "+0:00" }, { "zh", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "\\u683C\\u6797\\u5C3C\\u6CBB\\u6807\\u51C6\\u65F6\\u95F4", "+0:00" }, { "zh", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" }, @@ -2594,9 +2650,9 @@ void DateFormatTest::TestTimeZoneDisplayName() { "hi", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "\\u0911\\u0938\\u094d\\u091f\\u094d\\u0930\\u0947\\u0932\\u093f\\u092f\\u093e (\\u0938\\u093f\\u0921\\u0928\\u0940)", "Australia/Sydney" }, { "hi", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" }, - { "hi", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+\\u0966\\u0966:\\u0966\\u0966", "+0:00" }, - { "hi", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT+\\u0966\\u0966:\\u0966\\u0966", "+0:00" }, - { "hi", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT+\\u0966\\u0966:\\u0966\\u0966", "+0:00" }, + { "hi", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" }, + { "hi", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" }, + { "hi", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT", "+0:00" }, { "hi", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" }, { "hi", "Europe/London", "2004-07-15T00:00:00Z", "ZZZZ", "GMT+\\u0966\\u0967:\\u0966\\u0966", "+1:00" }, { "hi", "Europe/London", "2004-07-15T00:00:00Z", "z", "GMT+\\u0966\\u0967:\\u0966\\u0966", "+1:00" }, @@ -2700,8 +2756,8 @@ void DateFormatTest::TestTimeZoneDisplayName() { "bg", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "\\u0410\\u0432\\u0441\\u0442\\u0440\\u0430\\u043b\\u0438\\u044f (\\u0421\\u0438\\u0434\\u043D\\u0438)", "Australia/Sydney" }, { "bg", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" }, - { "bg", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "\\u0413\\u0440\\u0438\\u0438\\u043D\\u0443\\u0438\\u0447+0000", "+0:00" }, - { "bg", "Europe/London", "2004-01-15T00:00:00Z", "z", "\\u0413\\u0440\\u0438\\u0438\\u043D\\u0443\\u0438\\u0447+0000", "+0:00" }, + { "bg", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "\\u0413\\u0440\\u0438\\u0438\\u043D\\u0443\\u0438\\u0447", "+0:00" }, + { "bg", "Europe/London", "2004-01-15T00:00:00Z", "z", "\\u0413\\u0440\\u0438\\u0438\\u043D\\u0443\\u0438\\u0447", "+0:00" }, { "bg", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "\\u0427\\u0430\\u0441\\u043E\\u0432\\u0430 \\u0437\\u043E\\u043D\\u0430 \\u0413\\u0440\\u0438\\u043D\\u0443\\u0438\\u0447", "+0:00" }, { "bg", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" }, { "bg", "Europe/London", "2004-07-15T00:00:00Z", "ZZZZ", "\\u0413\\u0440\\u0438\\u0438\\u043D\\u0443\\u0438\\u0447+0100", "+1:00" }, @@ -2807,8 +2863,8 @@ void DateFormatTest::TestTimeZoneDisplayName() { "ja", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "\\u30aa\\u30fc\\u30b9\\u30c8\\u30e9\\u30ea\\u30a2 (\\u30b7\\u30c9\\u30cb\\u30fc)", "Australia/Sydney" }, { "ja", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" }, - { "ja", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+00:00", "+0:00" }, - { "ja", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT+00:00", "+0:00" }, + { "ja", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" }, + { "ja", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" }, { "ja", "Europe/London", "2004-01-15T00:00:00Z", "V", "GMT", "+0:00" }, { "ja", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "\\u30B0\\u30EA\\u30CB\\u30C3\\u30B8\\u6A19\\u6E96\\u6642", "+0:00" }, { "ja", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" }, @@ -2912,9 +2968,9 @@ void DateFormatTest::TestTimeZoneDisplayName() { "si", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "AU (Sydney)", "Australia/Sydney" }, { "si", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" }, - { "si", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+00:00", "+0:00" }, - { "si", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT+00:00", "+0:00" }, - { "si", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT+00:00", "+0:00" }, + { "si", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" }, + { "si", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" }, + { "si", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT", "+0:00" }, { "si", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" }, { "si", "Europe/London", "2004-07-15T00:00:00Z", "ZZZZ", "GMT+01:00", "+1:00" }, { "si", "Europe/London", "2004-07-15T00:00:00Z", "z", "GMT+01:00", "+1:00" }, diff --git a/icu4c/source/test/intltest/dtptngts.cpp b/icu4c/source/test/intltest/dtptngts.cpp index 28a89fab5eb..d53d2b6bcf3 100644 --- a/icu4c/source/test/intltest/dtptngts.cpp +++ b/icu4c/source/test/intltest/dtptngts.cpp @@ -272,7 +272,7 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/) UnicodeString("Thu, Oct 14, 1999 6:58:59 AM"), UnicodeString("6:58 AM"), UnicodeString("6:58 AM"), - UnicodeString("6:58 AM GMT+00:00"), + UnicodeString("6:58 AM GMT"), UnicodeString(""), }; diff --git a/icu4c/source/test/intltest/tzfmttst.cpp b/icu4c/source/test/intltest/tzfmttst.cpp index 145af36e4a9..84a64deb74f 100644 --- a/icu4c/source/test/intltest/tzfmttst.cpp +++ b/icu4c/source/test/intltest/tzfmttst.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2007-2010, International Business Machines Corporation and * +* Copyright (C) 2007-2011, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -102,16 +102,16 @@ TimeZoneFormatTest::TestTimeZoneRoundTrip(void) { } StringEnumeration *tzids = TimeZone::createEnumeration(); - if (U_FAILURE(status)) { - errln("tzids->count failed"); - return; - } - int32_t inRaw, inDst; int32_t outRaw, outDst; // Run the roundtrip test for (int32_t locidx = 0; locidx < nLocales; locidx++) { + UnicodeString localGMTString; + SimpleDateFormat gmtFmt(UnicodeString("ZZZZ"), LOCALES[locidx], status); + gmtFmt.setTimeZone(*TimeZone::getGMT()); + gmtFmt.format(0.0, localGMTString); + for (int32_t patidx = 0; patidx < NUM_PATTERNS; patidx++) { SimpleDateFormat *sdf = new SimpleDateFormat((UnicodeString)PATTERNS[patidx], LOCALES[locidx], status); @@ -208,7 +208,7 @@ TimeZoneFormatTest::TestTimeZoneRoundTrip(void) { numDigits++; } } - if (numDigits >= 3) { + if (tzstr == localGMTString || numDigits >= 3) { // Localized GMT or RFC: total offset (raw + dst) must be preserved. int32_t inOffset = inRaw + inDst; int32_t outOffset = outRaw + outDst; @@ -274,9 +274,9 @@ public: UBool expectedRoundTrip[4]; int32_t testLen = 0; - StringEnumeration *tzids = TimeZone::createEnumeration(); + StringEnumeration *tzids = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); if (U_FAILURE(status)) { - log.errln("tzids->count failed"); + log.errln("TimeZone::createTimeZoneIDEnumeration failed"); return; } @@ -330,17 +330,6 @@ public: timer = Calendar::getNow(); while ((tzid = tzids->snext(status))) { - UnicodeString canonical; - TimeZone::getCanonicalID(*tzid, canonical, status); - if (U_FAILURE(status)) { - // Unknown ID - we should not get here - status = U_ZERO_ERROR; - continue; - } - if (*tzid != canonical) { - // Skip aliases - continue; - } BasicTimeZone *tz = (BasicTimeZone*) TimeZone::createTimeZone(*tzid); sdf->setTimeZone(*tz); diff --git a/icu4c/source/test/intltest/tztest.cpp b/icu4c/source/test/intltest/tztest.cpp index f293b176dc1..250e1b0ac2b 100644 --- a/icu4c/source/test/intltest/tztest.cpp +++ b/icu4c/source/test/intltest/tztest.cpp @@ -1443,8 +1443,8 @@ TimeZoneTest::TestDisplayName() {FALSE, TimeZone::SHORT_COMMONLY_USED, "PST"}, {TRUE, TimeZone::SHORT_COMMONLY_USED, "PDT"}, - {FALSE, TimeZone::GENERIC_LOCATION, "United States (Los Angeles)"}, - {TRUE, TimeZone::GENERIC_LOCATION, "United States (Los Angeles)"}, + {FALSE, TimeZone::GENERIC_LOCATION, "United States Time (Los Angeles)"}, + {TRUE, TimeZone::GENERIC_LOCATION, "United States Time (Los Angeles)"}, {FALSE, TimeZone::LONG, ""} };