Cherrypick PR 2060 to fix DateIntervalFormat

This is a regression in 71-1 on DateIntervalFormat

https://github.com/unicode-org/icu/pull/2060
https://unicode-org.atlassian.net/browse/ICU-21984

Bug: 1316665
Change-Id: Icd3b9e329ffd993457e1c2f0b86ed178f4a7b259
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/deps/icu/+/3587906
Reviewed-by: Jungshik Shin <jshin@chromium.org>
diff --git a/README.chromium b/README.chromium
index 15973b5..d5ba188 100644
--- a/README.chromium
+++ b/README.chromium
@@ -248,3 +248,8 @@
 9.  Patch source/common/uposixdefs.h so it compiles on Fuchsia on Macs.
     patches/fuchsia.patch
   - context bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1184527
+
+10. Patch i18n/dtitvfmt.cpp to fix DateIntervalFormat regression
+    patches/DateIntervalFormatnormalizeHourMetacharacters.patch
+  - https://github.com/unicode-org/icu/pull/2060
+  - https://unicode-org.atlassian.net/browse/ICU-21984
diff --git a/patches/DateIntervalFormatnormalizeHourMetacharacters.patch b/patches/DateIntervalFormatnormalizeHourMetacharacters.patch
new file mode 100644
index 0000000..f40ee47
--- /dev/null
+++ b/patches/DateIntervalFormatnormalizeHourMetacharacters.patch
@@ -0,0 +1,103 @@
+diff --git a/source/i18n/dtitvfmt.cpp b/source/i18n/dtitvfmt.cpp
+index d51ddcd5..df9d23bd 100644
+--- a/source/i18n/dtitvfmt.cpp
++++ b/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;
+             }
+-            result.replace(metacharStart, metacharCount, hourAndDayPeriod);
++            for (int32_t i = 0; i < newDayPeriodLength; i++) {
++                hourAndDayPeriod.append(dayPeriodChar);
++            }
++        }
++        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/source/test/intltest/dtifmtts.cpp b/source/test/intltest/dtifmtts.cpp
+index 605eae43..f2774433 100644
+--- a/source/test/intltest/dtifmtts.cpp
++++ b/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/source/i18n/dtitvfmt.cpp b/source/i18n/dtitvfmt.cpp
index d51ddcd..df9d23b 100644
--- a/source/i18n/dtitvfmt.cpp
+++ b/source/i18n/dtitvfmt.cpp
@@ -966,23 +966,26 @@
     
     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 @@
             }
         }
         
-        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;
             }
-            result.replace(metacharStart, metacharCount, hourAndDayPeriod);
+            for (int32_t i = 0; i < newDayPeriodLength; i++) {
+                hourAndDayPeriod.append(dayPeriodChar);
+            }
         }
+        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/source/test/intltest/dtifmtts.cpp b/source/test/intltest/dtifmtts.cpp
index 605eae4..f277443 100644
--- a/source/test/intltest/dtifmtts.cpp
+++ b/source/test/intltest/dtifmtts.cpp
@@ -1194,6 +1194,10 @@
         "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));
 }