From 1641940f008239f4e2d404e82b0ec29936a437a3 Mon Sep 17 00:00:00 2001 From: Yoshito Umaoka Date: Tue, 21 Feb 2012 11:06:50 +0000 Subject: [PATCH] ICU-8513 Merging TimeZoneNames/TimeZoneFormat APIs (as 49 technology preview) into trunk. X-SVN-Rev: 31469 --- icu4c/source/allinone/allinone.sln | 6 +- icu4c/source/i18n/dtfmtsym.cpp | 97 +- icu4c/source/i18n/i18n.vcxproj | 10 +- icu4c/source/i18n/i18n.vcxproj.filters | 12 +- icu4c/source/i18n/smpdtfmt.cpp | 662 +------ icu4c/source/i18n/timezone.cpp | 181 +- icu4c/source/i18n/tzfmt.cpp | 2316 +++++++++++++++++++---- icu4c/source/i18n/tzfmt.h | 64 - icu4c/source/i18n/tzgnames.cpp | 372 +++- icu4c/source/i18n/tzgnames.h | 76 +- icu4c/source/i18n/tznames.cpp | 210 +- icu4c/source/i18n/tznames.h | 71 - icu4c/source/i18n/tznames_impl.cpp | 160 +- icu4c/source/i18n/tznames_impl.h | 9 +- icu4c/source/i18n/ucln_in.h | 4 +- icu4c/source/i18n/unicode/dtfmtsym.h | 24 - icu4c/source/i18n/unicode/smpdtfmt.h | 78 +- icu4c/source/i18n/unicode/tzfmt.h | 829 ++++++++ icu4c/source/i18n/unicode/tznames.h | 378 ++++ icu4c/source/i18n/zonemeta.cpp | 53 +- icu4c/source/i18n/zonemeta.h | 10 +- icu4c/source/test/intltest/dtfmttst.cpp | 5 +- icu4c/source/test/intltest/tzfmttst.cpp | 23 +- 23 files changed, 4034 insertions(+), 1616 deletions(-) delete mode 100644 icu4c/source/i18n/tzfmt.h delete mode 100644 icu4c/source/i18n/tznames.h create mode 100644 icu4c/source/i18n/unicode/tzfmt.h create mode 100644 icu4c/source/i18n/unicode/tznames.h diff --git a/icu4c/source/allinone/allinone.sln b/icu4c/source/allinone/allinone.sln index 0571f4edc44..5a00c57a662 100644 --- a/icu4c/source/allinone/allinone.sln +++ b/icu4c/source/allinone/allinone.sln @@ -174,12 +174,10 @@ Global {77C78066-746F-4EA6-B3FE-B8C8A4A97891}.Release|x64.Build.0 = Release|x64 {0178B127-6269-407D-B112-93877BB62776}.Debug|Win32.ActiveCfg = Debug|Win32 {0178B127-6269-407D-B112-93877BB62776}.Debug|Win32.Build.0 = Debug|Win32 - {0178B127-6269-407D-B112-93877BB62776}.Debug|x64.ActiveCfg = Debug|x64 - {0178B127-6269-407D-B112-93877BB62776}.Debug|x64.Build.0 = Debug|x64 + {0178B127-6269-407D-B112-93877BB62776}.Debug|x64.ActiveCfg = Debug|Win32 {0178B127-6269-407D-B112-93877BB62776}.Release|Win32.ActiveCfg = Release|Win32 {0178B127-6269-407D-B112-93877BB62776}.Release|Win32.Build.0 = Release|Win32 - {0178B127-6269-407D-B112-93877BB62776}.Release|x64.ActiveCfg = Release|x64 - {0178B127-6269-407D-B112-93877BB62776}.Release|x64.Build.0 = Release|x64 + {0178B127-6269-407D-B112-93877BB62776}.Release|x64.ActiveCfg = Release|Win32 {73632960-B3A6-464D-83A3-4B43365F19B8}.Debug|Win32.ActiveCfg = Debug|Win32 {73632960-B3A6-464D-83A3-4B43365F19B8}.Debug|Win32.Build.0 = Debug|Win32 {73632960-B3A6-464D-83A3-4B43365F19B8}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/icu4c/source/i18n/dtfmtsym.cpp b/icu4c/source/i18n/dtfmtsym.cpp index b6807dd3168..0f5ba326fc8 100644 --- a/icu4c/source/i18n/dtfmtsym.cpp +++ b/icu4c/source/i18n/dtfmtsym.cpp @@ -26,6 +26,7 @@ #include "unicode/dtfmtsym.h" #include "unicode/smpdtfmt.h" #include "unicode/msgfmt.h" +#include "unicode/tznames.h" #include "cpputils.h" #include "ucln_in.h" #include "umutex.h" @@ -36,7 +37,6 @@ #include "hash.h" #include "uresimp.h" #include "ureslocs.h" -#include "tznames.h" // ***************************************************************************** // class DateFormatSymbols @@ -120,31 +120,6 @@ 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" */ -//}; - -static const UChar gLastResortGmtZero[] = - {0x0047, 0x004D, 0x0054, 0x0000}; /* GMT */ - -static const UChar gLastResortGmtFormat[] = - {0x0047, 0x004D, 0x0054, 0x007B, 0x0030, 0x007D, 0x0000}; /* GMT{0} */ - -static const UChar gLastResortGmtHourFormats[4][10] = -{ - {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}, /* -HH:mm:ss */ - {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000, 0x0000, 0x0000, 0x0000}, /* -HH:mm */ - {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}, /* +HH:mm:ss */ - {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000, 0x0000, 0x0000, 0x0000} /* +HH:mm */ -}; - /* Sizes for the last resort string arrays */ typedef enum LastResortSize { kMonthNum = 13, @@ -197,9 +172,6 @@ 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"; static const char gLocalPatternCharsTag[]="localPatternChars"; @@ -352,9 +324,6 @@ DateFormatSymbols::copyData(const DateFormatSymbols& other) { fShortYearNames = NULL; fShortYearNamesCount = 0; } - fGmtZero = other.fGmtZero; - fGmtFormat = other.fGmtFormat; - assignArray(fGmtHourFormats, fGmtHourFormatsCount, other.fGmtHourFormats, other.fGmtHourFormatsCount); if (other.fZoneStrings != NULL) { fZoneStringsColCount = other.fZoneStringsColCount; @@ -416,7 +385,6 @@ void DateFormatSymbols::dispose() if (fStandaloneShortQuarters) delete[] fStandaloneShortQuarters; if (fLeapMonthPatterns) delete[] fLeapMonthPatterns; if (fShortYearNames) delete[] fShortYearNames; - if (fGmtHourFormats) delete[] fGmtHourFormats; disposeZoneStrings(); } @@ -485,9 +453,6 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const fStandaloneShortQuartersCount == other.fStandaloneShortQuartersCount && fLeapMonthPatternsCount == other.fLeapMonthPatternsCount && fShortYearNamesCount == other.fShortYearNamesCount && - fGmtHourFormatsCount == other.fGmtHourFormatsCount && - fGmtZero == other.fGmtZero && - fGmtFormat == other.fGmtFormat && (uprv_memcmp(fCapitalization, other.fCapitalization, sizeof(fCapitalization))==0)) { // Now compare the arrays themselves @@ -512,8 +477,7 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const arrayCompare(fStandaloneQuarters, other.fStandaloneQuarters, fStandaloneQuartersCount) && arrayCompare(fStandaloneShortQuarters, other.fStandaloneShortQuarters, fStandaloneShortQuartersCount) && arrayCompare(fLeapMonthPatterns, other.fLeapMonthPatterns, fLeapMonthPatternsCount) && - arrayCompare(fShortYearNames, other.fShortYearNames, fShortYearNamesCount) && - arrayCompare(fGmtHourFormats, other.fGmtHourFormats, fGmtHourFormatsCount)) + arrayCompare(fShortYearNames, other.fShortYearNames, fShortYearNamesCount)) { // Compare the contents of fZoneStrings if (fZoneStrings == NULL && other.fZoneStrings == NULL) { @@ -1333,8 +1297,6 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError fLeapMonthPatternsCount = 0; fShortYearNames = NULL; fShortYearNamesCount = 0; - fGmtHourFormats = NULL; - fGmtHourFormatsCount = 0; fZoneStringsRowCount = 0; fZoneStringsColCount = 0; fZoneStrings = NULL; @@ -1356,12 +1318,6 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError */ CalendarData calData(locale, type, status); - /** - * Use the localeBundle for getting zone GMT formatting patterns - */ - UResourceBundle *zoneBundle = ures_open(U_ICUDATA_ZONE, locale.getName(), &status); - UResourceBundle *zoneStringsArray = ures_getByKeyWithFallback(zoneBundle, gZoneStringsTag, NULL, &status); - // load the first data item UResourceBundle *erasMain = calData.getByKey(gErasTag, status); UResourceBundle *eras = ures_getByKeyWithFallback(erasMain, gNamesAbbrTag, NULL, &status); @@ -1488,9 +1444,6 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError initField(&fShortQuarters, fShortQuartersCount, (const UChar *)gLastResortQuarters, kQuarterNum, kQuarterLen, status); 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); } goto cleanup; @@ -1555,50 +1508,6 @@ 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) { - fGmtFormat.setTo(TRUE, resStr, len); - } - - resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gHourFormatTag, &len, &status); - if (len > 0) { - UChar *sep = u_strchr(resStr, (UChar)0x003B /* ';' */); - if (sep != NULL) { - fGmtHourFormats = newUnicodeStringArray(GMT_HOUR_COUNT); - if (fGmtHourFormats == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - } else { - fGmtHourFormatsCount = GMT_HOUR_COUNT; - fGmtHourFormats[GMT_NEGATIVE_HM].setTo(TRUE, sep + 1, -1); - fGmtHourFormats[GMT_POSITIVE_HM].setTo(FALSE, resStr, (int32_t)(sep - resStr)); - - // CLDR 1.5 does not have GMT offset pattern including second field. - // For now, append "ss" to the end. - if (fGmtHourFormats[GMT_NEGATIVE_HM].indexOf((UChar)0x003A /* ':' */) != -1) { - fGmtHourFormats[GMT_NEGATIVE_HMS] = fGmtHourFormats[GMT_NEGATIVE_HM] + UNICODE_STRING_SIMPLE(":ss"); - } else if (fGmtHourFormats[GMT_NEGATIVE_HM].indexOf((UChar)0x002E /* '.' */) != -1) { - fGmtHourFormats[GMT_NEGATIVE_HMS] = fGmtHourFormats[GMT_NEGATIVE_HM] + UNICODE_STRING_SIMPLE(".ss"); - } else { - fGmtHourFormats[GMT_NEGATIVE_HMS] = fGmtHourFormats[GMT_NEGATIVE_HM] + UNICODE_STRING_SIMPLE("ss"); - } - if (fGmtHourFormats[GMT_POSITIVE_HM].indexOf((UChar)0x003A /* ':' */) != -1) { - fGmtHourFormats[GMT_POSITIVE_HMS] = fGmtHourFormats[GMT_POSITIVE_HM] + UNICODE_STRING_SIMPLE(":ss"); - } else if (fGmtHourFormats[GMT_POSITIVE_HM].indexOf((UChar)0x002E /* '.' */) != -1) { - fGmtHourFormats[GMT_POSITIVE_HMS] = fGmtHourFormats[GMT_POSITIVE_HM] + UNICODE_STRING_SIMPLE(".ss"); - } else { - fGmtHourFormats[GMT_POSITIVE_HMS] = fGmtHourFormats[GMT_POSITIVE_HM] + UNICODE_STRING_SIMPLE("ss"); - } - } - } - } - // ICU 3.8 or later version no longer uses localized date-time pattern characters by default (ticket#5597) /* // fastCopyFrom()/setTo() - see assignArray comments @@ -1737,8 +1646,6 @@ cleanup: ures_close(eras); ures_close(eraNames); ures_close(narrowEras); - ures_close(zoneStringsArray); - ures_close(zoneBundle); } Locale diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj index cdd2107561a..4c5fc9702b4 100644 --- a/icu4c/source/i18n/i18n.vcxproj +++ b/icu4c/source/i18n/i18n.vcxproj @@ -579,9 +579,7 @@ ..\..\include\unicode\%(Filename)%(Extension);%(Outputs) - - @@ -617,6 +615,14 @@ ..\..\include\unicode\%(Filename)%(Extension);%(Outputs) + + copy "%(FullPath)" ..\..\include\unicode + ..\..\include\unicode\%(Filename)%(Extension);%(Outputs) + + + ..\..\include\unicode\%(Filename)%(Extension);%(Outputs) + copy "%(FullPath)" ..\..\include\unicode + diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters index 227a7a914c9..36ec423493a 100644 --- a/icu4c/source/i18n/i18n.vcxproj.filters +++ b/icu4c/source/i18n/i18n.vcxproj.filters @@ -768,15 +768,9 @@ spoof - - formatting - formatting - - formatting - formatting @@ -1001,5 +995,11 @@ collation + + formatting + + + formatting + \ No newline at end of file diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index 95d0c54e37a..4b3abb3d90e 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -45,6 +45,7 @@ #include "unicode/basictz.h" #include "unicode/simpletz.h" #include "unicode/rbtz.h" +#include "unicode/tzfmt.h" #include "unicode/utf16.h" #include "unicode/vtzone.h" #include "olsontz.h" @@ -56,7 +57,6 @@ #include "uassert.h" #include "cmemory.h" #include "umutex.h" -#include "tzfmt.h" #include #include "smpdtfst.h" @@ -212,15 +212,6 @@ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleDateFormat) SimpleDateFormat::~SimpleDateFormat() { delete fSymbols; - if (fGMTFormatters) { - for (int32_t i = 0; i < kNumGMTFormatters; i++) { - if (fGMTFormatters[i]) { - delete fGMTFormatters[i]; - } - } - uprv_free(fGMTFormatters); - - } if (fNumberFormatters) { uprv_free(fNumberFormatters); } @@ -242,7 +233,6 @@ SimpleDateFormat::SimpleDateFormat(UErrorCode& status) : fLocale(Locale::getDefault()), fSymbols(NULL), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -259,7 +249,6 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fLocale(Locale::getDefault()), fSymbols(NULL), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -280,7 +269,6 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fLocale(Locale::getDefault()), fSymbols(NULL), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -303,7 +291,6 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, : fPattern(pattern), fLocale(locale), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -326,7 +313,6 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, : fPattern(pattern), fLocale(locale), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -352,7 +338,6 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fLocale(Locale::getDefault()), fSymbols(symbolsToAdopt), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -375,7 +360,6 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fLocale(Locale::getDefault()), fSymbols(new DateFormatSymbols(symbols)), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -399,7 +383,6 @@ SimpleDateFormat::SimpleDateFormat(EStyle timeStyle, : fLocale(locale), fSymbols(NULL), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -423,7 +406,6 @@ SimpleDateFormat::SimpleDateFormat(const Locale& locale, fLocale(locale), fSymbols(NULL), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -458,7 +440,6 @@ SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat& other) : DateFormat(other), fSymbols(NULL), fTimeZoneFormat(NULL), - fGMTFormatters(NULL), fNumberFormatters(NULL), fOverrideList(NULL), fDefaultCapitalizationContext(UDAT_CAPITALIZATION_UNKNOWN) @@ -1042,374 +1023,7 @@ _appendSymbolWithMonthPattern(UnicodeString& dst, int32_t value, const UnicodeSt } } -//--------------------------------------------------------------------- -void -SimpleDateFormat::appendGMT(NumberFormat *currentNumberFormat,UnicodeString &appendTo, Calendar& cal, UErrorCode& status) const{ - int32_t offset = cal.get(UCAL_ZONE_OFFSET, status) + cal.get(UCAL_DST_OFFSET, status); - if (U_FAILURE(status)) { - return; - } - if (offset == 0) { - // use GMT zero format - appendTo += fSymbols->fGmtZero; - } else { - 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); - } - } - } -} - -int32_t -SimpleDateFormat::parseGMT(const UnicodeString &text, ParsePosition &pos) const { - if (!isDefaultGMTFormat()) { - int32_t start = pos.getIndex(); - - // Quick check - UBool prefixMatch = FALSE; - int32_t prefixLen = fSymbols->fGmtFormat.indexOf((UChar)0x007B /* '{' */); - if (prefixLen > 0 && text.compare(start, prefixLen, fSymbols->fGmtFormat, 0, prefixLen) == 0) { - prefixMatch = TRUE; - } - if (prefixMatch) { - // Prefix matched - UErrorCode status = U_ZERO_ERROR; - ((SimpleDateFormat*)this)->initGMTFormatters(status); - if (U_SUCCESS(status)) { - Formattable parsed; - int32_t parsedCount; - - // Try negative Hms - fGMTFormatters[kGMTNegativeHMS]->parseObject(text, parsed, pos); - if (pos.getErrorIndex() == -1 && - (pos.getIndex() - start) >= fGMTFormatHmsMinLen[kGMTNegativeHMSMinLenIdx]) { - parsed.getArray(parsedCount); - if (parsedCount == 1 && parsed[0].getType() == Formattable::kDate) { - return (int32_t)(-1 * (int64_t)parsed[0].getDate()); - } - } - - // Reset ParsePosition - pos.setIndex(start); - pos.setErrorIndex(-1); - - // Try positive Hms - fGMTFormatters[kGMTPositiveHMS]->parseObject(text, parsed, pos); - if (pos.getErrorIndex() == -1 && - (pos.getIndex() - start) >= fGMTFormatHmsMinLen[kGMTPositiveHMSMinLenIdx]) { - parsed.getArray(parsedCount); - if (parsedCount == 1 && parsed[0].getType() == Formattable::kDate) { - return (int32_t)((int64_t)parsed[0].getDate()); - } - } - - // Reset ParsePosition - pos.setIndex(start); - pos.setErrorIndex(-1); - - // Try negative Hm - fGMTFormatters[kGMTNegativeHM]->parseObject(text, parsed, pos); - if (pos.getErrorIndex() == -1 && pos.getIndex() > start) { - parsed.getArray(parsedCount); - if (parsedCount == 1 && parsed[0].getType() == Formattable::kDate) { - return (int32_t)(-1 * (int64_t)parsed[0].getDate()); - } - } - - // Reset ParsePosition - pos.setIndex(start); - pos.setErrorIndex(-1); - - // Try positive Hm - fGMTFormatters[kGMTPositiveHM]->parseObject(text, parsed, pos); - if (pos.getErrorIndex() == -1 && pos.getIndex() > start) { - parsed.getArray(parsedCount); - if (parsedCount == 1 && parsed[0].getType() == Formattable::kDate) { - return (int32_t)((int64_t)parsed[0].getDate()); - } - } - - // Reset ParsePosition - pos.setIndex(start); - pos.setErrorIndex(-1); - } - // fall through to the default GMT parsing method - } - } - return parseGMTDefault(text, pos); -} - -void -SimpleDateFormat::formatGMTDefault(NumberFormat *currentNumberFormat,UnicodeString &appendTo, int32_t offset) const { - if (offset < 0) { - appendTo.append(gGmtMinus, 4); - offset = -offset; // suppress the '-' sign for text display. - }else{ - appendTo.append(gGmtPlus, 4); - } - - offset /= U_MILLIS_PER_SECOND; // now in seconds - int32_t sec = offset % 60; - offset /= 60; - int32_t min = offset % 60; - int32_t hour = offset / 60; - - - zeroPaddingNumber(currentNumberFormat,appendTo, hour, 2, 2); - appendTo += (UChar)0x003A /*':'*/; - zeroPaddingNumber(currentNumberFormat,appendTo, min, 2, 2); - if (sec != 0) { - appendTo += (UChar)0x003A /*':'*/; - zeroPaddingNumber(currentNumberFormat,appendTo, sec, 2, 2); - } -} - -int32_t -SimpleDateFormat::parseGMTDefault(const UnicodeString &text, ParsePosition &pos) const { - int32_t start = pos.getIndex(); - NumberFormat *currentNumberFormat = getNumberFormatByIndex(UDAT_TIMEZONE_RFC_FIELD); - - if (start + kUtLen + 1 >= text.length()) { - pos.setErrorIndex(start); - return 0; - } - - int32_t cur = start; - // "GMT" - if (text.compare(start, kGmtLen, gGmt) == 0) { - cur += kGmtLen; - } else if (text.compare(start, kUtLen, gUt) == 0) { - cur += kUtLen; - } else { - pos.setErrorIndex(start); - return 0; - } - // Sign - UBool negative = FALSE; - if (text.charAt(cur) == (UChar)0x002D /* minus */) { - negative = TRUE; - } else if (text.charAt(cur) != (UChar)0x002B /* plus */) { - pos.setErrorIndex(cur); - return 0; - } - cur++; - - // Numbers - int32_t numLen; - pos.setIndex(cur); - - Formattable number; - parseInt(text, number, 6, pos, FALSE,currentNumberFormat); - numLen = pos.getIndex() - cur; - - if (numLen <= 0) { - pos.setIndex(start); - pos.setErrorIndex(cur); - return 0; - } - - int32_t numVal = number.getLong(); - - int32_t hour = 0; - int32_t min = 0; - int32_t sec = 0; - - if (numLen <= 2) { - // H[H][:mm[:ss]] - hour = numVal; - cur += numLen; - if (cur + 2 < text.length() && text.charAt(cur) == (UChar)0x003A /* colon */) { - cur++; - pos.setIndex(cur); - parseInt(text, number, 2, pos, FALSE,currentNumberFormat); - numLen = pos.getIndex() - cur; - if (numLen == 2) { - // got minute field - min = number.getLong(); - cur += numLen; - if (cur + 2 < text.length() && text.charAt(cur) == (UChar)0x003A /* colon */) { - cur++; - pos.setIndex(cur); - parseInt(text, number, 2, pos, FALSE,currentNumberFormat); - numLen = pos.getIndex() - cur; - if (numLen == 2) { - // got second field - sec = number.getLong(); - } else { - // reset position - pos.setIndex(cur - 1); - pos.setErrorIndex(-1); - } - } - } else { - // reset postion - pos.setIndex(cur - 1); - pos.setErrorIndex(-1); - } - } - } else if (numLen == 3 || numLen == 4) { - // Hmm or HHmm - hour = numVal / 100; - min = numVal % 100; - } else if (numLen == 5 || numLen == 6) { - // Hmmss or HHmmss - hour = numVal / 10000; - min = (numVal % 10000) / 100; - sec = numVal % 100; - } else { - // HHmmss followed by bogus numbers - pos.setIndex(cur + 6); - - int32_t shift = numLen - 6; - while (shift > 0) { - numVal /= 10; - shift--; - } - hour = numVal / 10000; - min = (numVal % 10000) / 100; - sec = numVal % 100; - } - - int32_t offset = ((hour*60 + min)*60 + sec)*1000; - if (negative) { - offset = -offset; - } - return offset; -} - -UBool -SimpleDateFormat::isDefaultGMTFormat() const { - // GMT pattern - if (fSymbols->fGmtFormat.length() == 0) { - // No GMT pattern is set - return TRUE; - } else if (fSymbols->fGmtFormat.compare(gDefGmtPat, kGmtPatLen) != 0) { - return FALSE; - } - // Hour patterns - if (fSymbols->fGmtHourFormats == NULL || fSymbols->fGmtHourFormatsCount != DateFormatSymbols::GMT_HOUR_COUNT) { - // No Hour pattern is set - return TRUE; - } else if ((fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_NEGATIVE_HMS].compare(gDefGmtNegHmsPat, kNegHmsLen) != 0) - || (fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_NEGATIVE_HM].compare(gDefGmtNegHmPat, kNegHmLen) != 0) - || (fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_POSITIVE_HMS].compare(gDefGmtPosHmsPat, kPosHmsLen) != 0) - || (fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_POSITIVE_HM].compare(gDefGmtPosHmPat, kPosHmLen) != 0)) { - return FALSE; - } - return TRUE; -} - -void -SimpleDateFormat::formatRFC822TZ(UnicodeString &appendTo, int32_t offset) const { - UChar sign = 0x002B /* '+' */; - if (offset < 0) { - offset = -offset; - sign = 0x002D /* '-' */; - } - appendTo.append(sign); - - int32_t offsetH = offset / U_MILLIS_PER_HOUR; - offset = offset % U_MILLIS_PER_HOUR; - int32_t offsetM = offset / U_MILLIS_PER_MINUTE; - offset = offset % U_MILLIS_PER_MINUTE; - int32_t offsetS = offset / U_MILLIS_PER_SECOND; - - int32_t num = 0, denom = 0; - if (offsetS == 0) { - offset = offsetH*100 + offsetM; // HHmm - num = offset % 10000; - denom = 1000; - } else { - offset = offsetH*10000 + offsetM*100 + offsetS; // HHmmss - num = offset % 1000000; - denom = 100000; - } - while (denom >= 1) { - UChar digit = (UChar)0x0030 + (num / denom); - appendTo.append(digit); - num = num % denom; - denom /= 10; - } -} - -void -SimpleDateFormat::initGMTFormatters(UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - umtx_lock(&LOCK); - if (fGMTFormatters == NULL) { - fGMTFormatters = (MessageFormat**)uprv_malloc(kNumGMTFormatters * sizeof(MessageFormat*)); - if (fGMTFormatters) { - for (int32_t i = 0; i < kNumGMTFormatters; i++) { - const UnicodeString *hourPattern = NULL; //initialized it to avoid warning - switch (i) { - case kGMTNegativeHMS: - hourPattern = &(fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_NEGATIVE_HMS]); - break; - case kGMTNegativeHM: - hourPattern = &(fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_NEGATIVE_HM]); - break; - case kGMTPositiveHMS: - hourPattern = &(fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_POSITIVE_HMS]); - break; - case kGMTPositiveHM: - hourPattern = &(fSymbols->fGmtHourFormats[DateFormatSymbols::GMT_POSITIVE_HM]); - break; - } - fGMTFormatters[i] = new MessageFormat(fSymbols->fGmtFormat, status); - GregorianCalendar *gcal = new GregorianCalendar(TimeZone::createTimeZone(UnicodeString(gEtcUTC)), status); - if (U_FAILURE(status)) { - break; - } - SimpleDateFormat *sdf = (SimpleDateFormat*)this->clone(); - sdf->adoptCalendar(gcal); - sdf->applyPattern(*hourPattern); - - // This prevents an hours format pattern like "-HH:mm:ss" from matching - // in a string like "GMT-07:00 10:08:11 PM" - sdf->setLenient(FALSE); - - fGMTFormatters[i]->adoptFormat(0, sdf); - - // For parsing, we only allow Hms patterns to be equal or longer - // than its length with fixed minutes/seconds digits. - // See #6880 - if (i == kGMTNegativeHMS || i == kGMTPositiveHMS) { - UnicodeString tmp; - Formattable tmpParam(60*60*1000, Formattable::kIsDate); - FieldPosition fpos(0); - fGMTFormatters[i]->format(&tmpParam, 1, tmp, fpos, status); - if (U_FAILURE(status)) { - break; - } - if (i == kGMTNegativeHMS) { - fGMTFormatHmsMinLen[kGMTNegativeHMSMinLenIdx] = tmp.length(); - } else { - fGMTFormatHmsMinLen[kGMTPositiveHMSMinLenIdx] = tmp.length(); - } - } - } - } else { - status = U_MEMORY_ALLOCATION_ERROR; - } - } - umtx_unlock(&LOCK); -} - +//---------------------------------------------------------------------- void SimpleDateFormat::initNumberFormatters(const Locale &locale,UErrorCode &status) { if (U_FAILURE(status)) { @@ -1627,11 +1241,11 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, break; case UDAT_YEAR_NAME_FIELD: - if (fSymbols->fShortYearNames != NULL && value <= fSymbols->fShortYearNamesCount) { + if (fSymbols->fShortYearNames != NULL && value <= fSymbols->fShortYearNamesCount) { // the Calendar YEAR field runs 1 through 60 for cyclic years _appendSymbol(appendTo, value - 1, fSymbols->fShortYearNames, fSymbols->fShortYearNamesCount); break; - } + } // else fall through to numeric year handling, do not break here // OLD: for "yyyy", write out the whole year; for "yy", write out the last 2 digits @@ -1817,12 +1431,24 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, case UDAT_TIMEZONE_FIELD: case UDAT_TIMEZONE_GENERIC_FIELD: case UDAT_TIMEZONE_SPECIAL_FIELD: + case UDAT_TIMEZONE_RFC_FIELD: // 'Z' - TIMEZONE_RFC { UnicodeString zoneString; const TimeZone& tz = cal.getTimeZone(); UDate date = cal.getTime(status); if (U_SUCCESS(status)) { - if (patternCharIndex == UDAT_TIMEZONE_FIELD) { + if (patternCharIndex == UDAT_TIMEZONE_RFC_FIELD) { + if (count < 4) { + // "Z" + tzFormat()->format(UTZFMT_STYLE_RFC822, tz, date, zoneString); + } else if (count == 5) { + // "ZZZZZ" + tzFormat()->format(UTZFMT_STYLE_ISO8601, tz, date, zoneString); + } else { + // "ZZ", "ZZZ", "ZZZZ" + tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString); + } + } else if (patternCharIndex == UDAT_TIMEZONE_FIELD) { if (count < 4) { // "z", "zz", "zzz" tzFormat()->format(UTZFMT_STYLE_SPECIFIC_SHORT, tz, date, zoneString); @@ -1849,27 +1475,12 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneShort; } else if (count == 4) { // "VVVV" - tzFormat()->format(UTZFMT_STYLE_LOCATION, tz, date, zoneString); + tzFormat()->format(UTZFMT_STYLE_GENERIC_LOCATION, tz, date, zoneString); capContextUsageType = DateFormatSymbols::kCapContextUsageZoneLong; } } } - if (zoneString.isEmpty()) { - appendGMT(currentNumberFormat,appendTo, cal, status); - } else { - appendTo += zoneString; - } - } - break; - - case UDAT_TIMEZONE_RFC_FIELD: // 'Z' - TIMEZONE_RFC - if (count < 4) { - // RFC822 format, must use ASCII digits - value = (cal.get(UCAL_ZONE_OFFSET, status) + cal.get(UCAL_DST_OFFSET, status)); - formatRFC822TZ(appendTo, value); - } else { - // long form, localized GMT pattern - appendGMT(currentNumberFormat,appendTo, cal, status); + appendTo += zoneString; } break; @@ -1987,7 +1598,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& UBool lenient = isLenient(); // hack, reset tztype, cast away const - ((SimpleDateFormat*)this)->tztype = TZTYPE_UNK; + ((SimpleDateFormat*)this)->tztype = UTZFMT_TIME_TYPE_UNKNOWN; // For parsing abutting numeric fields. 'abutPat' is the // offset into 'pattern' of the first of 2 or more abutting @@ -2189,7 +1800,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& // when the two-digit year is equal to the start year, and thus might fall at the // front or the back of the default century. This only works because we adjust // the year correctly to start with in other cases -- see subParse(). - if (ambiguousYear[0] || tztype != TZTYPE_UNK) // If this is true then the two-digit year == the default start year + if (ambiguousYear[0] || tztype != UTZFMT_TIME_TYPE_UNKNOWN) // If this is true then the two-digit year == the default start year { // We need a copy of the fields, and we need to avoid triggering a call to // complete(), which will recalculate the fields. Since we can't access @@ -2212,7 +1823,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& delete copy; } - if (tztype != TZTYPE_UNK) { + if (tztype != UTZFMT_TIME_TYPE_UNKNOWN) { copy = cal.clone(); // Check for failed cloning. if (copy == NULL) { @@ -2238,7 +1849,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& // matches the rule used by the parsed time zone. int32_t raw, dst; if (btz != NULL) { - if (tztype == TZTYPE_STD) { + if (tztype == UTZFMT_TIME_TYPE_STANDARD) { btz->getOffsetFromLocal(localMillis, BasicTimeZone::kStandard, BasicTimeZone::kStandard, raw, dst, status); } else { @@ -2253,7 +1864,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& // Now, compare the results with parsed type, either standard or daylight saving time int32_t resolvedSavings = dst; - if (tztype == TZTYPE_STD) { + if (tztype == UTZFMT_TIME_TYPE_STANDARD) { if (dst != 0) { // Override DST_OFFSET = 0 in the result calendar resolvedSavings = 0; @@ -3166,178 +2777,51 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC break; case UDAT_TIMEZONE_FIELD: - case UDAT_TIMEZONE_RFC_FIELD: - case UDAT_TIMEZONE_GENERIC_FIELD: - case UDAT_TIMEZONE_SPECIAL_FIELD: { - int32_t offset = 0; - UBool parsed = FALSE; - - // Step 1 - // Check if this is a long GMT offset string (either localized or default) - offset = parseGMT(text, pos); - if (pos.getIndex() - start > 0) { - parsed = TRUE; - } - if (!parsed) { - // Step 2 - // Check if this is an RFC822 time zone offset. - // ICU supports the standard RFC822 format [+|-]HHmm - // and its extended form [+|-]HHmmSS. - do { - int32_t sign = 0; - UChar signChar = text.charAt(start); - if (signChar == (UChar)0x002B /* '+' */) { - sign = 1; - } else if (signChar == (UChar)0x002D /* '-' */) { - sign = -1; - } else { - // Not an RFC822 offset string - break; - } - - // Parse digits - int32_t orgPos = start + 1; - pos.setIndex(orgPos); - parseInt(text, number, 6, pos, FALSE,currentNumberFormat); - int32_t numLen = pos.getIndex() - orgPos; - if (numLen <= 0) { - break; - } - - // Followings are possible format (excluding sign char) - // HHmmSS - // HmmSS - // HHmm - // Hmm - // HH - // H - int32_t val = number.getLong(); - int32_t hour = 0, min = 0, sec = 0; - switch(numLen) { - case 1: // H - case 2: // HH - hour = val; - break; - case 3: // Hmm - case 4: // HHmm - hour = val / 100; - min = val % 100; - break; - case 5: // Hmmss - case 6: // HHmmss - hour = val / 10000; - min = (val % 10000) / 100; - sec = val % 100; - break; - } - if (hour > 23 || min > 59 || sec > 59) { - // Invalid value range - break; - } - offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign; - parsed = TRUE; - } while (FALSE); - - if (!parsed) { - // Failed to parse. Reset the position. - pos.setIndex(start); - } - } - - if (parsed) { - // offset was successfully parsed as either a long GMT string or RFC822 zone offset - // string. Create normalized zone ID for the offset. - - UnicodeString tzID(gGmt); - formatRFC822TZ(tzID, offset); - //TimeZone *customTZ = TimeZone::createTimeZone(tzID); - TimeZone *customTZ = new SimpleTimeZone(offset, tzID); // faster than TimeZone::createTimeZone - cal.adoptTimeZone(customTZ); - + UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_SPECIFIC_SHORT : UTZFMT_STYLE_SPECIFIC_LONG; + TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); + if (tz != NULL) { + ((SimpleDateFormat*)this)->tztype = tzTimeType; + cal.adoptTimeZone(tz); return pos.getIndex(); } - - // Step 3 - // Is this standalone Localized GMT zero or GMT/UT/UTC? - int32_t gmtLen = 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(UNICODE_STRING("Etc/GMT", 7)); + } + break; + case UDAT_TIMEZONE_RFC_FIELD: + { + UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_RFC822 : ((count == 5) ? UTZFMT_STYLE_ISO8601: UTZFMT_STYLE_LOCALIZED_GMT); + TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); + if (tz != NULL) { + ((SimpleDateFormat*)this)->tztype = tzTimeType; cal.adoptTimeZone(tz); - return start + gmtLen; + return pos.getIndex(); } - - // 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, 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; - default: - 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(UNICODE_STRING("Etc/GMT", 7)); + return -start; + } + case UDAT_TIMEZONE_GENERIC_FIELD: + { + UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_GENERIC_SHORT : UTZFMT_STYLE_GENERIC_LONG; + TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); + if (tz != NULL) { + ((SimpleDateFormat*)this)->tztype = tzTimeType; cal.adoptTimeZone(tz); - return start + gmtLen; + return pos.getIndex(); + } + return -start; + } + case UDAT_TIMEZONE_SPECIAL_FIELD: + { + UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_SPECIFIC_SHORT : UTZFMT_STYLE_GENERIC_LOCATION; + TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); + if (tz != NULL) { + ((SimpleDateFormat*)this)->tztype = tzTimeType; + cal.adoptTimeZone(tz); + return pos.getIndex(); } - - // complete failure return -start; } @@ -3579,6 +3063,27 @@ SimpleDateFormat::setDateFormatSymbols(const DateFormatSymbols& newFormatSymbols fSymbols = new DateFormatSymbols(newFormatSymbols); } +//---------------------------------------------------------------------- +const TimeZoneFormat* +SimpleDateFormat::getTimeZoneFormat(void) const { + return (const TimeZoneFormat*)tzFormat(); +} + +//---------------------------------------------------------------------- +void +SimpleDateFormat::adoptTimeZoneFormat(TimeZoneFormat* timeZoneFormatToAdopt) +{ + delete fTimeZoneFormat; + fTimeZoneFormat = timeZoneFormatToAdopt; +} + +//---------------------------------------------------------------------- +void +SimpleDateFormat::setTimeZoneFormat(const TimeZoneFormat& newTimeZoneFormat) +{ + delete fTimeZoneFormat; + fTimeZoneFormat = new TimeZoneFormat(newTimeZoneFormat); +} //---------------------------------------------------------------------- @@ -3845,6 +3350,7 @@ SimpleDateFormat::tzFormat() const { } 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 c39c71c4099..0f04b335e97 100644 --- a/icu4c/source/i18n/timezone.cpp +++ b/icu4c/source/i18n/timezone.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 1997-2011, International Business Machines Corporation and * +* Copyright (C) 1997-2012, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * @@ -68,10 +68,11 @@ static char gStrBuf[256]; #if !UCONFIG_NO_FORMATTING #include "unicode/simpletz.h" -#include "unicode/smpdtfmt.h" #include "unicode/calendar.h" #include "unicode/gregocal.h" #include "unicode/ures.h" +#include "unicode/tzfmt.h" +#include "unicode/numfmt.h" #include "gregoimp.h" #include "uresimp.h" // struct UResourceBundle #include "olsontz.h" @@ -1238,127 +1239,75 @@ TimeZone::getDSTSavings()const { UnicodeString& TimeZone::getDisplayName(UBool daylight, EDisplayType style, const Locale& locale, UnicodeString& result) const { - // SRL TODO: cache the SDF, just like java. UErrorCode status = U_ZERO_ERROR; -#ifdef U_DEBUG_TZ - char buf[128]; - fID.extract(0, sizeof(buf)-1, buf, sizeof(buf), ""); -#endif + UDate date = Calendar::getNow(); + UTimeZoneFormatTimeType timeType; + int32_t offset; - // select the proper format string - const UChar* patUChars; - switch(style){ - case LONG: - patUChars = ZZZZ_STR; - break; - case SHORT_GENERIC: - patUChars = V_STR; - break; - case LONG_GENERIC: - patUChars = VVVV_STR; - break; - case SHORT_GMT: - patUChars = Z_UC_STR; - break; - case LONG_GMT: - patUChars = ZZZZ_UC_STR; - break; - case SHORT_COMMONLY_USED: - //patUChars = V_UC_STR; - patUChars = Z_STR; - break; - case GENERIC_LOCATION: - patUChars = VVVV_UC_STR; - break; - default: // SHORT - //patUChars = Z_STR; - patUChars = V_UC_STR; - break; - } - UnicodeString pat(TRUE, patUChars, -1); - - SimpleDateFormat format(pat, locale, status); - U_DEBUG_TZ_MSG(("getDisplayName(%s)\n", buf)); - if(!U_SUCCESS(status)) - { -#ifdef U_DEBUG_TZ - char buf2[128]; - result.extract(0, sizeof(buf2)-1, buf2, sizeof(buf2), ""); - U_DEBUG_TZ_MSG(("getDisplayName(%s) -> %s\n", buf, buf2)); -#endif - return result.remove(); - } - - UDate d = Calendar::getNow(); - int32_t rawOffset; - int32_t dstOffset; - this->getOffset(d, FALSE, rawOffset, dstOffset, status); - if (U_FAILURE(status)) { - return result.remove(); - } - - if ((daylight && dstOffset != 0) || - (!daylight && dstOffset == 0) || - (style == SHORT_GENERIC) || - (style == LONG_GENERIC) - ) { - // Current time and the request (daylight / not daylight) agree. - format.setTimeZone(*this); - return format.format(d, result); - } - - // Create a new SimpleTimeZone as a stand-in for this zone; the - // stand-in will have no DST, or DST during July, but the same ID and offset, - // and hence the same display name. - // We don't cache these because they're small and cheap to create. - UnicodeString tempID; - getID(tempID); - SimpleTimeZone *tz = NULL; - if(daylight && useDaylightTime()){ - // The display name for daylight saving time was requested, but currently not in DST - // Set a fixed date (July 1) in this Gregorian year - GregorianCalendar cal(*this, status); - if (U_FAILURE(status)) { - return result.remove(); + if (style == GENERIC_LOCATION || style == LONG_GENERIC || style == SHORT_GENERIC) { + LocalPointer tzfmt(TimeZoneFormat::createInstance(locale, status)); + // Generic format + switch (style) { + case GENERIC_LOCATION: + tzfmt->format(UTZFMT_STYLE_GENERIC_LOCATION, *this, date, result, &timeType); + break; + case LONG_GENERIC: + tzfmt->format(UTZFMT_STYLE_GENERIC_LONG, *this, date, result, &timeType); + break; + case SHORT_GENERIC: + tzfmt->format(UTZFMT_STYLE_GENERIC_SHORT, *this, date, result, &timeType); + break; + default: + U_ASSERT(FALSE); } - cal.set(UCAL_MONTH, UCAL_JULY); - cal.set(UCAL_DATE, 1); - - // Get July 1 date - d = cal.getTime(status); - - // Check if it is in DST - if (cal.get(UCAL_DST_OFFSET, status) == 0) { - // We need to create a fake time zone - tz = new SimpleTimeZone(rawOffset, tempID, - UCAL_JUNE, 1, 0, 0, - UCAL_AUGUST, 1, 0, 0, - getDSTSavings(), status); - if (U_FAILURE(status) || tz == NULL) { - if (U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - return result.remove(); - } - format.adoptTimeZone(tz); - } else { - format.setTimeZone(*this); + // Generic format many use Localized GMT as the final fallback. + // When Localized GMT format is used, the result might not be + // appropriate for the requested daylight value. + if ((daylight && timeType == UTZFMT_TIME_TYPE_STANDARD) || (!daylight && timeType == UTZFMT_TIME_TYPE_DAYLIGHT)) { + offset = daylight ? getRawOffset() + getDSTSavings() : getRawOffset(); + tzfmt->formatOffsetLocalizedGMT(offset, result, status); } + } else if (style == LONG_GMT || style == SHORT_GMT) { + LocalPointer tzfmt(TimeZoneFormat::createInstance(locale, status)); + offset = daylight && useDaylightTime() ? getRawOffset() + getDSTSavings() : getRawOffset(); + switch (style) { + case LONG_GMT: + tzfmt->formatOffsetLocalizedGMT(offset, result, status); + break; + case SHORT_GMT: + tzfmt->formatOffsetRFC822(offset, result, status); + break; + default: + U_ASSERT(FALSE); + } + } else { - // The display name for standard time was requested, but currently in DST - // or display name for daylight saving time was requested, but this zone no longer - // observes DST. - tz = new SimpleTimeZone(rawOffset, tempID); - if (U_FAILURE(status) || tz == NULL) { - if (U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - return result.remove(); + U_ASSERT(style == LONG || style == SHORT || style == SHORT_COMMONLY_USED); + UTimeZoneNameType nameType = UTZNM_UNKNOWN; + switch (style) { + case LONG: + nameType = daylight ? UTZNM_LONG_DAYLIGHT : UTZNM_LONG_STANDARD; + break; + case SHORT: + case SHORT_COMMONLY_USED: + nameType = daylight ? UTZNM_SHORT_DAYLIGHT : UTZNM_SHORT_STANDARD; + break; + default: + U_ASSERT(FALSE); + } + LocalPointer tznames(TimeZoneNames::createInstance(locale, status)); + UnicodeString canonicalID(ZoneMeta::getCanonicalCLDRID(*this)); + tznames->getDisplayName(canonicalID, nameType, date, result); + if (result.isEmpty()) { + // Fallback to localized GMT + LocalPointer tzfmt(TimeZoneFormat::createInstance(locale, status)); + offset = daylight && useDaylightTime() ? getRawOffset() + getDSTSavings() : getRawOffset(); + tzfmt->formatOffsetLocalizedGMT(offset, result, status); } - format.adoptTimeZone(tz); } - - format.format(d, result, status); + if (U_FAILURE(status)) { + result.remove(); + } return result; } diff --git a/icu4c/source/i18n/tzfmt.cpp b/icu4c/source/i18n/tzfmt.cpp index 4afff2dce81..a1ad9bd1a43 100644 --- a/icu4c/source/i18n/tzfmt.cpp +++ b/icu4c/source/i18n/tzfmt.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2011, International Business Machines Corporation and * +* Copyright (C) 2011-2012, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -9,52 +9,245 @@ #if !UCONFIG_NO_FORMATTING -#include "tzfmt.h" +#include "unicode/calendar.h" +#include "unicode/tzfmt.h" +#include "unicode/numsys.h" +#include "unicode/uchar.h" +#include "unicode/udat.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 "uresimp.h" +#include "ureslocs.h" +#include "uvector.h" #include "zonemeta.h" U_NAMESPACE_BEGIN -// --------------------------------------------------- -// TimeZoneFormatImpl - the TimeZoneFormat implementation -// --------------------------------------------------- -class TimeZoneFormatImpl : public TimeZoneFormat { -public: - TimeZoneFormatImpl(const Locale& locale, UErrorCode& status); - virtual ~TimeZoneFormatImpl(); +static const char gZoneStringsTag[] = "zoneStrings"; +static const char gGmtFormatTag[]= "gmtFormat"; +static const char gGmtZeroFormatTag[] = "gmtZeroFormat"; +static const char gHourFormatTag[]= "hourFormat"; - const TimeZoneNames* getTimeZoneNames() const; +static const UChar TZID_GMT[] = {0x0045, 0x0074, 0x0063, 0x002F, 0x0047, 0x004D, 0x0054, 0}; // Etc/GMT - UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, - UnicodeString& name, UTimeZoneTimeType* timeType = NULL) const; +static const UChar DEFAULT_GMT_PATTERN[] = {0x0047, 0x004D, 0x0054, 0x007B, 0x0030, 0x007D, 0}; // GMT{0} +static const UChar DEFAULT_GMT_ZERO[] = {0x0047, 0x004D, 0x0054, 0}; // GMT +static const UChar DEFAULT_GMT_POSITIVE_HM[] = {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0}; // +HH:mm +static const UChar DEFAULT_GMT_POSITIVE_HMS[] = {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0}; // +HH:mm:ss +static const UChar DEFAULT_GMT_NEGATIVE_HM[] = {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0}; // -HH:mm +static const UChar DEFAULT_GMT_NEGATIVE_HMS[] = {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0}; // -HH:mm:ss - UnicodeString& parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, - UnicodeString& tzID, UTimeZoneTimeType* timeType = NULL) const; - -private: - UMTX fLock; - 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; - - const TimeZoneGenericNames* getTimeZoneGenericNames(UErrorCode& status) const; +static const UChar32 DEFAULT_GMT_DIGITS[] = { + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, + 0x0035, 0x0036, 0x0037, 0x0038, 0x0039 }; -TimeZoneFormatImpl::TimeZoneFormatImpl(const Locale& locale, UErrorCode& status) -: fLock(NULL),fLocale(locale), fTimeZoneNames(NULL), fTimeZoneGenericNames(NULL) { +static const UChar DEFAULT_GMT_OFFSET_SEP = 0x003A; // ':' + +static const UChar ARG0[] = {0x007B, 0x0030, 0x007D}; // "{0}" +static const int ARG0_LEN = 3; + +static const UChar DEFAULT_GMT_OFFSET_MINUTE_PATTERN[] = {0x006D, 0x006D, 0}; // "mm" +static const UChar DEFAULT_GMT_OFFSET_SECOND_PATTERN[] = {0x0073, 0x0073, 0}; // "ss" + +static const UChar ALT_GMT_STRINGS[][4] = { + {0x0047, 0x004D, 0x0054, 0}, // GMT + {0x0055, 0x0054, 0x0043, 0}, // UTC + {0x0055, 0x0054, 0, 0}, // UT + {0, 0, 0, 0} +}; + +// Order of GMT offset pattern parsing, *_HMS must be evaluated first +// because *_HM is most likely a substring of *_HMS +static const int32_t PARSE_GMT_OFFSET_TYPES[] = { + UTZFMT_PAT_POSITIVE_HMS, + UTZFMT_PAT_NEGATIVE_HMS, + UTZFMT_PAT_POSITIVE_HM, + UTZFMT_PAT_NEGATIVE_HM, + -1 +}; + +static const UChar SINGLEQUOTE = 0x0027; +static const UChar PLUS = 0x002B; +static const UChar MINUS = 0x002D; +static const UChar ISO8601_UTC = 0x005A; // 'Z' +static const UChar ISO8601_SEP = 0x003A; // ':' + +static const int32_t MILLIS_PER_HOUR = 60 * 60 * 1000; +static const int32_t MILLIS_PER_MINUTE = 60 * 1000; +static const int32_t MILLIS_PER_SECOND = 1000; + +// Maximum offset (exclusive) in millisecond supported by offset formats +static int32_t MAX_OFFSET = 24 * MILLIS_PER_HOUR; + +// Maximum values for GMT offset fields +static const int32_t MAX_OFFSET_HOUR = 23; +static const int32_t MAX_OFFSET_MINUTE = 59; +static const int32_t MAX_OFFSET_SECOND = 59; + +static const int32_t UNKNOWN_OFFSET = 0x7FFFFFFF; + +static const int32_t ALL_SPECIFIC_NAME_TYPES = UTZNM_LONG_STANDARD | UTZNM_LONG_DAYLIGHT | UTZNM_SHORT_STANDARD | UTZNM_SHORT_DAYLIGHT; +static const int32_t ALL_GENERIC_NAME_TYPES = UTZGNM_LOCATION | UTZGNM_LONG | UTZGNM_SHORT; + +#define STYLE_FLAG(c) (1 << (c)) +#define DIGIT_VAL(c) (0x0030 <= (c) && (c) <= 0x0039 ? (c) - 0x0030 : -1) +#define MAX_OFFSET_DIGITS 6 + + +// ------------------------------------------------------------------ +// GMTOffsetField +// +// This class represents a localized GMT offset pattern +// item and used by TimeZoneFormat +// ------------------------------------------------------------------ +class GMTOffsetField : public UMemory { +public: + enum FieldType { + TEXT = 0, + HOUR = 1, + MINUTE = 2, + SECOND = 4 + }; + + virtual ~GMTOffsetField(); + + static GMTOffsetField* createText(const UnicodeString& text, UErrorCode& status); + static GMTOffsetField* createTimeField(FieldType type, uint8_t width, UErrorCode& status); + static UBool isValid(FieldType type, int32_t width); + static FieldType getTypeByLetter(UChar ch); + + FieldType getType() const; + uint8_t getWidth() const; + const UChar* getPatternText(void) const; + +private: + UChar* fText; + FieldType fType; + uint8_t fWidth; + + GMTOffsetField(); +}; + +GMTOffsetField::GMTOffsetField() +: fText(NULL), fType(TEXT), fWidth(0) { +} + +GMTOffsetField::~GMTOffsetField() { + if (fText) { + uprv_free(fText); + } +} + +GMTOffsetField* +GMTOffsetField::createText(const UnicodeString& text, UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; + } + GMTOffsetField* result = new GMTOffsetField(); + if (result == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + + int32_t len = text.length(); + result->fText = (UChar*)uprv_malloc((len + 1) * sizeof(UChar)); + if (result->fText == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + delete result; + return NULL; + } + u_strncpy(result->fText, text.getBuffer(), len); + result->fText[len] = 0; + result->fType = TEXT; + + return result; +} + +GMTOffsetField* +GMTOffsetField::createTimeField(FieldType type, uint8_t width, UErrorCode& status) { + U_ASSERT(type != TEXT); + if (U_FAILURE(status)) { + return NULL; + } + GMTOffsetField* result = new GMTOffsetField(); + if (result == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + + result->fType = type; + result->fWidth = width; + + return result; +} + +UBool +GMTOffsetField::isValid(FieldType type, int32_t width) { + switch (type) { + case HOUR: + return (width == 1 || width == 2); + case MINUTE: + case SECOND: + return (width == 2); + default: + U_ASSERT(FALSE); + } + return (width > 0); +} + +GMTOffsetField::FieldType +GMTOffsetField::getTypeByLetter(UChar ch) { + if (ch == 0x0048 /* H */) { + return HOUR; + } else if (ch == 0x006D /* m */) { + return MINUTE; + } else if (ch == 0x0073 /* s */) { + return SECOND; + } + return TEXT; +} + +inline GMTOffsetField::FieldType +GMTOffsetField::getType() const { + return fType; + } + +inline uint8_t +GMTOffsetField::getWidth() const { + return fWidth; +} + +inline const UChar* +GMTOffsetField::getPatternText(void) const { + return fText; +} + + +U_CDECL_BEGIN +static void U_CALLCONV +deleteGMTOffsetField(void *obj) { + delete static_cast(obj); +} +U_CDECL_END + + +// ------------------------------------------------------------------ +// TimeZoneFormat +// ------------------------------------------------------------------ +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeZoneFormat) + +TimeZoneFormat::TimeZoneFormat(const Locale& locale, UErrorCode& status) +: fLock(NULL),fLocale(locale), fTimeZoneNames(NULL), fTimeZoneGenericNames(NULL), fDefParseOptionFlags(0) { + + for (int32_t i = 0; i <= UTZFMT_PAT_NEGATIVE_HMS; i++) { + fGMTOffsetPatternItems[i] = NULL; + } const char* region = fLocale.getCountry(); int32_t regionLen = uprv_strlen(region); @@ -76,56 +269,283 @@ TimeZoneFormatImpl::TimeZoneFormatImpl(const Locale& locale, UErrorCode& status) fTimeZoneNames = TimeZoneNames::createInstance(locale, status); // fTimeZoneGenericNames is lazily instantiated + + const UChar* gmtPattern = NULL; + const UChar* hourFormats = NULL; + + UResourceBundle *zoneBundle = ures_open(U_ICUDATA_ZONE, locale.getName(), &status); + UResourceBundle *zoneStringsArray = ures_getByKeyWithFallback(zoneBundle, gZoneStringsTag, NULL, &status); + if (U_SUCCESS(status)) { + const UChar* resStr; + int32_t len; + resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gGmtFormatTag, &len, &status); + if (len > 0) { + gmtPattern = resStr; + } + resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gGmtZeroFormatTag, &len, &status); + if (len > 0) { + fGMTZeroFormat.setTo(TRUE, resStr, len); + } + resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gHourFormatTag, &len, &status); + if (len > 0) { + hourFormats = resStr; + } + } + + if (gmtPattern == NULL) { + gmtPattern = DEFAULT_GMT_PATTERN; + } + initGMTPattern(UnicodeString(gmtPattern, -1), status); + + UBool useDefHourFmt = TRUE; + if (hourFormats) { + UChar *sep = u_strchr(hourFormats, (UChar)0x003B /* ';' */); + if (sep != NULL) { + fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HM].setTo(FALSE, hourFormats, (int32_t)(sep - hourFormats)); + fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HM].setTo(TRUE, sep + 1, -1); + expandOffsetPattern(fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HM], fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HMS]); + expandOffsetPattern(fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HM], fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HMS]); + useDefHourFmt = FALSE; + } + } + if (useDefHourFmt) { + fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HM].setTo(TRUE, DEFAULT_GMT_POSITIVE_HM, -1); + fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HMS].setTo(TRUE, DEFAULT_GMT_POSITIVE_HMS, -1); + fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HM].setTo(TRUE, DEFAULT_GMT_NEGATIVE_HM, -1); + fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HMS].setTo(TRUE, DEFAULT_GMT_NEGATIVE_HMS, -1); + } + initGMTOffsetPatterns(status); + + NumberingSystem* ns = NumberingSystem::createInstance(locale, status); + UBool useDefDigits = TRUE; + if (ns && !ns->isAlgorithmic()) { + UnicodeString digits = ns->getDescription(); + useDefDigits = !toCodePoints(digits, fGMTOffsetDigits, 10); + } + if (useDefDigits) { + uprv_memcpy(fGMTOffsetDigits, DEFAULT_GMT_DIGITS, sizeof(UChar32) * 10); + } + delete ns; } -TimeZoneFormatImpl::~TimeZoneFormatImpl() { - if (fTimeZoneNames != NULL) { - delete fTimeZoneNames; +TimeZoneFormat::TimeZoneFormat(const TimeZoneFormat& other) +: Format(other), fTimeZoneNames(NULL), fTimeZoneGenericNames(NULL) { + + for (int32_t i = 0; i <= UTZFMT_PAT_NEGATIVE_HMS; i++) { + fGMTOffsetPatternItems[i] = NULL; } - if (fTimeZoneGenericNames != NULL) { - delete fTimeZoneGenericNames; + *this = other; +} + + +TimeZoneFormat::~TimeZoneFormat() { + delete fTimeZoneNames; + delete fTimeZoneGenericNames; + for (int32_t i = 0; i <= UTZFMT_PAT_NEGATIVE_HMS; i++) { + delete fGMTOffsetPatternItems[i]; } umtx_destroy(&fLock); } -const TimeZoneNames* -TimeZoneFormatImpl::getTimeZoneNames() const { - return fTimeZoneNames; +TimeZoneFormat& +TimeZoneFormat::operator=(const TimeZoneFormat& other) { + if (this == &other) { + return *this; + } + + delete fTimeZoneNames; + delete fTimeZoneGenericNames; + fTimeZoneGenericNames = NULL; + + fLocale = other.fLocale; + uprv_memcpy(fTargetRegion, other.fTargetRegion, sizeof(fTargetRegion)); + + fTimeZoneNames = other.fTimeZoneNames->clone(); + if (other.fTimeZoneGenericNames) { + fTimeZoneGenericNames = other.fTimeZoneGenericNames->clone(); + } + + fGMTPattern = other.fGMTPattern; + fGMTPatternPrefix = other.fGMTPatternPrefix; + fGMTPatternSuffix = other.fGMTPatternSuffix; + + UErrorCode status = U_ZERO_ERROR; + for (int32_t i = 0; i <= UTZFMT_PAT_NEGATIVE_HMS; i++) { + fGMTOffsetPatterns[i] = other.fGMTOffsetPatterns[i]; + delete fGMTOffsetPatternItems[i]; + } + initGMTOffsetPatterns(status); + U_ASSERT(U_SUCCESS(status)); + + fGMTZeroFormat = other.fGMTZeroFormat; + + uprv_memcpy(fGMTOffsetDigits, other.fGMTOffsetDigits, sizeof(fGMTOffsetDigits)); + + fDefParseOptionFlags = other.fDefParseOptionFlags; + + return *this; } -const TimeZoneGenericNames* -TimeZoneFormatImpl::getTimeZoneGenericNames(UErrorCode& status) const { - if (U_FAILURE(status)) { - return NULL; - } - UBool create; - UMTX_CHECK(&gZoneMetaLock, (fTimeZoneGenericNames == NULL), create); - if (create) { - TimeZoneFormatImpl *nonConstThis = const_cast(this); - umtx_lock(&nonConstThis->fLock); - { - if (fTimeZoneGenericNames == NULL) { - nonConstThis->fTimeZoneGenericNames = new TimeZoneGenericNames(fLocale, status); - if (U_SUCCESS(status) && fTimeZoneGenericNames == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - } - } - } - umtx_unlock(&nonConstThis->fLock); - } +UBool +TimeZoneFormat::operator==(const Format& other) const { + TimeZoneFormat* tzfmt = (TimeZoneFormat*)&other; - return fTimeZoneGenericNames; + UBool isEqual = + fLocale == tzfmt->fLocale + && fGMTPattern == tzfmt->fGMTPattern + && fGMTZeroFormat == tzfmt->fGMTZeroFormat + && *fTimeZoneNames == *tzfmt->fTimeZoneNames; + + for (int32_t i = 0; i <= UTZFMT_PAT_NEGATIVE_HMS && isEqual; i++) { + isEqual = fGMTOffsetPatterns[i] == tzfmt->fGMTOffsetPatterns[i]; + } + for (int32_t i = 0; i < 10 && isEqual; i++) { + isEqual = fGMTOffsetDigits[i] == tzfmt->fGMTOffsetDigits[i]; + } + // TODO + // Check fTimeZoneGenericNames. For now, + // if fTimeZoneNames is same, fTimeZoneGenericNames should + // be also equivalent. + return isEqual; +} + +Format* +TimeZoneFormat::clone() const { + return new TimeZoneFormat(*this); +} + +TimeZoneFormat* U_EXPORT2 +TimeZoneFormat::createInstance(const Locale& locale, UErrorCode& status) { + TimeZoneFormat* tzfmt = new TimeZoneFormat(locale, status); + if (U_SUCCESS(status)) { + return tzfmt; + } + delete tzfmt; + return NULL; +} + +// ------------------------------------------------------------------ +// Setter and Getter + +const TimeZoneNames* +TimeZoneFormat::getTimeZoneNames() const { + return (const TimeZoneNames*)fTimeZoneNames; +} + +void +TimeZoneFormat::adoptTimeZoneNames(TimeZoneNames *tznames) { + delete fTimeZoneNames; + fTimeZoneNames = tznames; + + // TODO - We should also update fTimeZoneGenericNames +} + +void +TimeZoneFormat::setTimeZoneNames(const TimeZoneNames &tznames) { + delete fTimeZoneNames; + fTimeZoneNames = tznames.clone(); + + // TODO - We should also update fTimeZoneGenericNames +} + +void +TimeZoneFormat::setDefaultParseOptions(int32_t flags) { + fDefParseOptionFlags = flags; +} + +int32_t +TimeZoneFormat::getDefaultParseOptions(void) const { + return fDefParseOptionFlags; +} + + +UnicodeString& +TimeZoneFormat::getGMTPattern(UnicodeString& pattern) const { + return pattern.setTo(fGMTPattern); +} + +void +TimeZoneFormat::setGMTPattern(const UnicodeString& pattern, UErrorCode& status) { + initGMTPattern(pattern, status); } UnicodeString& -TimeZoneFormatImpl::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, - UnicodeString& name, UTimeZoneTimeType* timeType /* = NULL */) const { +TimeZoneFormat::getGMTOffsetPattern(UTimeZoneFormatGMTOffsetPatternType type, UnicodeString& pattern) const { + return pattern.setTo(fGMTOffsetPatterns[type]); +} + +void +TimeZoneFormat::setGMTOffsetPattern(UTimeZoneFormatGMTOffsetPatternType type, const UnicodeString& pattern, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + if (pattern == fGMTOffsetPatterns[type]) { + // No need to reset + return; + } + + OffsetFields required = (type == UTZFMT_PAT_POSITIVE_HMS || type == UTZFMT_PAT_NEGATIVE_HMS) ? FIELDS_HMS : FIELDS_HM; + + UVector* patternItems = parseOffsetPattern(pattern, required, status); + if (patternItems == NULL) { + return; + } + + fGMTOffsetPatterns[type].setTo(pattern); + delete fGMTOffsetPatternItems[type]; + fGMTOffsetPatternItems[type] = patternItems; +} + +UnicodeString& +TimeZoneFormat::getGMTOffsetDigits(UnicodeString& digits) const { + digits.remove(); + for (int32_t i = 0; i < 10; i++) { + digits.append(fGMTOffsetDigits[i]); + } + return digits; +} + +void +TimeZoneFormat::setGMTOffsetDigits(const UnicodeString& digits, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + UChar32 digitArray[10]; + if (!toCodePoints(digits, digitArray, 10)) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + uprv_memcpy(fGMTOffsetDigits, digitArray, sizeof(UChar32)*10); +} + +UnicodeString& +TimeZoneFormat::getGMTZeroFormat(UnicodeString& gmtZeroFormat) const { + return gmtZeroFormat.setTo(fGMTZeroFormat); +} + +void +TimeZoneFormat::setGMTZeroFormat(const UnicodeString& gmtZeroFormat, UErrorCode& status) { + if (U_SUCCESS(status)) { + if (gmtZeroFormat.isEmpty()) { + status = U_ILLEGAL_ARGUMENT_ERROR; + } else if (gmtZeroFormat != fGMTZeroFormat) { + fGMTZeroFormat.setTo(gmtZeroFormat); + } + } +} + +// ------------------------------------------------------------------ +// Format and Parse + +UnicodeString& +TimeZoneFormat::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, + UnicodeString& name, UTimeZoneFormatTimeType* timeType /* = NULL */) const { if (timeType) { *timeType = UTZFMT_TIME_TYPE_UNKNOWN; } switch (style) { - case UTZFMT_STYLE_LOCATION: + case UTZFMT_STYLE_GENERIC_LOCATION: formatGeneric(tz, UTZGNM_LOCATION, date, name); break; case UTZFMT_STYLE_GENERIC_LONG: @@ -140,109 +560,402 @@ TimeZoneFormatImpl::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate case UTZFMT_STYLE_SPECIFIC_SHORT: formatSpecific(tz, UTZNM_SHORT_STANDARD, UTZNM_SHORT_DAYLIGHT, date, name, timeType); break; + case UTZFMT_STYLE_RFC822: + case UTZFMT_STYLE_ISO8601: + case UTZFMT_STYLE_LOCALIZED_GMT: + // will be handled below + break; } + + if (name.isEmpty()) { + UErrorCode status = U_ZERO_ERROR; + int32_t rawOffset, dstOffset; + tz.getOffset(date, FALSE, rawOffset, dstOffset, status); + if (U_SUCCESS(status)) { + switch (style) { + case UTZFMT_STYLE_RFC822: + formatOffsetRFC822(rawOffset + dstOffset, name, status); + break; + case UTZFMT_STYLE_ISO8601: + formatOffsetISO8601(rawOffset + dstOffset, name, status); + break; + default: + formatOffsetLocalizedGMT(rawOffset + dstOffset, name, status); + break; + } + if (timeType) { + *timeType = (dstOffset != 0) ? UTZFMT_TIME_TYPE_DAYLIGHT : UTZFMT_TIME_TYPE_STANDARD; + } + } + U_ASSERT(U_SUCCESS(status)); + } + return name; } UnicodeString& -TimeZoneFormatImpl::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, - UnicodeString& tzID, UTimeZoneTimeType* timeType /* = NULL */) const { +TimeZoneFormat::format(const Formattable& obj, UnicodeString& appendTo, + FieldPosition& pos, UErrorCode& status) const { + if (U_FAILURE(status)) { + return appendTo; + } + UDate date = Calendar::getNow(); + if (obj.getType() == Formattable::kObject) { + const UObject* formatObj = obj.getObject(); + const TimeZone* tz = dynamic_cast(formatObj); + if (tz == NULL) { + const Calendar* cal = dynamic_cast(formatObj); + if (cal != NULL) { + tz = &cal->getTimeZone(); + date = cal->getTime(status); + } + } + if (tz != NULL) { + int32_t rawOffset, dstOffset; + tz->getOffset(date, FALSE, rawOffset, dstOffset, status); + UnicodeString result; + formatOffsetLocalizedGMT(rawOffset + dstOffset, result, status); + if (U_SUCCESS(status)) { + appendTo.append(result); + if (pos.getField() == UDAT_TIMEZONE_FIELD) { + pos.setBeginIndex(0); + pos.setEndIndex(result.length()); + } + } + } + } + return appendTo; +} + +TimeZone* +TimeZoneFormat::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UTimeZoneFormatTimeType* timeType /*= NULL*/) const { + return parse(style, text, pos, getDefaultParseOptions(), timeType); +} + +TimeZone* +TimeZoneFormat::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + int32_t parseOptions, UTimeZoneFormatTimeType* timeType /* = NULL */) const { if (timeType) { *timeType = UTZFMT_TIME_TYPE_UNKNOWN; } - tzID.setToBogus(); int32_t startIdx = pos.getIndex(); + int32_t maxPos = text.length(); + int32_t offset; - 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; + UBool fallbackLocalizedGMT = FALSE; + if (style == UTZFMT_STYLE_SPECIFIC_LONG || style == UTZFMT_STYLE_SPECIFIC_SHORT + || style == UTZFMT_STYLE_GENERIC_LONG || style == UTZFMT_STYLE_GENERIC_SHORT || style == UTZFMT_STYLE_GENERIC_LOCATION) { + // above styles may use localized gmt format as fallback + fallbackLocalizedGMT = TRUE; + } + + int32_t evaluated = 0; + ParsePosition tmpPos(startIdx); + + int32_t parsedOffset = UNKNOWN_OFFSET; // stores successfully parsed offset for later use + int32_t parsedPos = -1; // stores successfully parsed offset position for later use + + // Try localized GMT format first if necessary + if (fallbackLocalizedGMT) { + UBool hasDigitOffset = FALSE; + offset = parseOffsetLocalizedGMT(text, tmpPos, &hasDigitOffset); + if (tmpPos.getErrorIndex() == -1) { + // Even when the input text was successfully parsed as a localized GMT format text, + // we may still need to evaluate the specified style if - + // 1) GMT zero format was used, and + // 2) The input text was not completely processed + if (tmpPos.getIndex() == maxPos || hasDigitOffset) { + pos.setIndex(tmpPos.getIndex()); + return createTimeZoneForOffset(offset); + } + parsedOffset = offset; + parsedPos = tmpPos.getIndex(); + } + evaluated |= STYLE_FLAG(UTZFMT_STYLE_LOCALIZED_GMT); + + tmpPos.setIndex(startIdx); + tmpPos.setErrorIndex(-1); } - UTimeZoneTimeType parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN; - UnicodeString parsedTzID; UErrorCode status = U_ZERO_ERROR; + UnicodeString tzID; + UTimeZoneFormatTimeType parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN; - if (isGeneric) { - int32_t len = 0; - const TimeZoneGenericNames *gnames = getTimeZoneGenericNames(status); - if (U_SUCCESS(status)) { - len = gnames->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; + // Try the specified style + switch (style) { + case UTZFMT_STYLE_RFC822: + { + offset = parseOffsetRFC822(text, tmpPos); + if (tmpPos.getErrorIndex() == -1) { + pos.setIndex(tmpPos.getIndex()); + return createTimeZoneForOffset(offset); } } - 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); + break; + + case UTZFMT_STYLE_LOCALIZED_GMT: + { + offset = parseOffsetLocalizedGMT(text, tmpPos); + if (tmpPos.getErrorIndex() == -1) { + pos.setIndex(tmpPos.getIndex()); + return createTimeZoneForOffset(offset); } - UTimeZoneNameType nameType = matchInfo->getNameType(bestIdx); - switch (nameType) { - case UTZNM_LONG_STANDARD: - case UTZNM_SHORT_STANDARD: - parsedTimeType = UTZFMT_TIME_TYPE_STANDARD; + } + break; + + case UTZFMT_STYLE_ISO8601: + { + offset = parseOffsetISO8601(text, tmpPos); + if (tmpPos.getErrorIndex() == -1) { + pos.setIndex(tmpPos.getIndex()); + return createTimeZoneForOffset(offset); + } + // Note: ISO 8601 parser also support basic format (without ':'), + // which is same with RFC 822 format. + evaluated |= STYLE_FLAG(UTZFMT_STYLE_RFC822); + } + break; + + case UTZFMT_STYLE_SPECIFIC_LONG: + case UTZFMT_STYLE_SPECIFIC_SHORT: + { + // Specific styles + int32_t nameTypes = 0; + if (style == UTZFMT_STYLE_SPECIFIC_LONG) { + nameTypes = (UTZNM_LONG_STANDARD | UTZNM_LONG_DAYLIGHT); + } else { + U_ASSERT(style == UTZFMT_STYLE_SPECIFIC_SHORT); + nameTypes = (UTZNM_SHORT_STANDARD | UTZNM_SHORT_DAYLIGHT); + } + LocalPointer specificMatches(fTimeZoneNames->find(text, startIdx, nameTypes, status)); + if (U_FAILURE(status)) { + pos.setErrorIndex(startIdx); + return NULL; + } + if (!specificMatches.isNull()) { + int32_t matchIdx = -1; + int32_t matchPos = -1; + for (int32_t i = 0; i < specificMatches->size(); i++) { + matchPos = startIdx + specificMatches->getMatchLengthAt(i); + if (matchPos > parsedPos) { + matchIdx = i; + parsedPos = matchPos; + } + } + if (matchIdx >= 0) { + if (timeType) { + *timeType = getTimeType(specificMatches->getNameTypeAt(matchIdx)); + } + pos.setIndex(matchPos); + getTimeZoneID(specificMatches.getAlias(), matchIdx, tzID); + U_ASSERT(!tzID.isEmpty()); + return TimeZone::createTimeZone(tzID); + } + } + } + break; + + case UTZFMT_STYLE_GENERIC_LONG: + case UTZFMT_STYLE_GENERIC_SHORT: + case UTZFMT_STYLE_GENERIC_LOCATION: + { + int32_t genericNameTypes = 0; + switch (style) { + case UTZFMT_STYLE_GENERIC_LOCATION: + genericNameTypes = UTZGNM_LOCATION; break; - case UTZNM_LONG_DAYLIGHT: - case UTZNM_SHORT_DAYLIGHT: - parsedTimeType = UTZFMT_TIME_TYPE_DAYLIGHT; + + case UTZFMT_STYLE_GENERIC_LONG: + genericNameTypes = UTZGNM_LONG | UTZGNM_LOCATION; break; + + case UTZFMT_STYLE_GENERIC_SHORT: + genericNameTypes = UTZGNM_SHORT | UTZGNM_LOCATION; + break; + default: - parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN; - break; + U_ASSERT(FALSE); + } + + int32_t len = 0; + const TimeZoneGenericNames *gnames = getTimeZoneGenericNames(status); + if (U_SUCCESS(status)) { + len = gnames->findBestMatch(text, startIdx, genericNameTypes, tzID, parsedTimeType, status); + } + if (U_FAILURE(status)) { + pos.setErrorIndex(startIdx); + return NULL; + } + if (len > 0) { + // Found a match + if (timeType) { + *timeType = parsedTimeType; + } + pos.setIndex(startIdx + len); + U_ASSERT(!tzID.isEmpty()); + return TimeZone::createTimeZone(tzID); } - pos.setIndex(startIdx + bestLen); } - delete matchInfo; + break; } - if (timeType) { - *timeType = parsedTimeType; + evaluated |= STYLE_FLAG(style); + + + if (parsedPos > startIdx) { + // When the specified style is one of SPECIFIC_XXX or GENERIC_XXX, we tried to parse the input + // as localized GMT format earlier. If parsedOffset is positive, it means it was successfully + // parsed as localized GMT format, but offset digits were not detected (more specifically, GMT + // zero format). Then, it tried to find a match within the set of display names, but could not + // find a match. At this point, we can safely assume the input text contains the localized + // GMT format. + U_ASSERT(parsedOffset != UNKNOWN_OFFSET); + pos.setIndex(parsedPos); + return createTimeZoneForOffset(parsedOffset); } - tzID.setTo(parsedTzID); - return tzID; + + // Failed to parse the input text as the time zone format in the specified style. + // Check the longest match among other styles below. + U_ASSERT(parsedPos < 0); + U_ASSERT(parsedOffset == UNKNOWN_OFFSET); + tmpPos.setIndex(startIdx); + tmpPos.setErrorIndex(-1); + + // ISO 8601 + if ((evaluated & STYLE_FLAG(UTZFMT_STYLE_ISO8601)) == 0) { + UBool hasDigitOffset = FALSE; + offset = parseOffsetISO8601(text, tmpPos, FALSE, &hasDigitOffset); + if (tmpPos.getErrorIndex() == -1) { + if (tmpPos.getIndex() == maxPos || hasDigitOffset) { + pos.setIndex(tmpPos.getIndex()); + return createTimeZoneForOffset(offset); + } + // Note: When ISO 8601 format contains offset digits, it should not + // collide with other formats (except RFC 822, which is compatible with + // ISO 8601 basic format). However, ISO 8601 UTC format "Z" (single letter) + // may collide with other names. In this case, we need to evaluate other + // names. + parsedOffset = offset; + parsedPos = tmpPos.getIndex(); + U_ASSERT(parsedPos == startIdx + 1); // only when "Z" is used + } + tmpPos.setIndex(startIdx); + tmpPos.setErrorIndex(-1); + } + + // RFC 822 + // Note: ISO 8601 parser supports RFC 822 format. So we do not need to parse + // it as RFC 822 here. This might be changed in future when we support + // strict format option for ISO 8601 or RFC 822. + + //if ((evaluated & STYLE_FLAG(UTZFMT_STYLE_RFC822)) == 0) { + // offset = parseOffsetRFC822(text, tmpPos); + // if (tmpPos.getErrorIndex() == -1) { + // pos.setIndex(tmpPos.getIndex()); + // return createTimeZoneForOffset(offset); + // } + // tmpPos.setIndex(startIdx); + // tmpPos.setErrorIndex(-1); + //} + + // Localized GMT format + if ((evaluated & STYLE_FLAG(UTZFMT_STYLE_LOCALIZED_GMT)) == 0) { + UBool hasDigitOffset = FALSE; + offset = parseOffsetLocalizedGMT(text, tmpPos, &hasDigitOffset); + if (tmpPos.getErrorIndex() == -1) { + if (tmpPos.getIndex() == maxPos || hasDigitOffset) { + pos.setIndex(tmpPos.getIndex()); + return createTimeZoneForOffset(offset); + } + // Evaluate other names - see the comment earlier in this method. + parsedOffset = offset; + parsedPos = tmpPos.getIndex(); + } + } + + // When ParseOption.ALL_STYLES is available, we also try to look all possible display names. + // For example, when style is GENERIC_LONG, "EST" (SPECIFIC_SHORT) is never + // used for America/New_York. With parseAllStyles true, this code parses "EST" + // as America/New_York. + + // Note: Adding all possible names into the trie used by the implementation is quite heavy operation, + // which we want to avoid normally (note that we cache the trie, so this is applicable to the + // first time only as long as the cache does not expire). + if (parseOptions & UTZFMT_PARSE_OPTION_ALL_STYLES) { + // Try all specific names first + LocalPointer spAllMatches(fTimeZoneNames->find(text, startIdx, ALL_SPECIFIC_NAME_TYPES, status)); + if (U_FAILURE(status)) { + pos.setErrorIndex(startIdx); + return NULL; + } + int32_t spMatchIdx = -1; + if (!spAllMatches.isNull()) { + int32_t matchPos = -1; + for (int32_t i = 0; i < spAllMatches->size(); i++) { + matchPos = startIdx + spAllMatches->getMatchLengthAt(i); + if (matchPos > parsedPos) { + spMatchIdx = i; + parsedPos = matchPos; + } + } + } + int32_t genMatchLen = -1; + if (parsedPos < maxPos) { + const TimeZoneGenericNames *gnames = getTimeZoneGenericNames(status); + if (U_SUCCESS(status)) { + genMatchLen = gnames->findBestMatch(text, startIdx, ALL_GENERIC_NAME_TYPES, tzID, parsedTimeType, status); + } + if (U_FAILURE(status)) { + pos.setErrorIndex(startIdx); + return NULL; + } + } + // Pick up better match + if (startIdx + genMatchLen > parsedPos) { + // use generic name match + parsedPos = startIdx + genMatchLen; + if (timeType) { + *timeType = parsedTimeType; + } + pos.setIndex(parsedPos); + U_ASSERT(!tzID.isEmpty()); + return TimeZone::createTimeZone(tzID); + } else if (spMatchIdx >= 0) { + // use specific name match + if (timeType) { + *timeType = getTimeType(spAllMatches->getNameTypeAt(spMatchIdx)); + } + pos.setIndex(parsedPos); + getTimeZoneID(spAllMatches.getAlias(), spMatchIdx, tzID); + U_ASSERT(!tzID.isEmpty()); + return TimeZone::createTimeZone(tzID); + } + } + + if (parsedPos > startIdx) { + // Parsed successfully as one of 'offset' format + U_ASSERT(parsedOffset != UNKNOWN_OFFSET); + pos.setIndex(parsedPos); + return createTimeZoneForOffset(parsedOffset); + } + + pos.setErrorIndex(startIdx); + return NULL; } +void +TimeZoneFormat::parseObject(const UnicodeString& source, Formattable& result, + ParsePosition& parse_pos) const { + result.adoptObject(parse(UTZFMT_STYLE_GENERIC_LOCATION, source, parse_pos, UTZFMT_PARSE_OPTION_ALL_STYLES)); +} + + +// ------------------------------------------------------------------ +// Private zone name format/parse implementation + UnicodeString& -TimeZoneFormatImpl::formatGeneric(const TimeZone& tz, UTimeZoneGenericNameType genType, UDate date, UnicodeString& name) const { +TimeZoneFormat::formatGeneric(const TimeZone& tz, int32_t genType, UDate date, UnicodeString& name) const { UErrorCode status = U_ZERO_ERROR; const TimeZoneGenericNames* gnames = getTimeZoneGenericNames(status); if (U_FAILURE(status)) { @@ -258,12 +971,12 @@ TimeZoneFormatImpl::formatGeneric(const TimeZone& tz, UTimeZoneGenericNameType g } return gnames->getGenericLocationName(UnicodeString(canonicalID), name); } - return gnames->getDisplayName(tz, genType, date, name); + return gnames->getDisplayName(tz, (UTimeZoneGenericNameType)genType, date, name); } UnicodeString& -TimeZoneFormatImpl::formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType, - UDate date, UnicodeString& name, UTimeZoneTimeType *timeType) const { +TimeZoneFormat::formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType, + UDate date, UnicodeString& name, UTimeZoneFormatTimeType *timeType) const { if (fTimeZoneNames == NULL) { name.setToBogus(); return name; @@ -290,236 +1003,1135 @@ TimeZoneFormatImpl::formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType 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; +const TimeZoneGenericNames* +TimeZoneFormat::getTimeZoneGenericNames(UErrorCode& status) const { + if (U_FAILURE(status)) { + return 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); + UBool create; + UMTX_CHECK(&gZoneMetaLock, (fTimeZoneGenericNames == NULL), create); + if (create) { + TimeZoneFormat *nonConstThis = const_cast(this); + umtx_lock(&nonConstThis->fLock); { - if (!gTimeZoneFormatCacheInitialized) { - gTimeZoneFormatCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); - if (U_SUCCESS(status)) { - uhash_setKeyDeleter(gTimeZoneFormatCache, uprv_free); - uhash_setValueDeleter(gTimeZoneFormatCache, deleteTimeZoneFormatCacheEntry); - gTimeZoneFormatCacheInitialized = TRUE; - ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEFORMAT, timeZoneFormat_cleanup); + if (fTimeZoneGenericNames == NULL) { + nonConstThis->fTimeZoneGenericNames = TimeZoneGenericNames::createInstance(fLocale, status); + } + } + umtx_unlock(&nonConstThis->fLock); + } + + return fTimeZoneGenericNames; +} + +// ------------------------------------------------------------------ +// Zone offset format and parse + +UnicodeString& +TimeZoneFormat::formatOffsetRFC822(int32_t offset, UnicodeString& result, UErrorCode& status) const { + if (U_FAILURE(status)) { + result.setToBogus(); + return result; + } + if (offset <= -MAX_OFFSET || offset >= MAX_OFFSET) { + result.setToBogus(); + status = U_ILLEGAL_ARGUMENT_ERROR; + return result; + } + + // Note: FIELDS_HMS as maxFields is an ICU extension. RFC822 specification + // defines exactly 4 digits for the offset field in HHss format. + return formatOffsetWithAsciiDigits(offset, 0, FIELDS_HM, FIELDS_HMS, result); +} + +UnicodeString& +TimeZoneFormat::formatOffsetISO8601(int32_t offset, UnicodeString& result, UErrorCode& status) const { + if (U_FAILURE(status)) { + result.setToBogus(); + return result; + } + if (offset <= -MAX_OFFSET || offset >= MAX_OFFSET) { + result.setToBogus(); + status = U_ILLEGAL_ARGUMENT_ERROR; + return result; + } + + if (offset == 0) { + result.setTo(ISO8601_UTC); + return result; + } + return formatOffsetWithAsciiDigits(offset, ISO8601_SEP, FIELDS_HM, FIELDS_HMS, result); +} + +UnicodeString& +TimeZoneFormat::formatOffsetLocalizedGMT(int32_t offset, UnicodeString& result, UErrorCode& status) const { + if (U_FAILURE(status)) { + result.setToBogus(); + return result; + } + if (offset <= -MAX_OFFSET || offset >= MAX_OFFSET) { + result.setToBogus(); + status = U_ILLEGAL_ARGUMENT_ERROR; + return result; + } + + if (offset == 0) { + result.setTo(fGMTZeroFormat); + return result; + } + + UBool positive = TRUE; + if (offset < 0) { + offset = -offset; + positive = FALSE; + } + + int32_t offsetH = offset / MILLIS_PER_HOUR; + offset = offset % MILLIS_PER_HOUR; + int32_t offsetM = offset / MILLIS_PER_MINUTE; + offset = offset % MILLIS_PER_MINUTE; + int32_t offsetS = offset / MILLIS_PER_SECOND; + + U_ASSERT(offsetH <= MAX_OFFSET_HOUR && offsetM <= MAX_OFFSET_MINUTE && offsetS <= MAX_OFFSET_SECOND); + + const UVector* offsetPatternItems = NULL; + if (positive) { + offsetPatternItems = (offsetS == 0) ? + fGMTOffsetPatternItems[UTZFMT_PAT_POSITIVE_HM] : + fGMTOffsetPatternItems[UTZFMT_PAT_POSITIVE_HMS]; + } else { + offsetPatternItems = (offsetS == 0) ? + fGMTOffsetPatternItems[UTZFMT_PAT_NEGATIVE_HM] : + fGMTOffsetPatternItems[UTZFMT_PAT_NEGATIVE_HMS]; + } + + U_ASSERT(offsetPatternItems != NULL); + + // Building the GMT format string + result.setTo(fGMTPatternPrefix); + + for (int32_t i = 0; i < offsetPatternItems->size(); i++) { + const GMTOffsetField* item = (GMTOffsetField*)offsetPatternItems->elementAt(i); + GMTOffsetField::FieldType type = item->getType(); + + switch (type) { + case GMTOffsetField::TEXT: + result.append(item->getPatternText(), -1); + break; + + case GMTOffsetField::HOUR: + appendOffsetDigits(result, offsetH, item->getWidth()); + break; + + case GMTOffsetField::MINUTE: + appendOffsetDigits(result, offsetM, item->getWidth()); + break; + + case GMTOffsetField::SECOND: + appendOffsetDigits(result, offsetS, item->getWidth()); + break; + } + } + + result.append(fGMTPatternSuffix); + return result; +} + +int32_t +TimeZoneFormat::parseOffsetRFC822(const UnicodeString& text, ParsePosition& pos) const { + int32_t start = pos.getIndex(); + if (start >= text.length()) { + pos.setErrorIndex(start); + return 0; + } + + int32_t sign = 1; + UChar signChar = text.charAt(start); + if (signChar == PLUS) { + sign = 1; + } else if (signChar == MINUS) { + sign = -1; + } else { + // Not an RFC822 offset string + pos.setErrorIndex(start); + return 0; + } + + // Parse digits + pos.setIndex(start + 1); + int32_t offset = parseAbuttingAsciiOffsetFields(text, pos, FIELDS_H, FIELDS_HMS, false); + + if (pos.getErrorIndex() != -1) { + pos.setIndex(start); // reset + pos.setErrorIndex(start); + return 0; + } + + return sign * offset; +} + +int32_t +TimeZoneFormat::parseOffsetISO8601(const UnicodeString& text, ParsePosition& pos) const { + return parseOffsetISO8601(text, pos, FALSE); +} + +int32_t +TimeZoneFormat::parseOffsetLocalizedGMT(const UnicodeString& text, ParsePosition& pos) const { + return parseOffsetLocalizedGMT(text, pos, NULL); +} + + + +// ------------------------------------------------------------------ +// Private zone offset format/parse implementation + +int32_t +TimeZoneFormat::parseOffsetISO8601(const UnicodeString& text, ParsePosition& pos, UBool extendedOnly, UBool* hasDigitOffset /* = NULL */) const { + if (hasDigitOffset) { + *hasDigitOffset = FALSE; + } + int32_t start = pos.getIndex(); + if (start >= text.length()) { + pos.setErrorIndex(start); + return 0; + } + + UChar firstChar = text.charAt(start); + if (firstChar == ISO8601_UTC || firstChar == (UChar)(ISO8601_UTC + 0x20)) { + // "Z" (or "z") - indicates UTC + pos.setIndex(start + 1); + return 0; + } + + int32_t sign = 1; + if (firstChar == PLUS) { + sign = 1; + } else if (firstChar == MINUS) { + sign = -1; + } else { + // Not an ISO 8601 offset string + pos.setErrorIndex(start); + return 0; + } + ParsePosition posOffset(start + 1); + int32_t offset = parseAsciiOffsetFields(text, posOffset, ISO8601_SEP, FIELDS_H, FIELDS_HMS, FALSE); + if (posOffset.getErrorIndex() == -1 && !extendedOnly && (posOffset.getIndex() - start <= 3)) { + // If the text is successfully parsed as extended format with the options above, it can be also parsed + // as basic format. For example, "0230" can be parsed as offset 2:00 (only first digits are valid for + // extended format), but it can be parsed as offset 2:30 with basic format. We use longer result. + ParsePosition posBasic(start + 1); + int32_t tmpOffset = parseAbuttingAsciiOffsetFields(text, posBasic, FIELDS_H, FIELDS_HMS, FALSE); + if (posBasic.getErrorIndex() == -1 && posBasic.getIndex() > posOffset.getIndex()) { + offset = tmpOffset; + posOffset.setIndex(posBasic.getIndex()); + } + } + + if (posOffset.getErrorIndex() != -1) { + pos.setErrorIndex(start); + return 0; + } + + pos.setIndex(posOffset.getIndex()); + if (hasDigitOffset) { + *hasDigitOffset = TRUE; + } + return sign * offset; +} + +int32_t +TimeZoneFormat::parseOffsetLocalizedGMT(const UnicodeString& text, ParsePosition& pos, UBool* hasDigitOffset) const { + int32_t start = pos.getIndex(); + int32_t idx = start; + UBool parsed = FALSE; + int32_t offset = 0; + + if (hasDigitOffset) { + *hasDigitOffset = FALSE; + } + + do { + // Prefix part + int32_t len = fGMTPatternPrefix.length(); + if (len > 0 && text.caseCompare(idx, len, fGMTPatternPrefix, 0) != 0) { + // prefix match failed + break; + } + idx += len; + + // Offset part + offset = parseOffsetFields(text, idx, FALSE, len); + if (len == 0) { + // offset field match failed + break; + } + idx += len; + + // Suffix part + len = fGMTPatternSuffix.length(); + if (len > 0 && text.caseCompare(idx, len, fGMTPatternSuffix, 0) != 0) { + // no suffix match + break; + } + idx += len; + parsed = TRUE; + + } while (false); + + if (parsed) { + if (hasDigitOffset) { + *hasDigitOffset = TRUE; + } + pos.setIndex(idx); + return offset; + } + + // Try the default patterns + int32_t parsedLength = 0; + offset = parseOffsetDefaultLocalizedGMT(text, start, parsedLength); + if (parsedLength > 0) { + if (hasDigitOffset) { + *hasDigitOffset = TRUE; + } + pos.setIndex(start + parsedLength); + return offset; + } + + // Check if this is a GMT zero format + if (text.caseCompare(start, fGMTZeroFormat.length(), fGMTZeroFormat, 0) == 0) { + pos.setIndex(start + fGMTZeroFormat.length()); + return 0; + } + + // Check if this is a default GMT zero format + for (int32_t i = 0; ALT_GMT_STRINGS[i][0] != 0; i++) { + const UChar* defGMTZero = ALT_GMT_STRINGS[i]; + int32_t defGMTZeroLen = u_strlen(defGMTZero); + if (text.caseCompare(start, defGMTZeroLen, defGMTZero, 0) == 0) { + pos.setIndex(start + defGMTZeroLen); + return 0; + } + } + + // Nothing matched + pos.setErrorIndex(start); + return 0; +} + +int32_t +TimeZoneFormat::parseOffsetFields(const UnicodeString& text, int32_t start, UBool minimumHourWidth, int32_t& parsedLen) const { + int32_t offset = 0; + UBool sawVarHourAndAbuttingField = FALSE; + + parsedLen = 0; + + for (int32_t patidx = 0; PARSE_GMT_OFFSET_TYPES[patidx] >= 0; patidx++) { + int32_t gmtPatType = PARSE_GMT_OFFSET_TYPES[patidx]; + int32_t offsetH = 0, offsetM = 0, offsetS = 0; + int32_t idx = start; + UVector* items = fGMTOffsetPatternItems[gmtPatType]; + U_ASSERT(items != NULL); + + UBool failed = FALSE; + for (int32_t i = 0; i < items->size(); i++) { + int32_t tmpParsedLen = 0; + const GMTOffsetField* field = (const GMTOffsetField*)items->elementAt(i); + GMTOffsetField::FieldType fieldType = field->getType(); + if (fieldType == GMTOffsetField::TEXT) { + const UChar* patStr = field->getPatternText(); + tmpParsedLen = u_strlen(patStr); + if (text.caseCompare(idx, tmpParsedLen, patStr, 0) != 0) { + failed = TRUE; + break; + } + idx += tmpParsedLen; + } else { + if (fieldType == GMTOffsetField::HOUR) { + uint8_t minDigits = 1; + uint8_t maxDigits = minimumHourWidth ? 1 : 2; + if (!minimumHourWidth && !sawVarHourAndAbuttingField) { + if (i + 1 < items->size()) { + const GMTOffsetField* nextField = (const GMTOffsetField*)items->elementAt(i + 1); + if (nextField->getType() != GMTOffsetField::TEXT) { + sawVarHourAndAbuttingField = true; + } + } + } + offsetH = parseOffsetFieldWithLocalizedDigits(text, idx, minDigits, maxDigits, 0, MAX_OFFSET_HOUR, tmpParsedLen); + } else if (fieldType == GMTOffsetField::MINUTE) { + offsetM = parseOffsetFieldWithLocalizedDigits(text, idx, 2, 2, 0, MAX_OFFSET_MINUTE, tmpParsedLen); + } else if (fieldType == GMTOffsetField::SECOND) { + offsetS = parseOffsetFieldWithLocalizedDigits(text, idx, 2, 2, 0, MAX_OFFSET_SECOND, tmpParsedLen); + } + + if (tmpParsedLen == 0) { + failed = TRUE; + break; + } + idx += tmpParsedLen; + } + } + if (!failed) { + int32_t sign = (gmtPatType == UTZFMT_PAT_POSITIVE_HM || gmtPatType == UTZFMT_PAT_POSITIVE_HMS) ? 1 : -1; + offset = ((((offsetH * 60) + offsetM) * 60) + offsetS) * 1000 * sign; + parsedLen = idx - start; + break; + } + } + + if (parsedLen == 0 && sawVarHourAndAbuttingField && !minimumHourWidth) { + // When hour field is variable width and another non-literal pattern + // field follows, the parse loop above might eat up the digit from + // the abutting field. For example, with pattern "-Hmm" and input "-100", + // the hour is parsed as -10 and fails to parse minute field. + // + // If this is the case, try parsing the text one more time with the arg + // minimumHourWidth = true + // + // Note: This fallback is not applicable when quitAtHourField is true, because + // the option is designed for supporting the case like "GMT+5". In this case, + // we should get better result for parsing hour digits as much as possible. + + return parseOffsetFields(text, start, true, parsedLen); + } + + return offset; +} + +int32_t +TimeZoneFormat::parseAbuttingOffsetFields(const UnicodeString& text, int32_t start, int32_t& parsedLen) const { + int32_t digits[MAX_OFFSET_DIGITS]; + int32_t parsed[MAX_OFFSET_DIGITS]; // accumulative offsets + + // Parse digits into int[] + int32_t idx = start; + int32_t len = 0; + int32_t numDigits = 0; + for (int32_t i = 0; i < MAX_OFFSET_DIGITS; i++) { + digits[i] = parseSingleLocalizedDigit(text, idx, len); + if (digits[i] < 0) { + break; + } + idx += len; + parsed[i] = idx - start; + numDigits++; + } + + if (numDigits == 0) { + parsedLen = 0; + return 0; + } + + int32_t offset = 0; + while (numDigits > 0) { + int32_t hour = 0; + int32_t min = 0; + int32_t sec = 0; + + U_ASSERT(numDigits > 0 && numDigits <= MAX_OFFSET_DIGITS); + switch (numDigits) { + case 1: // H + hour = digits[0]; + break; + case 2: // HH + hour = digits[0] * 10 + digits[1]; + break; + case 3: // Hmm + hour = digits[0]; + min = digits[1] * 10 + digits[2]; + break; + case 4: // HHmm + hour = digits[0] * 10 + digits[1]; + min = digits[2] * 10 + digits[3]; + break; + case 5: // Hmmss + hour = digits[0]; + min = digits[1] * 10 + digits[2]; + sec = digits[3] * 10 + digits[4]; + break; + case 6: // HHmmss + hour = digits[0] * 10 + digits[1]; + min = digits[2] * 10 + digits[3]; + sec = digits[4] * 10 + digits[5]; + break; + } + if (hour <= MAX_OFFSET_HOUR && min <= MAX_OFFSET_MINUTE && sec <= MAX_OFFSET_SECOND) { + // found a valid combination + offset = hour * MILLIS_PER_HOUR + min * MILLIS_PER_MINUTE + sec * MILLIS_PER_SECOND; + parsedLen = parsed[numDigits - 1]; + break; + } + numDigits--; + } + return offset; +} + +int32_t +TimeZoneFormat::parseOffsetDefaultLocalizedGMT(const UnicodeString& text, int start, int32_t& parsedLen) const { + int32_t idx = start; + int32_t offset = 0; + int32_t parsed = 0; + + do { + // check global default GMT alternatives + int32_t gmtLen = 0; + + for (int32_t i = 0; ALT_GMT_STRINGS[i][0] != 0; i++) { + const UChar* gmt = ALT_GMT_STRINGS[i]; + int32_t len = u_strlen(gmt); + if (text.caseCompare(start, len, gmt, 0) == 0) { + gmtLen = len; + break; + } + } + if (gmtLen == 0) { + break; + } + idx += gmtLen; + + // offset needs a sign char and a digit at minimum + if (idx + 1 >= text.length()) { + break; + } + + // parse sign + int32_t sign = 1; + UChar c = text.charAt(idx); + if (c == PLUS) { + sign = 1; + } else if (c == MINUS) { + sign = -1; + } else { + break; + } + idx++; + + // offset part + // try the default pattern with the separator first + int32_t lenWithSep = 0; + int32_t offsetWithSep = parseDefaultOffsetFields(text, idx, DEFAULT_GMT_OFFSET_SEP, lenWithSep); + if (lenWithSep == text.length() - idx) { + // maximum match + offset = offsetWithSep * sign; + idx += lenWithSep; + } else { + // try abutting field pattern + int32_t lenAbut = 0; + int32_t offsetAbut = parseAbuttingOffsetFields(text, idx, lenAbut); + + if (lenWithSep > lenAbut) { + offset = offsetWithSep * sign; + idx += lenWithSep; + } else { + offset = offsetAbut * sign; + idx += lenAbut; + } + } + parsed = idx - start; + } while (false); + + parsedLen = parsed; + return offset; +} + +int32_t +TimeZoneFormat::parseDefaultOffsetFields(const UnicodeString& text, int32_t start, UChar separator, int32_t& parsedLen) const { + int32_t max = text.length(); + int32_t idx = start; + int32_t len = 0; + int32_t hour = 0, min = 0, sec = 0; + + parsedLen = 0; + + do { + hour = parseOffsetFieldWithLocalizedDigits(text, idx, 1, 2, 0, MAX_OFFSET_HOUR, len); + if (len == 0) { + break; + } + idx += len; + + if (idx + 1 < max && text.charAt(idx) == separator) { + min = parseOffsetFieldWithLocalizedDigits(text, idx + 1, 2, 2, 0, MAX_OFFSET_MINUTE, len); + if (len == 0) { + break; + } + idx += (1 + len); + + if (idx + 1 < max && text.charAt(idx) == separator) { + sec = parseOffsetFieldWithLocalizedDigits(text, idx + 1, 2, 2, 0, MAX_OFFSET_SECOND, len); + if (len == 0) { + break; + } + idx += (1 + len); + } + } + } while (FALSE); + + if (idx == start) { + return 0; + } + + parsedLen = idx - start; + return hour * MILLIS_PER_HOUR + min * MILLIS_PER_MINUTE + sec * MILLIS_PER_SECOND; +} + +int32_t +TimeZoneFormat::parseOffsetFieldWithLocalizedDigits(const UnicodeString& text, int32_t start, uint8_t minDigits, uint8_t maxDigits, uint16_t minVal, uint16_t maxVal, int32_t& parsedLen) const { + parsedLen = 0; + + int32_t decVal = 0; + int32_t numDigits = 0; + int32_t idx = start; + int32_t digitLen = 0; + + while (idx < text.length() && numDigits < maxDigits) { + int32_t digit = parseSingleLocalizedDigit(text, idx, digitLen); + if (digit < 0) { + break; + } + int32_t tmpVal = decVal * 10 + digit; + if (tmpVal > maxVal) { + break; + } + decVal = tmpVal; + numDigits++; + idx += digitLen; + } + + // Note: maxVal is checked in the while loop + if (numDigits < minDigits || decVal < minVal) { + decVal = -1; + numDigits = 0; + } else { + parsedLen = idx - start; + } + + return decVal; +} + +int32_t +TimeZoneFormat::parseSingleLocalizedDigit(const UnicodeString& text, int32_t start, int32_t& len) const { + int32_t digit = -1; + len = 0; + if (start < text.length()) { + UChar32 cp = text.char32At(start); + + // First, try digits configured for this instance + for (int32_t i = 0; i < 10; i++) { + if (cp == fGMTOffsetDigits[i]) { + digit = i; + break; + } + } + // If failed, check if this is a Unicode digit + if (digit < 0) { + int32_t tmp = u_charDigitValue(cp); + digit = (tmp >= 0 && tmp <= 9) ? tmp : -1; + } + + if (digit >= 0) { + int32_t next = text.moveIndex32(start, 1); + len = next - start; + } + } + return digit; +} + +UnicodeString& +TimeZoneFormat::formatOffsetWithAsciiDigits(int32_t offset, UChar sep, OffsetFields minFields, OffsetFields maxFields, UnicodeString& result) { + U_ASSERT(maxFields >= minFields); + U_ASSERT(offset > -MAX_OFFSET && offset < MAX_OFFSET); + + UChar sign = PLUS; + if (offset < 0) { + sign = MINUS; + offset = -offset; + } + result.setTo(sign); + + int fields[3]; + fields[0] = offset / MILLIS_PER_HOUR; + offset = offset % MILLIS_PER_HOUR; + fields[1] = offset / MILLIS_PER_MINUTE; + offset = offset % MILLIS_PER_MINUTE; + fields[2] = offset / MILLIS_PER_SECOND; + + U_ASSERT(fields[0] >= 0 && fields[0] <= MAX_OFFSET_HOUR); + U_ASSERT(fields[1] >= 0 && fields[1] <= MAX_OFFSET_MINUTE); + U_ASSERT(fields[2] >= 0 && fields[2] <= MAX_OFFSET_SECOND); + + int32_t lastIdx = maxFields; + while (lastIdx > minFields) { + if (fields[lastIdx] != 0) { + break; + } + lastIdx--; + } + + for (int32_t idx = 0; idx <= lastIdx; idx++) { + if (sep && idx != 0) { + result.append(sep); + } + result.append((UChar)(0x0030 + fields[idx]/10)); + result.append((UChar)(0x0030 + fields[idx]%10)); + } + + return result; +} + +int32_t +TimeZoneFormat::parseAbuttingAsciiOffsetFields(const UnicodeString& text, ParsePosition& pos, OffsetFields minFields, OffsetFields maxFields, UBool fixedHourWidth) { + int32_t start = pos.getIndex(); + + int32_t minDigits = 2 * (minFields + 1) - (fixedHourWidth ? 0 : 1); + int32_t maxDigits = 2 * (maxFields + 1); + + U_ASSERT(maxDigits <= MAX_OFFSET_DIGITS); + + int32_t digits[MAX_OFFSET_DIGITS]; + int32_t numDigits = 0; + int32_t idx = start; + while (numDigits < maxDigits && idx < text.length()) { + UChar uch = text.charAt(idx); + int32_t digit = DIGIT_VAL(uch); + if (digit < 0) { + break; + } + digits[numDigits] = digit; + numDigits++; + idx++; + } + + if (fixedHourWidth && (numDigits & 1)) { + // Fixed digits, so the number of digits must be even number. Truncating. + numDigits--; + } + + if (numDigits < minDigits) { + pos.setErrorIndex(start); + return 0; + } + + int32_t hour = 0, min = 0, sec = 0; + UBool bParsed = FALSE; + while (numDigits >= minDigits) { + switch (numDigits) { + case 1: //H + hour = digits[0]; + break; + case 2: //HH + hour = digits[0] * 10 + digits[1]; + break; + case 3: //Hmm + hour = digits[0]; + min = digits[1] * 10 + digits[2]; + break; + case 4: //HHmm + hour = digits[0] * 10 + digits[1]; + min = digits[2] * 10 + digits[3]; + break; + case 5: //Hmmss + hour = digits[0]; + min = digits[1] * 10 + digits[2]; + sec = digits[3] * 10 + digits[4]; + break; + case 6: //HHmmss + hour = digits[0] * 10 + digits[1]; + min = digits[2] * 10 + digits[3]; + sec = digits[4] * 10 + digits[5]; + break; + } + + if (hour <= MAX_OFFSET_HOUR && min <= MAX_OFFSET_MINUTE && sec <= MAX_OFFSET_SECOND) { + // Successfully parsed + bParsed = true; + break; + } + + // Truncating + numDigits -= (fixedHourWidth ? 2 : 1); + hour = min = sec = 0; + } + + if (!bParsed) { + pos.setErrorIndex(start); + return 0; + } + pos.setIndex(start + numDigits); + return ((((hour * 60) + min) * 60) + sec) * 1000; +} + +int32_t +TimeZoneFormat::parseAsciiOffsetFields(const UnicodeString& text, ParsePosition& pos, UChar sep, OffsetFields minFields, OffsetFields maxFields, UBool fixedHourWidth) { + int32_t start = pos.getIndex(); + int32_t fieldVal[] = {0, 0, 0}; + int32_t fieldLen[] = {0, -1, -1}; + for (int32_t idx = start, fieldIdx = 0; idx < text.length() && fieldIdx <= maxFields; idx++) { + UChar c = text.charAt(idx); + if (c == sep) { + if (fieldLen[fieldIdx] < 0) { + // next field - expected + fieldLen[fieldIdx] = 0; + } else if (fieldIdx == 0 && !fixedHourWidth) { + // 1 digit hour, move to next field + fieldIdx++; + fieldLen[fieldIdx] = 0; + } else { + // otherwise, premature field + break; + } + continue; + } + int32_t digit = DIGIT_VAL(c); + if (digit < 0) { + // not a digit + break; + } + fieldVal[fieldIdx] = fieldVal[fieldIdx] * 10 + digit; + fieldLen[fieldIdx]++; + if (fieldLen[fieldIdx] >= 2) { + // parsed 2 digits, move to next field + fieldIdx++; + } + } + + int32_t offset = 0; + int32_t parsedLen = 0; + int32_t parsedFields = -1; + do { + // hour + if (fieldLen[0] == 0 || (fieldLen[0] == 1 && fixedHourWidth)) { + break; + } + if (fieldVal[0] > MAX_OFFSET_HOUR) { + if (fixedHourWidth) { + break; + } + offset = (fieldVal[0] / 10) * MILLIS_PER_HOUR; + parsedFields = FIELDS_H; + parsedLen = 1; + break; + } + offset = fieldVal[0] * MILLIS_PER_HOUR; + parsedLen = fieldLen[0]; + parsedFields = FIELDS_H; + + // minute + if (fieldLen[1] != 2 || fieldVal[1] > MAX_OFFSET_MINUTE) { + break; + } + offset += fieldVal[1] * MILLIS_PER_MINUTE; + parsedLen += (1 + fieldLen[1]); + parsedFields = FIELDS_HM; + + // second + if (fieldLen[2] != 2 || fieldVal[2] > MAX_OFFSET_SECOND) { + break; + } + offset += fieldVal[2] * MILLIS_PER_SECOND; + parsedLen += (1 + fieldLen[2]); + parsedFields = FIELDS_HMS; + } while (false); + + if (parsedFields < minFields) { + pos.setErrorIndex(start); + return 0; + } + + pos.setIndex(start + parsedLen); + return offset; +} + +void +TimeZoneFormat::appendOffsetDigits(UnicodeString& buf, int32_t n, uint8_t minDigits) const { + U_ASSERT(n >= 0 && n < 60); + int32_t numDigits = n >= 10 ? 2 : 1; + for (int32_t i = 0; i < minDigits - numDigits; i++) { + buf.append(fGMTOffsetDigits[0]); + } + if (numDigits == 2) { + buf.append(fGMTOffsetDigits[n / 10]); + } + buf.append(fGMTOffsetDigits[n % 10]); +} + +// ------------------------------------------------------------------ +// Private misc +void +TimeZoneFormat::initGMTPattern(const UnicodeString& gmtPattern, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + // This implementation not perfect, but sufficient practically. + int32_t idx = gmtPattern.indexOf(ARG0, ARG0_LEN, 0); + if (idx < 0) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + fGMTPattern.setTo(gmtPattern); + unquote(gmtPattern.tempSubString(0, idx), fGMTPatternPrefix); + unquote(gmtPattern.tempSubString(idx + ARG0_LEN), fGMTPatternSuffix); +} + +UnicodeString& +TimeZoneFormat::unquote(const UnicodeString& pattern, UnicodeString& result) { + if (pattern.indexOf(SINGLEQUOTE) < 0) { + result.setTo(pattern); + return result; + } + result.remove(); + UBool isPrevQuote = FALSE; + UBool inQuote = FALSE; + for (int32_t i = 0; i < pattern.length(); i++) { + UChar c = pattern.charAt(i); + if (c == SINGLEQUOTE) { + if (isPrevQuote) { + result.append(c); + isPrevQuote = FALSE; + } else { + isPrevQuote = TRUE; + } + inQuote = !inQuote; + } else { + isPrevQuote = FALSE; + result.append(c); + } + } + return result; +} + +UVector* +TimeZoneFormat::parseOffsetPattern(const UnicodeString& pattern, OffsetFields required, UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; + } + UVector* result = new UVector(deleteGMTOffsetField, NULL, status); + if (result == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + + int32_t checkBits = 0; + UBool isPrevQuote = FALSE; + UBool inQuote = FALSE; + UnicodeString text; + GMTOffsetField::FieldType itemType = GMTOffsetField::TEXT; + int32_t itemLength = 1; + + for (int32_t i = 0; i < pattern.length(); i++) { + UChar ch = pattern.charAt(i); + if (ch == SINGLEQUOTE) { + if (isPrevQuote) { + text.append(SINGLEQUOTE); + isPrevQuote = FALSE; + } else { + isPrevQuote = TRUE; + if (itemType != GMTOffsetField::TEXT) { + if (GMTOffsetField::isValid(itemType, itemLength)) { + GMTOffsetField* fld = GMTOffsetField::createTimeField(itemType, (uint8_t)itemLength, status); + result->addElement(fld, status); + if (U_FAILURE(status)) { + break; + } + } else { + status = U_ILLEGAL_ARGUMENT_ERROR; + break; + } + itemType = GMTOffsetField::TEXT; + } + } + inQuote = !inQuote; + } else { + isPrevQuote = FALSE; + if (inQuote) { + text.append(ch); + } else { + GMTOffsetField::FieldType tmpType = GMTOffsetField::getTypeByLetter(ch); + if (tmpType != GMTOffsetField::TEXT) { + // an offset time pattern character + if (tmpType == itemType) { + itemLength++; + } else { + if (itemType == GMTOffsetField::TEXT) { + if (text.length() > 0) { + GMTOffsetField* textfld = GMTOffsetField::createText(text, status); + result->addElement(textfld, status); + if (U_FAILURE(status)) { + break; + } + text.remove(); + } + } else { + if (GMTOffsetField::isValid(itemType, itemLength)) { + GMTOffsetField* fld = GMTOffsetField::createTimeField(itemType, itemLength, status); + result->addElement(fld, status); + if (U_FAILURE(status)) { + break; + } + } else { + status = U_ILLEGAL_ARGUMENT_ERROR; + break; + } + } + itemType = tmpType; + itemLength = 1; + checkBits |= tmpType; + } + } else { + // a string literal + if (itemType != GMTOffsetField::TEXT) { + if (GMTOffsetField::isValid(itemType, itemLength)) { + GMTOffsetField* fld = GMTOffsetField::createTimeField(itemType, itemLength, status); + result->addElement(fld, status); + if (U_FAILURE(status)) { + break; + } + } else { + status = U_ILLEGAL_ARGUMENT_ERROR; + break; + } + itemType = GMTOffsetField::TEXT; + } + text.append(ch); } } } - 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; + // handle last item + if (U_SUCCESS(status)) { + if (itemType == GMTOffsetField::TEXT) { + if (text.length() > 0) { + GMTOffsetField* tfld = GMTOffsetField::createText(text, status); + result->addElement(tfld, status); } } else { - // Update the reference count - cacheEntry->refCount++; - cacheEntry->lastAccess = (double)uprv_getUTCtime(); + if (GMTOffsetField::isValid(itemType, itemLength)) { + GMTOffsetField* fld = GMTOffsetField::createTimeField(itemType, itemLength, status); + result->addElement(fld, status); + } else { + status = U_ILLEGAL_ARGUMENT_ERROR; + } } - gAccessCount++; - if (gAccessCount >= SWEEP_INTERVAL) { - // sweep - sweepCache(); - gAccessCount = 0; + + // Check all required fields are set + if (U_SUCCESS(status)) { + int32_t reqBits = 0; + switch (required) { + case FIELDS_H: + reqBits = GMTOffsetField::HOUR; + break; + case FIELDS_HM: + reqBits = GMTOffsetField::HOUR | GMTOffsetField::MINUTE; + break; + case FIELDS_HMS: + reqBits = GMTOffsetField::HOUR | GMTOffsetField::MINUTE | GMTOffsetField::SECOND; + break; + } + if (checkBits == reqBits) { + // all required fields are set, no extra fields + return result; + } } } - 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); - } + // error + delete result; 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; +UnicodeString& +TimeZoneFormat::expandOffsetPattern(const UnicodeString& offsetHM, UnicodeString& result) { + U_ASSERT(u_strlen(DEFAULT_GMT_OFFSET_MINUTE_PATTERN) == 2); + + int32_t idx_mm = offsetHM.indexOf(DEFAULT_GMT_OFFSET_MINUTE_PATTERN, 2, 0); + if (idx_mm < 0) { + // we cannot do anything with this... + result.setTo(offsetHM); + result.append(DEFAULT_GMT_OFFSET_SEP); + result.append(DEFAULT_GMT_OFFSET_SECOND_PATTERN, -1); + return result; } - return tzfmt; + + UnicodeString sep; + int32_t idx_H = offsetHM.tempSubString(0, idx_mm).lastIndexOf(0x0048 /* H */); + if (idx_H >= 0) { + sep = offsetHM.tempSubString(idx_H + 1, idx_mm - (idx_H + 1)); + } + result.setTo(offsetHM.tempSubString(0, idx_mm + 2)); + result.append(sep); + result.append(DEFAULT_GMT_OFFSET_SECOND_PATTERN, -1); + result.append(offsetHM.tempSubString(idx_mm + 2)); + return result; } +void +TimeZoneFormat::initGMTOffsetPatterns(UErrorCode& status) { + for (int32_t type = 0; type <= UTZFMT_PAT_NEGATIVE_HMS; type++) { + switch (type) { + case UTZFMT_PAT_POSITIVE_HM: + case UTZFMT_PAT_NEGATIVE_HM: + fGMTOffsetPatternItems[type] = parseOffsetPattern(fGMTOffsetPatterns[type], FIELDS_HM, status); + break; + case UTZFMT_PAT_POSITIVE_HMS: + case UTZFMT_PAT_NEGATIVE_HMS: + fGMTOffsetPatternItems[type] = parseOffsetPattern(fGMTOffsetPatterns[type], FIELDS_HMS, status); + break; + } + } +} + +UBool +TimeZoneFormat::toCodePoints(const UnicodeString& str, UChar32* codeArray, int32_t size) { + int32_t count = str.countChar32(); + if (count != size) { + return FALSE; + } + + for (int32_t idx = 0, start = 0; idx < size; idx++) { + codeArray[idx] = str.char32At(start); + start = str.moveIndex32(start, 1); + } + + return TRUE; +} + +TimeZone* +TimeZoneFormat::createTimeZoneForOffset(int32_t offset) const { + if (offset == 0) { + // when offset is 0, we should use "Etc/GMT" + return TimeZone::createTimeZone(UnicodeString(TZID_GMT)); + } + return ZoneMeta::createCustomTimeZone(offset); +} + +UTimeZoneFormatTimeType +TimeZoneFormat::getTimeType(UTimeZoneNameType nameType) { + switch (nameType) { + case UTZNM_LONG_STANDARD: + case UTZNM_SHORT_STANDARD: + return UTZFMT_TIME_TYPE_STANDARD; + + case UTZNM_LONG_DAYLIGHT: + case UTZNM_SHORT_DAYLIGHT: + return UTZFMT_TIME_TYPE_DAYLIGHT; + + default: + U_ASSERT(FALSE); + } + return UTZFMT_TIME_TYPE_UNKNOWN; +} + +UnicodeString& +TimeZoneFormat::getTimeZoneID(const TimeZoneNames::MatchInfoCollection* matches, int32_t idx, UnicodeString& tzID) const { + if (!matches->getTimeZoneIDAt(idx, tzID)) { + UnicodeString mzID; + if (matches->getMetaZoneIDAt(idx, mzID)) { + fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, tzID); + } + } + return tzID; +} U_NAMESPACE_END diff --git a/icu4c/source/i18n/tzfmt.h b/icu4c/source/i18n/tzfmt.h deleted file mode 100644 index 8773c18145f..00000000000 --- a/icu4c/source/i18n/tzfmt.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -******************************************************************************* -* 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 -} 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 index 4b19cf713f1..a492305bde7 100644 --- a/icu4c/source/i18n/tzgnames.cpp +++ b/icu4c/source/i18n/tzgnames.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2012, International Business Machines Corporation and +* Copyright (C) 2011-2012, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* */ @@ -28,6 +28,7 @@ #include "zonemeta.h" #include "tznames_impl.h" #include "olsontz.h" +#include "ucln_in.h" U_NAMESPACE_BEGIN @@ -112,7 +113,7 @@ typedef struct GNameInfo { typedef struct GMatchInfo { const GNameInfo* gnameInfo; int32_t matchLength; - UTimeZoneTimeType timeType; + UTimeZoneFormatTimeType timeType; } ZMatchInfo; U_CDECL_END @@ -120,6 +121,20 @@ U_CDECL_END // --------------------------------------------------- // The class stores time zone generic name match information // --------------------------------------------------- +class TimeZoneGenericNameMatchInfo : public UMemory { +public: + TimeZoneGenericNameMatchInfo(UVector* matches); + ~TimeZoneGenericNameMatchInfo(); + + int32_t size() const; + UTimeZoneGenericNameType getGenericNameType(int32_t index) const; + int32_t getMatchLength(int32_t index) const; + UnicodeString& getTimeZoneID(int32_t index, UnicodeString& tzID) const; + +private: + UVector* fMatches; // vector of MatchEntry +}; + TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector* matches) : fMatches(matches) { } @@ -252,14 +267,69 @@ GNameSearchHandler::getMatches(int32_t& maxMatchLen) { return results; } +class TZGNCore : public UMemory { +public: + TZGNCore(const Locale& locale, UErrorCode& status); + virtual ~TZGNCore(); + + UnicodeString& getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, + UDate date, UnicodeString& name) const; + + UnicodeString& getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const; + + int32_t findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, + UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const; + +private: + Locale fLocale; + 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; + + TimeZoneNames::MatchInfoCollection* findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; +}; + + // --------------------------------------------------- -// TimeZoneGenericNames +// TZGNCore - core implmentation of TimeZoneGenericNames // // TimeZoneGenericNames is parallel to TimeZoneNames, // but handles run-time generated time zone names. // This is the main part of this module. // --------------------------------------------------- -TimeZoneGenericNames::TimeZoneGenericNames(const Locale& locale, UErrorCode& status) +TZGNCore::TZGNCore(const Locale& locale, UErrorCode& status) : fLocale(locale), fLock(NULL), fTimeZoneNames(NULL), @@ -275,13 +345,13 @@ TimeZoneGenericNames::TimeZoneGenericNames(const Locale& locale, UErrorCode& sta initialize(locale, status); } -TimeZoneGenericNames::~TimeZoneGenericNames() { +TZGNCore::~TZGNCore() { cleanup(); umtx_destroy(&fLock); } void -TimeZoneGenericNames::initialize(const Locale& locale, UErrorCode& status) { +TZGNCore::initialize(const Locale& locale, UErrorCode& status) { if (U_FAILURE(status)) { return; } @@ -384,7 +454,7 @@ TimeZoneGenericNames::initialize(const Locale& locale, UErrorCode& status) { } void -TimeZoneGenericNames::cleanup() { +TZGNCore::cleanup() { if (fRegionFormat != NULL) { delete fRegionFormat; } @@ -405,8 +475,9 @@ TimeZoneGenericNames::cleanup() { uhash_close(fPartialLocationNamesMap); } + UnicodeString& -TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { +TZGNCore::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { name.setToBogus(); switch (type) { case UTZGNM_LOCATION: @@ -434,14 +505,14 @@ TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameTyp } UnicodeString& -TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { +TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { if (tzCanonicalID.isEmpty()) { name.setToBogus(); return name; } const UChar *locname = NULL; - TimeZoneGenericNames *nonConstThis = const_cast(this); + TZGNCore *nonConstThis = const_cast(this); umtx_lock(&nonConstThis->fLock); { locname = nonConstThis->getGenericLocationName(tzCanonicalID); @@ -451,7 +522,7 @@ TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, if (locname == NULL) { name.setToBogus(); } else { - name.setTo(TRUE, locname, -1); + name.setTo(locname, u_strlen(locname)); } return name; @@ -461,7 +532,7 @@ TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, * This method updates the cache and must be called with a lock */ const UChar* -TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID) { +TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID) { U_ASSERT(!tzCanonicalID.isEmpty()); if (tzCanonicalID.length() > ZID_KEY_MAX) { return NULL; @@ -556,7 +627,7 @@ TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID) } UnicodeString& -TimeZoneGenericNames::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { +TZGNCore::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { U_ASSERT(type == UTZGNM_LONG || type == UTZGNM_SHORT); name.setToBogus(); @@ -694,7 +765,7 @@ TimeZoneGenericNames::formatGenericNonLocationName(const TimeZone& tz, UTimeZone } UnicodeString& -TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID, +TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID, const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName, UnicodeString& name) const { name.setToBogus(); @@ -703,7 +774,7 @@ TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID, } const UChar *uplname = NULL; - TimeZoneGenericNames *nonConstThis = const_cast(this); + TZGNCore *nonConstThis = const_cast(this); umtx_lock(&nonConstThis->fLock); { uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName); @@ -722,7 +793,7 @@ TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID, * This method updates the cache and must be called with a lock */ const UChar* -TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID, +TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID, const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName) { U_ASSERT(!tzCanonicalID.isEmpty()); U_ASSERT(!mzID.isEmpty()); @@ -810,7 +881,7 @@ TimeZoneGenericNames::getPartialLocationName(const UnicodeString& tzCanonicalID, * except initializer. */ void -TimeZoneGenericNames::loadStrings(const UnicodeString& tzCanonicalID) { +TZGNCore::loadStrings(const UnicodeString& tzCanonicalID) { // load the generic location name getGenericLocationName(tzCanonicalID); @@ -851,8 +922,8 @@ TimeZoneGenericNames::loadStrings(const UnicodeString& tzCanonicalID) { } int32_t -TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, - UnicodeString& tzID, UTimeZoneTimeType& timeType, UErrorCode& status) const { +TZGNCore::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, + UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const { timeType = UTZFMT_TIME_TYPE_UNKNOWN; tzID.setToBogus(); @@ -861,13 +932,13 @@ TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, ui } // Find matches in the TimeZoneNames first - TimeZoneNameMatchInfo *tznamesMatches = findTimeZoneNames(text, start, types, status); + TimeZoneNames::MatchInfoCollection *tznamesMatches = findTimeZoneNames(text, start, types, status); if (U_FAILURE(status)) { return 0; } int32_t bestMatchLen = 0; - UTimeZoneTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; + UTimeZoneFormatTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; UnicodeString bestMatchTzID; // UBool isLongStandard = FALSE; // workaround - see the comments below UBool isStandard = FALSE; // TODO: Temporary hack (on hack) for short standard name/location name conflict (found in zh_Hant), should be removed after CLDR 21m1 integration @@ -875,17 +946,19 @@ TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, ui if (tznamesMatches != NULL) { UnicodeString mzID; for (int32_t i = 0; i < tznamesMatches->size(); i++) { - int32_t len = tznamesMatches->getMatchLength(i); + int32_t len = tznamesMatches->getMatchLengthAt(i); if (len > bestMatchLen) { bestMatchLen = len; - tznamesMatches->getTimeZoneID(i, bestMatchTzID); - if (bestMatchTzID.isEmpty()) { + if (!tznamesMatches->getTimeZoneIDAt(i, bestMatchTzID)) { // name for a meta zone - tznamesMatches->getMetaZoneID(i, mzID); - U_ASSERT(mzID.length() > 0); - fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, bestMatchTzID); + if (tznamesMatches->getMetaZoneIDAt(i, mzID)) { + fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, bestMatchTzID); + } + } + UTimeZoneNameType nameType = tznamesMatches->getNameTypeAt(i); + if (U_FAILURE(status)) { + break; } - UTimeZoneNameType nameType = tznamesMatches->getNameType(i); switch (nameType) { case UTZNM_LONG_STANDARD: // isLongStandard = TRUE; @@ -903,6 +976,9 @@ TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, ui } } delete tznamesMatches; + if (U_FAILURE(status)) { + return 0; + } if (bestMatchLen == (text.length() - start)) { // Full match @@ -967,10 +1043,10 @@ TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, ui } TimeZoneGenericNameMatchInfo* -TimeZoneGenericNames::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { +TZGNCore::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { GNameSearchHandler handler(types); - TimeZoneGenericNames *nonConstThis = const_cast(this); + TZGNCore *nonConstThis = const_cast(this); umtx_lock(&nonConstThis->fLock); { @@ -1051,10 +1127,8 @@ TimeZoneGenericNames::findLocal(const UnicodeString& text, int32_t start, uint32 return gmatchInfo; } -TimeZoneNameMatchInfo* -TimeZoneGenericNames::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { - TimeZoneNameMatchInfo *matchInfo = NULL; - +TimeZoneNames::MatchInfoCollection* +TZGNCore::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { // Check if the target name typs is really in the TimeZoneNames uint32_t nameTypes = 0; if (types & UTZGNM_LONG) { @@ -1066,10 +1140,236 @@ TimeZoneGenericNames::findTimeZoneNames(const UnicodeString& text, int32_t start if (types) { // Find matches in the TimeZoneNames - matchInfo = fTimeZoneNames->find(text, start, nameTypes, status); + return fTimeZoneNames->find(text, start, nameTypes, status); } - return matchInfo; + return NULL; +} + +typedef struct TZGNCoreRef { + TZGNCore* obj; + int32_t refCount; + double lastAccess; +} TZGNCoreRef; + +// TZGNCore object cache handling +static UMTX gTZGNLock = NULL; +static UHashtable *gTZGNCoreCache = NULL; +static UBool gTZGNCoreCacheInitialized = FALSE; + +// Access count - incremented every time up to SWEEP_INTERVAL, +// then reset to 0 +static int32_t gAccessCount = 0; + +// Interval for calling the cache sweep function - every 100 times +#define SWEEP_INTERVAL 100 + +// Cache expiration in millisecond. When a cached entry is no +// longer referenced and exceeding this threshold since last +// access time, then the cache entry will be deleted by the sweep +// function. For now, 3 minutes. +#define CACHE_EXPIRATION 180000.0 + +U_CDECL_BEGIN +/** + * Cleanup callback func + */ +static UBool U_CALLCONV tzgnCore_cleanup(void) +{ + umtx_destroy(&gTZGNLock); + + if (gTZGNCoreCache != NULL) { + uhash_close(gTZGNCoreCache); + gTZGNCoreCache = NULL; + } + gTZGNCoreCacheInitialized = FALSE; + return TRUE; +} + +/** + * Deleter for TZGNCoreRef + */ +static void U_CALLCONV +deleteTZGNCoreRef(void *obj) { + icu::TZGNCoreRef *entry = (icu::TZGNCoreRef*)obj; + delete (icu::TZGNCore*) entry->obj; + uprv_free(entry); +} +U_CDECL_END + +/** + * Function used for removing unreferrenced cache entries exceeding + * the expiration time. This function must be called with in the mutex + * block. + */ +static void sweepCache() { + int32_t pos = -1; + const UHashElement* elem; + double now = (double)uprv_getUTCtime(); + + while ((elem = uhash_nextElement(gTZGNCoreCache, &pos))) { + TZGNCoreRef *entry = (TZGNCoreRef *)elem->value.pointer; + if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) { + // delete this entry + uhash_removeElement(gTZGNCoreCache, elem); + } + } +} + +TimeZoneGenericNames::TimeZoneGenericNames() +: fRef(0) { +} + +TimeZoneGenericNames::~TimeZoneGenericNames() { + umtx_lock(&gTZGNLock); + { + U_ASSERT(fRef->refCount > 0); + // Just decrement the reference count + fRef->refCount--; + } + umtx_unlock(&gTZGNLock); +} + +TimeZoneGenericNames* +TimeZoneGenericNames::createInstance(const Locale& locale, UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; + } + TimeZoneGenericNames* instance = new TimeZoneGenericNames(); + if (instance == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + + UBool initialized; + UMTX_CHECK(&gTZGNLock, gTZGNCoreCacheInitialized, initialized); + if (!initialized) { + // Create empty hashtable + umtx_lock(&gTZGNLock); + { + if (!gTZGNCoreCacheInitialized) { + gTZGNCoreCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); + if (U_SUCCESS(status)) { + uhash_setKeyDeleter(gTZGNCoreCache, uprv_free); + uhash_setValueDeleter(gTZGNCoreCache, deleteTZGNCoreRef); + gTZGNCoreCacheInitialized = TRUE; + ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES, tzgnCore_cleanup); + } + } + } + umtx_unlock(&gTZGNLock); + + if (U_FAILURE(status)) { + return NULL; + } + } + + // Check the cache, if not available, create new one and cache + TZGNCoreRef *cacheEntry = NULL; + umtx_lock(&gTZGNLock); + { + const char *key = locale.getName(); + cacheEntry = (TZGNCoreRef *)uhash_get(gTZGNCoreCache, key); + if (cacheEntry == NULL) { + TZGNCore *tzgnCore = NULL; + char *newKey = NULL; + + tzgnCore = new TZGNCore(locale, status); + if (tzgnCore == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } + if (U_SUCCESS(status)) { + newKey = (char *)uprv_malloc(uprv_strlen(key) + 1); + if (newKey == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + uprv_strcpy(newKey, key); + } + } + if (U_SUCCESS(status)) { + cacheEntry = (TZGNCoreRef *)uprv_malloc(sizeof(TZGNCoreRef)); + if (cacheEntry == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + cacheEntry->obj = tzgnCore; + cacheEntry->refCount = 1; + cacheEntry->lastAccess = (double)uprv_getUTCtime(); + + uhash_put(gTZGNCoreCache, newKey, cacheEntry, &status); + } + } + if (U_FAILURE(status)) { + if (tzgnCore != NULL) { + delete tzgnCore; + } + if (newKey != NULL) { + uprv_free(newKey); + } + if (cacheEntry != NULL) { + uprv_free(cacheEntry); + } + cacheEntry = NULL; + } + } else { + // Update the reference count + cacheEntry->refCount++; + cacheEntry->lastAccess = (double)uprv_getUTCtime(); + } + gAccessCount++; + if (gAccessCount >= SWEEP_INTERVAL) { + // sweep + sweepCache(); + gAccessCount = 0; + } + } + umtx_unlock(&gTZGNLock); + + if (cacheEntry == NULL) { + delete instance; + return NULL; + } + + instance->fRef = cacheEntry; + return instance; +} + +UBool +TimeZoneGenericNames::operator==(const TimeZoneGenericNames& other) const { + // Just compare if the other object also use the same + // ref entry + return fRef == other.fRef; +} + +TimeZoneGenericNames* +TimeZoneGenericNames::clone() const { + TimeZoneGenericNames* other = new TimeZoneGenericNames(); + if (other) { + umtx_lock(&gTZGNLock); + { + // Just increments the reference count + fRef->refCount++; + other->fRef = fRef; + } + umtx_unlock(&gTZGNLock); + } + return other; +} + +UnicodeString& +TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, + UDate date, UnicodeString& name) const { + return fRef->obj->getDisplayName(tz, type, date, name); +} + +UnicodeString& +TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { + return fRef->obj->getGenericLocationName(tzCanonicalID, name); +} + +int32_t +TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, + UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const { + return fRef->obj->findBestMatch(text, start, types, tzID, timeType, status); } U_NAMESPACE_END diff --git a/icu4c/source/i18n/tzgnames.h b/icu4c/source/i18n/tzgnames.h index 49a471f840c..66f9e8423d8 100644 --- a/icu4c/source/i18n/tzgnames.h +++ b/icu4c/source/i18n/tzgnames.h @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2011, International Business Machines Corporation and * +* Copyright (C) 2011-2012, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -17,13 +17,9 @@ #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" +#include "unicode/tzfmt.h" +#include "unicode/tznames.h" U_CDECL_BEGIN @@ -38,76 +34,30 @@ 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 -}; +struct TZGNCoreRef; class U_I18N_API TimeZoneGenericNames : public UMemory { public: - TimeZoneGenericNames(const Locale& locale, UErrorCode& status); virtual ~TimeZoneGenericNames(); + static TimeZoneGenericNames* createInstance(const Locale& locale, UErrorCode& status); + + virtual UBool operator==(const TimeZoneGenericNames& other) const; + virtual UBool operator!=(const TimeZoneGenericNames& other) const {return !operator==(other);}; + virtual TimeZoneGenericNames* clone() const; + 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; + UnicodeString& tzID, UTimeZoneFormatTimeType& 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; + TimeZoneGenericNames(); + TZGNCoreRef* fRef; }; U_NAMESPACE_END diff --git a/icu4c/source/i18n/tznames.cpp b/icu4c/source/i18n/tznames.cpp index 8660bd1ad3a..55ee4168b77 100644 --- a/icu4c/source/i18n/tznames.cpp +++ b/icu4c/source/i18n/tznames.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2011, International Business Machines Corporation and * +* Copyright (C) 2011-2012, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -9,18 +9,18 @@ #if !UCONFIG_NO_FORMATTING -#include "tznames.h" -#include "tznames_impl.h" - #include "unicode/locid.h" +#include "unicode/tznames.h" #include "unicode/uenum.h" #include "cmemory.h" #include "cstring.h" #include "putilimp.h" +#include "tznames_impl.h" #include "uassert.h" #include "ucln_in.h" #include "uhash.h" #include "umutex.h" +#include "uvector.h" U_NAMESPACE_BEGIN @@ -102,14 +102,18 @@ static void sweepCache() { } } -TimeZoneNameMatchInfo::~TimeZoneNameMatchInfo() { -} - +// --------------------------------------------------- +// TimeZoneNamesDelegate +// --------------------------------------------------- class TimeZoneNamesDelegate : public TimeZoneNames { public: TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status); virtual ~TimeZoneNamesDelegate(); + virtual UBool operator==(const TimeZoneNames& other) const; + virtual UBool operator!=(const TimeZoneNames& other) const {return !operator==(other);}; + virtual TimeZoneNames* clone() const; + StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const; StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const; UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const; @@ -120,11 +124,16 @@ public: UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; - TimeZoneNameMatchInfo* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; + MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; private: + TimeZoneNamesDelegate(); TimeZoneNamesCacheEntry* fTZnamesCacheEntry; }; +TimeZoneNamesDelegate::TimeZoneNamesDelegate() +: fTZnamesCacheEntry(0) { +} + TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) { UBool initialized; UMTX_CHECK(&gTimeZoneNamesLock, gTimeZoneNamesCacheInitialized, initialized); @@ -215,13 +224,44 @@ TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& s TimeZoneNamesDelegate::~TimeZoneNamesDelegate() { umtx_lock(&gTimeZoneNamesLock); { - U_ASSERT(fTZnamesCacheEntry->refCount > 0); - // Just decrement the reference count - fTZnamesCacheEntry->refCount--; + if (fTZnamesCacheEntry) { + U_ASSERT(fTZnamesCacheEntry->refCount > 0); + // Just decrement the reference count + fTZnamesCacheEntry->refCount--; + } } umtx_unlock(&gTimeZoneNamesLock); } +UBool +TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const { + if (this == &other) { + return TRUE; + } + // Just compare if the other object also use the same + // cache entry + const TimeZoneNamesDelegate* rhs = dynamic_cast(&other); + if (rhs) { + return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry; + } + return FALSE; +} + +TimeZoneNames* +TimeZoneNamesDelegate::clone() const { + TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate(); + if (other != NULL) { + umtx_lock(&gTimeZoneNamesLock); + { + // Just increment the reference count + fTZnamesCacheEntry->refCount++; + other->fTZnamesCacheEntry = fTZnamesCacheEntry; + } + umtx_unlock(&gTimeZoneNamesLock); + } + return other; +} + StringEnumeration* TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const { return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status); @@ -257,12 +297,15 @@ TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, Unicod return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name); } -TimeZoneNameMatchInfo* +TimeZoneNames::MatchInfoCollection* TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { return fTZnamesCacheEntry->names->find(text, start, types, status); } - +// --------------------------------------------------- +// TimeZoneNames base class +// --------------------------------------------------- +UOBJECT_DEFINE_NO_RTTI_IMPLEMENTATION(TimeZoneNames) TimeZoneNames::~TimeZoneNames() { } @@ -302,5 +345,146 @@ TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, return name; } + +struct MatchInfo : UMemory { + UTimeZoneNameType nameType; + UnicodeString id; + int32_t matchLength; + UBool isTZID; + + MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) { + this->nameType = nameType; + this->matchLength = matchLength; + if (tzID != NULL) { + this->id.setTo(*tzID); + this->isTZID = TRUE; + } else { + this->id.setTo(*mzID); + this->isTZID = FALSE; + } + } +}; + +U_CDECL_BEGIN +static void U_CALLCONV +deleteMatchInfo(void *obj) { + delete static_cast(obj); +} +U_CDECL_END + +// --------------------------------------------------- +// MatchInfoCollection class +// --------------------------------------------------- +TimeZoneNames::MatchInfoCollection::MatchInfoCollection() +: fMatches(NULL) { +} + +TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() { + if (fMatches != NULL) { + delete fMatches; + } +} + +void +TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength, + const UnicodeString& tzID, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, &tzID, NULL); + if (matchInfo == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + matches(status)->addElement(matchInfo, status); + if (U_FAILURE(status)) { + delete matchInfo; + } +} + +void +TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength, + const UnicodeString& mzID, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, NULL, &mzID); + if (matchInfo == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + matches(status)->addElement(matchInfo, status); + if (U_FAILURE(status)) { + delete matchInfo; + } +} + +int32_t +TimeZoneNames::MatchInfoCollection::size() const { + if (fMatches == NULL) { + return 0; + } + return fMatches->size(); +} + +UTimeZoneNameType +TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const { + const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); + if (match) { + return match->nameType; + } + return UTZNM_UNKNOWN; +} + +int32_t +TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const { + const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); + if (match) { + return match->matchLength; + } + return 0; +} + +UBool +TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const { + tzID.remove(); + const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); + if (match && match->isTZID) { + tzID.setTo(match->id); + return TRUE; + } + return FALSE; +} + +UBool +TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const { + mzID.remove(); + const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); + if (match && !match->isTZID) { + mzID.setTo(match->id); + return TRUE; + } + return FALSE; +} + +UVector* +TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) { + if (U_FAILURE(status)) { + return NULL; + } + if (fMatches != NULL) { + return fMatches; + } + fMatches = new UVector(deleteMatchInfo, NULL, status); + if (fMatches == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else if (U_FAILURE(status)) { + delete fMatches; + fMatches = NULL; + } + return fMatches; +} + + U_NAMESPACE_END #endif diff --git a/icu4c/source/i18n/tznames.h b/icu4c/source/i18n/tznames.h deleted file mode 100644 index 3d8c3cd7162..00000000000 --- a/icu4c/source/i18n/tznames.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -******************************************************************************* -* 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 -} 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; -}; - -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; -}; - -U_NAMESPACE_END -#endif -#endif diff --git a/icu4c/source/i18n/tznames_impl.cpp b/icu4c/source/i18n/tznames_impl.cpp index ba2d0a10a21..a699ec8c0b4 100644 --- a/icu4c/source/i18n/tznames_impl.cpp +++ b/icu4c/source/i18n/tznames_impl.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2011, International Business Machines Corporation and +* Copyright (C) 2011-2012, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* * @@ -52,7 +52,7 @@ static const UTimeZoneNameType ALL_NAME_TYPES[] = { #define DEFAULT_CHARACTERNODE_CAPACITY 1 // --------------------------------------------------- -// CaracterNode class implementation +// CharacterNode class implementation // --------------------------------------------------- void CharacterNode::clear() { uprv_memset(this, 0, sizeof(*this)); @@ -722,81 +722,6 @@ typedef struct ZMatchInfo { } 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 @@ -807,16 +732,16 @@ public: virtual ~ZNameSearchHandler(); UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status); - UVector* getMatches(int32_t& maxMatchLen); + TimeZoneNames::MatchInfoCollection* getMatches(int32_t& maxMatchLen); private: uint32_t fTypes; - UVector* fResults; int32_t fMaxMatchLen; + TimeZoneNames::MatchInfoCollection* fResults; }; ZNameSearchHandler::ZNameSearchHandler(uint32_t types) -: fTypes(types), fResults(NULL), fMaxMatchLen(0) { +: fTypes(types), fMaxMatchLen(0), fResults(NULL) { } ZNameSearchHandler::~ZNameSearchHandler() { @@ -840,28 +765,21 @@ ZNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, if ((nameinfo->type & fTypes) != 0) { // matches a requested type if (fResults == NULL) { - fResults = new UVector(uprv_free, NULL, status); + fResults = new TimeZoneNames::MatchInfoCollection(); if (fResults == NULL) { status = U_MEMORY_ALLOCATION_ERROR; } } if (U_SUCCESS(status)) { U_ASSERT(fResults != NULL); - ZMatchInfo *zmatch = (ZMatchInfo *)uprv_malloc(sizeof(ZMatchInfo)); - if (zmatch == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; + if (nameinfo->tzID) { + fResults->addZone(nameinfo->type, matchLength, UnicodeString(nameinfo->tzID, -1), status); } 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; - } - } + U_ASSERT(nameinfo->mzID); + fResults->addMetaZone(nameinfo->type, matchLength, UnicodeString(nameinfo->mzID, -1), status); + } + if (U_SUCCESS(status) && matchLength > fMaxMatchLen) { + fMaxMatchLen = matchLength; } } } @@ -870,10 +788,10 @@ ZNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, return TRUE; } -UVector* +TimeZoneNames::MatchInfoCollection* ZNameSearchHandler::getMatches(int32_t& maxMatchLen) { // give the ownership to the caller - UVector *results = fResults; + TimeZoneNames::MatchInfoCollection* results = fResults; maxMatchLen = fMaxMatchLen; // reset @@ -1012,6 +930,21 @@ TimeZoneNamesImpl::cleanup() { } } +UBool +TimeZoneNamesImpl::operator==(const TimeZoneNames& other) const { + if (this == &other) { + return TRUE; + } + // No implementation for now + return FALSE; +} + +TimeZoneNames* +TimeZoneNamesImpl::clone() const { + UErrorCode status = U_ZERO_ERROR; + return new TimeZoneNamesImpl(fLocale, status); +} + StringEnumeration* TimeZoneNamesImpl::getAvailableMetaZoneIDs(UErrorCode& status) const { if (U_FAILURE(status)) { @@ -1307,7 +1240,7 @@ TimeZoneNamesImpl::loadTimeZoneNames(const UnicodeString& tzID) { return tznames; } -TimeZoneNameMatchInfo* +TimeZoneNames::MatchInfoCollection* TimeZoneNamesImpl::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { ZNameSearchHandler handler(types); @@ -1323,24 +1256,14 @@ TimeZoneNamesImpl::find(const UnicodeString& text, int32_t start, uint32_t types return NULL; } - TimeZoneNameMatchInfoImpl *matchInfo = NULL; - int32_t maxLen = 0; - UVector *results = handler.getMatches(maxLen); - if (results != NULL && ((maxLen == (text.length() - start)) || fNamesTrieFullyLoaded)) { + TimeZoneNames::MatchInfoCollection* matches = handler.getMatches(maxLen); + if (matches != 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; + return matches; } - if (results != NULL) { - delete results; - } + delete matches; // All names are not yet loaded into the trie umtx_lock(&nonConstThis->fLock); @@ -1380,20 +1303,9 @@ TimeZoneNamesImpl::find(const UnicodeString& text, int32_t start, uint32_t types } 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; + return handler.getMatches(maxLen); } - U_NAMESPACE_END diff --git a/icu4c/source/i18n/tznames_impl.h b/icu4c/source/i18n/tznames_impl.h index 909a2926d58..1e64710bebb 100644 --- a/icu4c/source/i18n/tznames_impl.h +++ b/icu4c/source/i18n/tznames_impl.h @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (C) 2011, International Business Machines Corporation and * + * Copyright (C) 2011-2012, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -18,7 +18,7 @@ #if !UCONFIG_NO_FORMATTING -#include "tznames.h" +#include "unicode/tznames.h" #include "unicode/ures.h" #include "unicode/locid.h" #include "uhash.h" @@ -168,6 +168,9 @@ public: virtual ~TimeZoneNamesImpl(); + virtual UBool operator==(const TimeZoneNames& other) const; + virtual TimeZoneNames* clone() const; + StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const; StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const; @@ -179,7 +182,7 @@ public: UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; - TimeZoneNameMatchInfo* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; + TimeZoneNames::MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; private: diff --git a/icu4c/source/i18n/ucln_in.h b/icu4c/source/i18n/ucln_in.h index ffe9247aace..152674c9933 100644 --- a/icu4c/source/i18n/ucln_in.h +++ b/icu4c/source/i18n/ucln_in.h @@ -1,7 +1,7 @@ /* ****************************************************************************** * * -* Copyright (C) 2001-2011, International Business Machines * +* Copyright (C) 2001-2012, International Business Machines * * Corporation and others. All Rights Reserved. * * * ****************************************************************************** @@ -33,7 +33,7 @@ typedef enum ECleanupI18NType { UCLN_I18N_HEBREW_CALENDAR, UCLN_I18N_ASTRO_CALENDAR, UCLN_I18N_CALENDAR, - UCLN_I18N_TIMEZONEFORMAT, + UCLN_I18N_TIMEZONEGENERICNAMES, UCLN_I18N_TIMEZONENAMES, UCLN_I18N_ZONEMETA, UCLN_I18N_TIMEZONE, diff --git a/icu4c/source/i18n/unicode/dtfmtsym.h b/icu4c/source/i18n/unicode/dtfmtsym.h index cdf789e1ef9..dd511bc2ff4 100644 --- a/icu4c/source/i18n/unicode/dtfmtsym.h +++ b/icu4c/source/i18n/unicode/dtfmtsym.h @@ -713,30 +713,6 @@ private: 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}" - */ - UnicodeString fGmtFormat; - - /** - * Pattern strings used for formatting zone offset in a localized time zone GMT string. - */ - UnicodeString *fGmtHourFormats; - int32_t fGmtHourFormatsCount; - - enum GMTHourType { - GMT_NEGATIVE_HMS = 0, - GMT_NEGATIVE_HM, - GMT_POSITIVE_HMS, - GMT_POSITIVE_HM, - GMT_HOUR_COUNT - }; - /** * Localized date-time pattern characters. For example: use 'u' as 'y'. */ diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h index 61c9db71925..f9677d4ea8a 100644 --- a/icu4c/source/i18n/unicode/smpdtfmt.h +++ b/icu4c/source/i18n/unicode/smpdtfmt.h @@ -94,6 +94,7 @@ class TimeZoneFormat; * zzzz time zone (Text) Pacific Standard Time * Z time zone (RFC 822) (Number) -0800 * ZZZZ time zone (RFC 822) (Text & Number) GMT-08:00 + * ZZZZZ time zone (ISO 8601) (Text & Number) -08:00 & Z * v time zone (generic) (Text) PT * vvvv time zone (generic) (Text) Pacific Time * V time zone (abreviation) (Text) PST @@ -831,6 +832,31 @@ public: */ virtual int32_t getDefaultContext(UDateFormatContextType type, UErrorCode& status) const; +#ifndef U_HIDE_INTERNAL_API + /** + * Sets the TimeZoneFormat to be used by this date/time formatter. + * The caller should not delete the TimeZoneFormat object after + * it is adopted by this call. + * @param timeZoneFormatToAdopt The TimeZoneFormat object to be adopted. + * @internal ICU 49 technology preview + */ + virtual void adoptTimeZoneFormat(TimeZoneFormat* timeZoneFormatToAdopt); + + /** + * Sets the TimeZoneFormat to be used by this date/time formatter. + * @param newTimeZoneFormat The TimeZoneFormat object to copy. + * @internal ICU 49 technology preview + */ + virtual void setTimeZoneFormat(const TimeZoneFormat& newTimeZoneFormat); + + /** + * Gets the time zone format object associated with this date/time formatter. + * @return the time zone format associated with this date/time formatter. + * @internal ICU 49 technology preview + */ + virtual const TimeZoneFormat* getTimeZoneFormat(void) const; +#endif /* U_HIDE_INTERNAL_API */ + #ifndef U_HIDE_INTERNAL_API /** * This is for ICU internal use only. Please do not use. @@ -1127,22 +1153,6 @@ private: */ int32_t skipUWhiteSpace(const UnicodeString& text, int32_t pos) const; - /** - * Private methods for formatting/parsing GMT string - */ - void appendGMT(NumberFormat *currentNumberFormat,UnicodeString &appendTo, Calendar& cal, UErrorCode& status) const; - void formatGMTDefault(NumberFormat *currentNumberFormat,UnicodeString &appendTo, int32_t offset) const; - int32_t parseGMT(const UnicodeString &text, ParsePosition &pos) const; - int32_t parseGMTDefault(const UnicodeString &text, ParsePosition &pos) const; - UBool isDefaultGMTFormat() const; - - void formatRFC822TZ(UnicodeString &appendTo, int32_t offset) const; - - /** - * Initialize MessageFormat instances used for GMT formatting/parsing - */ - void initGMTFormatters(UErrorCode &status); - /** * Initialize NumberFormat instances used for numbering system overrides. */ @@ -1229,13 +1239,7 @@ private: */ /*transient*/ int32_t fDefaultCenturyStartYear; - enum ParsedTZType { - TZTYPE_UNK, - TZTYPE_STD, - TZTYPE_DST - }; - - ParsedTZType tztype; // here to avoid api change + int32_t tztype; // here to avoid api change typedef struct NSOverride { NumberFormat *nf; @@ -1243,34 +1247,6 @@ private: NSOverride *next; } NSOverride; - /* - * MessageFormat instances used for localized GMT format - */ - enum { - kGMTNegativeHMS = 0, - kGMTNegativeHM, - kGMTPositiveHMS, - kGMTPositiveHM, - - kNumGMTFormatters - }; - enum { - kGMTNegativeHMSMinLenIdx = 0, - kGMTPositiveHMSMinLenIdx, - - kNumGMTFormatMinLengths - }; - - MessageFormat **fGMTFormatters; - // If a GMT hour format has a second field, we need to make sure - // the length of input localized GMT string must match the expected - // length. Otherwise, sub DateForamt handling offset format may - // unexpectedly success parsing input GMT string without second field. - // See #6880 about this issue. - // TODO: SimpleDateFormat should provide an option to invalidate - // - int32_t fGMTFormatHmsMinLen[kNumGMTFormatMinLengths]; - NumberFormat **fNumberFormatters; NSOverride *fOverrideList; diff --git a/icu4c/source/i18n/unicode/tzfmt.h b/icu4c/source/i18n/unicode/tzfmt.h new file mode 100644 index 00000000000..d915850434b --- /dev/null +++ b/icu4c/source/i18n/unicode/tzfmt.h @@ -0,0 +1,829 @@ +/* +******************************************************************************* +* Copyright (C) 2011-2012, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ +#ifndef __TZFMT_H +#define __TZFMT_H + +/** + * \file + * \brief C++ API: TimeZoneFormat + */ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef U_HIDE_INTERNAL_API + +#include "unicode/format.h" +#include "unicode/timezone.h" +#include "unicode/tznames.h" + +U_CDECL_BEGIN +/** + * Constants for time zone display format style used by format/parse APIs + * in TimeZoneFormat. + * @internal ICU 49 technology preview + */ +typedef enum UTimeZoneFormatStyle { + /** + * Generic location format, such as "United States Time (New York)", "Italy Time" + * @internal ICU 49 technology preview + */ + UTZFMT_STYLE_GENERIC_LOCATION, + /** + * Generic long non-location format, such as "Eastern Time". + * @internal ICU 49 technology preview + */ + UTZFMT_STYLE_GENERIC_LONG, + /** + * Generic short non-location format, such as "ET". + * @internal ICU 49 technology preview + */ + UTZFMT_STYLE_GENERIC_SHORT, + /** + * Specific long format, such as "Eastern Standard Time". + * @internal ICU 49 technology preview + */ + UTZFMT_STYLE_SPECIFIC_LONG, + /** + * Specific short format, such as "EST", "PDT". + * @internal ICU 49 technology preview + */ + UTZFMT_STYLE_SPECIFIC_SHORT, + /** + * RFC822 format, such as "-0500" + * @internal ICU 49 technology preview + */ + UTZFMT_STYLE_RFC822, + /** + * Localized GMT offset format, such as "GMT-05:00", "UTC+0100" + * @internal ICU 49 technology preview + */ + UTZFMT_STYLE_LOCALIZED_GMT, + /** + * ISO 8601 format (extended), such as "-05:00", "Z"(UTC) + * @internal ICU 49 technology preview + */ + UTZFMT_STYLE_ISO8601 +} UTimeZoneFormatStyle; + +/** + * Constants for GMT offset pattern types. + * @internal ICU 49 technology preview + */ +typedef enum UTimeZoneFormatGMTOffsetPatternType { + /** + * Positive offset with hour and minute fields + * @internal ICU 49 technology preview + */ + UTZFMT_PAT_POSITIVE_HM, + /** + * Positive offset with hour, minute and second fields + * @internal ICU 49 technology preview + */ + UTZFMT_PAT_POSITIVE_HMS, + /** + * Negative offset with hour and minute fields + * @internal ICU 49 technology preview + */ + UTZFMT_PAT_NEGATIVE_HM, + /** + * Negative offset with hour, minute and second fields + * @internal ICU 49 technology preview + */ + UTZFMT_PAT_NEGATIVE_HMS +} UTimeZoneFormatGMTOffsetPatternType; + +/** + * Constants for time types used by TimeZoneFormat APIs for + * receiving time type (standard time, daylight time or unknown). + * @internal ICU 49 technology preview + */ +typedef enum UTimeZoneFormatTimeType { + /** + * Unknown + * @internal ICU 49 technology preview + */ + UTZFMT_TIME_TYPE_UNKNOWN, + /** + * Standard time + * @internal ICU 49 technology preview + */ + UTZFMT_TIME_TYPE_STANDARD, + /** + * Daylight saving time + * @internal ICU 49 technology preview + */ + UTZFMT_TIME_TYPE_DAYLIGHT +} UTimeZoneFormatTimeType; + +/** + * Constants for parse option flags, used for specifying optional parse behavior. + * @internal ICU 49 technology preview + */ +typedef enum UTimeZoneFormatParseOption { + /** + * No option. + * @internal ICU 49 technology preview + */ + UTZFMT_PARSE_OPTION_NONE = 0x00, + /** + * When a time zone display name is not found within a set of display names + * used for the specified style, look for the name from display names used + * by other styles. + * @internal ICU 49 technology preview + */ + UTZFMT_PARSE_OPTION_ALL_STYLES = 0x01 +} UTimeZoneFormatParseOption; + +U_CDECL_END + +typedef void *UMTX; + +U_NAMESPACE_BEGIN + +class TimeZoneGenericNames; +class UVector; + +/** + * TimeZoneFormat supports time zone display name formatting and parsing. + * An instance of TimeZoneFormat works as a subformatter of {@link SimpleDateFormat}, + * but you can also directly get a new instance of TimeZoneFormat and + * formatting/parsing time zone display names. + *

