From 17d23d71c03fa0f722935b4aa488cc0d3bfb1052 Mon Sep 17 00:00:00 2001 From: Mihai Nita Date: Fri, 8 Nov 2019 21:25:37 +0000 Subject: [PATCH] ICU-20739 Force seconds if the skeleton has fractional seconds --- icu4c/source/i18n/dtptngen.cpp | 19 +++++++++++++ icu4c/source/test/intltest/dtfmttst.cpp | 17 +++++++++++- .../icu/text/DateTimePatternGenerator.java | 19 +++++++++++++ .../icu/dev/test/format/DateFormatTest.java | 27 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/icu4c/source/i18n/dtptngen.cpp b/icu4c/source/i18n/dtptngen.cpp index c5f8618a6d6..4948d2cbc97 100644 --- a/icu4c/source/i18n/dtptngen.cpp +++ b/icu4c/source/i18n/dtptngen.cpp @@ -2162,6 +2162,25 @@ DateTimeMatcher::set(const UnicodeString& pattern, FormatParser* fp, PtnSkeleton } skeletonResult.type[field] = subField; } + + // #20739, we have a skeleton with milliseconde, but no seconds + if (!skeletonResult.original.isFieldEmpty(UDATPG_FRACTIONAL_SECOND_FIELD) + && skeletonResult.original.isFieldEmpty(UDATPG_SECOND_FIELD)) { + // Force the use of seconds + for (i = 0; dtTypes[i].patternChar != 0; i++) { + if (dtTypes[i].field == UDATPG_SECOND_FIELD) { + // first entry for UDATPG_SECOND_FIELD + skeletonResult.original.populate(UDATPG_SECOND_FIELD, dtTypes[i].patternChar, dtTypes[i].minLen); + skeletonResult.baseOriginal.populate(UDATPG_SECOND_FIELD, dtTypes[i].patternChar, dtTypes[i].minLen); + // We add value.length, same as above, when type is first initialized. + // The value we want to "fake" here is "s", and 1 means "s".length() + int16_t subField = dtTypes[i].type; + skeletonResult.type[UDATPG_SECOND_FIELD] = (subField > 0) ? subField + 1 : subField; + break; + } + } + } + // #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) { diff --git a/icu4c/source/test/intltest/dtfmttst.cpp b/icu4c/source/test/intltest/dtfmttst.cpp index 9684e882b0f..46f4be2b5fd 100644 --- a/icu4c/source/test/intltest/dtfmttst.cpp +++ b/icu4c/source/test/intltest/dtfmttst.cpp @@ -4912,7 +4912,22 @@ void DateFormatTest::TestPatternFromSkeleton() { {Locale::getEnglish(), "jjmm", "h:mm a"}, {Locale::getEnglish(), "JJmm", "hh:mm"}, {Locale::getGerman(), "jjmm", "HH:mm"}, - {Locale::getGerman(), "JJmm", "HH:mm"} + {Locale::getGerman(), "JJmm", "HH:mm"}, + // Ticket #20739 + {Locale::getEnglish(), "SSSSm", "mm:ss.SSSS"}, + {Locale::getEnglish(), "mSSSS", "mm:ss.SSSS"}, + {Locale::getEnglish(), "SSSm", "mm:ss.SSS"}, + {Locale::getEnglish(), "mSSS", "mm:ss.SSS"}, + {Locale::getEnglish(), "SSm", "mm:ss.SS"}, + {Locale::getEnglish(), "mSS", "mm:ss.SS"}, + {Locale::getEnglish(), "Sm", "mm:ss.S"}, + {Locale::getEnglish(), "mS", "mm:ss.S"}, + {Locale::getEnglish(), "S", "S"}, + {Locale::getEnglish(), "SS", "SS"}, + {Locale::getEnglish(), "SSS", "SSS"}, + {Locale::getEnglish(), "SSSS", "SSSS"}, + {Locale::getEnglish(), "jmsSSS", "h:mm:ss.SSS a"}, + {Locale::getEnglish(), "jmSSS", "h:mm:ss.SSS a"} }; for (size_t i = 0; i < UPRV_LENGTHOF(TESTDATA); i++) { 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 70c136c41af..eb233181a69 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 @@ -2649,6 +2649,25 @@ public class DateTimePatternGenerator implements Freezable 0) subField += value.length(); type[field] = subField; } + + // #20739, we have a skeleton with milliseconde, but no seconds + if (!original.isFieldEmpty(FRACTIONAL_SECOND) && original.isFieldEmpty(SECOND)) { + // Force the use of seconds + for (int i = 0; i < types.length; ++i) { + int[] row = types[i]; + if (row[1] == SECOND) { + // first entry for SECOND + original.populate(SECOND, (char)row[0], row[3]); + baseOriginal.populate(SECOND, (char)row[0], row[3]); + // We add value.length, same as above, when type is first initialized. + // The value we want to "fake" here is "s", and 1 means "s".length() + int subField = row[2]; + type[SECOND] = (subField > 0) ? subField + 1 : subField; + break; + } + } + } + // #13183, handle special behavior for day period characters (a, b, B) if (!original.isFieldEmpty(HOUR)) { if (original.getFieldChar(HOUR)=='h' || original.getFieldChar(HOUR)=='K') { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java index f2e38498055..735d45dfcc7 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java @@ -5431,4 +5431,31 @@ public class DateFormatTest extends TestFmwk { dfmt.parse(inDate, pos); assertEquals("Error index", inDate.length(), pos.getErrorIndex()); } + + @Test + public void test20739_MillisecondsWithoutSeconds() { + String[][] cases = new String[][]{ + {"SSSSm", "mm:ss.SSSS"}, + {"mSSSS", "mm:ss.SSSS"}, + {"SSSm", "mm:ss.SSS"}, + {"mSSS", "mm:ss.SSS"}, + {"SSm", "mm:ss.SS"}, + {"mSS", "mm:ss.SS"}, + {"Sm", "mm:ss.S"}, + {"mS", "mm:ss.S"}, + {"S", "S"}, + {"SS", "SS"}, + {"SSS", "SSS"}, + {"SSSS", "SSSS"}, + {"jmsSSS", "h:mm:ss.SSS a"}, + {"jmSSS", "h:mm:ss.SSS a"} + }; + + ULocale locale = ULocale.ENGLISH; + for (String[] cas : cases) { + DateFormat fmt = DateFormat.getInstanceForSkeleton( cas[0], locale); + String pattern = ((SimpleDateFormat) fmt).toPattern(); + assertEquals("Format pattern", cas[1], pattern); + } + } }