diff --git a/icu4c/source/i18n/dtitvfmt.cpp b/icu4c/source/i18n/dtitvfmt.cpp index d51ddcd5c70..df9d23bd444 100644 --- a/icu4c/source/i18n/dtitvfmt.cpp +++ b/icu4c/source/i18n/dtitvfmt.cpp @@ -966,23 +966,26 @@ DateIntervalFormat::normalizeHourMetacharacters(const UnicodeString& skeleton) c UChar hourMetachar = u'\0'; UChar dayPeriodChar = u'\0'; - int32_t metacharStart = 0; - int32_t metacharCount = 0; + int32_t hourFieldStart = 0; + int32_t hourFieldLength = 0; + int32_t dayPeriodStart = 0; + int32_t dayPeriodLength = 0; for (int32_t i = 0; i < result.length(); i++) { UChar c = result[i]; if (c == LOW_J || c == CAP_J || c == CAP_C || c == LOW_H || c == CAP_H || c == LOW_K || c == CAP_K) { if (hourMetachar == u'\0') { hourMetachar = c; - metacharStart = i; + hourFieldStart = i; } - ++metacharCount; + ++hourFieldLength; } else if (c == LOW_A || c == LOW_B || c == CAP_B) { if (dayPeriodChar == u'\0') { dayPeriodChar = c; + dayPeriodStart = i; } - ++metacharCount; + ++dayPeriodLength; } else { - if (hourMetachar != u'\0') { + if (hourMetachar != u'\0' && dayPeriodChar != u'\0') { break; } } @@ -1022,31 +1025,27 @@ DateIntervalFormat::normalizeHourMetacharacters(const UnicodeString& skeleton) c } } - if (hourChar == CAP_H || hourChar == LOW_K) { - result.replace(metacharStart, metacharCount, hourChar); - } else { - UnicodeString hourAndDayPeriod(hourChar); - switch (metacharCount) { - case 1: - case 2: - default: - hourAndDayPeriod.append(UnicodeString(dayPeriodChar)); - break; - case 3: - case 4: - for (int32_t i = 0; i < 4; i++) { - hourAndDayPeriod.append(dayPeriodChar); - } - break; - case 5: - case 6: - for (int32_t i = 0; i < 5; i++) { - hourAndDayPeriod.append(dayPeriodChar); - } - break; + UnicodeString hourAndDayPeriod(hourChar); + if (hourChar != CAP_H && hourChar != LOW_K) { + int32_t newDayPeriodLength = 0; + if (dayPeriodLength >= 5 || hourFieldLength >= 5) { + newDayPeriodLength = 5; + } else if (dayPeriodLength >= 3 || hourFieldLength >= 3) { + newDayPeriodLength = 3; + } else { + newDayPeriodLength = 1; + } + for (int32_t i = 0; i < newDayPeriodLength; i++) { + hourAndDayPeriod.append(dayPeriodChar); } - result.replace(metacharStart, metacharCount, hourAndDayPeriod); } + result.replace(hourFieldStart, hourFieldLength, hourAndDayPeriod); + if (dayPeriodStart > hourFieldStart) { + // before deleting the original day period field, adjust its position in case + // we just changed the size of the hour field (and new day period field) + dayPeriodStart += hourAndDayPeriod.length() - hourFieldLength; + } + result.remove(dayPeriodStart, dayPeriodLength); } return result; } diff --git a/icu4c/source/test/intltest/dtifmtts.cpp b/icu4c/source/test/intltest/dtifmtts.cpp index 605eae43614..f2774433296 100644 --- a/icu4c/source/test/intltest/dtifmtts.cpp +++ b/icu4c/source/test/intltest/dtifmtts.cpp @@ -1194,6 +1194,10 @@ void DateIntervalFormatTest::testHourMetacharacters() { "en", "CE 2010 09 27 00:00:00", "CE 2010 09 27 01:00:00", "KK", "12 \\u2013 1 AM", // (this was producing "0 - 1 AM" before) "en", "CE 2010 09 27 00:00:00", "CE 2010 09 27 00:00:00", "jj", "12 AM", "en", "CE 2010 09 27 00:00:00", "CE 2010 09 27 01:00:00", "jj", "12 \\u2013 1 AM", + + // regression test for ICU-21984 (multiple day-period characters in date-interval patterns) + "en", "CE 2010 09 27 00:00:00", "CE 2010 09 27 01:00:00", "MMMdhhmma", "Sep 27, 12:00 \\u2013 1:00 AM", + "sq", "CE 2010 09 27 00:00:00", "CE 2010 09 27 01:00:00", "Bhm", "12:00 \\u2013 1:00 e nat\\u00EBs", }; expect(DATA, UPRV_LENGTHOF(DATA)); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java index aba183fa166..53d2b975e11 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java @@ -1616,23 +1616,26 @@ public class DateIntervalFormat extends UFormat { char hourMetachar = '\0'; char dayPeriodChar = '\0'; - int metacharStart = 0; - int metacharCount = 0; + int hourFieldStart = 0; + int hourFieldLength = 0; + int dayPeriodStart = 0; + int dayPeriodLength = 0; for (int i = 0; i < result.length(); i++) { char c = result.charAt(i); if (c == 'j' || c == 'J' || c == 'C' || c == 'h' || c == 'H' || c == 'k' || c == 'K') { if (hourMetachar == '\0') { hourMetachar = c; - metacharStart = i; + hourFieldStart = i; } - ++metacharCount; + ++hourFieldLength; } else if (c == 'a' || c == 'b' || c == 'B') { if (dayPeriodChar == '\0') { dayPeriodChar = c; + dayPeriodStart = i; } - ++metacharCount; + ++dayPeriodLength; } else { - if (hourMetachar != '\0') { + if (hourMetachar != '\0' && dayPeriodChar != '\0') { break; } } @@ -1671,32 +1674,26 @@ public class DateIntervalFormat extends UFormat { dayPeriodChar = 'a'; } - if (hourChar == 'H' || hourChar == 'k') { - result.replace(metacharStart, metacharStart + metacharCount, String.valueOf(hourChar)); - } else { - StringBuilder hourAndDayPeriod = new StringBuilder(); - hourAndDayPeriod.append(hourChar); - switch (metacharCount) { - case 1: - case 2: - default: - hourAndDayPeriod.append(dayPeriodChar); - break; - case 3: - case 4: - for (int i = 0; i < 4; i++) { - hourAndDayPeriod.append(dayPeriodChar); - } - break; - case 5: - case 6: - for (int i = 0; i < 5; i++) { - hourAndDayPeriod.append(dayPeriodChar); - } - break; + StringBuilder hourAndDayPeriod = new StringBuilder(); + hourAndDayPeriod.append(hourChar); + if (hourChar != 'H' && hourChar != 'k') { + int newDayPeriodLength = 0; + if (dayPeriodLength >= 5 || hourFieldLength >= 5) { + newDayPeriodLength = 5; + } else if (dayPeriodLength >= 3 || hourFieldLength >= 3) { + newDayPeriodLength = 3; + } else { + newDayPeriodLength = 1; + } + for (int i = 0; i < newDayPeriodLength; i++) { + hourAndDayPeriod.append(dayPeriodChar); } - result.replace(metacharStart, metacharStart + metacharCount, hourAndDayPeriod.toString()); } + result.replace(hourFieldStart, hourFieldStart + hourFieldLength, hourAndDayPeriod.toString()); + if (dayPeriodStart > hourFieldStart) { + dayPeriodStart += hourAndDayPeriod.length() - hourFieldLength; + } + result.delete(dayPeriodStart, dayPeriodStart + dayPeriodLength); } return result.toString(); } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java index 7ad08e1d75c..858d933d013 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java @@ -839,6 +839,10 @@ public class DateIntervalFormatTest extends TestFmwk { "en", "CE 2010 09 27 00:00:00", "CE 2010 09 27 01:00:00", "KK", "12 \\u2013 1 AM", // (this was producing "0 - 1 AM" before) "en", "CE 2010 09 27 00:00:00", "CE 2010 09 27 00:00:00", "jj", "12 AM", "en", "CE 2010 09 27 00:00:00", "CE 2010 09 27 01:00:00", "jj", "12 \\u2013 1 AM", + + // regression test for ICU-21984 (multiple day-period characters in date-interval patterns) + "en", "CE 2010 09 27 00:00:00", "CE 2010 09 27 01:00:00", "MMMdhhmma", "Sep 27, 12:00 \\u2013 1:00 AM", + "sq", "CE 2010 09 27 00:00:00", "CE 2010 09 27 01:00:00", "Bhm", "12:00 \\u2013 1:00 e nat\\u00EBs", }; expect(DATA, DATA.length); }