]> granicus.if.org Git - icu/commitdiff
ICU-20967 add millisecond to DateIntervalFormat
authorFrank Tang <ftang@chromium.org>
Sat, 22 Feb 2020 01:41:58 +0000 (01:41 +0000)
committerFrank Yung-Fong Tang <41213225+FrankYFTang@users.noreply.github.com>
Thu, 5 Mar 2020 18:55:19 +0000 (10:55 -0800)
See #978

icu4c/source/i18n/dtitvfmt.cpp
icu4c/source/i18n/dtitvinf.cpp
icu4c/source/i18n/unicode/dtitvfmt.h
icu4c/source/i18n/unicode/dtitvinf.h
icu4c/source/test/intltest/dtifmtts.cpp
icu4c/source/test/intltest/dtifmtts.h
icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/DateIntervalInfo.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateIntervalFormatTest.java

index f47e7708ccc128555177f82cbbbef91cd54358b1..64b4065292dd5b390bce58efdf1ec3af94002b0f 100644 (file)
@@ -444,6 +444,9 @@ DateIntervalFormat::formatImpl(Calendar& fromCalendar,
     } else if ( fromCalendar.get(UCAL_SECOND, status) !=
                 toCalendar.get(UCAL_SECOND, status) ) {
         field = UCAL_SECOND;
+    } else if ( fromCalendar.get(UCAL_MILLISECOND, status) !=
+                toCalendar.get(UCAL_MILLISECOND, status) ) {
+        field = UCAL_MILLISECOND;
     }
 
     if ( U_FAILURE(status) ) {
@@ -455,7 +458,7 @@ DateIntervalFormat::formatImpl(Calendar& fromCalendar,
          */
         return fDateFormat->_format(fromCalendar, appendTo, fphandler, status);
     }
-    UBool fromToOnSameDay = (field==UCAL_AM_PM || field==UCAL_HOUR || field==UCAL_MINUTE || field==UCAL_SECOND);
+    UBool fromToOnSameDay = (field==UCAL_AM_PM || field==UCAL_HOUR || field==UCAL_MINUTE || field==UCAL_SECOND || field==UCAL_MILLISECOND);
 
     // following call should not set wrong status,
     // all the pass-in fields are valid till here
index 0ee17e88e8180816fc985acf4ed612613224e32a..b263765c06678b4761f7e6312905d8444b630071 100644 (file)
@@ -711,6 +711,9 @@ DateIntervalInfo::calendarFieldToIntervalIndex(UCalendarDateFields field,
       case UCAL_SECOND:
         index = kIPI_SECOND;
         break;
+      case UCAL_MILLISECOND:
+        index = kIPI_MILLISECOND;
+        break;
       default:
         status = U_ILLEGAL_ARGUMENT_ERROR;
     }
index 23fc02e2a7b16b5bb0c09bf95d443395f841d450..4ec216e71d3bfc2f2b0fd341e50d02db756dfb7a 100644 (file)
@@ -174,11 +174,12 @@ class U_I18N_API FormattedDateInterval : public UMemory, public FormattedValue {
  *
  * <P>
  * The calendar fields we support for interval formatting are:
- * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and second
+ * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, second,
+ * and millisecond.
  * (though we do not currently have specific intervalFormat date for skeletons
- * with seconds).
+ * with seconds and millisecond).
  * Those calendar fields can be defined in the following order:
- * year >  month > date > hour (in day) >  minute > second
+ * year >  month > date > hour (in day) >  minute > second > millisecond
  *
  * The largest different calendar fields between 2 calendars is the
  * first different calendar field in above order.
index a894d12ca4428b6476c7172df102f9631acf0270..68bfa4352a6557c43a6742af49732aa6d5213419 100644 (file)
@@ -137,8 +137,8 @@ U_NAMESPACE_BEGIN
  * After a DateIntervalInfo object is created, clients may modify
  * the interval patterns using setIntervalPattern function as so desired.
  * Currently, users can only set interval patterns when the following
- * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH,
- * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, and MINUTE.
+ * calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH,
+ * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND.
  * Interval patterns when other calendar fields are different is not supported.
  * <P>
  * DateIntervalInfo objects are cloneable.
@@ -245,7 +245,7 @@ public:
      * Restriction:
      * Currently, users can only set interval patterns when the following
      * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH,
-     * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, and MINUTE.
+     * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE, SECOND and MILLISECOND.
      * Interval patterns when other calendar fields are different are
      * not supported.
      *
@@ -348,7 +348,7 @@ private:
     /**
      * Following is for saving the interval patterns.
      * We only support interval patterns on
-     * ERA, YEAR, MONTH, DAY, AM_PM, HOUR, and MINUTE
+     * ERA, YEAR, MONTH, DAY, AM_PM, HOUR, MINUTE, SECOND and MILLISECOND.
      */
     enum IntervalPatternIndex
     {
@@ -360,6 +360,7 @@ private:
         kIPI_HOUR,
         kIPI_MINUTE,
         kIPI_SECOND,
+        kIPI_MILLISECOND,
         kIPI_MAX_INDEX
     };
 public:
@@ -453,8 +454,8 @@ private:
      * hash table.
      *
      * Since we only support the following calendar fields:
-     * ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, DAY_OF_WEEK,
-     * AM_PM,  HOUR, HOUR_OF_DAY, and MINUTE,
+     * ERA, YEAR, MONTH, DATE, DAY_OF_MONTH, DAY_OF_WEEK,
+     * AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND.
      * We reserve only 4 interval patterns for a skeleton.
      *
      * @param field    calendar field
index 2976558dcf5368140ee72714ecdb59dcf306976a..2f59c1905cb4e309cfdc49b35e6553569d4bfa48 100644 (file)
@@ -59,6 +59,7 @@ void DateIntervalFormatTest::runIndexedTest( int32_t index, UBool exec, const ch
         TESTCASE(10, testFormattedDateInterval);
         TESTCASE(11, testCreateInstanceForAllLocales);
         TESTCASE(12, testTicket20707);
+        TESTCASE(13, testFormatMillisecond);
         default: name = ""; break;
     }
 }
