]> granicus.if.org Git - icu/commitdiff
ICU-11706 (and #11726) Fix DateIntervalFormat handling of (1) skeletons with seconds...
authorPeter Edberg <pedberg@unicode.org>
Wed, 24 Jun 2015 07:40:11 +0000 (07:40 +0000)
committerPeter Edberg <pedberg@unicode.org>
Wed, 24 Jun 2015 07:40:11 +0000 (07:40 +0000)
X-SVN-Rev: 37615

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 8ab7ea708114ccf5dfc5830eec59bb52367c4aeb..8b8f7d997e78500ac5fc42d2b3d0b9d0cf6d6e26 100644 (file)
@@ -85,9 +85,11 @@ import com.ibm.icu.util.ULocale.Category;
  *
  * <P>
  * The calendar fields we support for interval formatting are:
- * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.
+ * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and
+ * second (though we do not currently have specific intervalFormat data for
+ * skeletons with seconds). 
  * Those calendar fields can be defined in the following order:
- * year >  month > date > hour (in day) >  minute 
+ * year >  month > date > hour (in day) > minute > second
  *  
  * The largest different calendar fields between 2 calendars is the
  * first different calendar field in above order.
@@ -216,7 +218,8 @@ import com.ibm.icu.util.ULocale.Category;
  *     dtitvinf = new DateIntervalInfo();
  *     
  *     // a series of set interval patterns.
- *     // Only ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, and MINUTE  are supported.
+ *     // Only ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY,
+ *     MINUTE and SECOND 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");
@@ -307,7 +310,7 @@ public class DateIntervalFormat extends UFormat {
 
     /*
      * Following are transient interval information
-     * relavent (locale) to this formatter.
+     * relevant (locale) to this formatter.
      */
     private String fSkeleton = null;
     
@@ -321,6 +324,13 @@ public class DateIntervalFormat extends UFormat {
      *  Interval patterns for this instance's locale.
      */
     private transient Map<String, PatternInfo> fIntervalPatterns = null;
+
+    /*
+     * Patterns for fallback formatting. 
+     */
+    private String fDatePattern = null;
+    private String fTimePattern = null;
+    private String fDateTimeFormat = null;
     
    
     /*
@@ -556,6 +566,9 @@ public class DateIntervalFormat extends UFormat {
         other.fInfo = (DateIntervalInfo) fInfo.clone();
         other.fFromCalendar = (Calendar) fFromCalendar.clone();
         other.fToCalendar = (Calendar) fToCalendar.clone();
+        other.fDatePattern = fDatePattern;
+        other.fTimePattern = fTimePattern;
+        other.fDateTimeFormat = fDateTimeFormat;
         return other;
     }
 
@@ -572,6 +585,9 @@ public class DateIntervalFormat extends UFormat {
      *                          Result is appended to existing contents.
      * @param fieldPosition     On input: an alignment field, if desired.
      *                          On output: the offsets of the alignment field.
+     *                          There may be multiple instances of a given field type
+     *                          in an interval format; in this case the fieldPosition
+     *                          offsets refer to the first instance.
      * @return                  Reference to 'appendTo' parameter.
      * @throws    IllegalArgumentException  if the formatted object is not 
      *                                      DateInterval object
@@ -596,6 +612,9 @@ public class DateIntervalFormat extends UFormat {
      *                          Result is appended to existing contents.
      * @param fieldPosition     On input: an alignment field, if desired.
      *                          On output: the offsets of the alignment field.
+     *                          There may be multiple instances of a given field type
+     *                          in an interval format; in this case the fieldPosition
+     *                          offsets refer to the first instance.
      * @return                  Reference to 'appendTo' parameter.
      * @stable ICU 4.0
      */
@@ -638,6 +657,9 @@ public class DateIntervalFormat extends UFormat {
         } else if ( fromCalendar.get(Calendar.MINUTE) !=
                     toCalendar.get(Calendar.MINUTE) ) {
             field = Calendar.MINUTE;
+        } else if ( fromCalendar.get(Calendar.SECOND) !=
+                    toCalendar.get(Calendar.SECOND) ) {
+            field = Calendar.SECOND;
         } else {
             return null;
         }
@@ -657,6 +679,9 @@ public class DateIntervalFormat extends UFormat {
      *                          Result is appended to existing contents.
      * @param pos               On input: an alignment field, if desired.
      *                          On output: the offsets of the alignment field.
+     *                          There may be multiple instances of a given field type
+     *                          in an interval format; in this case the fieldPosition
+     *                          offsets refer to the first instance.
      * @return                  Reference to 'appendTo' parameter.
      * @throws    IllegalArgumentException  if the two calendars are not equivalent.
      * @stable ICU 4.0
@@ -694,12 +719,16 @@ public class DateIntervalFormat extends UFormat {
         } else if ( fromCalendar.get(Calendar.MINUTE) !=
                     toCalendar.get(Calendar.MINUTE) ) {
             field = Calendar.MINUTE;
-        } else {
-            /* ignore the second/millisecond etc. small fields' difference.
+         } else if ( fromCalendar.get(Calendar.SECOND) !=
+                    toCalendar.get(Calendar.SECOND) ) {
+            field = Calendar.SECOND;
+       } else {
+            /* ignore the millisecond etc. small fields' difference.
              * use single date when all the above are the same.
              */
             return fDateFormat.format(fromCalendar, appendTo, pos);
         }
+        boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND);
         
         // get interval pattern
         PatternInfo intervalPattern = fIntervalPatterns.get(
@@ -714,7 +743,7 @@ public class DateIntervalFormat extends UFormat {
                 return fDateFormat.format(fromCalendar, appendTo, pos);
             }
 
-            return fallbackFormat(fromCalendar, toCalendar, appendTo, pos);
+            return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos);
         }
 
         // If the first part in interval pattern is empty, 
@@ -722,7 +751,7 @@ public class DateIntervalFormat extends UFormat {
         // For a 'real' interval pattern, the first part will never be empty.
         if ( intervalPattern.getFirstPart() == null ) {
             // fall back
-            return fallbackFormat(fromCalendar, toCalendar, appendTo, pos,
+            return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos,
                                     intervalPattern.getSecondPart());
         }
         Calendar firstCal;
@@ -741,12 +770,48 @@ public class DateIntervalFormat extends UFormat {
         fDateFormat.format(firstCal, appendTo, pos);
         if ( intervalPattern.getSecondPart() != null ) {
             fDateFormat.applyPattern(intervalPattern.getSecondPart());
-            fDateFormat.format(secondCal, appendTo, pos);
+            FieldPosition otherPos = new FieldPosition(pos.getField());
+            fDateFormat.format(secondCal, appendTo, otherPos);
+            if (pos.getEndIndex() == 0 && otherPos.getEndIndex() > 0) {
+                pos = otherPos;
+            }
         }
         fDateFormat.applyPattern(originalPattern);
         return appendTo;
     }
 
+    private void adjustPosition(String combiningPattern, // has {0} and {1} in it
+                                String pat0, FieldPosition pos0, // pattern and pos corresponding to {0}
+                                String pat1, FieldPosition pos1, // pattern and pos corresponding to {1}
+                                FieldPosition posResult) {
+        int index0 = combiningPattern.indexOf("{0}");
+        int index1 = combiningPattern.indexOf("{1}");
+        if (index0 < 0 || index1 < 0) {
+            return;
+        }
+        int placeholderLen = 3; // length of "{0}" or "{1}"
+        if (index0 < index1) {
+            if (pos0.getEndIndex() > 0) {
+                posResult.setBeginIndex(pos0.getBeginIndex() + index0);
+                posResult.setEndIndex(pos0.getEndIndex() + index0);
+            } else if (pos1.getEndIndex() > 0) {
+                // here index1 >= 3
+                index1 += pat0.length() - placeholderLen; // adjust for pat0 replacing {0}
+                posResult.setBeginIndex(pos1.getBeginIndex() + index1);
+                posResult.setEndIndex(pos1.getEndIndex() + index1);
+            }
+        } else {
+            if (pos1.getEndIndex() > 0) {
+                posResult.setBeginIndex(pos1.getBeginIndex() + index1);
+                posResult.setEndIndex(pos1.getEndIndex() + index1);
+            } else if (pos0.getEndIndex() > 0) {
+                // here index0 >= 3
+                index0 += pat1.length() - placeholderLen; // adjust for pat1 replacing {1}
+                posResult.setBeginIndex(pos0.getBeginIndex() + index0);
+                posResult.setEndIndex(pos0.getEndIndex() + index0);
+            }
+        }
+    }
 
     /*
      * Format 2 Calendars to using fall-back interval pattern
@@ -766,17 +831,41 @@ public class DateIntervalFormat extends UFormat {
      */
     private final StringBuffer fallbackFormat(Calendar fromCalendar,
                                               Calendar toCalendar,
+                                              boolean fromToOnSameDay,
                                               StringBuffer appendTo,
                                               FieldPosition pos)  {
+            String fullPattern = null; // for saving the pattern in fDateFormat
+            boolean formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern != null && fTimePattern != null);
             // the fall back
+            if (formatDatePlusTimeRange) {
+                fullPattern = fDateFormat.toPattern(); // save current pattern, restore later
+                fDateFormat.applyPattern(fTimePattern);
+            }
+            FieldPosition otherPos = new FieldPosition(pos.getField());
             StringBuffer earlierDate = new StringBuffer(64);
             earlierDate = fDateFormat.format(fromCalendar, earlierDate, pos);
             StringBuffer laterDate = new StringBuffer(64);
-            laterDate = fDateFormat.format(toCalendar, laterDate, pos);
+            laterDate = fDateFormat.format(toCalendar, laterDate, otherPos);
             String fallbackPattern = fInfo.getFallbackIntervalPattern();
-            String fallback = MessageFormat.format(fallbackPattern, new Object[]
+            adjustPosition(fallbackPattern, earlierDate.toString(), pos, laterDate.toString(), otherPos, pos);
+            String fallbackRange = MessageFormat.format(fallbackPattern, new Object[]
                             {earlierDate.toString(), laterDate.toString()});
-            appendTo.append(fallback);
+            if (formatDatePlusTimeRange) {
+                // fallbackRange has just the time range, need to format the date part and combine that
+                fDateFormat.applyPattern(fDatePattern);
+                StringBuffer datePortion = new StringBuffer(64);
+                otherPos.setBeginIndex(0);
+                otherPos.setEndIndex(0);
+                datePortion = fDateFormat.format(fromCalendar, datePortion, otherPos);
+                adjustPosition(fDateTimeFormat, fallbackRange, pos, datePortion.toString(), otherPos, pos);
+                fallbackRange = MessageFormat.format(fDateTimeFormat, new Object[]
+                            {fallbackRange, datePortion.toString()});
+            }
+            appendTo.append(fallbackRange);
+            if (formatDatePlusTimeRange) {
+                // restore full pattern
+                fDateFormat.applyPattern(fullPattern);
+            }
             return appendTo;
     }
 
@@ -800,12 +889,13 @@ public class DateIntervalFormat extends UFormat {
      */
     private final StringBuffer fallbackFormat(Calendar fromCalendar,
                                               Calendar toCalendar,
+                                              boolean fromToOnSameDay,
                                               StringBuffer appendTo,
                                               FieldPosition pos, 
                                               String fullPattern)  {
             String originalPattern = fDateFormat.toPattern();
             fDateFormat.applyPattern(fullPattern);
-            fallbackFormat(fromCalendar, toCalendar, appendTo, pos);
+            fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos);
             fDateFormat.applyPattern(originalPattern);
             return appendTo;
     }
@@ -1030,10 +1120,22 @@ public class DateIntervalFormat extends UFormat {
         String normalizedDateSkeleton = normalizedDate.toString();
         String normalizedTimeSkeleton = normalizedTime.toString();
 
+        // move this up here since we need it for fallbacks
+        if (time.length() != 0 && date.length() != 0) {
+            // Need the Date/Time pattern for concatnation the date with
+            // the time interval.
+            // The date/time pattern ( such as {0} {1} ) is saved in
+            // calendar, that is why need to get the CalendarData here.
+            CalendarData calData = new CalendarData(locale, null);
+            String[] patterns = calData.getDateTimePatterns();
+            fDateTimeFormat = patterns[8];
+        }
+
         boolean found = genSeparateDateTimePtn(normalizedDateSkeleton, 
                                                normalizedTimeSkeleton,
-                                               intervalPatterns);
+                                               intervalPatterns, dtpng);
 
+        // for skeletons with seconds, found is false and we enter this block
         if ( found == false ) {
             // use fallback
             // TODO: if user asks "m", but "d" differ
@@ -1137,16 +1239,13 @@ public class DateIntervalFormat extends UFormat {
              * 2) otherwise, present the date followed by the 
              * range expression for the time. 
              */
-            // Need the Date/Time pattern for concatnation the date with
-            // the time interval.
-            // The date/time pattern ( such as {0} {1} ) is saved in
-            // calendar, that is why need to get the CalendarData here.
-            CalendarData calData = new CalendarData(locale, null);
-            String[] patterns = calData.getDateTimePatterns();
+            if (fDateTimeFormat == null) {
+                fDateTimeFormat = "{1} {0}";
+            }
             String datePattern =dtpng.getBestPattern(dateSkeleton);
-            concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.AM_PM, intervalPatterns);
-            concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.HOUR, intervalPatterns);
-            concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.MINUTE, intervalPatterns);
+            concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.AM_PM, intervalPatterns);
+            concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.HOUR, intervalPatterns);
+            concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.MINUTE, intervalPatterns);
         }
 
         return intervalPatterns;
