diff --git a/icu4c/source/i18n/dtfmtsym.cpp b/icu4c/source/i18n/dtfmtsym.cpp index bebd9b12b7..0866b33be6 100644 --- a/icu4c/source/i18n/dtfmtsym.cpp +++ b/icu4c/source/i18n/dtfmtsym.cpp @@ -565,6 +565,34 @@ DateFormatSymbols::initializeData(const Locale& locale, UErrorCode& status, UBoo * @see java.util.SimpleTimeZone */ int32_t DateFormatSymbols::getZoneIndex(const UnicodeString& ID) const +{ + int32_t result = _getZoneIndex(ID); + if (result >= 0) { + return result; + } + + // Do a search through the equivalency group for the given ID + int32_t n = TimeZone::countEquivalentIDs(ID); + if (n > 1) { + int32_t i; + for (i=0; i= 0) { + return equivResult; + } + } + } + } + + return -1; +} + +/** + * Lookup the given ID. Do NOT do an equivalency search. + */ +int32_t DateFormatSymbols::_getZoneIndex(const UnicodeString& ID) const { // {sfb} kludge to support case-insensitive comparison UnicodeString lcaseID(ID); @@ -573,8 +601,9 @@ int32_t DateFormatSymbols::getZoneIndex(const UnicodeString& ID) const for(int32_t index = 0; index < fZoneStringsRowCount; index++) { UnicodeString lcase(fZoneStrings[index][0]); lcase.toLower(); - if (lcaseID == lcase) + if (lcaseID == lcase) { return index; + } } return -1; diff --git a/icu4c/source/i18n/timezone.cpp b/icu4c/source/i18n/timezone.cpp index 8b2402e789..1b5d21fffc 100644 --- a/icu4c/source/i18n/timezone.cpp +++ b/icu4c/source/i18n/timezone.cpp @@ -218,6 +218,22 @@ TimeZone::createSystemTimeZone(const UnicodeString& name) { return 0; } + const TZEquivalencyGroup *eg = lookupEquivalencyGroup(name); + if (eg != 0) { + return eg->isDST ? + new SimpleTimeZone(eg->u.d.zone, name) : + new SimpleTimeZone(eg->u.s.zone, name); + } + return 0; +} + +/** + * Lookup the given ID in the system time zone equivalency group table. + * Return a pointer to the equivalency group, or NULL if not found. + * DATA MUST BE INITIALIZED AND NON-NULL. + */ +const TZEquivalencyGroup* +TimeZone::lookupEquivalencyGroup(const UnicodeString& id) { // Perform a binary search. Possible optimization: Unroll the // search. Not worth it given the small number of zones (416 in // 1999j). @@ -227,22 +243,15 @@ TimeZone::createSystemTimeZone(const UnicodeString& name) { // Invariant: match, if present, must be in the range [low, // high). uint32_t i = (low + high) / 2; - int8_t c = name.compare(ZONE_IDS[i]); + int8_t c = id.compare(ZONE_IDS[i]); if (c == 0) { - const TZEquivalencyGroup *eg = (TZEquivalencyGroup*) - ((int8_t*)DATA + INDEX_BY_ID[i]); - // NOTE: standard zones must be before DST zones. We test - // for this when loading up the data; see loadZoneData(). - return eg->isDST ? - new SimpleTimeZone(eg->u.d.zone, name) : - new SimpleTimeZone(eg->u.s.zone, name); + return (TZEquivalencyGroup*) ((int8_t*)DATA + INDEX_BY_ID[i]); } else if (c < 0) { high = i; } else { low = i + 1; } } - return 0; } @@ -473,6 +482,39 @@ TimeZone::createAvailableIDs(int32_t& numIDs) // --------------------------------------- +int32_t +TimeZone::countEquivalentIDs(const UnicodeString& id) { + if (!DATA_LOADED) { + loadZoneData(); + } + if (0 == DATA) { + return 0; + } + const TZEquivalencyGroup *eg = lookupEquivalencyGroup(id); + return (eg != 0) ? (eg->isDST ? eg->u.d.count : eg->u.s.count) : 0; +} + +// --------------------------------------- + +const UnicodeString +TimeZone::getEquivalentID(const UnicodeString& id, int32_t index) { + if (!DATA_LOADED) { + loadZoneData(); + } + if (0 != DATA) { + const TZEquivalencyGroup *eg = lookupEquivalencyGroup(id); + if (eg != 0) { + const uint16_t *p = eg->isDST ? &eg->u.d.count : &eg->u.s.count; + if (index >= 0 && index < *p) { + return ZONE_IDS[p[index+1]]; + } + } + } + return UnicodeString(); +} + +// --------------------------------------- + UnicodeString& TimeZone::getDisplayName(UnicodeString& result) const diff --git a/icu4c/source/i18n/unicode/dtfmtsym.h b/icu4c/source/i18n/unicode/dtfmtsym.h index 61db4d2454..c27b72df34 100644 --- a/icu4c/source/i18n/unicode/dtfmtsym.h +++ b/icu4c/source/i18n/unicode/dtfmtsym.h @@ -344,6 +344,9 @@ private: */ int32_t getZoneIndex(const UnicodeString& ID) const; + // Internal method; see source for documentation + int32_t _getZoneIndex(const UnicodeString& id) const; + /** * Delete all the storage owned by this object and reset the fIsOwned flag * to indicate that arrays have been deleted. diff --git a/icu4c/source/i18n/unicode/timezone.h b/icu4c/source/i18n/unicode/timezone.h index 452a72cb37..c410bc4429 100644 --- a/icu4c/source/i18n/unicode/timezone.h +++ b/icu4c/source/i18n/unicode/timezone.h @@ -34,6 +34,7 @@ class SimpleTimeZone; struct TZHeader; struct OffsetIndex; +struct TZEquivalencyGroup; /** * TimeZone represents a time zone offset, and also figures out daylight @@ -171,6 +172,44 @@ public: */ static const UnicodeString** const createAvailableIDs(int32_t& numIDs); + /** + * Returns the number of IDs in the equivalency group that + * includes the given ID. An equivalency group contains zones + * that have the same GMT offset and rules. + * + *

The returned count includes the given ID; it is always >= 1. + * The given ID must be a system time zone. If it is not, returns + * zero. + * @param id a system time zone ID + * @return the number of zones in the equivalency group containing + * 'id', or zero if 'id' is not a valid system ID + * @see #getEquivalentID + * @draft + */ + static int32_t countEquivalentIDs(const UnicodeString& id); + + /** + * Returns an ID in the equivalency group that + * includes the given ID. An equivalency group contains zones + * that have the same GMT offset and rules. + * + *

The given index must be in the range 0..n-1, where n is the + * value returned by countEquivalentIDs(id). For + * some value of 'index', the returned value will be equal to the + * given id. If the given id is not a valid system time zone, or + * if 'index' is out of range, then returns an empty string. + * @param id a system time zone ID + * @param index a value from 0 to n-1, where n is the value + * returned by countEquivalentIDs(id) + * @return the ID of the index-th zone in the equivalency group + * containing 'id', or an empty string if 'id' is not a valid + * system ID or 'index' is out of range + * @see #countEquivalentIDs + * @draft + */ + static const UnicodeString getEquivalentID(const UnicodeString& id, + int32_t index); + /** * Creates a new copy of the default TimeZone for this host. Unless the default time * zone has already been set using adoptDefault() or setDefault(), the default is @@ -544,6 +583,9 @@ private: // See source file for documentation static TimeZone* createSystemTimeZone(const UnicodeString& name); + // See source file for documentation + static const TZEquivalencyGroup* lookupEquivalencyGroup(const UnicodeString& id); + UnicodeString fID; // this time zone's ID }; diff --git a/icu4c/source/test/intltest/tzregts.cpp b/icu4c/source/test/intltest/tzregts.cpp index f90b6c29c4..42032e0df1 100644 --- a/icu4c/source/test/intltest/tzregts.cpp +++ b/icu4c/source/test/intltest/tzregts.cpp @@ -17,7 +17,7 @@ // class TimeZoneRegressionTest // ***************************************************************************** -#define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break; +#define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break void TimeZoneRegressionTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) @@ -25,22 +25,23 @@ TimeZoneRegressionTest::runIndexedTest( int32_t index, UBool exec, const char* & // if (exec) logln((UnicodeString)"TestSuite NumberFormatRegressionTest"); switch (index) { - CASE(0, Test4052967) - CASE(1, Test4073209) - CASE(2, Test4073215) - CASE(3, Test4084933) - CASE(4, Test4096952) - CASE(5, Test4109314) - CASE(6, Test4126678) - CASE(7, Test4151406) - CASE(8, Test4151429) - CASE(9, Test4154537) - CASE(10, Test4154542) - CASE(11, Test4154650) - CASE(12, Test4154525) - CASE(13, Test4162593) - CASE(14, TestJ186) - CASE(15, TestJDK12API) + CASE(0, Test4052967); + CASE(1, Test4073209); + CASE(2, Test4073215); + CASE(3, Test4084933); + CASE(4, Test4096952); + CASE(5, Test4109314); + CASE(6, Test4126678); + CASE(7, Test4151406); + CASE(8, Test4151429); + CASE(9, Test4154537); + CASE(10, Test4154542); + CASE(11, Test4154650); + CASE(12, Test4154525); + CASE(13, Test4162593); + CASE(14, TestJ186); + CASE(15, TestJ449); + CASE(16, TestJDK12API); default: name = ""; break; } @@ -877,6 +878,63 @@ void TimeZoneRegressionTest::TestJ186() { } } +/** + * Test to see if DateFormat understands zone equivalency groups. It + * might seem that this should be a DateFormat test, but it's really a + * TimeZone test -- the changes to DateFormat are minor. + * + * We use two known, stable zones that shouldn't change much over time + * -- America/Vancouver and America/Los_Angeles. However, they MAY + * change at some point -- if that happens, replace them with any two + * zones in an equivalency group where one zone has localized name + * data, and the other doesn't, in some locale. + */ +void TimeZoneRegressionTest::TestJ449() { + UErrorCode status = U_ZERO_ERROR; + UnicodeString str; + + // Modify the following three as necessary. The two IDs must + // specify two zones in the same equivalency group. One must have + // locale data in 'loc'; the other must not. + const char* idWithLocaleData = "America/Los_Angeles"; + const char* idWithoutLocaleData = "America/Vancouver"; + const Locale loc("en", "", ""); + + TimeZone *zoneWith = TimeZone::createTimeZone(idWithLocaleData); + TimeZone *zoneWithout = TimeZone::createTimeZone(idWithoutLocaleData); + // Make sure we got valid zones + if (zoneWith->getID(str) != UnicodeString(idWithLocaleData) || + zoneWithout->getID(str) != UnicodeString(idWithoutLocaleData)) { + errln("Fail: Unable to create zones"); + } else { + GregorianCalendar calWith(*zoneWith, status); + GregorianCalendar calWithout(*zoneWithout, status); + SimpleDateFormat fmt("MMM d yyyy hh:mm a zzz", loc, status); + if (U_FAILURE(status)) { + errln("Fail: Unable to create GregorianCalendar/SimpleDateFormat"); + } else { + UDate date = 0; + UnicodeString strWith, strWithout; + fmt.setCalendar(calWith); + fmt.format(date, strWith); + fmt.setCalendar(calWithout); + fmt.format(date, strWithout); + if (strWith == strWithout) { + logln((UnicodeString)"Ok: " + idWithLocaleData + " -> " + + strWith + "; " + idWithoutLocaleData + " -> " + + strWithout); + } else { + errln((UnicodeString)"FAIL: " + idWithLocaleData + " -> " + + strWith + "; " + idWithoutLocaleData + " -> " + + strWithout); + } + } + } + + delete zoneWith; + delete zoneWithout; +} + // test new API for JDK 1.2 8/31 putback void TimeZoneRegressionTest::TestJDK12API() diff --git a/icu4c/source/test/intltest/tzregts.h b/icu4c/source/test/intltest/tzregts.h index d210b2dcb4..efa1378ccd 100644 --- a/icu4c/source/test/intltest/tzregts.h +++ b/icu4c/source/test/intltest/tzregts.h @@ -40,6 +40,7 @@ public: void Test4154525(void); void Test4162593(void); void TestJ186(void); + void TestJ449(void); void TestJDK12API(void); UBool checkCalendar314(GregorianCalendar *testCal, TimeZone *testTZ);