From 7dd19b65ab853d993e7819bd38559f029cda5865 Mon Sep 17 00:00:00 2001 From: Peter Edberg Date: Mon, 22 May 2017 06:16:11 +0000 Subject: [PATCH] ICU-13183 on branch - Handle abB as normal fields but force add/remove depending on hour field; other fixes X-SVN-Rev: 40124 --- icu4c/source/i18n/dtptngen.cpp | 122 +++++++++++----- icu4c/source/i18n/dtptngen_impl.h | 7 +- icu4c/source/i18n/unicode/dtptngen.h | 35 +++-- icu4c/source/test/intltest/dtptngts.cpp | 67 ++++++++- icu4c/source/test/intltest/dtptngts.h | 1 + .../icu/text/DateTimePatternGenerator.java | 131 ++++++++++++------ .../test/format/DateTimeGeneratorTest.java | 42 +++++- 7 files changed, 305 insertions(+), 100 deletions(-) diff --git a/icu4c/source/i18n/dtptngen.cpp b/icu4c/source/i18n/dtptngen.cpp index 5ce3630d98f..00eb7683de6 100644 --- a/icu4c/source/i18n/dtptngen.cpp +++ b/icu4c/source/i18n/dtptngen.cpp @@ -134,15 +134,18 @@ U_NAMESPACE_BEGIN // class DateTimePatternGenerator // ***************************************************************************** static const UChar Canonical_Items[] = { - // GyQMwWEdDFHmsSv - CAP_G, LOW_Y, CAP_Q, CAP_M, LOW_W, CAP_W, CAP_E, LOW_D, CAP_D, CAP_F, + // GyQMwWEDFdaHmsSv + CAP_G, LOW_Y, CAP_Q, CAP_M, LOW_W, CAP_W, CAP_E, + CAP_D, CAP_F, LOW_D, LOW_A, // The UDATPG_x_FIELD constants and these fields have a different order than in ICU4J CAP_H, LOW_M, LOW_S, CAP_S, LOW_V, 0 }; static const dtTypeElem dtTypes[] = { // patternChar, field, type, minLen, weight {CAP_G, UDATPG_ERA_FIELD, DT_SHORT, 1, 3,}, - {CAP_G, UDATPG_ERA_FIELD, DT_LONG, 4, 0}, + {CAP_G, UDATPG_ERA_FIELD, DT_LONG, 4, 0}, + {CAP_G, UDATPG_ERA_FIELD, DT_NARROW, 5, 0}, + {LOW_Y, UDATPG_YEAR_FIELD, DT_NUMERIC, 1, 20}, {CAP_Y, UDATPG_YEAR_FIELD, DT_NUMERIC + DT_DELTA, 1, 20}, {LOW_U, UDATPG_YEAR_FIELD, DT_NUMERIC + 2*DT_DELTA, 1, 20}, @@ -150,12 +153,16 @@ static const dtTypeElem dtTypes[] = { {CAP_U, UDATPG_YEAR_FIELD, DT_SHORT, 1, 3}, {CAP_U, UDATPG_YEAR_FIELD, DT_LONG, 4, 0}, {CAP_U, UDATPG_YEAR_FIELD, DT_NARROW, 5, 0}, + {CAP_Q, UDATPG_QUARTER_FIELD, DT_NUMERIC, 1, 2}, {CAP_Q, UDATPG_QUARTER_FIELD, DT_SHORT, 3, 0}, {CAP_Q, UDATPG_QUARTER_FIELD, DT_LONG, 4, 0}, + {CAP_Q, UDATPG_QUARTER_FIELD, DT_NARROW, 5, 0}, {LOW_Q, UDATPG_QUARTER_FIELD, DT_NUMERIC + DT_DELTA, 1, 2}, - {LOW_Q, UDATPG_QUARTER_FIELD, DT_SHORT + DT_DELTA, 3, 0}, - {LOW_Q, UDATPG_QUARTER_FIELD, DT_LONG + DT_DELTA, 4, 0}, + {LOW_Q, UDATPG_QUARTER_FIELD, DT_SHORT - DT_DELTA, 3, 0}, + {LOW_Q, UDATPG_QUARTER_FIELD, DT_LONG - DT_DELTA, 4, 0}, + {LOW_Q, UDATPG_QUARTER_FIELD, DT_NARROW - DT_DELTA, 5, 0}, + {CAP_M, UDATPG_MONTH_FIELD, DT_NUMERIC, 1, 2}, {CAP_M, UDATPG_MONTH_FIELD, DT_SHORT, 3, 0}, {CAP_M, UDATPG_MONTH_FIELD, DT_LONG, 4, 0}, @@ -165,32 +172,66 @@ static const dtTypeElem dtTypes[] = { {CAP_L, UDATPG_MONTH_FIELD, DT_LONG - DT_DELTA, 4, 0}, {CAP_L, UDATPG_MONTH_FIELD, DT_NARROW - DT_DELTA, 5, 0}, {LOW_L, UDATPG_MONTH_FIELD, DT_NUMERIC + DT_DELTA, 1, 1}, + {LOW_W, UDATPG_WEEK_OF_YEAR_FIELD, DT_NUMERIC, 1, 2}, - {CAP_W, UDATPG_WEEK_OF_MONTH_FIELD, DT_NUMERIC + DT_DELTA, 1, 0}, + + {CAP_W, UDATPG_WEEK_OF_MONTH_FIELD, DT_NUMERIC, 1, 0}, + {CAP_E, UDATPG_WEEKDAY_FIELD, DT_SHORT, 1, 3}, {CAP_E, UDATPG_WEEKDAY_FIELD, DT_LONG, 4, 0}, {CAP_E, UDATPG_WEEKDAY_FIELD, DT_NARROW, 5, 0}, + {CAP_E, UDATPG_WEEKDAY_FIELD, DT_SHORTER, 6, 0}, {LOW_C, UDATPG_WEEKDAY_FIELD, DT_NUMERIC + 2*DT_DELTA, 1, 2}, {LOW_C, UDATPG_WEEKDAY_FIELD, DT_SHORT - 2*DT_DELTA, 3, 0}, {LOW_C, UDATPG_WEEKDAY_FIELD, DT_LONG - 2*DT_DELTA, 4, 0}, {LOW_C, UDATPG_WEEKDAY_FIELD, DT_NARROW - 2*DT_DELTA, 5, 0}, + {LOW_C, UDATPG_WEEKDAY_FIELD, DT_SHORTER - 2*DT_DELTA, 6, 0}, {LOW_E, UDATPG_WEEKDAY_FIELD, DT_NUMERIC + DT_DELTA, 1, 2}, // LOW_E is currently not used in CLDR data, should not be canonical {LOW_E, UDATPG_WEEKDAY_FIELD, DT_SHORT - DT_DELTA, 3, 0}, {LOW_E, UDATPG_WEEKDAY_FIELD, DT_LONG - DT_DELTA, 4, 0}, {LOW_E, UDATPG_WEEKDAY_FIELD, DT_NARROW - DT_DELTA, 5, 0}, + {LOW_E, UDATPG_WEEKDAY_FIELD, DT_SHORTER - DT_DELTA, 6, 0}, + {LOW_D, UDATPG_DAY_FIELD, DT_NUMERIC, 1, 2}, - {CAP_D, UDATPG_DAY_OF_YEAR_FIELD, DT_NUMERIC + DT_DELTA, 1, 3}, - {CAP_F, UDATPG_DAY_OF_WEEK_IN_MONTH_FIELD, DT_NUMERIC + 2*DT_DELTA, 1, 0}, - {LOW_G, UDATPG_DAY_FIELD, DT_NUMERIC + 3*DT_DELTA, 1, 20}, // really internal use, so we don't care - {LOW_A, UDATPG_DAYPERIOD_FIELD, DT_SHORT, 1, 0}, + {LOW_G, UDATPG_DAY_FIELD, DT_NUMERIC + DT_DELTA, 1, 20}, // really internal use, so we don't care + + {CAP_D, UDATPG_DAY_OF_YEAR_FIELD, DT_NUMERIC, 1, 3}, + + {CAP_F, UDATPG_DAY_OF_WEEK_IN_MONTH_FIELD, DT_NUMERIC, 1, 0}, + + {LOW_A, UDATPG_DAYPERIOD_FIELD, DT_SHORT, 1, 3}, + {LOW_A, UDATPG_DAYPERIOD_FIELD, DT_LONG, 4, 0}, + {LOW_A, UDATPG_DAYPERIOD_FIELD, DT_NARROW, 5, 0}, + {LOW_B, UDATPG_DAYPERIOD_FIELD, DT_SHORT - DT_DELTA, 1, 3}, + {LOW_B, UDATPG_DAYPERIOD_FIELD, DT_LONG - DT_DELTA, 4, 0}, + {LOW_B, UDATPG_DAYPERIOD_FIELD, DT_NARROW - DT_DELTA, 5, 0}, + // b needs to be closer to a than to B, so we make this 3*DT_DELTA + {CAP_B, UDATPG_DAYPERIOD_FIELD, DT_SHORT - 3*DT_DELTA, 1, 3}, + {CAP_B, UDATPG_DAYPERIOD_FIELD, DT_LONG - 3*DT_DELTA, 4, 0}, + {CAP_B, UDATPG_DAYPERIOD_FIELD, DT_NARROW - 3*DT_DELTA, 5, 0}, + {CAP_H, UDATPG_HOUR_FIELD, DT_NUMERIC + 10*DT_DELTA, 1, 2}, // 24 hour {LOW_K, UDATPG_HOUR_FIELD, DT_NUMERIC + 11*DT_DELTA, 1, 2}, // 24 hour {LOW_H, UDATPG_HOUR_FIELD, DT_NUMERIC, 1, 2}, // 12 hour {CAP_K, UDATPG_HOUR_FIELD, DT_NUMERIC + DT_DELTA, 1, 2}, // 12 hour + // The C code has had versions of the following 3, keep & update. Should not need these, but... + // Without these, certain tests using e.g. staticGetSkeleton fail because j/J in patterns + // get skipped instead of mapped to the right hour chars, for example in + // DateFormatTest::TestPatternFromSkeleton + // IntlTestDateTimePatternGeneratorAPI:: testStaticGetSkeleton + // DateIntervalFormatTest::testTicket11985 + // Need to investigate better handling of jJC replacement e.g. in staticGetSkeleton. + {CAP_J, UDATPG_HOUR_FIELD, DT_NUMERIC + 5*DT_DELTA, 1, 2}, // 12/24 hour no AM/PM + {LOW_J, UDATPG_HOUR_FIELD, DT_NUMERIC + 6*DT_DELTA, 1, 6}, // 12/24 hour + {CAP_C, UDATPG_HOUR_FIELD, DT_NUMERIC + 7*DT_DELTA, 1, 6}, // 12/24 hour with preferred dayPeriods for 12 + {LOW_M, UDATPG_MINUTE_FIELD, DT_NUMERIC, 1, 2}, + {LOW_S, UDATPG_SECOND_FIELD, DT_NUMERIC, 1, 2}, - {CAP_S, UDATPG_FRACTIONAL_SECOND_FIELD, DT_NUMERIC + DT_DELTA, 1, 1000}, - {CAP_A, UDATPG_SECOND_FIELD, DT_NUMERIC + 2*DT_DELTA, 1, 1000}, + {CAP_A, UDATPG_SECOND_FIELD, DT_NUMERIC + DT_DELTA, 1, 1000}, + + {CAP_S, UDATPG_FRACTIONAL_SECOND_FIELD, DT_NUMERIC, 1, 1000}, + {LOW_V, UDATPG_ZONE_FIELD, DT_SHORT - 2*DT_DELTA, 1, 0}, {LOW_V, UDATPG_ZONE_FIELD, DT_LONG - 2*DT_DELTA, 4, 0}, {LOW_Z, UDATPG_ZONE_FIELD, DT_SHORT, 1, 3}, @@ -202,24 +243,27 @@ static const dtTypeElem dtTypes[] = { {CAP_O, UDATPG_ZONE_FIELD, DT_LONG - DT_DELTA, 4, 0}, {CAP_V, UDATPG_ZONE_FIELD, DT_SHORT - DT_DELTA, 1, 0}, {CAP_V, UDATPG_ZONE_FIELD, DT_LONG - DT_DELTA, 2, 0}, + {CAP_V, UDATPG_ZONE_FIELD, DT_LONG-1 - DT_DELTA, 3, 0}, + {CAP_V, UDATPG_ZONE_FIELD, DT_LONG-2 - DT_DELTA, 4, 0}, {CAP_X, UDATPG_ZONE_FIELD, DT_NARROW - DT_DELTA, 1, 0}, {CAP_X, UDATPG_ZONE_FIELD, DT_SHORT - DT_DELTA, 2, 0}, {CAP_X, UDATPG_ZONE_FIELD, DT_LONG - DT_DELTA, 4, 0}, {LOW_X, UDATPG_ZONE_FIELD, DT_NARROW - DT_DELTA, 1, 0}, {LOW_X, UDATPG_ZONE_FIELD, DT_SHORT - DT_DELTA, 2, 0}, {LOW_X, UDATPG_ZONE_FIELD, DT_LONG - DT_DELTA, 4, 0}, - {LOW_J, UDATPG_HOUR_FIELD, DT_NUMERIC, 1, 2}, // 12/24 hour - {CAP_J, UDATPG_HOUR_FIELD, DT_NUMERIC, 1, 2}, // 12/24 hour no AM/PM + {0, UDATPG_FIELD_COUNT, 0, 0, 0} , // last row of dtTypes[] }; static const char* const CLDR_FIELD_APPEND[] = { - "Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week", "Day", "*", "*", "*", + "Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week", + "*", "*", "Day", "*", // The UDATPG_x_FIELD constants and these fields have a different order than in ICU4J "Hour", "Minute", "Second", "*", "Timezone" }; static const char* const CLDR_FIELD_NAME[] = { - "era", "year", "quarter", "month", "week", "*", "weekday", "*", "*", "day", "dayperiod", + "era", "year", "quarter", "month", "week", "weekOfMonth", "weekday", + "dayOfYear", "weekdayOfMonth", "day", "dayperiod", // The UDATPG_x_FIELD constants and these fields have a different order than in ICU4J "hour", "minute", "second", "*", "zone" }; @@ -988,10 +1032,11 @@ DateTimePatternGenerator::getBestPattern(const UnicodeString& patternForm, UDate patternFormCopy.setCharAt(patPos, LOW_H); } + // in #13183 just add to skeleton, no longer need to set special flags if (preferred == ALLOWED_HOUR_FORMAT_HB || preferred == ALLOWED_HOUR_FORMAT_hB) { - flags |= kDTPGSkeletonUsesCapB; + patternFormCopy.append('B'); } else if (preferred == ALLOWED_HOUR_FORMAT_Hb || preferred == ALLOWED_HOUR_FORMAT_hb) { - flags |= kDTPGSkeletonUsesLowB; + patternFormCopy.append('b'); } } else if (patChr == CAP_J) { // Get pattern for skeleton with H, then replace H or k @@ -1299,17 +1344,7 @@ DateTimePatternGenerator::adjustFieldTypes(const UnicodeString& pattern, const dtTypeElem *row = &dtTypes[canonicalIndex]; int32_t typeValue = row->field; - // Handle special day periods. - if (typeValue == UDATPG_DAYPERIOD_FIELD && flags != 0) { - UChar c = NONE; // '0' - if (flags & kDTPGSkeletonUsesCapB) { c = CAP_B; } - if (flags & kDTPGSkeletonUsesLowB) { c = LOW_B; } - - if (c != NONE) { - for (int32_t i = 0; i < field.length(); ++i) - field.setCharAt(i, c); - } - } + // handle day periods - with #13183, no longer need special handling here, integrated with normal types if ((flags & kDTPGFixFractionalSeconds) != 0 && typeValue == UDATPG_SECOND_FIELD) { field += decimal; @@ -1844,9 +1879,7 @@ DateTimeMatcher::set(const UnicodeString& pattern, FormatParser* fp, PtnSkeleton fp->set(pattern); for (i=0; i < fp->itemNumber; i++) { const UnicodeString& value = fp->items[i]; - if ( value.charAt(0) == LOW_A ) { - continue; // skip 'a' - } + // don't skip 'a' anymore, dayPeriod handled specially below if ( fp->isQuoteLiteral(value) ) { UnicodeString quoteLiteral; @@ -1861,7 +1894,7 @@ DateTimeMatcher::set(const UnicodeString& pattern, FormatParser* fp, PtnSkeleton int32_t field = row->field; skeletonResult.original.populate(field, value); UChar repeatChar = row->patternChar; - int32_t repeatCount = row->minLen; // #7930 removes cap at 3 + int32_t repeatCount = row->minLen; skeletonResult.baseOriginal.populate(field, repeatChar, repeatCount); int16_t subField = row->type; if ( row->type > 0) { @@ -1869,6 +1902,29 @@ DateTimeMatcher::set(const UnicodeString& pattern, FormatParser* fp, PtnSkeleton } skeletonResult.type[field] = subField; } + // #13183, handle special behavior for day period characters (a, b, B) + if (!skeletonResult.original.isFieldEmpty(UDATPG_HOUR_FIELD)) { + if (skeletonResult.original.getFieldChar(UDATPG_HOUR_FIELD)==LOW_H || skeletonResult.original.getFieldChar(UDATPG_HOUR_FIELD)==CAP_K) { + // We have a skeleton with 12-hour-cycle format + if (skeletonResult.original.isFieldEmpty(UDATPG_DAYPERIOD_FIELD)) { + // But we do not have a day period in the skeleton; add the default DAYPERIOD (currently "a") + for (i = 0; dtTypes[i].patternChar != 0; i++) { + if ( dtTypes[i].field == UDATPG_DAYPERIOD_FIELD ) { + // first entry for UDATPG_DAYPERIOD_FIELD + skeletonResult.original.populate(UDATPG_DAYPERIOD_FIELD, dtTypes[i].patternChar, dtTypes[i].minLen); + skeletonResult.baseOriginal.populate(UDATPG_DAYPERIOD_FIELD, dtTypes[i].patternChar, dtTypes[i].minLen); + skeletonResult.type[UDATPG_DAYPERIOD_FIELD] = dtTypes[i].type; + break; + } + } + } + } else { + // Skeleton has 24-hour-cycle hour format and has dayPeriod, delete dayPeriod (i.e. ignore it) + skeletonResult.original.clearField(UDATPG_DAYPERIOD_FIELD); + skeletonResult.baseOriginal.clearField(UDATPG_DAYPERIOD_FIELD); + skeletonResult.type[UDATPG_DAYPERIOD_FIELD] = NONE; + } + } copyFrom(skeletonResult); } diff --git a/icu4c/source/i18n/dtptngen_impl.h b/icu4c/source/i18n/dtptngen_impl.h index 38afd5ff5a8..4fbadbb9331 100644 --- a/icu4c/source/i18n/dtptngen_impl.h +++ b/icu4c/source/i18n/dtptngen_impl.h @@ -92,10 +92,11 @@ #define LOW_X ((UChar)0x0078) #define LOW_Y ((UChar)0x0079) #define LOW_Z ((UChar)0x007A) -#define DT_SHORT -0x102 -#define DT_LONG -0x103 -#define DT_NUMERIC 0x100 #define DT_NARROW -0x101 +#define DT_SHORTER -0x102 +#define DT_SHORT -0x103 +#define DT_LONG -0x104 +#define DT_NUMERIC 0x100 #define DT_DELTA 0x10 U_NAMESPACE_BEGIN diff --git a/icu4c/source/i18n/unicode/dtptngen.h b/icu4c/source/i18n/unicode/dtptngen.h index 6fd5f5fd308..e424f0b1c78 100644 --- a/icu4c/source/i18n/unicode/dtptngen.h +++ b/icu4c/source/i18n/unicode/dtptngen.h @@ -206,11 +206,11 @@ public: * @return conflicting status. The value could be UDATPG_NO_CONFLICT, * UDATPG_BASE_CONFLICT or UDATPG_CONFLICT. * @stable ICU 3.8 - *

