ICU-20739 Force seconds if the skeleton has fractional seconds
diff --git a/icu4c/source/i18n/dtptngen.cpp b/icu4c/source/i18n/dtptngen.cpp
index c5f8618..4948d2c 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 9684e88..46f4be2 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 70c136c..eb23318 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 @@ DateTimeMatcher set(String pattern, FormatParser fp, boolean allowDuplicateField
if (subField > 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 f2e3849..735d45d 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 void TestParseRegression13744() {
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);
+ }
+ }
}