+ * ICU implements the time zone display names defined by UTS#35 + * Unicode Locale Data Markup Language (LDML). {@link TimeZoneNames} represents the + * time zone display name data model and this class implements the algorithm for actual + * formatting and parsing. + * + * @see SimpleDateFormat + * @see TimeZoneNames + * @internal ICU 49 technology preview + */ +class U_I18N_API TimeZoneFormat : public Format { +public: + /** + * Copy constructor. + * @internal ICU 49 technology preview + */ + TimeZoneFormat(const TimeZoneFormat& other); + + /** + * Destructor. + * @internal ICU 49 technology preview + */ + virtual ~TimeZoneFormat(); + + /** + * Assignment operator. + * @internal ICU 49 technology preview + */ + TimeZoneFormat& operator=(const TimeZoneFormat& other); + + /** + * Return true if the given Format objects are semantically equal. + * Objects of different subclasses are considered unequal. + * @param other The object to be compared with. + * @return Return TRUE if the given Format objects are semantically equal. + * Objects of different subclasses are considered unequal. + * @internal ICU 49 technology preview + */ + virtual UBool operator==(const Format& other) const; + + /** + * Clone this object polymorphically. The caller is responsible + * for deleting the result when done. + * @return A copy of the object + * @internal ICU 49 technology preview + */ + virtual Format* clone() const; + + /** + * Creates an instance of TimeZoneFormat for the given locale. + * @param locale The locale. + * @param status Recevies the status. + * @return An instance of TimeZoneFormat for the given locale, + * owned by the caller. + * @internal ICU 49 technology preview + */ + static TimeZoneFormat* U_EXPORT2 createInstance(const Locale& locale, UErrorCode& status); + + /** + * Returns the time zone display name data used by this instance. + * @return The time zone display name data. + * @internal ICU 49 technology preview + */ + const TimeZoneNames* getTimeZoneNames() const; + + /** + * Sets the time zone display name data to this format instnace. + * The caller should not delete the TimeZoenNames object after it is adopted + * by this call. + * @param tznames TimeZoneNames object to be adopted. + * @internal ICU 49 technology preview + */ + void adoptTimeZoneNames(TimeZoneNames *tznames); + + /** + * Sets the time zone display name data to this format instnace. + * @param tznames TimeZoneNames object to be set. + * @internal ICU 49 technology preview + */ + void setTimeZoneNames(const TimeZoneNames &tznames); + + /** + * Returns the localized GMT format pattern. + * @param pattern Receives the localized GMT format pattern. + * @return A reference to the result pattern. + * @see #setGMTPattern + * @internal ICU 49 technology preview + */ + UnicodeString& getGMTPattern(UnicodeString& pattern) const; + + /** + * Sets the localized GMT format pattern. The pattern must contain + * a single argument {0}, for example "GMT {0}". + * @param pattern The localized GMT format pattern to be used by this object. + * @param status Recieves the status. + * @see #getGMTPattern + * @internal ICU 49 technology preview + */ + void setGMTPattern(const UnicodeString& pattern, UErrorCode& status); + + /** + * Returns the offset pattern used for localized GMT format. + * @param type The offset pattern type enum. + * @param pattern Receives the offset pattern. + * @return A reference to the result pattern. + * @see #setGMTOffsetPattern + * @internal ICU 49 technology preview + */ + UnicodeString& getGMTOffsetPattern(UTimeZoneFormatGMTOffsetPatternType type, UnicodeString& pattern) const; + + /** + * Sets the offset pattern for the given offset type. + * @param type The offset pattern type enum. + * @param pattern The offset pattern used for localized GMT format for the type. + * @param status Receives the status. + * @see #getGMTOffsetPattern + * @internal ICU 49 technology preview + */ + void setGMTOffsetPattern(UTimeZoneFormatGMTOffsetPatternType type, const UnicodeString& pattern, UErrorCode& status); + + /** + * Returns the decimal digit characters used for localized GMT format in a single string + * containing from 0 to 9 in the ascending order. + * @param digits Receives the decimal digits used for localized GMT format. + * @see #setGMTOffsetDigits + */ + UnicodeString& getGMTOffsetDigits(UnicodeString& digits) const; + + /** + * Sets the decimal digit characters used for localized GMT format. + * @param digits The decimal digits used for localized GMT format. + * @param status Receives the status. + * @see #getGMTOffsetDigits + */ + void setGMTOffsetDigits(const UnicodeString& digits, UErrorCode& status); + + /** + * Returns the localized GMT format string for GMT(UTC) itself (GMT offset is 0). + * @param gmtZeroFormat Receives the localized GMT string string for GMT(UTC) itself. + * @return A reference to the result GMT string. + * @see #setGMTZeroFormat + */ + UnicodeString& getGMTZeroFormat(UnicodeString& gmtZeroFormat) const; + + /** + * Sets the localized GMT format string for GMT(UTC) itself (GMT offset is 0). + * @param gmtZeroFormat The localized GMT format string for GMT(UTC). + * @param status Receives the status. + * @see #getGMTZeroFormat + */ + void setGMTZeroFormat(const UnicodeString& gmtZeroFormat, UErrorCode& status); + + /** + * Returns the bitwise flags of UTimeZoneFormatParseOption representing the default parse + * options used by this object. + * @return the default parse options. + * @see ParseOption + * @internal ICU 49 technology preview + */ + int32_t getDefaultParseOptions(void) const; + + /** + * Sets the default parse options. + *

