From 721d41153ec4f80cd8556ac57dc023d169ef9dd2 Mon Sep 17 00:00:00 2001 From: Rich Gillam <62772518+richgillam@users.noreply.github.com> Date: Mon, 11 Jul 2022 18:41:10 -0700 Subject: [PATCH] ICU-22071 Fixed DateTimePatternGenerator to respect the locale's "rg" subtag (when it has one) when determining the hour cycle. --- icu4c/source/i18n/dtptngen.cpp | 11 +++++++ icu4c/source/test/cintltst/udatpg_test.c | 33 +++++++++++++++++++ icu4c/source/test/intltest/dtptngts.cpp | 29 ++++++++++++++++ icu4c/source/test/intltest/dtptngts.h | 1 + .../icu/text/DateTimePatternGenerator.java | 10 ++++++ .../test/format/DateTimeGeneratorTest.java | 23 +++++++++++++ 6 files changed, 107 insertions(+) diff --git a/icu4c/source/i18n/dtptngen.cpp b/icu4c/source/i18n/dtptngen.cpp index 3171dfd5c3d..87eae8c7245 100644 --- a/icu4c/source/i18n/dtptngen.cpp +++ b/icu4c/source/i18n/dtptngen.cpp @@ -656,6 +656,17 @@ void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErro const char *language = locale.getLanguage(); const char *country = locale.getCountry(); + + char regionOverride[8]; + int32_t regionOverrideLength = locale.getKeywordValue("rg", regionOverride, sizeof(regionOverride), status); + if (U_SUCCESS(status) && regionOverrideLength > 0) { + country = regionOverride; + if (regionOverrideLength > 2) { + // chop off any subdivision codes that may have been included + regionOverride[2] = '\0'; + } + } + Locale maxLocale; // must be here for correct lifetime if (*language == '\0' || *country == '\0') { maxLocale = locale; diff --git a/icu4c/source/test/cintltst/udatpg_test.c b/icu4c/source/test/cintltst/udatpg_test.c index 9a70ce1eae0..c9bd0942a64 100644 --- a/icu4c/source/test/cintltst/udatpg_test.c +++ b/icu4c/source/test/cintltst/udatpg_test.c @@ -47,6 +47,7 @@ static void TestGetDefaultHourCycle(void); static void TestGetDefaultHourCycleOnEmptyInstance(void); static void TestEras(void); static void TestDateTimePatterns(void); +static void TestRegionOverride(void); void addDateTimePatternGeneratorTest(TestNode** root) { TESTCASE(TestOpenClose); @@ -58,6 +59,7 @@ void addDateTimePatternGeneratorTest(TestNode** root) { TESTCASE(TestGetDefaultHourCycleOnEmptyInstance); TESTCASE(TestEras); TESTCASE(TestDateTimePatterns); + TESTCASE(TestRegionOverride); } /* @@ -790,4 +792,35 @@ static void doDTPatternTest(UDateTimePatternGenerator* udtpg, } } +static void TestRegionOverride(void) { + typedef struct RegionOverrideTest { + const char* locale; + const UChar* expectedPattern; + UDateFormatHourCycle expectedHourCycle; + } RegionOverrideTest; + + const RegionOverrideTest testCases[] = { + { "en_US", u"h:mm\u202fa", UDAT_HOUR_CYCLE_12 }, + { "en_GB", u"HH:mm", UDAT_HOUR_CYCLE_23 }, + { "en_US@rg=GBZZZZ", u"HH:mm", UDAT_HOUR_CYCLE_23 }, + { "en_US@hours=h23", u"HH:mm", UDAT_HOUR_CYCLE_23 }, + }; + + for (int32_t i = 0; i < UPRV_LENGTHOF(testCases); i++) { + UErrorCode err = U_ZERO_ERROR; + UChar actualPattern[200]; + UDateTimePatternGenerator* dtpg = udatpg_open(testCases[i].locale, &err); + + if (assertSuccess("Error creating dtpg", &err)) { + UDateFormatHourCycle actualHourCycle = udatpg_getDefaultHourCycle(dtpg, &err); + udatpg_getBestPattern(dtpg, u"jmm", -1, actualPattern, 200, &err); + + if (assertSuccess("Error using dtpg", &err)) { + assertIntEquals("Wrong hour cycle", testCases[i].expectedHourCycle, actualHourCycle); + assertUEquals("Wrong pattern", testCases[i].expectedPattern, actualPattern); + } + } + udatpg_close(dtpg); + } +} #endif diff --git a/icu4c/source/test/intltest/dtptngts.cpp b/icu4c/source/test/intltest/dtptngts.cpp index 63611d05789..2b270fcc2bb 100644 --- a/icu4c/source/test/intltest/dtptngts.cpp +++ b/icu4c/source/test/intltest/dtptngts.cpp @@ -47,6 +47,7 @@ void IntlTestDateTimePatternGeneratorAPI::runIndexedTest( int32_t index, UBool e TESTCASE(11, test_jConsistencyOddLocales); TESTCASE(12, testBestPattern); TESTCASE(13, testDateTimePatterns); + TESTCASE(14, testRegionOverride); default: name = ""; break; } } @@ -1760,6 +1761,34 @@ void IntlTestDateTimePatternGeneratorAPI::testDateTimePatterns() { } } +void IntlTestDateTimePatternGeneratorAPI::testRegionOverride() { + const struct TestCase { + const char* locale; + const UChar* expectedPattern; + UDateFormatHourCycle expectedHourCycle; + } testCases[] = { + { "en_US", u"h:mm\u202fa", UDAT_HOUR_CYCLE_12 }, + { "en_GB", u"HH:mm", UDAT_HOUR_CYCLE_23 }, + { "en_US@rg=GBZZZZ", u"HH:mm", UDAT_HOUR_CYCLE_23 }, + { "en_US@hours=h23", u"HH:mm", UDAT_HOUR_CYCLE_23 }, + }; + + for (int32_t i = 0; i < UPRV_LENGTHOF(testCases); i++) { + UErrorCode err = U_ZERO_ERROR; + LocalPointer dtpg(DateTimePatternGenerator::createInstance(testCases[i].locale, err)); + + if (assertSuccess("Error creating dtpg", err)) { + UDateFormatHourCycle actualHourCycle = dtpg->getDefaultHourCycle(err); + UnicodeString actualPattern = dtpg->getBestPattern(u"jmm", err); + + if (assertSuccess("Error using dtpg", err)) { + assertEquals("Wrong hour cycle", testCases[i].expectedHourCycle, actualHourCycle); + assertEquals("Wrong pattern", testCases[i].expectedPattern, actualPattern); + } + } + } +} + void IntlTestDateTimePatternGeneratorAPI::doDTPatternTest(DateTimePatternGenerator* dtpg, UnicodeString* skeletons, DTPLocaleAndResults* localeAndResultsPtr) { for (int32_t patStyle = 0; patStyle < kNumDateTimePatterns; patStyle++) { UErrorCode status = U_ZERO_ERROR; diff --git a/icu4c/source/test/intltest/dtptngts.h b/icu4c/source/test/intltest/dtptngts.h index ec700603eb1..30083f1b8b5 100644 --- a/icu4c/source/test/intltest/dtptngts.h +++ b/icu4c/source/test/intltest/dtptngts.h @@ -41,6 +41,7 @@ private: void test_jConsistencyOddLocales(); void testBestPattern(); void testDateTimePatterns(); + void testRegionOverride(); enum { kNumDateTimePatterns = 4 }; typedef struct { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java index af88dbd19cc..3792850a8b2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java @@ -358,6 +358,7 @@ public class DateTimePatternGenerator implements Freezable 2) { + regionOverride = regionOverride.substring(0, 2); + } + country = regionOverride; + } + if (language.isEmpty()) { // Unexpected, but fail gracefully language = "und"; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateTimeGeneratorTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateTimeGeneratorTest.java index e17615cb390..988ebf65c8a 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateTimeGeneratorTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateTimeGeneratorTest.java @@ -2030,4 +2030,27 @@ public class DateTimeGeneratorTest extends TestFmwk { } } } + + @Test + public void testRegionOverride() { + String[][] testCases = { + { "en_US", "h:mm\u202fa", "HOUR_CYCLE_12" }, + { "en_GB", "HH:mm", "HOUR_CYCLE_23" }, + { "en_US@rg=GBZZZZ", "HH:mm", "HOUR_CYCLE_23" }, + { "en_US@hours=h23", "HH:mm", "HOUR_CYCLE_23" }, + }; + + for (String[] testCase : testCases) { + String localeID = testCase[0]; + String expectedPattern = testCase[1]; + String expectedHourCycle = testCase[2]; + + DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(new ULocale(localeID)); + String actualHourCycle = dtpg.getDefaultHourCycle().toString(); + String actualPattern = dtpg.getBestPattern("jmm"); + + assertEquals("Wrong hour cycle", expectedHourCycle, actualHourCycle); + assertEquals("Wrong pattern", expectedPattern, actualPattern); + } + } }