- *

Sample code

- * \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1 - * \snippet samples/dtptngsample/dtptngsample.cpp addPatternExample - *

+ *

+ *

Sample code

+ * \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1 + * \snippet samples/dtptngsample/dtptngsample.cpp addPatternExample + *

*/ UDateTimePatternConflict addPattern(const UnicodeString& pattern, UBool override, @@ -312,11 +312,11 @@ public: * @return bestPattern * The best pattern found from the given skeleton. * @stable ICU 3.8 - *

- *

Sample code

- * \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1 - * \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample - *

+ *

+ *

Sample code

+ * \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1 + * \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample + *

*/ UnicodeString getBestPattern(const UnicodeString& skeleton, UErrorCode& status); @@ -360,11 +360,11 @@ public: * which must not indicate a failure before the function call. * @return pattern adjusted to match the skeleton fields widths and subtypes. * @stable ICU 3.8 - *

- *

Sample code

- * \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1 - * \snippet samples/dtptngsample/dtptngsample.cpp replaceFieldTypesExample - *

+ *

+ *

Sample code

+ * \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1 + * \snippet samples/dtptngsample/dtptngsample.cpp replaceFieldTypesExample + *

*/ UnicodeString replaceFieldTypes(const UnicodeString& pattern, const UnicodeString& skeleton, @@ -526,9 +526,8 @@ private: enum { kDTPGNoFlags = 0, kDTPGFixFractionalSeconds = 1, - kDTPGSkeletonUsesCapJ = 2, - kDTPGSkeletonUsesLowB = 3, - kDTPGSkeletonUsesCapB = 4 + kDTPGSkeletonUsesCapJ = 2 + // with #13183, no longer need flags for b, B }; void initData(const Locale &locale, UErrorCode &status); diff --git a/icu4c/source/test/intltest/dtptngts.cpp b/icu4c/source/test/intltest/dtptngts.cpp index e9cc4dec17b..b6e6de847ee 100644 --- a/icu4c/source/test/intltest/dtptngts.cpp +++ b/icu4c/source/test/intltest/dtptngts.cpp @@ -35,6 +35,7 @@ void IntlTestDateTimePatternGeneratorAPI::runIndexedTest( int32_t index, UBool e TESTCASE(2, testAllFieldPatterns); TESTCASE(3, testStaticGetSkeleton); TESTCASE(4, testC); + TESTCASE(5, testSkeletonsWithDayPeriods); default: name = ""; break; } } @@ -976,13 +977,17 @@ void IntlTestDateTimePatternGeneratorAPI::testAllFieldPatterns(/*char *par*/) { 'e', {1,2,3,4,5,6}, "Eec" }, // local day of week { 'c', {1,2,3,4,5,6}, "Eec" }, // standalone local day of week // day period - // { 'a', {1,0}, "a" }, // am or pm // not clear this one is supposed to work (it doesn't) + { 'a', {1,2,3,4,5,0}, "a" }, // am or pm + { 'b', {1,2,3,4,5,0}, "b" }, // dayPeriod AM/PM/noon + { 'B', {1,2,3,4,5,0}, "B" }, // dayPeriod ranges // hour { 'h', {1,2,0}, "hK" }, // 12 (1-12) { 'H', {1,2,0}, "Hk" }, // 24 (0-23) { 'K', {1,2,0}, "hK" }, // 12 (0-11) { 'k', {1,2,0}, "Hk" }, // 24 (1-24) { 'j', {1,2,0}, "hHKk" }, // locale default + { 'J', {1,2,0}, "hHKk" }, // locale default, without any dayPeriod + { 'C', {1,2,0}, "hHKk" }, // locale allowed first entry, possibly with b or B // minute { 'm', {1,2,0}, "m" }, // x // second & fractions @@ -1114,4 +1119,64 @@ void IntlTestDateTimePatternGeneratorAPI::testC() { } } +enum { kCharBufMax = 31 }; +void IntlTestDateTimePatternGeneratorAPI::testSkeletonsWithDayPeriods() { + const char * patterns[] = { + // since icu4c getEmptyInstance does not call addCanonicalItems (unlike J), set these here: + "a", // should get skeleton a + "H", // should get skeleton H + "m", // should get skeleton m + "s", // should get skeleton s + // patterns from which to construct sample data for a locale + //"H", // should get skeleton H + "h a", // should get skeleton ah + "B h", // should get skeleton Bh + }; + const char* testItems[][2] = { + // sample requested skeletons and results + // skel pattern + { "H", "H"}, + { "aH", "H"}, + { "BH", "H"}, + { "h", "h a"}, + { "ah", "h a"}, + { "bh", "h b"}, + { "Bh", "B h"}, + { "a", "a"}, + { "b", "b"}, + { "B", "B"} + }; + UErrorCode status = U_ZERO_ERROR; + DateTimePatternGenerator *gen = DateTimePatternGenerator::createEmptyInstance(status); + if (U_FAILURE(status)) { + errln("ERROR: createEmptyInstance fails, status: %s", u_errorName(status)); + } else { + int32_t i, len = UPRV_LENGTHOF(patterns); + for (i = 0; i < len; i++) { + UnicodeString conflictingPattern; + (void)gen->addPattern(UnicodeString(patterns[i]), TRUE, conflictingPattern, status); + if (U_FAILURE(status)) { + errln("ERROR: addPattern %s fail, status: %s", patterns[i], u_errorName(status)); + break; + } + } + if (U_SUCCESS(status)) { + len = UPRV_LENGTHOF(testItems); + for (i = 0; i < len; i++) { + status = U_ZERO_ERROR; + UnicodeString result = gen->getBestPattern(UnicodeString(testItems[i][0]), status); + if (U_FAILURE(status)) { + errln("ERROR: getBestPattern %s fail, status: %s", testItems[i][0], u_errorName(status)); + } else if (result != UnicodeString(testItems[i][1])) { + char charResult[kCharBufMax+1]; + result.extract(0, result.length(), charResult, kCharBufMax); + charResult[kCharBufMax] = 0; // ensure termination + errln("ERROR: getBestPattern %s, expected %s, got %s", testItems[i][0], testItems[i][1], charResult); + } + } + } + } + delete gen; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/dtptngts.h b/icu4c/source/test/intltest/dtptngts.h index 701eddfa695..03da8485984 100644 --- a/icu4c/source/test/intltest/dtptngts.h +++ b/icu4c/source/test/intltest/dtptngts.h @@ -30,6 +30,7 @@ private: void testAllFieldPatterns(/* char* par */); void testStaticGetSkeleton(/* char* par */); void testC(); + void testSkeletonsWithDayPeriods(); }; #endif /* #if !UCONFIG_NO_FORMATTING */ 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 46a07ef206d..6532b345746 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 @@ -561,9 +561,10 @@ public class DateTimePatternGenerator implements Freezable flags, int options) { @@ -1923,18 +1914,7 @@ public class DateTimePatternGenerator implements Freezable 0; --i) { - fieldBuilder.append(c); - } - } - } + // handle day periods - with #13183, no longer need special handling here, integrated with normal types if (flags.contains(DTPGflags.FIX_FRACTIONAL_SECONDS) && type == SECOND) { fieldBuilder.append(decimal); @@ -2054,8 +2034,8 @@ public class DateTimePatternGenerator implements Freezable CANONICAL_SET = new HashSet(Arrays.asList(CANONICAL_ITEMS)); private Set cldrAvailableFormatKeys = new HashSet(20); @@ -2084,8 +2084,9 @@ public class DateTimePatternGenerator implements Freezable= 0) repeatCount = 1; baseOriginal.populate(field, repeatChar, repeatCount); int subField = row[2]; if (subField > 0) subField += value.length(); type[field] = subField; } + // #13183, handle special behavior for day period characters (a, b, B) + if (!original.isFieldEmpty(HOUR)) { + if (original.getFieldChar(HOUR)=='h' || original.getFieldChar(HOUR)=='K') { + // We have a skeleton with 12-hour-cycle format + if (original.isFieldEmpty(DAYPERIOD)) { + // But we do not have a day period in the skeleton; add the default DAYPERIOD (currently "a") + for (int i = 0; i < types.length; ++i) { + int[] row = types[i]; + if (row[1] == DAYPERIOD) { + // first entry for DAYPERIOD + original.populate(DAYPERIOD, (char)row[0], row[3]); + baseOriginal.populate(DAYPERIOD, (char)row[0], row[3]); + type[DAYPERIOD] = row[2]; + break; + } + } + } + } else if (!original.isFieldEmpty(DAYPERIOD)) { + // Skeleton has 24-hour-cycle hour format and has dayPeriod, delete dayPeriod (i.e. ignore it) + original.clearField(DAYPERIOD); + baseOriginal.clearField(DAYPERIOD); + type[DAYPERIOD] = NONE; + } + } return this; } 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 272f5a02559..77f569e60fc 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 @@ -70,6 +70,46 @@ public class DateTimeGeneratorTest extends TestFmwk { } } + @Test + public void TestSkeletonsWithDayPeriods() { + String[][] dataItems = { + // sample data in a locale (base is not in locale, just here for test) + // skel (base) pattern + { "aH", "H", "H" }, // should ignore a + { "h", "ah", "h a"}, + { "Bh", "Bh", "B h"}, + }; + String[][] testItems = { + // sample requested skeletons and results + // skel pattern + { "H", "H"}, + { "aH", "H"}, + { "BH", "H"}, + { "h", "h a"}, + { "ah", "h a"}, + { "bh", "h b"}, + { "Bh", "B h"}, + { "a", "a"}, + { "b", "b"}, + { "B", "B"}, + }; + DateTimePatternGenerator gen = DateTimePatternGenerator.getEmptyInstance(); + DateTimePatternGenerator.PatternInfo returnInfo = new DateTimePatternGenerator.PatternInfo(); + for (String[] dataItem : dataItems) { + gen.addPatternWithSkeleton(dataItem[2], dataItem[0], true, returnInfo); + String base = gen.getBaseSkeleton(dataItem[0]); + if (!base.equals(dataItem[1])) { + errln("getBaseSkeleton for skeleton " + dataItem[0] + ", expected " + dataItem[1] + ", got " + base); + } + } + for (String[] testItem : testItems) { + String pattern = gen.getBestPattern(testItem[0]); + if (!pattern.equals(testItem[1])) { + errln("getBestPattern for skeleton " + testItem[0] + ", expected " + testItem[1] + ", got " + pattern); + } + } + } + @Test public void TestSimple() { // some simple use cases @@ -587,7 +627,7 @@ public class DateTimeGeneratorTest extends TestFmwk { @Test public void TestVariableCharacters() { - UnicodeSet valid = new UnicodeSet("[G y Y u U r Q q M L l w W d D F g E e c a h H K k m s S A z Z O v V X x]"); + UnicodeSet valid = new UnicodeSet("[G y Y u U r Q q M L l w W d D F g E e c a b B h H K k m s S A z Z O v V X x]"); for (char c = 0; c < 0xFF; ++c) { boolean works = false; try { -- 2.40.0