Note: By default, an instance of TimeZoneFormat + * created by {@link #createInstance} has no parse options set (UTZFMT_PARSE_OPTION_NONE). + * To specify multipe options, use bitwise flags of UTimeZoneFormatParseOption. + * @see #UTimeZoneFormatParseOption + * @internal ICU 49 technology preview + */ + void setDefaultParseOptions(int32_t flags); + + /** + * Returns the RFC822 style time zone string for the given offset. + * For example, "-0800". + * @param offset The offset from GMT(UTC) in milliseconds. + * @param result Recevies the RFC822 style GMT(UTC) offset format. + * @return A reference to the result. + * @see #parseOffsetRFC822 + * @internal ICU 49 technology preview + */ + UnicodeString& formatOffsetRFC822(int32_t offset, UnicodeString& result, UErrorCode& status) const; + + /** + * Returns the ISO 8601 style time zone string for the given offset. + * For example, "-08:00" and "Z". + * @param offset The offset from GMT(UTC) in milliseconds. + * @param result Recevies the ISO 8601 style GMT(UTC) offset format. + * @return A reference to the result. + * @see #parseOffsetISO8601 + * @internal ICU 49 technology preview + */ + UnicodeString& formatOffsetISO8601(int32_t offset, UnicodeString& result, UErrorCode& status) const; + + /** + * Returns the localized GMT(UTC) offset format for the given offset. + * The localized GMT offset is defined by; + *

    + *
  • GMT format pattern (e.g. "GMT {0}" - see {@link #getGMTPattern}) + *
  • Offset time pattern (e.g. "+HH:mm" - see {@link #getGMTOffsetPattern}) + *
  • Offset digits (e.g. "0123456789" - see {@link #getGMTOffsetDigits}) + *
  • GMT zero format (e.g. "GMT" - see {@link #getGMTZeroFormat}) + *
+ * @param offset the offset from GMT(UTC) in milliseconds. + * @param result Receives the localized GMT format string. + * @return A reference to the result. + * @see #parseOffsetLocalizedGMT + * @internal ICU 49 technology preview + */ + UnicodeString& formatOffsetLocalizedGMT(int32_t offset, UnicodeString& result, UErrorCode& status) const; + + /** + * Returns the display name of the time zone at the given date for the style. + * @param style The style (e.g. UTZFMT_STYLE_GENERIC_LONG, UTZFMT_STYLE_LOCALIZED_GMT...) + * @param tz The time zone. + * @param date The date. + * @param name Receives the display name. + * @param timeType the output argument for receiving the time type (standard/daylight/unknown) + * used for the display name, or NULL if the information is not necessary. + * @return A reference to the result + * @see #UTimeZoneFormatStyle + * @see #UTimeZoneFormatTimeType + * @internal ICU 49 technology preview + */ + virtual UnicodeString& format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date, + UnicodeString& name, UTimeZoneFormatTimeType* timeType = NULL) const; + + /** + * Returns offset from GMT(UTC) in milliseconds for the given RFC822 + * style time zone string. When the given string is not an RFC822 time zone + * string, this method sets the current position as the error index + * to ParsePosition pos and returns 0. + * @param text The text contains RFC822 style time zone string (e.g. "-0800") + * at the position. + * @param pos The ParsePosition object. + * @return The offset from GMT(UTC) in milliseconds for the given RFC822 style + * time zone string. + * @see #formatOffsetRFC822 + * @internal ICU 49 technology preview + */ + int32_t parseOffsetRFC822(const UnicodeString& text, ParsePosition& pos) const; + + /** + * Returns offset from GMT(UTC) in milliseconds for the given ISO 8601 + * style time zone string. When the given string is not an ISO 8601 time zone + * string, this method sets the current position as the error index + * to ParsePosition pos and returns 0. + * @param text The text contains RFC822 style time zone string (e.g. "-08:00", "Z") + * at the position. + * @param pos The ParsePosition object. + * @return The offset from GMT(UTC) in milliseconds for the given ISO 8601 style + * time zone string. + * @see #formatOffsetISO8601 + * @internal ICU 49 technology preview + */ + int32_t parseOffsetISO8601(const UnicodeString& text, ParsePosition& pos) const; + + /** + * Returns offset from GMT(UTC) in milliseconds for the given localized GMT + * offset format string. When the given string cannot be parsed, this method + * sets the current position as the error index to ParsePosition pos + * and returns 0. + * @param text The text contains a localized GMT offset string at the position. + * @param pos The ParsePosition object. + * @return The offset from GMT(UTC) in milliseconds for the given localized GMT + * offset format string. + * @see #formatOffsetLocalizedGMT + * @internal ICU 49 technology preview + */ + int32_t parseOffsetLocalizedGMT(const UnicodeString& text, ParsePosition& pos) const; + + /** + * Returns a TimeZone by parsing the time zone string according to + * the given parse position, the specified format style and parse options. + * + * @param text The text contains a time zone string at the position. + * @param style The format style + * @param pos The position. + * @param parseOptions The parse options repesented by bitwise flags of UTimeZoneFormatParseOption. + * @param timeType The output argument for receiving the time type (standard/daylight/unknown), + * or NULL if the information is not necessary. + * @return A TimeZone, or null if the input could not be parsed. + * @see UTimeZoneFormatStyle + * @see UTimeZoneFormatParseOption + * @see UTimeZoneFormatTimeType + * @internal ICU 49 technology preview + */ + virtual TimeZone* parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + int32_t parseOptions, UTimeZoneFormatTimeType* timeType = NULL) const; + + /** + * Returns a TimeZone by parsing the time zone string according to + * the given parse position, the specified format style and the default parse options. + * + * @param text The text contains a time zone string at the position. + * @param style The format style + * @param pos The position. + * @param timeType The output argument for receiving the time type (standard/daylight/unknown), + * or NULL if the information is not necessary. + * @return A TimeZone, or null if the input could not be parsed. + * @see UTimeZoneFormatStyle + * @see UTimeZoneFormatParseOption + * @see UTimeZoneFormatTimeType + * @internal ICU 49 technology preview + */ + TimeZone* parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos, + UTimeZoneFormatTimeType* timeType = NULL) const; + + /* ---------------------------------------------- + * Format APIs + * ---------------------------------------------- */ + + /** + * Format an object to produce a time zone display string using localized GMT offset format. + * This method handles Formattable objects with a TimeZone. If a the Formattable + * object type is not a TimeZone, then it returns a failing UErrorCode. + * @param obj The object to format. Must be a TimeZone. + * @param appendTo Output parameter to receive result. Result is appended to existing contents. + * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment field. + * @param status Output param filled with success/failure status. + * @return Reference to 'appendTo' parameter. + * @internal ICU 49 technology preview + */ + virtual UnicodeString& format(const Formattable& obj, UnicodeString& appendTo, + FieldPosition& pos, UErrorCode& status) const; + + /** + * Parse a string to produce an object. This methods handles parsing of + * time zone display strings into Formattable objects with TimeZone. + * @param source The string to be parsed into an object. + * @param result Formattable to be set to the parse result. If parse fails, return contents are undefined. + * @param parse_pos The position to start parsing at. Upon return this param is set to the position after the + * last character successfully parsed. If the source is not parsed successfully, this param + * will remain unchanged. + * @return A newly created Formattable* object, or NULL on failure. The caller owns this and should + * delete it when done. + * @internal ICU 49 technology preview + */ + virtual void parseObject(const UnicodeString& source, Formattable& result, ParsePosition& parse_pos) const; + + /** + * ICU "poor man's RTTI", returns a UClassID for this class. + * @internal ICU 49 technology preview + */ + static UClassID U_EXPORT2 getStaticClassID(void); + + /** + * ICU "poor man's RTTI", returns a UClassID for the actual class. + * @internal ICU 49 technology preview + */ + virtual UClassID getDynamicClassID() const; + +protected: + /** + * Constructs a TimeZoneFormat object for the specified locale. + * @param locale the locale + * @param status receives the status. + * @internal ICU 49 technology preview + */ + TimeZoneFormat(const Locale& locale, UErrorCode& status); + +private: + /* mutex */ + UMTX fLock; + + /* Locale of this object */ + Locale fLocale; + + /* Stores the region (could be implicit default) */ + char fTargetRegion[ULOC_COUNTRY_CAPACITY]; + + /* TimeZoneNames object used by this formatter */ + TimeZoneNames* fTimeZoneNames; + + /* TimeZoneGenericNames object used by this formatter - lazily instantiated */ + TimeZoneGenericNames* fTimeZoneGenericNames; + + /* Localized GMT format pattern - e.g. "GMT{0}" */ + UnicodeString fGMTPattern; + + /* Array of offset patterns used by Localized GMT format - e.g. "+HH:mm" */ + UnicodeString fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HMS + 1]; + + /* Localized decimal digits used by Localized GMT format */ + UChar32 fGMTOffsetDigits[10]; + + /* Localized GMT zero format - e.g. "GMT" */ + UnicodeString fGMTZeroFormat; + + /* Bit flags representing parse options */ + int32_t fDefParseOptionFlags; + + /* Constant parts of GMT format pattern, populated from localized GMT format pattern*/ + UnicodeString fGMTPatternPrefix; /* Substring before {0} */ + UnicodeString fGMTPatternSuffix; /* Substring after {0} */ + + /* Compiled offset patterns generated from fGMTOffsetPatterns[] */ + UVector* fGMTOffsetPatternItems[UTZFMT_PAT_NEGATIVE_HMS + 1]; + + /** + * Returns the time zone's specific format string. + * @param tz the time zone + * @param stdType the name type used for standard time + * @param dstType the name type used for daylight time + * @param date the date + * @param name receives the time zone's specific format name string + * @param timeType when null, actual time type is set + * @return a reference to name. + */ + UnicodeString& formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType, + UDate date, UnicodeString& name, UTimeZoneFormatTimeType *timeType) const; + + /** + * Returns the time zone's generic format string. + * @param tz the time zone + * @param genType the generic name type + * @param date the date + * @param name receives the time zone's generic format name string + * @return a reference to name. + */ + UnicodeString& formatGeneric(const TimeZone& tz, int32_t genType, UDate date, UnicodeString& name) const; + + /** + * Lazily create a TimeZoneGenericNames instance + * @param status receives the status + * @return the cached TimeZoneGenericNames. + */ + const TimeZoneGenericNames* getTimeZoneGenericNames(UErrorCode& status) const; + + /** + * Private enum specifying a combination of offset fields + */ + enum OffsetFields { + FIELDS_H, + FIELDS_HM, + FIELDS_HMS + }; + + /** + * Parses the localized GMT pattern string and initialize + * localized gmt pattern fields. + * @param gmtPattern the localized GMT pattern string such as "GMT {0}" + * @param status U_ILLEGAL_ARGUMENT_ERROR is set when the specified pattern does not + * contain an argument "{0}". + */ + void initGMTPattern(const UnicodeString& gmtPattern, UErrorCode& status); + + /** + * Parse the GMT offset pattern into runtime optimized format. + * @param pattern the offset pattern string + * @param required the required set of fields, such as FIELDS_HM + * @param status U_ILLEGAL_ARGUMENT is set when the specified pattern does not contain + * pattern letters for the required fields. + * @return A list of GMTOffsetField objects, or NULL on error. + */ + static UVector* parseOffsetPattern(const UnicodeString& pattern, OffsetFields required, UErrorCode& status); + + /** + * Appends second field to the offset pattern with hour/minute + * Note: This code will be obsoleted once we add hour-minute-second pattern data in CLDR. + * @param offsetHM the offset pattern including hour and minute fields + * @param result the output offset pattern including hour, minute and second fields + * @return a reference to result + */ + static UnicodeString& expandOffsetPattern(const UnicodeString& offsetHM, UnicodeString& result); + + /** + * Break input string into UChar32[]. Each array element represents + * a code point. This method is used for parsing localized digit + * characters and support characters in Unicode supplemental planes. + * @param str the string + * @param codeArray receives the result + * @param capacity the capacity of codeArray + * @return TRUE when the specified code array is fully filled with code points + * (no under/overflow). + */ + static UBool toCodePoints(const UnicodeString& str, UChar32* codeArray, int32_t capacity); + + /** + * Returns offset from GMT(UTC) in milliseconds for the given ISO 8601 style + * (extended format) time zone string. When the given string is not an ISO 8601 time + * zone string, this method sets the current position as the error index + * to ParsePosition pos and returns 0. + * @param text the text contains ISO 8601 style time zone string (e.g. "-08:00", "Z") + * at the position. + * @param pos the position, non-negative error index will be set on failure. + * @param extendedOnly TRUE if parsing the text as ISO 8601 extended offset format (e.g. "-08:00"), + * or FALSE to evaluate the text as basic format. + * @param hasDigitOffset receiving if the parsed zone string contains offset digits. + * @return the offset from GMT(UTC) in milliseconds for the given ISO 8601 style + * time zone string. + */ + int32_t parseOffsetISO8601(const UnicodeString& text, ParsePosition& pos, UBool extendedOnly, + UBool* hasDigitOffset = NULL) const; + + /** + * Appends localized digits to the buffer. + * This code assumes that the input number is 0 - 59 + * @param buf the target buffer + * @param n the integer number + * @param minDigits the minimum digits width + */ + void appendOffsetDigits(UnicodeString& buf, int32_t n, uint8_t minDigits) const; + + /** + * Returns offset from GMT(UTC) in milliseconds for the given localized GMT + * offset format string. When the given string cannot be parsed, this method + * sets the current position as the error index to ParsePosition pos + * and returns 0. + * @param text the text contains a localized GMT offset string at the position. + * @param pos the position, non-negative error index will be set on failure. + * @param hasDigitOffset receiving if the parsed zone string contains offset digits. + * @return the offset from GMT(UTC) in milliseconds for the given localized GMT + * offset format string. + */ + int32_t parseOffsetLocalizedGMT(const UnicodeString& text, ParsePosition& pos, + UBool* hasDigitOffset) const; + + /** + * Parses localized GMT offset fields into offset. + * @param text the input text + * @param start the start index + * @param minimumHourWidth true if the parser allows hour field width to be 1 + * @param parsedLen the parsed length, or 0 on failure. + * @return the parsed offset in milliseconds. + */ + int32_t parseOffsetFields(const UnicodeString& text, int32_t start, UBool minimumHourWidth, + int32_t& parsedLen) const; + + /** + * Parses abutting localized GMT offset fields (such as 0800) into offset. + * @param text the input text + * @param start the start index + * @param parsedLen the parsed length, or 0 on failure + * @return the parsed offset in milliseconds. + */ + int32_t parseAbuttingOffsetFields(const UnicodeString& text, int32_t start, int32_t& parsedLen) const; + + /** + * Parses the input text using the default format patterns (e.g. "UTC{0}"). + * @param text the input text + * @param start the start index + * @param parsedLen the parsed length, or 0 on failure + * @return the parsed offset in milliseconds. + */ + int32_t parseOffsetDefaultLocalizedGMT(const UnicodeString& text, int start, int32_t& parsedLen) const; + + /** + * Parses the input GMT offset fields with the default offset pattern. + * @param text the input text + * @param start the start index + * @param separator the separator character, e.g. ':' + * @param parsedLen the parsed length, or 0 on failure. + * @return the parsed offset in milliseconds. + */ + int32_t parseDefaultOffsetFields(const UnicodeString& text, int32_t start, UChar separator, + int32_t& parsedLen) const; + + /** + * Reads an offset field value. This method will stop parsing when + * 1) number of digits reaches maxDigits + * 2) just before already parsed number exceeds maxVal + * + * @param text the text + * @param start the start offset + * @param minDigits the minimum number of required digits + * @param maxDigits the maximum number of digits + * @param minVal the minimum value + * @param maxVal the maximum value + * @param parsedLen the actual parsed length. + * @return the integer value parsed + */ + int32_t parseOffsetFieldWithLocalizedDigits(const UnicodeString& text, int32_t start, + uint8_t minDigits, uint8_t maxDigits, uint16_t minVal, uint16_t maxVal, int32_t& parsedLen) const; + + /** + * Reads a single decimal digit, either localized digits used by this object + * or any Unicode numeric character. + * @param text the text + * @param start the start index + * @param len the actual length read from the text + * the start index is not a decimal number. + * @return the integer value of the parsed digit, or -1 on failure. + */ + int32_t parseSingleLocalizedDigit(const UnicodeString& text, int32_t start, int32_t& len) const; + + /** + * Formats offset using ASCII digits. The input offset range must be + * within +/-24 hours (exclusive). + * @param offset The offset + * @param sep The field separator character or 0 if not required + * @param minFields The minimum fields + * @param maxFields The maximum fields + * @return The offset string + */ + static UnicodeString& formatOffsetWithAsciiDigits(int32_t offset, UChar sep, + OffsetFields minFields, OffsetFields maxFields, UnicodeString& result); + + /** + * Parses offset represented by contiguous ASCII digits. + *

+ * Note: This method expects the input position is already at the start of + * ASCII digits and does not parse sign (+/-). + * @param text The text contains a sequence of ASCII digits + * @param pos The parse position + * @param minFields The minimum Fields to be parsed + * @param maxFields The maximum Fields to be parsed + * @param fixedHourWidth true if hour field must be width of 2 + * @return Parsed offset, 0 or positive number. + */ + static int32_t parseAbuttingAsciiOffsetFields(const UnicodeString& text, ParsePosition& pos, + OffsetFields minFields, OffsetFields maxFields, UBool fixedHourWidth); + + /** + * Parses offset represented by ASCII digits and separators. + *

+ * Note: This method expects the input position is already at the start of + * ASCII digits and does not parse sign (+/-). + * @param text The text + * @param pos The parse position + * @param sep The separator character + * @param minFields The minimum Fields to be parsed + * @param maxFields The maximum Fields to be parsed + * @param fixedHourWidth true if hour field must be width of 2 + * @return Parsed offset, 0 or positive number. + */ + static int32_t parseAsciiOffsetFields(const UnicodeString& text, ParsePosition& pos, UChar sep, + OffsetFields minFields, OffsetFields maxFields, UBool fixedHourWidth); + + /** + * Unquotes the message format style pattern. + * @param pattern the pattern + * @param result receive the unquoted pattern. + * @return A reference to result. + */ + static UnicodeString& unquote(const UnicodeString& pattern, UnicodeString& result); + + /** + * Initialize localized GMT format offset hour/min/sec patterns. + * This method parses patterns into optimized run-time format. + * @param status receives the status. + */ + void initGMTOffsetPatterns(UErrorCode& status); + + /** + * Creates an instance of TimeZone for the given offset + * @param offset the offset + * @return A TimeZone with the given offset + */ + TimeZone* createTimeZoneForOffset(int32_t offset) const; + + /** + * Returns the time type for the given name type + * @param nameType the name type + * @return the time type (unknown/standard/daylight) + */ + static UTimeZoneFormatTimeType getTimeType(UTimeZoneNameType nameType); + + /* + * Returns the time zone ID of a match at the specified index within + * the MatchInfoCollection. + * @param matches the collection of matches + * @param idx the index withing matches + * @param tzID receives the resolved time zone ID + * @return a reference to tzID. + */ + UnicodeString& getTimeZoneID(const TimeZoneNames::MatchInfoCollection* matches, int32_t idx, UnicodeString& tzID) const; +}; + +U_NAMESPACE_END + +#endif /* U_HIDE_INTERNAL_API */ +#endif +#endif + diff --git a/icu4c/source/i18n/unicode/tznames.h b/icu4c/source/i18n/unicode/tznames.h new file mode 100644 index 00000000000..b90df2af275 --- /dev/null +++ b/icu4c/source/i18n/unicode/tznames.h @@ -0,0 +1,378 @@ +/* +******************************************************************************* +* Copyright (C) 2011-2012, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ +#ifndef __TZNAMES_H +#define __TZNAMES_H + +/** + * \file + * \brief C++ API: TimeZoneNames + */ +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef U_HIDE_INTERNAL_API + +#include "unicode/uloc.h" +#include "unicode/unistr.h" + +U_CDECL_BEGIN + +/** + * Constants for time zone display name types. + * @internal ICU 49 technology preview + */ +typedef enum UTimeZoneNameType { + /** + * Unknown display name type. + * @internal ICU 49 technology preview + */ + UTZNM_UNKNOWN = 0x00, + /** + * Long display name, such as "Eastern Time". + * @internal ICU 49 technology preview + */ + UTZNM_LONG_GENERIC = 0x01, + /** + * Long display name for standard time, such as "Eastern Standard Time". + * @internal ICU 49 technology preview + */ + UTZNM_LONG_STANDARD = 0x02, + /** + * Long display name for daylight saving time, such as "Eastern Daylight Time". + * @internal ICU 49 technology preview + */ + UTZNM_LONG_DAYLIGHT = 0x04, + /** + * Short display name, such as "ET". + * @internal ICU 49 technology preview + */ + UTZNM_SHORT_GENERIC = 0x08, + /** + * Short display name for standard time, such as "EST". + * @internal ICU 49 technology preview + */ + UTZNM_SHORT_STANDARD = 0x10, + /** + * Short display name for daylight saving time, such as "EDT". + * @internal ICU 49 technology preview + */ + UTZNM_SHORT_DAYLIGHT = 0x20 +} UTimeZoneNameType; + +U_CDECL_END + +U_NAMESPACE_BEGIN + +class UVector; +struct MatchInfo; + +/** + * TimeZoneNames is an abstract class representing the time zone display name data model defined + * by UTS#35 Unicode Locale Data Markup Language (LDML). + * The model defines meta zone, which is used for storing a set of display names. A meta zone can be shared + * by multiple time zones. Also a time zone may have multiple meta zone historic mappings. + *

+ * For example, people in the United States refer the zone used by the east part of North America as "Eastern Time". + * The tz database contains multiple time zones "America/New_York", "America/Detroit", "America/Montreal" and some + * others that belong to "Eastern Time". However, assigning different display names to these time zones does not make + * much sense for most of people. + *

+ * In CLDR (which uses LDML for representing locale data), the display name + * "Eastern Time" is stored as long generic display name of a meta zone identified by the ID "America_Eastern". + * Then, there is another table maintaining the historic mapping to meta zones for each time zone. The time zones in + * the above example ("America/New_York", "America/Detroit"...) are mapped to the meta zone "America_Eastern". + *

+ * Sometimes, a time zone is mapped to a different time zone in the past. For example, "America/Indiana/Knox" + * had been moving "Eastern Time" and "Central Time" back and forth. Therefore, it is necessary that time zone + * to meta zones mapping data are stored by date range. + * + *

Note: + * The methods in this class assume that time zone IDs are already canonicalized. For example, you may not get proper + * result returned by a method with time zone ID "America/Indiana/Indianapolis", because it's not a canonical time zone + * ID (the canonical time zone ID for the time zone is "America/Indianapolis". See + * {@link TimeZone#getCanonicalID(const UnicodeString& id, UnicodeString& canonicalID, UErrorCode& status)} about ICU + * canonical time zone IDs. + * + *

+ * In CLDR, most of time zone display names except location names are provided through meta zones. But a time zone may + * have a specific name that is not shared with other time zones. + * + * For example, time zone "Europe/London" has English long name for standard time "Greenwich Mean Time", which is also + * shared with other time zones. However, the long name for daylight saving time is "British Summer Time", which is only + * used for "Europe/London". + * + *

+ * {@link #getTimeZoneDisplayName} is designed for accessing a name only used by a single time zone. + * But is not necessarily mean that a subclass implementation use the same model with CLDR. A subclass implementation + * may provide time zone names only through {@link #getTimeZoneDisplayName}, or only through {@link #getMetaZoneDisplayName}, + * or both. + * + * @internal ICU 49 technology preview + */ +class U_I18N_API TimeZoneNames : public UObject { +public: + /** + * Destructor. + * @internal ICU 49 technology preview + */ + virtual ~TimeZoneNames(); + + /** + * Return true if the given TimeZoneNames objects are emantically equal. + * @param other the object to be compared with. + * @return Return TRUE if the given Format objects are semantically equal. + * @internal ICU 49 technology preview + */ + virtual UBool operator==(const TimeZoneNames& other) const = 0; + + /** + * Return true if the given TimeZoneNames objects are not semantically + * equal. + * @param other the object to be compared with. + * @return Return TRUE if the given Format objects are not semantically equal. + * @internal ICU 49 technology preview + */ + UBool operator!=(const TimeZoneNames& other) const { return !operator==(other); } + + /** + * Clone this object polymorphically. The caller is responsible + * for deleting the result when done. + * @return A copy of the object + * @internal ICU 49 technology preview + */ + virtual TimeZoneNames* clone() const = 0; + + /** + * Returns an instance of TimeZoneDisplayNames for the specified locale. + * + * @param locale The locale. + * @param status Recevies the status. + * @return An instance of TimeZoneDisplayNames + * @internal ICU 49 technology preview + */ + static TimeZoneNames* U_EXPORT2 createInstance(const Locale& locale, UErrorCode& status); + + /** + * Returns an enumeration of all available meta zone IDs. + * @param status Recevies the status. + * @return an enumeration object, owned by the caller. + * @internal ICU 49 technology preview + */ + virtual StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const = 0; + + /** + * Returns an enumeration of all available meta zone IDs used by the given time zone. + * @param tzID The canoical tiem zone ID. + * @param status Recevies the status. + * @return an enumeration object, owned by the caller. + * @internal ICU 49 technology preview + */ + virtual StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const = 0; + + /** + * Returns the meta zone ID for the given canonical time zone ID at the given date. + * @param tzID The canonical time zone ID. + * @param date The date. + * @param mzID Receives the meta zone ID for the given time zone ID at the given date. If the time zone does not have a + * corresponding meta zone at the given date or the implementation does not support meta zones, "bogus" state + * is set. + * @return A reference to the result. + * @internal ICU 49 technology preview + */ + virtual UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const = 0; + + /** + * Returns the reference zone ID for the given meta zone ID for the region. + * @param mzID The meta zone ID. + * @param region The region. + * @param tzID Receives the reference zone ID ("golden zone" in the LDML specification) for the given time zone ID for the + * region. If the meta zone is unknown or the implementation does not support meta zones, "bogus" state + * is set. + * @return A reference to the result. + * @internal ICU 49 technology preview + */ + virtual UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const = 0; + + /** + * Returns the display name of the meta zone. + * @param mzID The meta zone ID. + * @param type The display name type. See {@link #UTimeZoneNameType}. + * @param name Receives the display name of the meta zone. When this object does not have a localized display name for the given + * meta zone with the specified type or the implementation does not provide any display names associated + * with meta zones, "bogus" state is set. + * @return A reference to the result. + * @internal ICU 49 technology preview + */ + virtual UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const = 0; + + /** + * Returns the display name of the time zone. Unlike {@link #getDisplayName}, + * this method does not get a name from a meta zone used by the time zone. + * @param tzID The canonical time zone ID. + * @param type The display name type. See {@link #UTimeZoneNameType}. + * @param name Receives the display name for the time zone. When this object does not have a localized display name for the given + * time zone with the specified type, "bogus" state is set. + * @return A reference to the result. + * @internal ICU 49 technology preview + */ + virtual UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const = 0; + + /** + * Returns the exemplar location name for the given time zone. When this object does not have a localized location + * name, the default implementation may still returns a programmatically generated name with the logic described + * below. + *

    + *
  1. Check if the ID contains "/". If not, return null. + *
  2. Check if the ID does not start with "Etc/" or "SystemV/". If it does, return null. + *
  3. Extract a substring after the last occurrence of "/". + *
  4. Replace "_" with " ". + *
+ * For example, "New York" is returned for the time zone ID "America/New_York" when this object does not have the + * localized location name. + * + * @param tzID The canonical time zone ID + * @param name Receives the exemplar location name for the given time zone, or "bogus" state is set when a localized + * location name is not available and the fallback logic described above cannot extract location from the ID. + * @return A reference to the result. + * @internal ICU 49 technology preview + */ + virtual UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; + + /** + * Returns the display name of the time zone at the given date. + *

+ * Note: This method calls the subclass's {@link #getTimeZoneDisplayName} first. When the + * result is bogus, this method calls {@link #getMetaZoneID} to get the meta zone ID mapped from the + * time zone, then calls {@link #getMetaZoneDisplayName}. + * + * @param tzID The canonical time zone ID. + * @param type The display name type. See {@link #UTimeZoneNameType}. + * @param date The date. + * @param name Receives the display name for the time zone at the given date. When this object does not have a localized display + * name for the time zone with the specified type and date, "bogus" state is set. + * @return A reference to the result. + * @internal ICU 49 technology preview + */ + virtual UnicodeString& getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const; + + /** + * MatchInfoCollection represents a collection of time zone name matches used by + * {@link TimeZoneNames#find}. + * @internal ICU 49 technology preview + */ + class U_I18N_API MatchInfoCollection : public UMemory { + public: + /** + * Constructor. + * @internal ICU 49 technology preview + */ + MatchInfoCollection(); + /** + * Destructor. + * @internal ICU 49 technology preview + */ + virtual ~MatchInfoCollection(); + + /** + * Adds a zone match. + * @param nameType The name type. + * @param matchLength The match length. + * @param tzID The time zone ID. + * @param status Receives the status + * @internal ICU 49 technology preview + */ + void addZone(UTimeZoneNameType nameType, int32_t matchLength, + const UnicodeString& tzID, UErrorCode& status); + + /** + * Adds a meata zone match. + * @param nameType The name type. + * @param matchLength The match length. + * @param mzID The metazone ID. + * @param status Receives the status + * @internal ICU 49 technology preview + */ + void addMetaZone(UTimeZoneNameType nameType, int32_t matchLength, + const UnicodeString& mzID, UErrorCode& status); + + /** + * Returns the number of entries available in this object. + * @return The number of entries. + * @internal ICU 49 technology preview + */ + int32_t size() const; + + /** + * Returns the time zone name type of a match at the specified index. + * @param idx The index + * @return The time zone name type. If the specified idx is out of range, + * it returns UTZNM_UNKNOWN. + * @see UTimeZoneNameType + * @internal ICU 49 technology preview + */ + UTimeZoneNameType getNameTypeAt(int32_t idx) const; + + /** + * Returns the match length of a match at the specified index. + * @param idx The index + * @param status Receives the status + * @return The match length. If the specified idx is out of range, + * it returns 0. + * @internal ICU 49 technology preview + */ + int32_t getMatchLengthAt(int32_t idx) const; + + /** + * Gets the zone ID of a match at the specified index. + * @param idx The index + * @param tzID Receives the zone ID. + * @return TRUE if the zone ID was set to tzID. + * @internal ICU 49 technology preview + */ + UBool getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const; + + /** + * Gets the metazone ID of a match at the specified index. + * @param idx The index + * @param mzID Receives the metazone ID + * @param status Receives the status. + * @return TRUE if the meta zone ID was set to mzID. + * @internal ICU 49 technology preview + */ + UBool getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const; + + private: + UVector* fMatches; // vector of MatchEntry + + UVector* matches(UErrorCode& status); + }; + + /** + * Finds time zone name prefix matches for the input text at the + * given offset and returns a collection of the matches. + * @param text The text. + * @param start The starting offset within the text. + * @param types The set of name types represented by bitwise flags of UTimeZoneNameType enums, + * or UTZNM_UNKNOWN for all name types. + * @param status Receives the status. + * @return A collection of matches (owned by the caller), or NULL if no matches are found. + * @see UTimeZoneNameType + * @see MatchInfoCollection + * @internal ICU 49 technology preview + */ + virtual MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const = 0; + +private: + // No ICU "poor man's RTTI" for this class nor its subclasses. + virtual UClassID getDynamicClassID() const; +}; + +U_NAMESPACE_END + +#endif /* U_HIDE_INTERNAL_API */ +#endif +#endif diff --git a/icu4c/source/i18n/zonemeta.cpp b/icu4c/source/i18n/zonemeta.cpp index 0d90498cf2e..e244f9c2f82 100644 --- a/icu4c/source/i18n/zonemeta.cpp +++ b/icu4c/source/i18n/zonemeta.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2007-2011, International Business Machines Corporation and * +* Copyright (C) 2007-2012, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -14,6 +14,7 @@ #include "unicode/timezone.h" #include "unicode/ustring.h" #include "unicode/putil.h" +#include "unicode/simpletz.h" #include "umutex.h" #include "uvector.h" @@ -132,6 +133,8 @@ static const UChar gDefaultFrom[] = {0x31, 0x39, 0x37, 0x30, 0x2D, 0x30, 0x31, 0 static const UChar gDefaultTo[] = {0x39, 0x39, 0x39, 0x39, 0x2D, 0x31, 0x32, 0x2D, 0x33, 0x31, 0x20, 0x32, 0x33, 0x3A, 0x35, 0x39, 0x00}; // "9999-12-31 23:59" +static const UChar gCustomTzPrefix[] = {0x47, 0x4D, 0x54, 0}; // "GMT" + #define ASCII_DIGIT(c) (((c)>=0x30 && (c)<=0x39) ? (c)-0x30 : -1) /* @@ -842,6 +845,54 @@ ZoneMeta::findTimeZoneID(const UnicodeString& tzid) { return TimeZone::findID(tzid); } + +TimeZone* +ZoneMeta::createCustomTimeZone(int32_t offset) { + UBool negative = FALSE; + int32_t tmp = offset; + if (offset < 0) { + negative = TRUE; + tmp = -offset; + } + int32_t hour, min, sec; + + tmp /= 1000; + sec = tmp % 60; + tmp /= 60; + min = tmp % 60; + hour = tmp / 60; + + UnicodeString zid; + formatCustomID(hour, min, sec, negative, zid); + return new SimpleTimeZone(offset, zid); +} + +UnicodeString& +ZoneMeta::formatCustomID(uint8_t hour, uint8_t min, uint8_t sec, UBool negative, UnicodeString& id) { + // Create normalized time zone ID - GMT[+|-]HH:mm[:ss] + id.setTo(gCustomTzPrefix, -1); + if (hour != 0 || min != 0) { + if (negative) { + id.append(0x2D); // '-' + } else { + id.append(0x2B); // '+' + } + // Always use US-ASCII digits + id.append(0x30 + (hour%100)/10); + id.append(0x30 + (hour%10)); + id.append(0x3A); // ':' + id.append(0x30 + (min%100)/10); + id.append(0x30 + (min%10)); + if (sec != 0) { + id.append(0x3A); // ':' + id.append(0x30 + (sec%100)/10); + id.append(0x30 + (sec%10)); + } + } + return id; +} + + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/zonemeta.h b/icu4c/source/i18n/zonemeta.h index 085c2af023d..433957dedb7 100644 --- a/icu4c/source/i18n/zonemeta.h +++ b/icu4c/source/i18n/zonemeta.h @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2007-2011, International Business Machines Corporation and * +* Copyright (C) 2007-2012, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -87,10 +87,18 @@ public: */ static const UChar* U_EXPORT2 findMetaZoneID(const UnicodeString& mzid); + /** + * Creates a custom zone for the offset + * @param offset GMT offset in milliseconds + * @return A custom TimeZone for the offset with normalized time zone id + */ + static TimeZone* createCustomTimeZone(int32_t offset); + private: ZoneMeta(); // Prevent construction. static UVector* createMetazoneMappings(const UnicodeString &tzid); static void initAvailableMetaZoneIDs(); + static UnicodeString& formatCustomID(uint8_t hour, uint8_t min, uint8_t sec, UBool negative, UnicodeString& id); }; U_NAMESPACE_END diff --git a/icu4c/source/test/intltest/dtfmttst.cpp b/icu4c/source/test/intltest/dtfmttst.cpp index a9361cdb353..a043da8c9cc 100644 --- a/icu4c/source/test/intltest/dtfmttst.cpp +++ b/icu4c/source/test/intltest/dtfmttst.cpp @@ -2291,6 +2291,7 @@ void DateFormatTest::TestTimeZoneDisplayName() const char *fallbackTests[][6] = { { "en", "America/Los_Angeles", "2004-01-15T00:00:00Z", "Z", "-0800", "-8:00" }, { "en", "America/Los_Angeles", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-08:00", "-8:00" }, + { "en", "America/Los_Angeles", "2004-01-15T00:00:00Z", "ZZZZZ", "-08:00", "-8:00" }, { "en", "America/Los_Angeles", "2004-01-15T00:00:00Z", "z", "PST", "America/Los_Angeles" }, { "en", "America/Los_Angeles", "2004-01-15T00:00:00Z", "V", "PST", "America/Los_Angeles" }, { "en", "America/Los_Angeles", "2004-01-15T00:00:00Z", "zzzz", "Pacific Standard Time", "America/Los_Angeles" }, @@ -3423,8 +3424,10 @@ void DateFormatTest::TestGMTParsing() { "HH:mm:ss zzzz", "10:20:30 UTC", "10:20:30 +0000", // standalone "UTC" "ZZZZ HH:mm:ss", "UT 10:20:30", "10:20:30 +0000", "V HH:mm:ss", "UT+0130 10:20:30", "10:20:30 +0130", - "V HH:mm:ss", "UTC+0130 10:20:30", NULL, // UTC+0130 is not a supported pattern + "V HH:mm:ss", "UTC+0130 10:20:30", "10:20:30 +0130", "HH mm Z ss", "10 20 GMT-1100 30", "10:20:30 -1100", + "HH:mm:ssZZZZZ", "14:25:45Z", "14:25:45 +0000", + "HH:mm:ssZZZZZ", "15:00:00-08:00", "15:00:00 -0800", }; const int32_t DATA_len = sizeof(DATA)/sizeof(DATA[0]); expectParse(DATA, DATA_len, Locale("en")); diff --git a/icu4c/source/test/intltest/tzfmttst.cpp b/icu4c/source/test/intltest/tzfmttst.cpp index 92da28304b2..2fbb62ccd37 100644 --- a/icu4c/source/test/intltest/tzfmttst.cpp +++ b/icu4c/source/test/intltest/tzfmttst.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 2007-2011, International Business Machines Corporation and * +* Copyright (C) 2007-2012, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -20,7 +20,7 @@ #include "unicode/basictz.h" #include "cstring.h" -static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"}; +static const char* PATTERNS[] = {"z", "zzzz", "Z", "ZZZZ", "ZZZZZ", "v", "vvvv", "V", "VVVV"}; static const int NUM_PATTERNS = sizeof(PATTERNS)/sizeof(const char*); void @@ -202,13 +202,18 @@ TimeZoneFormatTest::TestTimeZoneRoundTrip(void) { } else { // Check if localized GMT format or RFC format is used. - int32_t numDigits = 0; - for (int n = 0; n < tzstr.length(); n++) { - if (u_isdigit(tzstr.charAt(n))) { - numDigits++; + UBool isOffsetFormat = (*PATTERNS[patidx] == 'Z'); + if (!isOffsetFormat) { + // Check if localized GMT format is used as a fallback of name styles + int32_t numDigits = 0; + for (int n = 0; n < tzstr.length(); n++) { + if (u_isdigit(tzstr.charAt(n))) { + numDigits++; + } } + isOffsetFormat = (numDigits >= 3); } - if (tzstr == localGMTString || numDigits >= 3) { + if (isOffsetFormat || tzstr == localGMTString) { // Localized GMT or RFC: total offset (raw + dst) must be preserved. int32_t inOffset = inRaw + inDst; int32_t outOffset = outRaw + outDst; @@ -260,9 +265,9 @@ public: UBool REALLY_VERBOSE = FALSE; // Whether each pattern is ambiguous at DST->STD local time overlap - UBool AMBIGUOUS_DST_DECESSION[] = { FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE }; + UBool AMBIGUOUS_DST_DECESSION[] = { FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE }; // Whether each pattern is ambiguous at STD->STD/DST->DST local time overlap - UBool AMBIGUOUS_NEGATIVE_SHIFT[] = { TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE }; + UBool AMBIGUOUS_NEGATIVE_SHIFT[] = { TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE }; // Workaround for #6338 //UnicodeString BASEPATTERN("yyyy-MM-dd'T'HH:mm:ss.SSS");