@@ -1806,6 +1807,115 @@ void DateIntervalFormatTest::testCreateInstanceForAllLocales() {
     }
 }
 
+void DateIntervalFormatTest::testFormatMillisecond() {
+    struct
+    {
+        int year;
+        int month;
+        int day;
+        int from_hour;
+        int from_miniute;
+        int from_second;
+        int from_millisecond;
+        int to_hour;
+        int to_miniute;
+        int to_second;
+        int to_millisecond;
+        const char* skeleton;
+        const char16_t* expected;
+    }
+    kTestCases [] =
+    {
+        //           From            To
+        //   y  m  d   h  m   s   ms   h  m   s   ms   skeleton  expected
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "ms",     u"23:45"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "msS",    u"23:45.3"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "msSS",   u"23:45.32"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "msSSS",  u"23:45.321"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "ms",     u"23:45"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "msS",    u"23:45.3 \u2013 23:45.9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "msSS",   u"23:45.32 \u2013 23:45.98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "msSSS",  u"23:45.321 \u2013 23:45.987"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "ms",     u"23:45 \u2013 23:46"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "msS",    u"23:45.3 \u2013 23:46.9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "msSS",   u"23:45.32 \u2013 23:46.98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "msSSS",  u"23:45.321 \u2013 23:46.987"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "ms",     u"23:45 \u2013 24:45"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "msS",    u"23:45.3 \u2013 24:45.9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "msSS",   u"23:45.32 \u2013 24:45.98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "msSSS",  u"23:45.321 \u2013 24:45.987"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "s",      u"45"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "sS",     u"45.3"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "sSS",    u"45.32"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "sSSS",   u"45.321"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "s",      u"45"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "sS",     u"45.3 \u2013 45.9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "sSS",    u"45.32 \u2013 45.98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "sSSS",   u"45.321 \u2013 45.987"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "s",      u"45 \u2013 46"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "sS",     u"45.3 \u2013 46.9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "sSS",    u"45.32 \u2013 46.98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "sSSS",   u"45.321 \u2013 46.987"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "s",      u"45 \u2013 45"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "sS",     u"45.3 \u2013 45.9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "sSS",    u"45.32 \u2013 45.98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "sSSS",   u"45.321 \u2013 45.987"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "S",      u"3"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "SS",     u"32"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 321, "SSS",    u"321"},
+
+        // Same millisecond but in different second.
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 321, "S",      u"3 \u2013 3"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 321, "SS",     u"32 \u2013 32"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 321, "SSS",    u"321 \u2013 321"},
+
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "S",      u"3 \u2013 9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "SS",     u"32 \u2013 98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 45, 987, "SSS",    u"321 \u2013 987"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "S",      u"3 \u2013 9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "SS",     u"32 \u2013 98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 1, 23, 46, 987, "SSS",    u"321 \u2013 987"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "S",      u"3 \u2013 9"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "SS",     u"32 \u2013 98"},
+        { 2019, 2, 10, 1, 23, 45, 321, 2, 24, 45, 987, "SSS",    u"321 \u2013 987"},
+        { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr, nullptr},
+    };
+
+    const Locale &enLocale = Locale::getEnglish();
+    IcuTestErrorCode status(*this, "testFormatMillisecond");
+    LocalPointer<Calendar> calendar(Calendar::createInstance(enLocale, status));
+    if (status.errIfFailureAndReset()) { return; }
+
+    for (int32_t i = 0; kTestCases[i].year > 0; i++) {
+        LocalPointer<DateIntervalFormat> fmt(DateIntervalFormat::createInstance(
+            kTestCases[i].skeleton, enLocale, status));
+        if (status.errIfFailureAndReset()) { continue; }
+
+        calendar->clear();
+        calendar->set(kTestCases[i].year, kTestCases[i].month, kTestCases[i].day,
+                      kTestCases[i].from_hour, kTestCases[i].from_miniute, kTestCases[i].from_second);
+        UDate from = calendar->getTime(status) + kTestCases[i].from_millisecond;
+        if (status.errIfFailureAndReset()) { continue; }
+
+        calendar->clear();
+        calendar->set(kTestCases[i].year, kTestCases[i].month, kTestCases[i].day,
+                      kTestCases[i].to_hour, kTestCases[i].to_miniute, kTestCases[i].to_second);
+        UDate to = calendar->getTime(status) + kTestCases[i].to_millisecond;
+        FormattedDateInterval  res = fmt->formatToValue(DateInterval(from, to), status);
+        if (status.errIfFailureAndReset()) { continue; }
+
+        UnicodeString formatted = res.toString(status);
+        if (status.errIfFailureAndReset()) { continue; }
+        if (formatted != kTestCases[i].expected) {
+            std::string tmp1;
+            std::string tmp2;
+            errln("Case %d for skeleton %s : Got %s but expect %s",
+                  i, kTestCases[i].skeleton, formatted.toUTF8String<std::string>(tmp1).c_str(),
+                  UnicodeString(kTestCases[i].expected).toUTF8String<std::string>(tmp2).c_str());
+        }
+    }
+}
+
 void DateIntervalFormatTest::testTicket20707() {
     IcuTestErrorCode status(*this, "testTicket20707");
 
index 592996e9b390bd2a00451b1a580a4757c1a8d7d3..69b3d938d4fc5fbfdd5d08e51a00e07758ccccc1 100644 (file)
@@ -34,6 +34,8 @@ public:
      */
     void testFormat();
 
+    void testFormatMillisecond();
+
     /**
      * Test formatting using user defined DateIntervalInfo
      */
index e009d95866393ce5f60cdbd04d9ce2cff6b115af..9c7e957bdba36b26832d30742abc8495c65f5e3a 100644 (file)
@@ -230,7 +230,7 @@ import com.ibm.icu.util.UResourceBundle;
  *
  *     // a series of set interval patterns.
  *     // Only ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY,
- *     MINUTE and SECOND are supported.
+ *     MINUTE, SECOND and MILLISECOND are supported.
  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'");
  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d");
  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d");
@@ -856,6 +856,9 @@ public class DateIntervalFormat extends UFormat {
         } else if ( fromCalendar.get(Calendar.SECOND) !=
                     toCalendar.get(Calendar.SECOND) ) {
             field = Calendar.SECOND;
+        } else if ( fromCalendar.get(Calendar.MILLISECOND) !=
+                    toCalendar.get(Calendar.MILLISECOND) ) {
+            field = Calendar.MILLISECOND;
         } else {
             return null;
         }
@@ -952,16 +955,19 @@ public class DateIntervalFormat extends UFormat {
         } else if ( fromCalendar.get(Calendar.MINUTE) !=
                     toCalendar.get(Calendar.MINUTE) ) {
             field = Calendar.MINUTE;
-         } else if ( fromCalendar.get(Calendar.SECOND) !=
+        } else if ( fromCalendar.get(Calendar.SECOND) !=
                     toCalendar.get(Calendar.SECOND) ) {
             field = Calendar.SECOND;
-       } else {
+        } else if ( fromCalendar.get(Calendar.MILLISECOND) !=
+                    toCalendar.get(Calendar.MILLISECOND) ) {
+            field = Calendar.MILLISECOND;
+        } else {
             /* ignore the millisecond etc. small fields' difference.
              * use single date when all the above are the same.
              */
             return fDateFormat.format(fromCalendar, appendTo, pos, attributes);
         }
-        boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND);
+        boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND || field==Calendar.MILLISECOND);
 
         // get interval pattern
         PatternInfo intervalPattern = fIntervalPatterns.get(
index 9d4239a742808589a8ae25752f0b8f60dbacabed..d042bb93164b02df7103013109486d82633be760 100644 (file)
@@ -143,7 +143,7 @@ import com.ibm.icu.util.UResourceBundle;
  * the interval patterns using setIntervalPattern function as so desired.
  * Currently, users can only set interval patterns when the following
  * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH,
- * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE and SECOND.
+ * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND.
  * Interval patterns when other calendar fields are different is not supported.
  * <P>
  * DateIntervalInfo objects are cloneable.
@@ -298,7 +298,7 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
 
     private static final long serialVersionUID = 1;
     private static final int MINIMUM_SUPPORTED_CALENDAR_FIELD =
-                                                          Calendar.SECOND;
+                                                          Calendar.MILLISECOND;
     //private static boolean DEBUG = true;
 
     private static String CALENDAR_KEY = "calendar";
@@ -425,8 +425,9 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
          * Calendar.HOUR_OF_DAY
          * Calendar.MINUTE
          * Calendar.SECOND
+         * Calendar.MILLISECOND
          */
-        private static final String ACCEPTED_PATTERN_LETTERS = "GyMdahHms";
+        private static final String ACCEPTED_PATTERN_LETTERS = "GyMdahHmsS";
 
         // Output data
         DateIntervalInfo dateIntervalInfo;
@@ -705,7 +706,7 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
      * Restriction:
      * Currently, users can only set interval patterns when the following
      * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH,
-     * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE, and SECOND.
+     * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND.
      * Interval patterns when other calendar fields are different are
      * not supported.
      *
@@ -850,7 +851,7 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
     public PatternInfo getIntervalPattern(String skeleton, int field)
     {
         if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
-            throw new IllegalArgumentException("no support for field less than SECOND");
+            throw new IllegalArgumentException("no support for field less than MILLISECOND");
         }
         Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
         if ( patternsOfOneSkeleton != null ) {
index 4ec5d5e82119fd87cc9699c9b4c64481d14c681d..b5cb83c25a0a4672346c857ab4ec6cdfce24df4c 100644 (file)
@@ -1253,10 +1253,10 @@ public class DateIntervalFormatTest extends TestFmwk {
     @Test
     public void TestGetIntervalPattern(){
         // Tests when "if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD )" is true
-        // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.SECOND;
+        // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.MILLISECOND;
         DateIntervalInfo dii = new DateIntervalInfo();
         try{
-            dii.getIntervalPattern("", Calendar.SECOND+1);
+            dii.getIntervalPattern("", Calendar.MILLISECOND+1);
             errln("DateIntervalInfo.getIntervalPattern(String,int) was suppose " +
                     "to return an exception for the 'int field' parameter " +
                     "when it exceeds MINIMUM_SUPPORTED_CALENDAR_FIELD.");
@@ -1279,10 +1279,10 @@ public class DateIntervalFormatTest extends TestFmwk {
         } catch(Exception e){}
 
         // Tests when "if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD )" is true
-        // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.SECOND;
+        // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.MILLISECOND;
         try{
             dii = dii.cloneAsThawed();
-            dii.setIntervalPattern("", Calendar.SECOND+1, "");
+            dii.setIntervalPattern("", Calendar.MILLISECOND+1, "");
             errln("DateIntervalInfo.setIntervalPattern(String,int,String) " +
                     "was suppose to return an exception when the " +
                     "variable 'lrgDiffCalUnit' is greater than " +
@@ -2094,4 +2094,56 @@ public class DateIntervalFormatTest extends TestFmwk {
             i++;
         }
     }
+
+    @Test
+    public void testFormatMillisecond() {
+        Object[][] kTestCases = {
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "ms",     "23:45"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "msS",    "23:45.3"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "msSS",   "23:45.32"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "msSSS",  "23:45.321"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "ms",     "23:45"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "msS",    "23:45.3 – 23:45.9"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "msSS",   "23:45.32 – 23:45.98"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "msSSS",  "23:45.321 – 23:45.987"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "ms",     "23:45 – 23:46"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "msS",    "23:45.3 – 23:46.9"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "msSS",   "23:45.32 – 23:46.98"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "msSSS",  "23:45.321 – 23:46.987"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "ms",     "23:45 – 24:45"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "msS",    "23:45.3 – 24:45.9"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "msSS",   "23:45.32 – 24:45.98"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "msSSS",  "23:45.321 – 24:45.987"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "s",      "45"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "sS",     "45.3"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "sSS",    "45.32"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 321, "sSSS",   "45.321"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "s",      "45"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "sS",     "45.3 – 45.9"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "sSS",    "45.32 – 45.98"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 45), 987, "sSSS",   "45.321 – 45.987"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "s",      "45 – 46"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "sS",     "45.3 – 46.9"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "sSS",    "45.32 – 46.98"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 1, 23, 46), 987, "sSSS",   "45.321 – 46.987"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "s",      "45 – 45"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "sS",     "45.3 – 45.9"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "sSS",    "45.32 – 45.98"},
+            { new Date(2019, 2, 10, 1, 23, 45), 321, new Date(2019, 2, 10, 2, 24, 45), 987, "sSSS",   "45.321 – 45.987"},
+        };
+
+        Locale enLocale = Locale.ENGLISH;
+
+        for (Object[] testCase : kTestCases) {
+            DateIntervalFormat fmt = DateIntervalFormat.getInstance((String)testCase[4], enLocale);
+
+            Date fromDate = (Date)testCase[0];
+            long from = fromDate.getTime() + (Integer)testCase[1];
+            Date toDate = (Date)testCase[2];
+            long to = toDate.getTime() + (Integer)testCase[3];
+
+            FormattedDateInterval res = fmt.formatToValue(new DateInterval(from, to));
+            assertEquals("Formate for " + testCase[4], testCase[5], res.toString());
+        }
+    }
 }