@@ -1383,12 +1482,13 @@ public class DateIntervalFormat extends UFormat {
      */
     private boolean genSeparateDateTimePtn(String dateSkeleton, 
                                            String timeSkeleton,
-                                           Map<String, PatternInfo> intervalPatterns)
+                                           Map<String, PatternInfo> intervalPatterns,
+                                           DateTimePatternGenerator dtpng)
     {
         String skeleton;
         // if both date and time skeleton present,
         // the final interval pattern might include time interval patterns
-        // ( when, am_pm, hour, minute differ ),
+        // ( when, am_pm, hour, minute, second differ ),
         // but not date interval patterns ( when year, month, day differ ).
         // For year/month/day differ, it falls back to fall-back pattern.
         if ( timeSkeleton.length() != 0  ) {
@@ -1410,11 +1510,22 @@ public class DateIntervalFormat extends UFormat {
         String bestSkeleton = retValue.bestMatchSkeleton;
         int differenceInfo =  retValue.bestMatchDistanceInfo;
    
+        // Set patterns for fallback use, need to do this
+        // before returning if differenceInfo == -1
+        if (dateSkeleton.length() != 0  ) {
+            fDatePattern = dtpng.getBestPattern(dateSkeleton);
+        }
+        if (timeSkeleton.length() != 0  ) {
+            fTimePattern = dtpng.getBestPattern(timeSkeleton);
+        }
+
         // difference:
         // 0 means the best matched skeleton is the same as input skeleton
         // 1 means the fields are the same, but field width are different
         // 2 means the only difference between fields are v/z,
         // -1 means there are other fields difference 
+        // (this will happen, for instance, if the supplied skeleton has seconds,
+        //  but no skeletons in the intervalFormats data do)
         if ( differenceInfo == -1 ) { 
             // skeleton has different fields, not only  v/z difference
             return false;
index cfd60718594e5b39a7bc23ee937f6c132cef9fdf..fd8444b95814a217a3a41e83a38efdd22579b79f 100644 (file)
@@ -71,9 +71,11 @@ import com.ibm.icu.util.UResourceBundle;
  *
  * <P>
  * The calendar fields we support for interval formatting are:
- * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.
+ * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and
+ * second (though we do not currently have specific intervalFormat data for
+ * skeletons with seconds). 
  * Those calendar fields can be defined in the following order:
- * year >  month > date > am-pm > hour >  minute 
+ * year >  month > date > am-pm > hour >  minute > second
  *  
  * The largest different calendar fields between 2 calendars is the
  * first different calendar field in above order.
@@ -134,7 +136,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, and MINUTE.
+ * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE and SECOND.
  * Interval patterns when other calendar fields are different is not supported.
  * <P>
  * DateIntervalInfo objects are cloneable. 
@@ -285,7 +287,7 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
 
     private static final long serialVersionUID = 1;
     private static final int MINIMUM_SUPPORTED_CALENDAR_FIELD = 
-                                                          Calendar.MINUTE;
+                                                          Calendar.SECOND;
     //private static boolean DEBUG = true;
 
     private static String FALLBACK_STRING = "fallback";
@@ -478,6 +480,8 @@ public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>,
                             key = CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR];
                         } else if ( key.equals(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MINUTE]) ) {
                             calendarField = Calendar.MINUTE;    
+                        } else if ( key.equals(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.SECOND]) ) {
+                            calendarField = Calendar.SECOND;    
                         }
              
                         if ( calendarField != -1 ) {
@@ -592,7 +596,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, and MINUTE.
+     * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE, and SECOND.
      * Interval patterns when other calendar fields are different are 
      * not supported.
      *
@@ -737,7 +741,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 MINUTE");
+            throw new IllegalArgumentException("no support for field less than SECOND");
         }
         Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
         if ( patternsOfOneSkeleton != null ) {
index 8df1b15b5bc25a0d72fb7d35e9d823d1c2078b07..825ff05045f995d767ddb7369b7f518efeb61780 100644 (file)
@@ -410,7 +410,7 @@ public class DateIntervalFormatTest extends com.ibm.icu.dev.test.TestFmwk {
 
                 "en", "2007 01 10 10:10:10", "2007 01 10 10:10:20", "hhmmzz", "10:10 AM PST", 
 
-                "en", "2007 01 10 10:10:10", "2007 01 10 10:10:20", "hms", "10:10:10 AM", 
+                "en", "2007 01 10 10:10:10", "2007 01 10 10:10:20", "hms", "10:10:10 AM \u2013 10:10:20 AM", 
 
                 "en", "2007 01 01 22:00:00", "2007 01 01 23:00:00", "yMMMMdHm", "January 1, 2007, 22:00 \u2013 23:00", 
 
@@ -1196,10 +1196,10 @@ public class DateIntervalFormatTest extends com.ibm.icu.dev.test.TestFmwk {
      */
     public void TestGetIntervalPattern(){
         // Tests when "if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD )" is true
-        // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.MINUTE;
+        // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.SECOND;
         DateIntervalInfo dii = new DateIntervalInfo();
         try{
-            dii.getIntervalPattern("", Calendar.MINUTE+1);
+            dii.getIntervalPattern("", Calendar.SECOND+1);
             errln("DateIntervalInfo.getIntervalPattern(String,int) was suppose " +
                     "to return an exception for the 'int field' parameter " +
                     "when it exceeds MINIMUM_SUPPORTED_CALENDAR_FIELD.");
@@ -1221,10 +1221,10 @@ public class DateIntervalFormatTest extends com.ibm.icu.dev.test.TestFmwk {
         } catch(Exception e){}
 
         // Tests when "if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD )" is true
-        // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.MINUTE;
+        // MINIMUM_SUPPORTED_CALENDAR_FIELD = Calendar.SECOND;
         try{
             dii = (DateIntervalInfo) dii.cloneAsThawed();
-            dii.setIntervalPattern("", Calendar.MINUTE+1, "");
+            dii.setIntervalPattern("", Calendar.SECOND+1, "");
             errln("DateIntervalInfo.setIntervalPattern(String,int,String) " +
                     "was suppose to return an exception when the " +
                     "variable 'lrgDiffCalUnit' is greater than " + 
@@ -1528,4 +1528,189 @@ public class DateIntervalFormatTest extends com.ibm.icu.dev.test.TestFmwk {
         }
         return false;
     }
+
+    public void TestFPos_SkelWithSeconds () {
+        
+        final long[] deltas = {
+               0L, // none
+               200L, // 200 millisec
+               20000L, // 20 sec
+               1200000L, // 20 min
+               7200000L, // 2 hrs
+               43200000L, // 12 hrs
+               691200000L, // 8 days
+               1382400000L, // 16 days,
+               8640000000L, // 100 days
+        };
+
+        class ExpectPosAndFormat {
+            public int posBegin;
+            public int posEnd;
+            public String format;
+             // Simple constructor
+            public ExpectPosAndFormat(int pBegin, int pEnd, String fmt) {
+                posBegin = pBegin;
+                posEnd = pEnd;
+                format = fmt;
+            }
+        };
+        
+        final ExpectPosAndFormat[] exp_en_HHmm = {
+            new ExpectPosAndFormat(  3,  5, "09:00" ),
+            new ExpectPosAndFormat(  3,  5, "09:00" ),
+            new ExpectPosAndFormat(  3,  5, "09:00" ),
+            new ExpectPosAndFormat(  3,  5, "09:00 \u2013 09:20" ),
+            new ExpectPosAndFormat(  3,  5, "09:00 \u2013 11:00" ),
+            new ExpectPosAndFormat(  3,  5, "09:00 \u2013 21:00" ),
+            new ExpectPosAndFormat( 15, 17, "11/20/2014, 09:00 \u2013 11/28/2014, 09:00" ),
+            new ExpectPosAndFormat( 15, 17, "11/20/2014, 09:00 \u2013 12/6/2014, 09:00" ),
+            new ExpectPosAndFormat( 15, 17, "11/20/2014, 09:00 \u2013 2/28/2015, 09:00" )
+        };
+
+        final ExpectPosAndFormat[] exp_en_HHmmss = {
+            new ExpectPosAndFormat(  3,  5, "09:00:00" ),
+            new ExpectPosAndFormat(  3,  5, "09:00:00" ),
+            new ExpectPosAndFormat(  3,  5, "09:00:00 \u2013 09:00:20" ),
+            new ExpectPosAndFormat(  3,  5, "09:00:00 \u2013 09:20:00" ),
+            new ExpectPosAndFormat(  3,  5, "09:00:00 \u2013 11:00:00" ),
+            new ExpectPosAndFormat(  3,  5, "09:00:00 \u2013 21:00:00" ),
+            new ExpectPosAndFormat( 15, 17, "11/20/2014, 09:00:00 \u2013 11/28/2014, 09:00:00" ),
+            new ExpectPosAndFormat( 15, 17, "11/20/2014, 09:00:00 \u2013 12/6/2014, 09:00:00" ),
+            new ExpectPosAndFormat( 15, 17, "11/20/2014, 09:00:00 \u2013 2/28/2015, 09:00:00" )
+        };
+
+        final ExpectPosAndFormat[] exp_en_yyMMdd = {
+            new ExpectPosAndFormat(  0,  0, "11/20/14" ),
+            new ExpectPosAndFormat(  0,  0, "11/20/14" ),
+            new ExpectPosAndFormat(  0,  0, "11/20/14" ),
+            new ExpectPosAndFormat(  0,  0, "11/20/14" ),
+            new ExpectPosAndFormat(  0,  0, "11/20/14" ),
+            new ExpectPosAndFormat(  0,  0, "11/20/14" ),
+            new ExpectPosAndFormat(  0,  0, "11/20/14 \u2013 11/28/14" ),
+            new ExpectPosAndFormat(  0,  0, "11/20/14 \u2013 12/6/14" ),
+            new ExpectPosAndFormat(  0,  0, "11/20/14 \u2013 2/28/15" )
+        };
+
+        final ExpectPosAndFormat[] exp_en_yyMMddHHmm = {
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00 \u2013 09:20" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00 \u2013 11:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00 \u2013 21:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00 \u2013 11/28/14, 09:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00 \u2013 12/06/14, 09:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00 \u2013 02/28/15, 09:00" )
+        };
+
+        final ExpectPosAndFormat[] exp_en_yyMMddHHmmss = {
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00 \u2013 09:00:20" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00 \u2013 09:20:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00 \u2013 11:00:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00 \u2013 21:00:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00 \u2013 11/28/14, 09:00:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00 \u2013 12/06/14, 09:00:00" ),
+            new ExpectPosAndFormat( 13, 15, "11/20/14, 09:00:00 \u2013 02/28/15, 09:00:00" )
+        };
+
+        final ExpectPosAndFormat[] exp_en_yMMMdhmmssz = {
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT" ),
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT" ),
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \u2013 9:00:20 AM GMT" ),
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \u2013 9:20:00 AM GMT" ),
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \u2013 11:00:00 AM GMT" ),
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \u2013 9:00:00 PM GMT" ),
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \u2013 Nov 28, 2014, 9:00:00 AM GMT" ),
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \u2013 Dec 6, 2014, 9:00:00 AM GMT" ),
+            new ExpectPosAndFormat( 16, 18, "Nov 20, 2014, 9:00:00 AM GMT \u2013 Feb 28, 2015, 9:00:00 AM GMT" )
+        };
+
+        final ExpectPosAndFormat[] exp_ja_yyMMddHHmm = {
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9\u664200\u5206\uFF5E9\u664220\u5206" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9\u664200\u5206\uFF5E11\u664200\u5206" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9\u664200\u5206\uFF5E21\u664200\u5206" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00\uFF5E14/11/28 9:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00\uFF5E14/12/06 9:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00\uFF5E15/02/28 9:00" )
+        };
+
+        final ExpectPosAndFormat[] exp_ja_yyMMddHHmmss = {
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00\uFF5E9:00:20" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00\uFF5E9:20:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00\uFF5E11:00:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00\uFF5E21:00:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00\uFF5E14/11/28 9:00:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00\uFF5E14/12/06 9:00:00" ),
+            new ExpectPosAndFormat( 11, 13, "14/11/20 9:00:00\uFF5E15/02/28 9:00:00" )
+        };
+
+        final ExpectPosAndFormat[] exp_ja_yMMMdHHmmss = {
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00" ),
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00" ),
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00\uFF5E9:00:20" ),
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00\uFF5E9:20:00" ),
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00\uFF5E11:00:00" ),
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00\uFF5E21:00:00" ),
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00\uFF5E2014\u5E7411\u670828\u65E5 9:00:00" ),
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00\uFF5E2014\u5E7412\u67086\u65E5 9:00:00" ),
+            new ExpectPosAndFormat( 14, 16, "2014\u5E7411\u670820\u65E5 9:00:00\uFF5E2015\u5E742\u670828\u65E5 9:00:00" )
+        };
+
+        class LocaleAndSkeletonItem {
+            public String locale;
+            public String skeleton;
+            public int fieldToCheck;
+            public ExpectPosAndFormat[] expected;
+             // Simple constructor
+            public LocaleAndSkeletonItem(String loc, String skel, int field, ExpectPosAndFormat[] exp) {
+                locale = loc;
+                skeleton = skel;
+                fieldToCheck = field;
+                expected = exp;
+            }
+        };
+        
+        final LocaleAndSkeletonItem[] locSkelItems = {
+           new LocaleAndSkeletonItem( "en",            "HHmm",         DateFormat.MINUTE_FIELD, exp_en_HHmm ),
+           new LocaleAndSkeletonItem( "en",            "HHmmss",       DateFormat.MINUTE_FIELD, exp_en_HHmmss ),
+           new LocaleAndSkeletonItem( "en",            "yyMMdd",       DateFormat.MINUTE_FIELD, exp_en_yyMMdd ),
+           new LocaleAndSkeletonItem( "en",            "yyMMddHHmm",   DateFormat.MINUTE_FIELD, exp_en_yyMMddHHmm ),
+           new LocaleAndSkeletonItem( "en",            "yyMMddHHmmss", DateFormat.MINUTE_FIELD, exp_en_yyMMddHHmmss ),
+        // skip the following until ICU4J DateIntervalFormat has support for setting time zone
+        // new LocaleAndSkeletonItem( "en",            "yMMMdhmmssz",  DateFormat.MINUTE_FIELD, exp_en_yMMMdhmmssz ),
+           new LocaleAndSkeletonItem( "ja",            "yyMMddHHmm",   DateFormat.MINUTE_FIELD, exp_ja_yyMMddHHmm ),
+           new LocaleAndSkeletonItem( "ja",            "yyMMddHHmmss", DateFormat.MINUTE_FIELD, exp_ja_yyMMddHHmmss ),
+           new LocaleAndSkeletonItem( "ja",            "yMMMdHHmmss",  DateFormat.MINUTE_FIELD, exp_ja_yMMMdHHmmss )
+        };
+        
+        //final String zoneGMT = "GMT";
+        final long startTimeGMT = 1416474000000L; // 2014 Nov 20 09:00 GMT
+
+        TimeZone localZone = TimeZone.getDefault();
+        long startTime = startTimeGMT - localZone.getOffset(startTimeGMT);
+        for (LocaleAndSkeletonItem item: locSkelItems) {
+            DateIntervalFormat difmt = DateIntervalFormat.getInstance(item.skeleton, new ULocale(item.locale));
+            int dIdx, dCount = deltas.length;
+            for (dIdx = 0; dIdx < dCount; dIdx++) {
+                DateInterval di = new DateInterval(startTime, startTime + deltas[dIdx]);
+                StringBuffer actual = new StringBuffer(64);
+                FieldPosition pos = new FieldPosition(item.fieldToCheck);
+                String actualString = difmt.format(di, actual, pos).toString();
+                ExpectPosAndFormat expectPosFmt = item.expected[dIdx];
+                if (!actualString.equals(expectPosFmt.format) ||
+                        pos.getBeginIndex() != expectPosFmt.posBegin || pos.getEndIndex() != expectPosFmt.posEnd) {
+                    errln("For locale " + item.locale + ", skeleton " + item.skeleton + ", delta " + deltas[dIdx] +
+                           ": expect " + expectPosFmt.posBegin + "-" + expectPosFmt.posEnd + " \"" + expectPosFmt.format +
+                           "\"; get " + pos.getBeginIndex() + "-" + pos.getEndIndex() + " \"" + actualString + "\"");
+                }
+            }
+        }
+    }
 }