From 7dd19b65ab853d993e7819bd38559f029cda5865 Mon Sep 17 00:00:00 2001 From: Peter Edberg Date: Mon, 22 May 2017 06:16:11 +0000 Subject: [PATCH 1/4] 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 { From 72c605c1c43a52b705ba27603c8553a5bc647b6b Mon Sep 17 00:00:00 2001 From: Peter Edberg Date: Mon, 22 May 2017 16:37:07 +0000 Subject: [PATCH 2/4] ICU-13183 on branch - add locale data with Bh skeletons X-SVN-Rev: 40125 --- icu4c/source/data/locales/en.txt | 5 +++++ icu4c/source/data/locales/root.txt | 5 +++++ icu4c/source/test/intltest/dtptngts.cpp | 3 ++- icu4j/main/shared/data/icudata.jar | 4 ++-- .../com/ibm/icu/dev/test/format/DateTimeGeneratorTest.java | 3 ++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/icu4c/source/data/locales/en.txt b/icu4c/source/data/locales/en.txt index 5953ac9ee3d..d5fc618d9fc 100644 --- a/icu4c/source/data/locales/en.txt +++ b/icu4c/source/data/locales/en.txt @@ -633,7 +633,12 @@ en{ Year{"{0} {1}"} } availableFormats{ + Bh{"h B"} + Bhm{"h:mm B"} + Bhms{"h:mm:ss B"} E{"ccc"} + EBhm{"E h:mm B"} + EBhms{"E h:mm:ss B"} EHm{"E HH:mm"} EHms{"E HH:mm:ss"} Ed{"d E"} diff --git a/icu4c/source/data/locales/root.txt b/icu4c/source/data/locales/root.txt index c9d232707f4..3909422cb30 100644 --- a/icu4c/source/data/locales/root.txt +++ b/icu4c/source/data/locales/root.txt @@ -966,7 +966,12 @@ root{ Year{"{1} {0}"} } availableFormats{ + Bh{"h B"} + Bhm{"h:mm B"} + Bhms{"h:mm:ss B"} E{"ccc"} + EBhm{"E h:mm B"} + EBhms{"E h:mm:ss B"} EHm{"E HH:mm"} EHms{"E HH:mm:ss"} Ed{"d, E"} diff --git a/icu4c/source/test/intltest/dtptngts.cpp b/icu4c/source/test/intltest/dtptngts.cpp index b6e6de847ee..4242175bff3 100644 --- a/icu4c/source/test/intltest/dtptngts.cpp +++ b/icu4c/source/test/intltest/dtptngts.cpp @@ -1093,7 +1093,8 @@ void IntlTestDateTimePatternGeneratorAPI::testC() { const int32_t numLocales = 6; const char* tests[numLocales][3] = { - {"zh", "Cm", "Bh:mm"}, + // These may change with actual data for Bhmm/bhmm skeletons + {"zh", "Cm", "h:mm B"}, {"de", "Cm", "HH:mm"}, {"en", "Cm", "h:mm a"}, {"en-BN", "Cm", "h:mm b"}, diff --git a/icu4j/main/shared/data/icudata.jar b/icu4j/main/shared/data/icudata.jar index d457b4e333b..c9de7affbae 100755 --- a/icu4j/main/shared/data/icudata.jar +++ b/icu4j/main/shared/data/icudata.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c01f234ada00f14dddc0130a2b5c537c6dd18037a0387b9bb0147bbbcbb0dbf4 -size 12152967 +oid sha256:be32359b49e989b80821acea019b8e01807b726a4f57bf1322235f03c0c11709 +size 12153095 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 77f569e60fc..e3e4f1cc4de 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 @@ -55,7 +55,8 @@ public class DateTimeGeneratorTest extends TestFmwk { @Test public void TestC() { String[][] tests = { - {"zh", "Cm", "Bh:mm"}, + // These may change with actual data for Bhmm/bhmm skeletons + {"zh", "Cm", "h:mm B"}, {"de", "Cm", "HH:mm"}, {"en", "Cm", "h:mm a"}, {"en-BN", "Cm", "h:mm b"}, From 56e884e22e2e92bec3ebbbf940b24b2e5f16b866 Mon Sep 17 00:00:00 2001 From: Peter Edberg Date: Mon, 22 May 2017 22:24:39 +0000 Subject: [PATCH 3/4] ICU-13183 on branch - Handle different field lengths for skeleton metachars j,C X-SVN-Rev: 40126 --- icu4c/source/i18n/dtptngen.cpp | 119 ++++++++++++------ icu4c/source/i18n/unicode/dtptngen.h | 1 + icu4c/source/test/intltest/dtptngts.cpp | 30 +++-- .../icu/text/DateTimePatternGenerator.java | 93 ++++++++++---- .../test/format/DateTimeGeneratorTest.java | 27 ++-- 5 files changed, 181 insertions(+), 89 deletions(-) diff --git a/icu4c/source/i18n/dtptngen.cpp b/icu4c/source/i18n/dtptngen.cpp index 00eb7683de6..c91fbbe6f23 100644 --- a/icu4c/source/i18n/dtptngen.cpp +++ b/icu4c/source/i18n/dtptngen.cpp @@ -1007,48 +1007,13 @@ DateTimePatternGenerator::getBestPattern(const UnicodeString& patternForm, UDate int32_t timeMask=(1<set(patternFormCopy, fp); + dtMatcher->set(patternFormMapped, fp); const PtnSkeleton* specifiedSkeleton=NULL; bestPattern=getBestRaw(*dtMatcher, -1, distanceInfo, &specifiedSkeleton); if ( distanceInfo->missingFieldMask==0 && distanceInfo->extraFieldMask==0 ) { @@ -1077,6 +1042,82 @@ DateTimePatternGenerator::getBestPattern(const UnicodeString& patternForm, UDate return resultPattern; } +/* + * Map a skeleton that may have metacharacters jJC to one without, by replacing + * the metacharacters with locale-appropriate fields of of h/H/k/K and of a/b/B + * (depends on fDefaultHourFormatChar and fAllowedHourFormats being set, which in + * turn depends on initData having been run). This method also updates the flags + * as necessary. Returns the updated skeleton. + */ +UnicodeString +DateTimePatternGenerator::mapSkeletonMetacharacters(const UnicodeString& patternForm, int32_t* flags, UErrorCode& status) { + UnicodeString patternFormMapped; + patternFormMapped.remove(); + UBool inQuoted = FALSE; + int32_t patPos, patLen = patternForm.length(); + for (patPos = 0; patPos < patLen; patPos++) { + UChar patChr = patternForm.charAt(patPos); + if (patChr == SINGLE_QUOTE) { + inQuoted = !inQuoted; + } else if (!inQuoted) { + // Handle special mappings for 'j' and 'C' in which fields lengths + // 1,3,5 => hour field length 1 + // 2,4,6 => hour field length 2 + // 1,2 => abbreviated dayPeriod (field length 1..3) + // 3,4 => long dayPeriod (field length 4) + // 5,6 => narrow dayPeriod (field length 5) + if (patChr == LOW_J || patChr == CAP_C) { + int32_t extraLen = 0; // 1 less than total field length + while (patPos+1 < patLen && patternForm.charAt(patPos+1)==patChr) { + extraLen++; + patPos++; + } + int32_t hourLen = 1 + (extraLen & 1); + int32_t dayPeriodLen = (extraLen < 2)? 1: 3 + (extraLen >> 1); + UChar hourChar = LOW_H; + UChar dayPeriodChar = LOW_A; + if (patChr == LOW_J) { + hourChar = fDefaultHourFormatChar; + } else { + AllowedHourFormat preferred; + if (fAllowedHourFormats[0] != ALLOWED_HOUR_FORMAT_UNKNOWN) { + preferred = (AllowedHourFormat)fAllowedHourFormats[0]; + } else { + status = U_INVALID_FORMAT_ERROR; + return UnicodeString(); + } + if (preferred == ALLOWED_HOUR_FORMAT_H || preferred == ALLOWED_HOUR_FORMAT_HB || preferred == ALLOWED_HOUR_FORMAT_Hb) { + hourChar = CAP_H; + } + // in #13183 just add b/B to skeleton, no longer need to set special flags + if (preferred == ALLOWED_HOUR_FORMAT_HB || preferred == ALLOWED_HOUR_FORMAT_hB) { + dayPeriodChar = CAP_B; + } else if (preferred == ALLOWED_HOUR_FORMAT_Hb || preferred == ALLOWED_HOUR_FORMAT_hb) { + dayPeriodChar = LOW_B; + } + } + if (hourChar==CAP_H || hourChar==LOW_K) { + dayPeriodLen = 0; + } + while (dayPeriodLen-- > 0) { + patternFormMapped.append(dayPeriodChar); + } + while (hourLen-- > 0) { + patternFormMapped.append(hourChar); + } + } else if (patChr == CAP_J) { + // Get pattern for skeleton with H, then replace H or k + // with fDefaultHourFormatChar (if different) + patternFormMapped.append(CAP_H); + *flags |= kDTPGSkeletonUsesCapJ; + } else { + patternFormMapped.append(patChr); + } + } + } + return patternFormMapped; +} + UnicodeString DateTimePatternGenerator::replaceFieldTypes(const UnicodeString& pattern, const UnicodeString& skeleton, diff --git a/icu4c/source/i18n/unicode/dtptngen.h b/icu4c/source/i18n/unicode/dtptngen.h index e424f0b1c78..19ded6d8335 100644 --- a/icu4c/source/i18n/unicode/dtptngen.h +++ b/icu4c/source/i18n/unicode/dtptngen.h @@ -545,6 +545,7 @@ private: UDateTimePatternField getAppendNameNumber(const char* field) const; UnicodeString& getMutableAppendItemName(UDateTimePatternField field); void getAppendName(UDateTimePatternField field, UnicodeString& value); + UnicodeString mapSkeletonMetacharacters(const UnicodeString& patternForm, int32_t* flags, UErrorCode& status); int32_t getCanonicalIndex(const UnicodeString& field); const UnicodeString* getBestRaw(DateTimeMatcher& source, int32_t includeMask, DistanceInfo* missingFields, const PtnSkeleton** specifiedSkeletonPtr = 0); UnicodeString adjustFieldTypes(const UnicodeString& pattern, const PtnSkeleton* specifiedSkeleton, int32_t flags, UDateTimePatternMatchOptions options = UDATPG_MATCH_NO_OPTIONS); diff --git a/icu4c/source/test/intltest/dtptngts.cpp b/icu4c/source/test/intltest/dtptngts.cpp index 4242175bff3..422f9f89029 100644 --- a/icu4c/source/test/intltest/dtptngts.cpp +++ b/icu4c/source/test/intltest/dtptngts.cpp @@ -1090,13 +1090,18 @@ void IntlTestDateTimePatternGeneratorAPI::testStaticGetSkeleton(/*char *par*/) void IntlTestDateTimePatternGeneratorAPI::testC() { UErrorCode status = U_ZERO_ERROR; - const int32_t numLocales = 6; + const int32_t numLocales = 11; const char* tests[numLocales][3] = { // These may change with actual data for Bhmm/bhmm skeletons {"zh", "Cm", "h:mm B"}, + {"zh", "CCCm", "h:mm BBBB"}, + {"zh", "CCCCCm", "h:mm BBBBB"}, + {"zh", "Cm", "h:mm B"}, {"de", "Cm", "HH:mm"}, {"en", "Cm", "h:mm a"}, + {"en", "CCCm", "h:mm aaaa"}, + {"en", "CCCCCm", "h:mm aaaaa"}, {"en-BN", "Cm", "h:mm b"}, {"gu-IN", "Cm", "h:mm B"}, {"und-IN", "Cm", "h:mm a"}, @@ -1135,17 +1140,18 @@ void IntlTestDateTimePatternGeneratorAPI::testSkeletonsWithDayPeriods() { }; 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"} + // skel pattern + { "H", "H"}, + { "aH", "H"}, + { "BH", "H"}, + { "h", "h a"}, + { "ah", "h a"}, + { "bh", "h b"}, + { "Bh", "B h"}, + { "BBBBh", "BBBB h"}, + { "a", "a"}, + { "b", "b"}, + { "B", "B"} }; UErrorCode status = U_ZERO_ERROR; DateTimePatternGenerator *gen = DateTimePatternGenerator::createEmptyInstance(status); 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 6532b345746..21bf7615eb0 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 @@ -549,35 +549,10 @@ public class DateTimePatternGenerator implements Freezable flags = EnumSet.noneOf(DTPGflags.class); // Replace hour metacharacters 'j', 'C', and 'J', set flags as necessary - StringBuilder skeletonCopy = new StringBuilder(skeleton); - boolean inQuoted = false; - for (int patPos = 0; patPos < skeleton.length(); patPos++) { - char patChr = skeleton.charAt(patPos); - if (patChr == '\'') { - inQuoted = !inQuoted; - } else if (!inQuoted) { - if (patChr == 'j') { - skeletonCopy.setCharAt(patPos, defaultHourFormatChar); - } else if (patChr == 'C') { - String preferred = allowedHourFormats[0]; - skeletonCopy.setCharAt(patPos, preferred.charAt(0)); - // add to skeleton with #13183, no longer need to set special flags - char last = preferred.charAt(preferred.length()-1); - if (last=='b' || last=='B') { - skeletonCopy.append(last); - } - } else if (patChr == 'J') { - // Get pattern for skeleton with H, then (in adjustFieldTypes) - // replace H or k with defaultHourFormatChar - skeletonCopy.setCharAt(patPos, 'H'); - flags.add(DTPGflags.SKELETON_USES_CAP_J); - } - } - } - + String skeletonMapped = mapSkeletonMetacharacters(skeleton, flags); String datePattern, timePattern; synchronized(this) { - current.set(skeletonCopy.toString(), fp, false); + current.set(skeletonMapped, fp, false); PatternWithMatcher bestWithMatcher = getBestRaw(current, -1, _distanceInfo, skipMatcher); if (_distanceInfo.missingFieldMask == 0 && _distanceInfo.extraFieldMask == 0) { // we have a good item. Adjust the field types @@ -596,6 +571,70 @@ public class DateTimePatternGenerator implements Freezable flags) { + StringBuilder skeletonCopy = new StringBuilder(); + boolean inQuoted = false; + for (int patPos = 0; patPos < skeleton.length(); patPos++) { + char patChr = skeleton.charAt(patPos); + if (patChr == '\'') { + inQuoted = !inQuoted; + } else if (!inQuoted) { + // Handle special mappings for 'j' and 'C' in which fields lengths + // 1,3,5 => hour field length 1 + // 2,4,6 => hour field length 2 + // 1,2 => abbreviated dayPeriod (field length 1..3) + // 3,4 => long dayPeriod (field length 4) + // 5,6 => narrow dayPeriod (field length 5) + if (patChr == 'j' || patChr == 'C') { + int extraLen = 0; // 1 less than total field length + while (patPos+1 < skeleton.length() && skeleton.charAt(patPos+1) == patChr) { + extraLen++; + patPos++; + } + int hourLen = 1 + (extraLen & 1); + int dayPeriodLen = (extraLen < 2)? 1: 3 + (extraLen >> 1); + char hourChar = 'h'; + char dayPeriodChar = 'a'; + if (patChr == 'j') { + hourChar = defaultHourFormatChar; + } else { // patChr == 'C' + String preferred = allowedHourFormats[0]; + hourChar = preferred.charAt(0); + // in #13183 just add b/B to skeleton, no longer need to set special flags + char last = preferred.charAt(preferred.length()-1); + if (last=='b' || last=='B') { + dayPeriodChar = last; + } + } + if (hourChar=='H' || hourChar=='k') { + dayPeriodLen = 0; + } + while (dayPeriodLen-- > 0) { + skeletonCopy.append(dayPeriodChar); + } + while (hourLen-- > 0) { + skeletonCopy.append(hourChar); + } + } else if (patChr == 'J') { + // Get pattern for skeleton with H, then (in adjustFieldTypes) + // replace H or k with defaultHourFormatChar + skeletonCopy.append('H'); + flags.add(DTPGflags.SKELETON_USES_CAP_J); + } else { + skeletonCopy.append(patChr); + } + } + } + return skeletonCopy.toString(); + } + /** * PatternInfo supplies output parameters for addPattern(...). It is used because * Java doesn't have real output parameters. It is treated like a struct (eg 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 e3e4f1cc4de..2bfe9754039 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 @@ -57,8 +57,12 @@ public class DateTimeGeneratorTest extends TestFmwk { String[][] tests = { // These may change with actual data for Bhmm/bhmm skeletons {"zh", "Cm", "h:mm B"}, + {"zh", "CCCm", "h:mm BBBB"}, + {"zh", "CCCCCm", "h:mm BBBBB"}, {"de", "Cm", "HH:mm"}, {"en", "Cm", "h:mm a"}, + {"en", "CCCm", "h:mm aaaa"}, + {"en", "CCCCCm", "h:mm aaaaa"}, {"en-BN", "Cm", "h:mm b"}, {"gu-IN", "Cm", "h:mm B"}, {"und-IN", "Cm", "h:mm a"}, @@ -82,17 +86,18 @@ public class DateTimeGeneratorTest extends TestFmwk { }; 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"}, + // skel pattern + { "H", "H"}, + { "aH", "H"}, + { "BH", "H"}, + { "h", "h a"}, + { "ah", "h a"}, + { "bh", "h b"}, + { "Bh", "B h"}, + { "BBBBh", "BBBB h"}, + { "a", "a"}, + { "b", "b"}, + { "B", "B"}, }; DateTimePatternGenerator gen = DateTimePatternGenerator.getEmptyInstance(); DateTimePatternGenerator.PatternInfo returnInfo = new DateTimePatternGenerator.PatternInfo(); From 458a47f288013b0e2f04f1183f12caaada2c91a1 Mon Sep 17 00:00:00 2001 From: Peter Edberg Date: Tue, 23 May 2017 02:37:37 +0000 Subject: [PATCH 4/4] ICU-13183 on branch - Add more tests and sample data X-SVN-Rev: 40127 --- icu4c/source/data/locales/zh.txt | 5 ++ icu4c/source/test/intltest/dtptngts.cpp | 84 ++++++++++++------- icu4j/main/shared/data/icudata.jar | 4 +- .../test/format/DateTimeGeneratorTest.java | 74 ++++++++++------ 4 files changed, 111 insertions(+), 56 deletions(-) diff --git a/icu4c/source/data/locales/zh.txt b/icu4c/source/data/locales/zh.txt index 17a3c7d5472..2a95662159f 100644 --- a/icu4c/source/data/locales/zh.txt +++ b/icu4c/source/data/locales/zh.txt @@ -1133,7 +1133,12 @@ zh{ Timezone{"{1}{0}"} } availableFormats{ + Bh{"Bh时"} + Bhm{"Bh:mm"} + Bhms{"Bh:mm:ss"} E{"ccc"} + EBhm{"EBh:mm"} + EBms{"EBh:mm:ss"} EHm{"EHH:mm"} EHms{"EHH:mm:ss"} Ed{"d日E"} diff --git a/icu4c/source/test/intltest/dtptngts.cpp b/icu4c/source/test/intltest/dtptngts.cpp index 422f9f89029..714ee2e19b8 100644 --- a/icu4c/source/test/intltest/dtptngts.cpp +++ b/icu4c/source/test/intltest/dtptngts.cpp @@ -1089,31 +1089,39 @@ void IntlTestDateTimePatternGeneratorAPI::testStaticGetSkeleton(/*char *par*/) } void IntlTestDateTimePatternGeneratorAPI::testC() { - UErrorCode status = U_ZERO_ERROR; - const int32_t numLocales = 11; - - const char* tests[numLocales][3] = { + const char* tests[][3] = { // These may change with actual data for Bhmm/bhmm skeletons - {"zh", "Cm", "h:mm B"}, - {"zh", "CCCm", "h:mm BBBB"}, - {"zh", "CCCCCm", "h:mm BBBBB"}, - {"zh", "Cm", "h:mm B"}, - {"de", "Cm", "HH:mm"}, - {"en", "Cm", "h:mm a"}, - {"en", "CCCm", "h:mm aaaa"}, - {"en", "CCCCCm", "h:mm aaaaa"}, - {"en-BN", "Cm", "h:mm b"}, - {"gu-IN", "Cm", "h:mm B"}, - {"und-IN", "Cm", "h:mm a"}, + {"zh", "Cm", "Bh:mm"}, + {"zh", "CCm", "Bhh:mm"}, + {"zh", "CCCm", "BBBBh:mm"}, + {"zh", "CCCCm", "BBBBhh:mm"}, + {"zh", "CCCCCm", "BBBBBh:mm"}, + {"zh", "CCCCCCm", "BBBBBhh:mm"}, + {"de", "Cm", "HH:mm"}, + {"de", "CCm", "HH:mm"}, + {"de", "CCCm", "HH:mm"}, + {"de", "CCCCm", "HH:mm"}, + {"en", "Cm", "h:mm a"}, + {"en", "CCm", "hh:mm a"}, + {"en", "CCCm", "h:mm aaaa"}, + {"en", "CCCCm", "hh:mm aaaa"}, + {"en", "CCCCCm", "h:mm aaaaa"}, + {"en", "CCCCCCm", "hh:mm aaaaa"}, + {"en-BN", "Cm", "h:mm b"}, + {"gu-IN", "Cm", "h:mm B"}, + {"und-IN", "Cm", "h:mm a"} }; - for (int32_t i = 0; i < numLocales; ++i) { + UErrorCode status = U_ZERO_ERROR; + int32_t numTests = UPRV_LENGTHOF(tests); + for (int32_t i = 0; i < numTests; ++i) { DateTimePatternGenerator *gen = DateTimePatternGenerator::createInstance(Locale(tests[i][0]), status); if (gen == NULL) { dataerrln("FAIL: DateTimePatternGenerator::createInstance failed for %s", tests[i][0]); return; } - UnicodeString pattern = gen->getBestPattern(tests[i][1], status); + UDateTimePatternMatchOptions options = UDATPG_MATCH_HOUR_FIELD_LENGTH; + UnicodeString pattern = gen->getBestPattern(tests[i][1], options, status); UnicodeString expectedPattern = tests[i][2]; char message[100] = "\0"; @@ -1140,18 +1148,33 @@ void IntlTestDateTimePatternGeneratorAPI::testSkeletonsWithDayPeriods() { }; 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"}, - { "BBBBh", "BBBB h"}, - { "a", "a"}, - { "b", "b"}, - { "B", "B"} + // skel pattern + { "H", "H"}, + { "HH", "HH"}, + { "aH", "H"}, + { "aHH", "HH"}, + { "BH", "H"}, + { "BHH", "HH"}, + { "BBBBH", "H"}, + { "h", "h a"}, + { "hh", "hh a"}, + { "ah", "h a"}, + { "ahh", "hh a"}, + { "aaaah", "h aaaa"}, + { "aaaahh", "hh aaaa"}, + { "bh", "h b"}, + { "bhh", "hh b"}, + { "bbbbh", "h bbbb"}, + { "Bh", "B h"}, + { "Bhh", "B hh"}, + { "BBBBh", "BBBB h"}, + { "BBBBhh", "BBBB hh"}, + { "a", "a"}, + { "aaaaa", "aaaaa"}, + { "b", "b"}, + { "bbbb", "bbbb"}, + { "B", "B"}, + { "BBBB", "BBBB"}, }; UErrorCode status = U_ZERO_ERROR; DateTimePatternGenerator *gen = DateTimePatternGenerator::createEmptyInstance(status); @@ -1171,7 +1194,8 @@ void IntlTestDateTimePatternGeneratorAPI::testSkeletonsWithDayPeriods() { len = UPRV_LENGTHOF(testItems); for (i = 0; i < len; i++) { status = U_ZERO_ERROR; - UnicodeString result = gen->getBestPattern(UnicodeString(testItems[i][0]), status); + UDateTimePatternMatchOptions options = UDATPG_MATCH_HOUR_FIELD_LENGTH; + UnicodeString result = gen->getBestPattern(UnicodeString(testItems[i][0]), options, 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])) { diff --git a/icu4j/main/shared/data/icudata.jar b/icu4j/main/shared/data/icudata.jar index c9de7affbae..83fd6148dfa 100755 --- a/icu4j/main/shared/data/icudata.jar +++ b/icu4j/main/shared/data/icudata.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be32359b49e989b80821acea019b8e01807b726a4f57bf1322235f03c0c11709 -size 12153095 +oid sha256:a2e7756f45badde8ea612be307feb2a7674c0c52062e524c30a653fced4b710b +size 12153152 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 2bfe9754039..fb8c5126c47 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 @@ -56,21 +56,31 @@ public class DateTimeGeneratorTest extends TestFmwk { public void TestC() { String[][] tests = { // These may change with actual data for Bhmm/bhmm skeletons - {"zh", "Cm", "h:mm B"}, - {"zh", "CCCm", "h:mm BBBB"}, - {"zh", "CCCCCm", "h:mm BBBBB"}, - {"de", "Cm", "HH:mm"}, - {"en", "Cm", "h:mm a"}, - {"en", "CCCm", "h:mm aaaa"}, - {"en", "CCCCCm", "h:mm aaaaa"}, - {"en-BN", "Cm", "h:mm b"}, - {"gu-IN", "Cm", "h:mm B"}, - {"und-IN", "Cm", "h:mm a"}, + {"zh", "Cm", "Bh:mm"}, + {"zh", "CCm", "Bhh:mm"}, + {"zh", "CCCm", "BBBBh:mm"}, + {"zh", "CCCCm", "BBBBhh:mm"}, + {"zh", "CCCCCm", "BBBBBh:mm"}, + {"zh", "CCCCCCm", "BBBBBhh:mm"}, + {"de", "Cm", "HH:mm"}, + {"de", "CCm", "HH:mm"}, + {"de", "CCCm", "HH:mm"}, + {"de", "CCCCm", "HH:mm"}, + {"en", "Cm", "h:mm a"}, + {"en", "CCm", "hh:mm a"}, + {"en", "CCCm", "h:mm aaaa"}, + {"en", "CCCCm", "hh:mm aaaa"}, + {"en", "CCCCCm", "h:mm aaaaa"}, + {"en", "CCCCCCm", "hh:mm aaaaa"}, + {"en-BN", "Cm", "h:mm b"}, + {"gu-IN", "Cm", "h:mm B"}, + {"und-IN", "Cm", "h:mm a"}, }; for (String[] test : tests) { DateTimePatternGenerator gen = DateTimePatternGenerator.getInstance(ULocale.forLanguageTag(test[0])); String skeleton = test[1]; - String pattern = gen.getBestPattern(skeleton); + int options = DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH; + String pattern = gen.getBestPattern(skeleton, options); assertEquals(test[0] + "/" + skeleton, test[2], pattern); } } @@ -86,18 +96,33 @@ public class DateTimeGeneratorTest extends TestFmwk { }; 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"}, - { "BBBBh", "BBBB h"}, - { "a", "a"}, - { "b", "b"}, - { "B", "B"}, + // skel pattern + { "H", "H"}, + { "HH", "HH"}, + { "aH", "H"}, + { "aHH", "HH"}, + { "BH", "H"}, + { "BHH", "HH"}, + { "BBBBH", "H"}, + { "h", "h a"}, + { "hh", "hh a"}, + { "ah", "h a"}, + { "ahh", "hh a"}, + { "aaaah", "h aaaa"}, + { "aaaahh", "hh aaaa"}, + { "bh", "h b"}, + { "bhh", "hh b"}, + { "bbbbh", "h bbbb"}, + { "Bh", "B h"}, + { "Bhh", "B hh"}, + { "BBBBh", "BBBB h"}, + { "BBBBhh", "BBBB hh"}, + { "a", "a"}, + { "aaaaa", "aaaaa"}, + { "b", "b"}, + { "bbbb", "bbbb"}, + { "B", "B"}, + { "BBBB", "BBBB"}, }; DateTimePatternGenerator gen = DateTimePatternGenerator.getEmptyInstance(); DateTimePatternGenerator.PatternInfo returnInfo = new DateTimePatternGenerator.PatternInfo(); @@ -109,7 +134,8 @@ public class DateTimeGeneratorTest extends TestFmwk { } } for (String[] testItem : testItems) { - String pattern = gen.getBestPattern(testItem[0]); + int options = DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH; + String pattern = gen.getBestPattern(testItem[0], options); if (!pattern.equals(testItem[1])) { errln("getBestPattern for skeleton " + testItem[0] + ", expected " + testItem[1] + ", got " + pattern); }