From 923ec1ad30a182ad6a075a8bf51f8d26dcfb5225 Mon Sep 17 00:00:00 2001 From: Frank Tang Date: Wed, 11 Dec 2019 20:24:50 +0000 Subject: [PATCH] ICU-20436 Add getDefaultHourCycle to DateTimePatternGenerator See #901 --- icu4c/source/i18n/dtptngen.cpp | 16 +++++++ icu4c/source/i18n/udatpg.cpp | 5 +++ icu4c/source/i18n/unicode/dtptngen.h | 11 +++++ icu4c/source/i18n/unicode/udat.h | 30 +++++++++++++ icu4c/source/i18n/unicode/udatpg.h | 15 +++++++ icu4c/source/test/cintltst/udatpg_test.c | 44 +++++++++++++++++++ icu4c/source/test/intltest/dtptngts.cpp | 15 +++++-- .../core/src/com/ibm/icu/text/DateFormat.java | 30 +++++++++++++ .../icu/text/DateTimePatternGenerator.java | 15 ++++++- .../test/format/DateTimeGeneratorTest.java | 10 +++-- 10 files changed, 182 insertions(+), 9 deletions(-) diff --git a/icu4c/source/i18n/dtptngen.cpp b/icu4c/source/i18n/dtptngen.cpp index 4948d2cbc97..09faf5bb6cc 100644 --- a/icu4c/source/i18n/dtptngen.cpp +++ b/icu4c/source/i18n/dtptngen.cpp @@ -687,6 +687,22 @@ void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErro } } +UDateFormatHourCycle +DateTimePatternGenerator::getDefaultHourCycle(UErrorCode& /*status*/) const { + switch(fDefaultHourFormatChar) { + case CAP_K: + return UDAT_HOUR_CYCLE_11; + case LOW_H: + return UDAT_HOUR_CYCLE_12; + case CAP_H: + return UDAT_HOUR_CYCLE_23; + case LOW_K: + return UDAT_HOUR_CYCLE_24; + default: + UPRV_UNREACHABLE; + } +} + UnicodeString DateTimePatternGenerator::getSkeleton(const UnicodeString& pattern, UErrorCode& /*status*/) { diff --git a/icu4c/source/i18n/udatpg.cpp b/icu4c/source/i18n/udatpg.cpp index febf73b3ce4..332636a9388 100644 --- a/icu4c/source/i18n/udatpg.cpp +++ b/icu4c/source/i18n/udatpg.cpp @@ -291,4 +291,9 @@ udatpg_getPatternForSkeleton(const UDateTimePatternGenerator *dtpg, return result.getBuffer(); } +U_CAPI UDateFormatHourCycle U_EXPORT2 +udatpg_getDefaultHourCycle(const UDateTimePatternGenerator *dtpg, UErrorCode* pErrorCode) { + return ((const DateTimePatternGenerator *)dtpg)->getDefaultHourCycle(*pErrorCode); +} + #endif diff --git a/icu4c/source/i18n/unicode/dtptngen.h b/icu4c/source/i18n/unicode/dtptngen.h index a71938b31cf..af74059c745 100644 --- a/icu4c/source/i18n/unicode/dtptngen.h +++ b/icu4c/source/i18n/unicode/dtptngen.h @@ -483,6 +483,17 @@ public: */ const UnicodeString& getDecimal() const; +#ifndef U_HIDE_DRAFT_API + /** + * Get the default hour cycle. + * @param status Output param set to success/failure code on exit, + * which must not indicate a failure before the function call. + * @return the default hour cycle. + * @draft ICU 67 + */ + UDateFormatHourCycle getDefaultHourCycle(UErrorCode& status) const; +#endif /* U_HIDE_DRAFT_API */ + /** * ICU "poor man's RTTI", returns a UClassID for the actual class. * diff --git a/icu4c/source/i18n/unicode/udat.h b/icu4c/source/i18n/unicode/udat.h index bdbd080c005..cf7a165e70a 100644 --- a/icu4c/source/i18n/unicode/udat.h +++ b/icu4c/source/i18n/unicode/udat.h @@ -958,7 +958,37 @@ udat_getBooleanAttribute(const UDateFormat* fmt, UDateFormatBooleanAttribute att U_CAPI void U_EXPORT2 udat_setBooleanAttribute(UDateFormat *fmt, UDateFormatBooleanAttribute attr, UBool newValue, UErrorCode* status); +#ifndef U_HIDE_DRAFT_API +/** + * Hour Cycle. + * @draft ICU 67 + */ +typedef enum UDateFormatHourCycle { + /** + * Hour in am/pm (0~11) + * @draft ICU 67 + */ + UDAT_HOUR_CYCLE_11, + + /** + * Hour in am/pm (1~12) + * @draft ICU 67 + */ + UDAT_HOUR_CYCLE_12, + + /** + * Hour in day (0~23) + * @draft ICU 67 + */ + UDAT_HOUR_CYCLE_23, + /** + * Hour in day (1~24) + * @draft ICU 67 + */ + UDAT_HOUR_CYCLE_24 +} UDateFormatHourCycle; +#endif /* U_HIDE_DRAFT_API */ #if U_SHOW_CPLUSPLUS_API diff --git a/icu4c/source/i18n/unicode/udatpg.h b/icu4c/source/i18n/unicode/udatpg.h index 7f28b5a3197..2b398289426 100644 --- a/icu4c/source/i18n/unicode/udatpg.h +++ b/icu4c/source/i18n/unicode/udatpg.h @@ -20,6 +20,7 @@ #define __UDATPG_H__ #include "unicode/utypes.h" +#include "unicode/udat.h" #include "unicode/uenum.h" #include "unicode/localpointer.h" @@ -651,4 +652,18 @@ udatpg_getPatternForSkeleton(const UDateTimePatternGenerator *dtpg, const UChar *skeleton, int32_t skeletonLength, int32_t *pLength); +#ifndef U_HIDE_DRAFT_API +/** + * Return the default hour cycle. + * + * @param dtpg a pointer to UDateTimePatternGenerator. + * @param pErrorCode a pointer to the UErrorCode which must not indicate a + * failure before the function call. + * @return the default hour cycle. + * @draft ICU 67 + */ +U_DRAFT UDateFormatHourCycle U_EXPORT2 +udatpg_getDefaultHourCycle(const UDateTimePatternGenerator *dtpg, UErrorCode* pErrorCode); +#endif /* U_HIDE_DRAFT_API */ + #endif diff --git a/icu4c/source/test/cintltst/udatpg_test.c b/icu4c/source/test/cintltst/udatpg_test.c index 2338a2f687f..b7d8cd79ed9 100644 --- a/icu4c/source/test/cintltst/udatpg_test.c +++ b/icu4c/source/test/cintltst/udatpg_test.c @@ -43,6 +43,7 @@ static void TestUsage(void); static void TestBuilder(void); static void TestOptions(void); static void TestGetFieldDisplayNames(void); +static void TestGetDefaultHourCycle(void); void addDateTimePatternGeneratorTest(TestNode** root) { TESTCASE(TestOpenClose); @@ -50,6 +51,7 @@ void addDateTimePatternGeneratorTest(TestNode** root) { TESTCASE(TestBuilder); TESTCASE(TestOptions); TESTCASE(TestGetFieldDisplayNames); + TESTCASE(TestGetDefaultHourCycle); } /* @@ -510,4 +512,46 @@ static void TestGetFieldDisplayNames() { } } +typedef struct HourCycleData { + const char * locale; + UDateFormatHourCycle expected; +} HourCycleData; + +static void TestGetDefaultHourCycle() { + const HourCycleData testData[] = { + /*loc expected */ + { "ar_EG", UDAT_HOUR_CYCLE_12 }, + { "de_DE", UDAT_HOUR_CYCLE_23 }, + { "en_AU", UDAT_HOUR_CYCLE_12 }, + { "en_CA", UDAT_HOUR_CYCLE_12 }, + { "en_US", UDAT_HOUR_CYCLE_12 }, + { "es_ES", UDAT_HOUR_CYCLE_23 }, + { "fi", UDAT_HOUR_CYCLE_23 }, + { "fr", UDAT_HOUR_CYCLE_23 }, + { "ja_JP", UDAT_HOUR_CYCLE_23 }, + { "zh_CN", UDAT_HOUR_CYCLE_12 }, + { "zh_HK", UDAT_HOUR_CYCLE_12 }, + { "zh_TW", UDAT_HOUR_CYCLE_12 }, + { "ko_KR", UDAT_HOUR_CYCLE_12 }, + }; + int count = UPRV_LENGTHOF(testData); + const HourCycleData * testDataPtr = testData; + for (; count-- > 0; ++testDataPtr) { + UErrorCode status = U_ZERO_ERROR; + UDateTimePatternGenerator * dtpgen = + udatpg_open(testDataPtr->locale, &status); + if ( U_FAILURE(status) ) { + log_data_err( "ERROR udatpg_open failed for locale %s : %s - (Are you missing data?)\n", + testDataPtr->locale, myErrorName(status)); + } else { + UDateFormatHourCycle actual = udatpg_getDefaultHourCycle(dtpgen, &status); + if (U_FAILURE(status) || testDataPtr->expected != actual) { + log_err("ERROR dtpgen locale %s udatpg_getDefaultHourCycle expecte to get %d but get %d\n", + testDataPtr->locale, testDataPtr->expected, actual); + } + udatpg_close(dtpgen); + } + } +} + #endif diff --git a/icu4c/source/test/intltest/dtptngts.cpp b/icu4c/source/test/intltest/dtptngts.cpp index 02745c12a2f..0a9c5eaf9b3 100644 --- a/icu4c/source/test/intltest/dtptngts.cpp +++ b/icu4c/source/test/intltest/dtptngts.cpp @@ -1410,18 +1410,19 @@ void IntlTestDateTimePatternGeneratorAPI::test20640_HourCyclArsEnNH() { const char* localeName; const char16_t* expectedDtpgPattern; const char16_t* expectedTimePattern; + UDateFormatHourCycle expectedDefaultHourCycle; } cases[] = { // ars is interesting because it does not have a region, but it aliases // to ar_SA, which has a region. - {"ars", u"h a", u"h:mm a"}, + {"ars", u"h a", u"h:mm a", UDAT_HOUR_CYCLE_12}, // en_NH is interesting because NH is a deprecated region code; // formerly New Hebrides, now Vanuatu => VU => h. - {"en_NH", u"h a", u"h:mm a"}, + {"en_NH", u"h a", u"h:mm a", UDAT_HOUR_CYCLE_12}, // ch_ZH is a typo (should be zh_CN), but we should fail gracefully. // {"cn_ZH", u"HH", u"H:mm"}, // TODO(ICU-20653): Desired behavior - {"cn_ZH", u"HH", u"h:mm a"}, // Actual behavior + {"cn_ZH", u"HH", u"h:mm a", UDAT_HOUR_CYCLE_23 }, // Actual behavior // a non-BCP47 locale without a country code should not fail - {"ja_TRADITIONAL", u"H時", u"H:mm"}, + {"ja_TRADITIONAL", u"H時", u"H:mm", UDAT_HOUR_CYCLE_23}, }; for (auto& cas : cases) { @@ -1440,11 +1441,17 @@ void IntlTestDateTimePatternGeneratorAPI::test20640_HourCyclArsEnNH() { if (status.errIfFailureAndReset()) { return; } + UDateFormatHourCycle defaultHourCycle = dtpg->getDefaultHourCycle(status); + if (status.errIfFailureAndReset()) { + return; + } assertEquals(UnicodeString("dtpgPattern ") + cas.localeName, cas.expectedDtpgPattern, dtpgPattern); assertEquals(UnicodeString("timePattern ") + cas.localeName, cas.expectedTimePattern, timePattern); + assertEquals(UnicodeString("defaultHour ") + cas.localeName, + cas.expectedDefaultHourCycle, defaultHourCycle); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java index 5eb207e189b..bd42fbd4bf7 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java @@ -535,6 +535,36 @@ public abstract class DateFormat extends UFormat { */ private EnumSet booleanAttributes = EnumSet.allOf(BooleanAttribute.class); + /** + * Hour Cycle + * @draft ICU 67 + */ + public enum HourCycle { + /** + * hour in am/pm (0~11) + * @draft ICU 67 + */ + HOUR_CYCLE_11, + + /** + * hour in am/pm (1~12) + * @draft ICU 67 + */ + HOUR_CYCLE_12, + + /** + * hour in day (0~23) + * @draft ICU 67 + */ + HOUR_CYCLE_23, + + /** + * hour in day (1~24) + * @draft ICU 67 + */ + HOUR_CYCLE_24; + }; + /* * Capitalization setting, hoisted to DateFormat ICU 53 * Note that SimpleDateFormat serialization may call getContext/setContext to read/write 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 eb233181a69..96becb5c770 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 @@ -1205,7 +1205,6 @@ public class DateTimePatternGenerator implements Freezable