Home | History | Annotate | Download | only in text
      1 //  2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html#License
      3 /*
      4 *   Copyright (C) 2008-2016, International Business Machines
      5 *   Corporation and others.  All Rights Reserved.
      6 */
      7 
      8 package com.ibm.icu.text;
      9 
     10 import java.io.IOException;
     11 import java.io.ObjectInputStream;
     12 import java.text.FieldPosition;
     13 import java.text.ParsePosition;
     14 import java.util.Collections;
     15 import java.util.HashMap;
     16 import java.util.Locale;
     17 import java.util.Map;
     18 
     19 import com.ibm.icu.impl.ICUCache;
     20 import com.ibm.icu.impl.ICUData;
     21 import com.ibm.icu.impl.ICUResourceBundle;
     22 import com.ibm.icu.impl.SimpleCache;
     23 import com.ibm.icu.impl.SimpleFormatterImpl;
     24 import com.ibm.icu.text.DateIntervalInfo.PatternInfo;
     25 import com.ibm.icu.util.Calendar;
     26 import com.ibm.icu.util.DateInterval;
     27 import com.ibm.icu.util.Output;
     28 import com.ibm.icu.util.TimeZone;
     29 import com.ibm.icu.util.ULocale;
     30 import com.ibm.icu.util.ULocale.Category;
     31 import com.ibm.icu.util.UResourceBundle;
     32 
     33 
     34 /**
     35  * DateIntervalFormat is a class for formatting and parsing date
     36  * intervals in a language-independent manner.
     37  * Only formatting is supported. Parsing is not supported.
     38  *
     39  * <P>
     40  * Date interval means from one date to another date,
     41  * for example, from "Jan 11, 2008" to "Jan 18, 2008".
     42  * We introduced class DateInterval to represent it.
     43  * DateInterval is a pair of UDate, which is
     44  * the standard milliseconds since 24:00 GMT, Jan 1, 1970.
     45  *
     46  * <P>
     47  * DateIntervalFormat formats a DateInterval into
     48  * text as compactly as possible.
     49  * For example, the date interval format from "Jan 11, 2008" to "Jan 18,. 2008"
     50  * is "Jan 11-18, 2008" for English.
     51  * And it parses text into DateInterval,
     52  * although initially, parsing is not supported.
     53  *
     54  * <P>
     55  * There is no structural information in date time patterns.
     56  * For any punctuations and string literals inside a date time pattern,
     57  * we do not know whether it is just a separator, or a prefix, or a suffix.
     58  * Without such information, so, it is difficult to generate a sub-pattern
     59  * (or super-pattern) by algorithm.
     60  * So, formatting a DateInterval is pattern-driven. It is very
     61  * similar to formatting in SimpleDateFormat.
     62  * We introduce class DateIntervalInfo to save date interval
     63  * patterns, similar to date time pattern in SimpleDateFormat.
     64  *
     65  * <P>
     66  * Logically, the interval patterns are mappings
     67  * from (skeleton, the_largest_different_calendar_field)
     68  * to (date_interval_pattern).
     69  *
     70  * <P>
     71  * A skeleton
     72  * <ol>
     73  * <li>
     74  * only keeps the field pattern letter and ignores all other parts
     75  * in a pattern, such as space, punctuations, and string literals.
     76  * <li>
     77  * hides the order of fields.
     78  * <li>
     79  * might hide a field's pattern letter length.
     80  *
     81  * For those non-digit calendar fields, the pattern letter length is
     82  * important, such as MMM, MMMM, and MMMMM; EEE and EEEE,
     83  * and the field's pattern letter length is honored.
     84  *
     85  * For the digit calendar fields,  such as M or MM, d or dd, yy or yyyy,
     86  * the field pattern length is ignored and the best match, which is defined
     87  * in date time patterns, will be returned without honor the field pattern
     88  * letter length in skeleton.
     89  * </ol>
     90  *
     91  * <P>
     92  * The calendar fields we support for interval formatting are:
     93  * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and
     94  * second (though we do not currently have specific intervalFormat data for
     95  * skeletons with seconds).
     96  * Those calendar fields can be defined in the following order:
     97  * year &gt; month &gt; date &gt; hour (in day) &gt; minute &gt; second
     98  *
     99  * The largest different calendar fields between 2 calendars is the
    100  * first different calendar field in above order.
    101  *
    102  * For example: the largest different calendar fields between "Jan 10, 2007"
    103  * and "Feb 20, 2008" is year.
    104  *
    105  * <P>
    106  * For other calendar fields, the compact interval formatting is not
    107  * supported. And the interval format will be fall back to fall-back
    108  * patterns, which is mostly "{date0} - {date1}".
    109  *
    110  * <P>
    111  * There is a set of pre-defined static skeleton strings in DateFormat,
    112  * There are pre-defined interval patterns for those pre-defined skeletons
    113  * in locales' resource files.
    114  * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is  "yMMMd",
    115  * in  en_US, if the largest different calendar field between date1 and date2
    116  * is "year", the date interval pattern  is "MMM d, yyyy - MMM d, yyyy",
    117  * such as "Jan 10, 2007 - Jan 10, 2008".
    118  * If the largest different calendar field between date1 and date2 is "month",
    119  * the date interval pattern is "MMM d - MMM d, yyyy",
    120  * such as "Jan 10 - Feb 10, 2007".
    121  * If the largest different calendar field between date1 and date2 is "day",
    122  * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".
    123  *
    124  * For date skeleton, the interval patterns when year, or month, or date is
    125  * different are defined in resource files.
    126  * For time skeleton, the interval patterns when am/pm, or hour, or minute is
    127  * different are defined in resource files.
    128  *
    129  * <P>
    130  * If a skeleton is not found in a locale's DateIntervalInfo, which means
    131  * the interval patterns for the skeleton is not defined in resource file,
    132  * the interval pattern will falls back to the interval "fallback" pattern
    133  * defined in resource file.
    134  * If the interval "fallback" pattern is not defined, the default fall-back
    135  * is "{date0} - {data1}".
    136  *
    137  * <P>
    138  * For the combination of date and time,
    139  * The rule to genearte interval patterns are:
    140  * <ol>
    141  * <li>
    142  *    when the year, month, or day differs, falls back to fall-back
    143  *    interval pattern, which mostly is the concatenate the two original
    144  *    expressions with a separator between,
    145  *    For example, interval pattern from "Jan 10, 2007 10:10 am"
    146  *    to "Jan 11, 2007 10:10am" is
    147  *    "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
    148  * <li>
    149  *    otherwise, present the date followed by the range expression
    150  *    for the time.
    151  *    For example, interval pattern from "Jan 10, 2007 10:10 am"
    152  *    to "Jan 10, 2007 11:10am" is "Jan 10, 2007 10:10 am - 11:10am"
    153  * </ol>
    154  *
    155  *
    156  * <P>
    157  * If two dates are the same, the interval pattern is the single date pattern.
    158  * For example, interval pattern from "Jan 10, 2007" to "Jan 10, 2007" is
    159  * "Jan 10, 2007".
    160  *
    161  * Or if the presenting fields between 2 dates have the exact same values,
    162  * the interval pattern is the  single date pattern.
    163  * For example, if user only requests year and month,
    164  * the interval pattern from "Jan 10, 2007" to "Jan 20, 2007" is "Jan 2007".
    165  *
    166  * <P>
    167  * DateIntervalFormat needs the following information for correct
    168  * formatting: time zone, calendar type, pattern, date format symbols,
    169  * and date interval patterns.
    170  * It can be instantiated in several ways:
    171  * <ol>
    172  * <li>
    173  *    create an instance using default or given locale plus given skeleton.
    174  *    Users are encouraged to created date interval formatter this way and
    175  *    to use the pre-defined skeleton macros, such as
    176  *    YEAR_NUM_MONTH, which consists the calendar fields and
    177  *    the format style.
    178  * </li>
    179  * <li>
    180  *    create an instance using default or given locale plus given skeleton
    181  *    plus a given DateIntervalInfo.
    182  *    This factory method is for powerful users who want to provide their own
    183  *    interval patterns.
    184  *    Locale provides the timezone, calendar, and format symbols information.
    185  *    Local plus skeleton provides full pattern information.
    186  *    DateIntervalInfo provides the date interval patterns.
    187  * </li>
    188  * </ol>
    189  *
    190  * <P>
    191  * For the calendar field pattern letter, such as G, y, M, d, a, h, H, m, s etc.
    192  * DateIntervalFormat uses the same syntax as that of
    193  * DateTime format.
    194  *
    195  * <P>
    196  * Code Sample: general usage
    197  * <pre>
    198  *
    199  *   // the date interval object which the DateIntervalFormat formats on
    200  *   // and parses into
    201  *   DateInterval dtInterval = new DateInterval(1000*3600*24L, 1000*3600*24*2L);
    202  *   DateIntervalFormat dtIntervalFmt = DateIntervalFormat.getInstance(
    203  *                   YEAR_MONTH_DAY, Locale("en", "GB", ""));
    204  *   StringBuffer str = new StringBuffer("");
    205  *   FieldPosition pos = new FieldPosition(0);
    206  *   // formatting
    207  *   dtIntervalFmt.format(dtInterval, dateIntervalString, pos);
    208  *
    209  * </pre>
    210  *
    211  * <P>
    212  * Code Sample: for powerful users who wants to use their own interval pattern
    213  * <pre>
    214  *
    215  *     import com.ibm.icu.text.DateIntervalInfo;
    216  *     import com.ibm.icu.text.DateIntervalFormat;
    217  *     ....................
    218  *
    219  *     // Get DateIntervalFormat instance using default locale
    220  *     DateIntervalFormat dtitvfmt = DateIntervalFormat.getInstance(YEAR_MONTH_DAY);
    221  *
    222  *     // Create an empty DateIntervalInfo object, which does not have any interval patterns inside.
    223  *     dtitvinf = new DateIntervalInfo();
    224  *
    225  *     // a series of set interval patterns.
    226  *     // Only ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY,
    227  *     MINUTE and SECOND are supported.
    228  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'");
    229  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d");
    230  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d");
    231  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.HOUR_OF_DAY, "yyyy MMM d HH:mm ~ HH:mm");
    232  *
    233  *     // Set fallback interval pattern. Fallback pattern is used when interval pattern is not found.
    234  *     // If the fall-back pattern is not set,  falls back to {date0} - {date1} if interval pattern is not found.
    235  *     dtitvinf.setFallbackIntervalPattern("{0} - {1}");
    236  *
    237  *     // Set above DateIntervalInfo object as the interval patterns of date interval formatter
    238  *     dtitvfmt.setDateIntervalInfo(dtitvinf);
    239  *
    240  *     // Prepare to format
    241  *     pos = new FieldPosition(0);
    242  *     str = new StringBuffer("");
    243  *
    244  *     // The 2 calendars should be equivalent, otherwise,  IllegalArgumentException will be thrown by format()
    245  *     Calendar fromCalendar = (Calendar) dtfmt.getCalendar().clone();
    246  *     Calendar toCalendar = (Calendar) dtfmt.getCalendar().clone();
    247  *     fromCalendar.setTimeInMillis(....);
    248  *     toCalendar.setTimeInMillis(...);
    249  *
    250  *     //Formatting given 2 calendars
    251  *     dtitvfmt.format(fromCalendar, toCalendar, str, pos);
    252  *
    253  *
    254  * </pre>
    255  * <h3>Synchronization</h3>
    256  *
    257  * The format methods of DateIntervalFormat may be used concurrently from multiple threads.
    258  * Functions that alter the state of a DateIntervalFormat object (setters)
    259  * may not be used concurrently with any other functions.
    260  *
    261  * @stable ICU 4.0
    262  */
    263 
    264 public class DateIntervalFormat extends UFormat {
    265 
    266     private static final long serialVersionUID = 1;
    267 
    268     /**
    269      * Used to save the information for a skeleton's best match skeleton.
    270      * It is package accessible since it is used in DateIntervalInfo too.
    271      */
    272     static final class BestMatchInfo {
    273         // the best match skeleton
    274         final String bestMatchSkeleton;
    275         // 0 means the best matched skeleton is the same as input skeleton
    276         // 1 means the fields are the same, but field width are different
    277         // 2 means the only difference between fields are v/z,
    278         // -1 means there are other fields difference
    279         final int    bestMatchDistanceInfo;
    280         BestMatchInfo(String bestSkeleton, int difference) {
    281             bestMatchSkeleton = bestSkeleton;
    282             bestMatchDistanceInfo = difference;
    283         }
    284     }
    285 
    286 
    287     /*
    288      * Used to save the information on a skeleton and its best match.
    289      */
    290     private static final class SkeletonAndItsBestMatch {
    291         final String skeleton;
    292         final String bestMatchSkeleton;
    293         SkeletonAndItsBestMatch(String skeleton, String bestMatch) {
    294             this.skeleton = skeleton;
    295             bestMatchSkeleton = bestMatch;
    296         }
    297     }
    298 
    299 
    300     // Cache for the locale interval pattern
    301     private static ICUCache<String, Map<String, PatternInfo>> LOCAL_PATTERN_CACHE =
    302         new SimpleCache<String, Map<String, PatternInfo>>();
    303 
    304     /*
    305      * The interval patterns for this locale.
    306      */
    307     private DateIntervalInfo     fInfo;
    308 
    309     /*
    310      * The DateFormat object used to format single pattern.
    311      * Because fDateFormat is modified during format operations, all
    312      * access to it from logically const, thread safe functions must be synchronized.
    313      */
    314     private SimpleDateFormat     fDateFormat;
    315 
    316     /*
    317      * The 2 calendars with the from and to date.
    318      * could re-use the calendar in fDateFormat,
    319      * but keeping 2 calendars make it clear and clean.
    320      * Because these Calendars are modified during format operations, all
    321      * access to them from logically const, thread safe functions must be synchronized.
    322      */
    323     private Calendar fFromCalendar;
    324     private Calendar fToCalendar;
    325 
    326     /*
    327      * Following are transient interval information
    328      * relevant (locale) to this formatter.
    329      */
    330     private String fSkeleton = null;
    331 
    332     /*
    333      * Needed for efficient deserialization. If set, it means we can use the
    334      * cache to initialize fIntervalPatterns.
    335      */
    336     private boolean isDateIntervalInfoDefault;
    337 
    338     /**
    339      *  Interval patterns for this instance's locale.
    340      */
    341     private transient Map<String, PatternInfo> fIntervalPatterns = null;
    342 
    343     /*
    344      * Patterns for fallback formatting.
    345      */
    346     private String fDatePattern = null;
    347     private String fTimePattern = null;
    348     private String fDateTimeFormat = null;
    349 
    350 
    351     /*
    352      * default constructor; private because we don't want anyone to use
    353      */
    354     @SuppressWarnings("unused")
    355     private DateIntervalFormat() {
    356     }
    357 
    358     /**
    359      * Construct a DateIntervalFormat from DateFormat,
    360      * a DateIntervalInfo, and skeleton.
    361      * DateFormat provides the timezone, calendar,
    362      * full pattern, and date format symbols information.
    363      * It should be a SimpleDateFormat object which
    364      * has a pattern in it.
    365      * the DateIntervalInfo provides the interval patterns.
    366      *
    367      * @param skeleton  the skeleton of the date formatter
    368      * @param dtItvInfo  the DateIntervalInfo object to be adopted.
    369      * @param simpleDateFormat will be used for formatting
    370      *
    371      * @internal
    372      * @deprecated This API is ICU internal only.
    373      */
    374     @Deprecated
    375     public DateIntervalFormat(String skeleton, DateIntervalInfo dtItvInfo,
    376                                SimpleDateFormat simpleDateFormat)
    377     {
    378         fDateFormat = simpleDateFormat;
    379         // freeze date interval info
    380         dtItvInfo.freeze();
    381         fSkeleton = skeleton;
    382         fInfo = dtItvInfo;
    383         isDateIntervalInfoDefault = false;
    384         fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
    385         fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
    386         initializePattern(null);
    387     }
    388 
    389     private DateIntervalFormat(String skeleton, ULocale locale,
    390             SimpleDateFormat simpleDateFormat)
    391     {
    392         fDateFormat = simpleDateFormat;
    393         fSkeleton = skeleton;
    394         fInfo = new DateIntervalInfo(locale).freeze();
    395         isDateIntervalInfoDefault = true;
    396         fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
    397         fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
    398         initializePattern(LOCAL_PATTERN_CACHE);
    399 }
    400 
    401 
    402     /**
    403      * Construct a DateIntervalFormat from skeleton and  the default <code>FORMAT</code> locale.
    404      *
    405      * This is a convenient override of
    406      * getInstance(String skeleton, ULocale locale)
    407      * with the value of locale as default <code>FORMAT</code> locale.
    408      *
    409      * @param skeleton  the skeleton on which interval format based.
    410      * @return          a date time interval formatter.
    411      * @see Category#FORMAT
    412      * @stable ICU 4.0
    413      */
    414     public static final DateIntervalFormat
    415         getInstance(String skeleton)
    416 
    417     {
    418         return getInstance(skeleton, ULocale.getDefault(Category.FORMAT));
    419     }
    420 
    421 
    422     /**
    423      * Construct a DateIntervalFormat from skeleton and a given locale.
    424      *
    425      * This is a convenient override of
    426      * getInstance(String skeleton, ULocale locale)
    427      *
    428      * <p>Example code:{@.jcite com.ibm.icu.samples.text.dateintervalformat.DateIntervalFormatSample:---dtitvfmtPreDefinedExample}
    429      * @param skeleton  the skeleton on which interval format based.
    430      * @param locale    the given locale
    431      * @return          a date time interval formatter.
    432      * @stable ICU 4.0
    433      */
    434     public static final DateIntervalFormat
    435         getInstance(String skeleton, Locale locale)
    436     {
    437         return getInstance(skeleton, ULocale.forLocale(locale));
    438     }
    439 
    440 
    441     /**
    442      * Construct a DateIntervalFormat from skeleton and a given locale.
    443      * <P>
    444      * In this factory method,
    445      * the date interval pattern information is load from resource files.
    446      * Users are encouraged to created date interval formatter this way and
    447      * to use the pre-defined skeleton macros.
    448      *
    449      * <P>
    450      * There are pre-defined skeletons in DateFormat,
    451      * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
    452      *
    453      * Those skeletons have pre-defined interval patterns in resource files.
    454      * Users are encouraged to use them.
    455      * For example:
    456      * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc);
    457      *
    458      * The given Locale provides the interval patterns.
    459      * For example, for en_GB, if skeleton is YEAR_ABBR_MONTH_WEEKDAY_DAY,
    460      * which is "yMMMEEEd",
    461      * the interval patterns defined in resource file to above skeleton are:
    462      * "EEE, d MMM, yyyy - EEE, d MMM, yyyy" for year differs,
    463      * "EEE, d MMM - EEE, d MMM, yyyy" for month differs,
    464      * "EEE, d - EEE, d MMM, yyyy" for day differs,
    465      * @param skeleton  the skeleton on which interval format based.
    466      * @param locale    the given locale
    467      * @return          a date time interval formatter.
    468      * @stable ICU 4.0
    469      */
    470     public static final DateIntervalFormat
    471         getInstance(String skeleton, ULocale locale)
    472     {
    473         DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
    474         return new DateIntervalFormat(skeleton, locale, new SimpleDateFormat(generator.getBestPattern(skeleton), locale));
    475     }
    476 
    477 
    478 
    479     /**
    480      * Construct a DateIntervalFormat from skeleton
    481      *  DateIntervalInfo, and the default <code>FORMAT</code> locale.
    482      *
    483      * This is a convenient override of
    484      * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
    485      * with the locale value as default <code>FORMAT</code> locale.
    486      *
    487      * @param skeleton  the skeleton on which interval format based.
    488      * @param dtitvinf  the DateIntervalInfo object to be adopted.
    489      * @return          a date time interval formatter.
    490      * @see Category#FORMAT
    491      * @stable ICU 4.0
    492      */
    493     public static final DateIntervalFormat getInstance(String skeleton,
    494                                                    DateIntervalInfo dtitvinf)
    495     {
    496         return getInstance(skeleton, ULocale.getDefault(Category.FORMAT), dtitvinf);
    497     }
    498 
    499 
    500 
    501     /**
    502      * Construct a DateIntervalFormat from skeleton
    503      * a DateIntervalInfo, and the given locale.
    504      *
    505      * This is a convenient override of
    506      * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
    507      *
    508      * <p>Example code:{@.jcite com.ibm.icu.samples.text.dateintervalformat.DateIntervalFormatSample:---dtitvfmtCustomizedExample}
    509      * @param skeleton  the skeleton on which interval format based.
    510      * @param locale    the given locale
    511      * @param dtitvinf  the DateIntervalInfo object to be adopted.
    512      * @return          a date time interval formatter.
    513      * @stable ICU 4.0
    514      */
    515     public static final DateIntervalFormat getInstance(String skeleton,
    516                                                  Locale locale,
    517                                                  DateIntervalInfo dtitvinf)
    518     {
    519         return getInstance(skeleton, ULocale.forLocale(locale), dtitvinf);
    520     }
    521 
    522 
    523 
    524     /**
    525      * Construct a DateIntervalFormat from skeleton
    526      * a DateIntervalInfo, and the given locale.
    527      *
    528      * <P>
    529      * In this factory method, user provides its own date interval pattern
    530      * information, instead of using those pre-defined data in resource file.
    531      * This factory method is for powerful users who want to provide their own
    532      * interval patterns.
    533      *
    534      * <P>
    535      * There are pre-defined skeleton in DateFormat,
    536      * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
    537      *
    538      * Those skeletons have pre-defined interval patterns in resource files.
    539      * Users are encouraged to use them.
    540      * For example:
    541      * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc,itvinf);
    542      *
    543      * the DateIntervalInfo provides the interval patterns.
    544      *
    545      * User are encouraged to set default interval pattern in DateIntervalInfo
    546      * as well, if they want to set other interval patterns ( instead of
    547      * reading the interval patterns from resource files).
    548      * When the corresponding interval pattern for a largest calendar different
    549      * field is not found ( if user not set it ), interval format fallback to
    550      * the default interval pattern.
    551      * If user does not provide default interval pattern, it fallback to
    552      * "{date0} - {date1}"
    553      *
    554      * @param skeleton  the skeleton on which interval format based.
    555      * @param locale    the given locale
    556      * @param dtitvinf  the DateIntervalInfo object to be adopted.
    557      * @return          a date time interval formatter.
    558      * @stable ICU 4.0
    559      */
    560     public static final DateIntervalFormat getInstance(String skeleton,
    561                                                  ULocale locale,
    562                                                  DateIntervalInfo dtitvinf)
    563     {
    564         // clone. If it is frozen, clone returns itself, otherwise, clone
    565         // returns a copy.
    566         dtitvinf = (DateIntervalInfo)dtitvinf.clone();
    567         DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
    568         return new DateIntervalFormat(skeleton, dtitvinf, new SimpleDateFormat(generator.getBestPattern(skeleton), locale));
    569     }
    570 
    571 
    572     /**
    573      * Clone this Format object polymorphically.
    574      * @return    A copy of the object.
    575      * @stable ICU 4.0
    576      */
    577     public synchronized Object clone()
    578     {
    579         DateIntervalFormat other = (DateIntervalFormat) super.clone();
    580         other.fDateFormat = (SimpleDateFormat) fDateFormat.clone();
    581         other.fInfo = (DateIntervalInfo) fInfo.clone();
    582         other.fFromCalendar = (Calendar) fFromCalendar.clone();
    583         other.fToCalendar = (Calendar) fToCalendar.clone();
    584         other.fDatePattern = fDatePattern;
    585         other.fTimePattern = fTimePattern;
    586         other.fDateTimeFormat = fDateTimeFormat;
    587         return other;
    588     }
    589 
    590 
    591     /**
    592      * Format an object to produce a string. This method handles Formattable
    593      * objects with a DateInterval type.
    594      * If a the Formattable object type is not a DateInterval,
    595      * IllegalArgumentException is thrown.
    596      *
    597      * @param obj               The object to format.
    598      *                          Must be a DateInterval.
    599      * @param appendTo          Output parameter to receive result.
    600      *                          Result is appended to existing contents.
    601      * @param fieldPosition     On input: an alignment field, if desired.
    602      *                          On output: the offsets of the alignment field.
    603      *                          There may be multiple instances of a given field type
    604      *                          in an interval format; in this case the fieldPosition
    605      *                          offsets refer to the first instance.
    606      * @return                  Reference to 'appendTo' parameter.
    607      * @throws    IllegalArgumentException  if the formatted object is not
    608      *                                      DateInterval object
    609      * @stable ICU 4.0
    610      */
    611     public final StringBuffer
    612         format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition)
    613     {
    614         if ( obj instanceof DateInterval ) {
    615             return format( (DateInterval)obj, appendTo, fieldPosition);
    616         }
    617         else {
    618             throw new IllegalArgumentException("Cannot format given Object (" + obj.getClass().getName() + ") as a DateInterval");
    619         }
    620     }
    621 
    622     /**
    623      * Format a DateInterval to produce a string.
    624      *
    625      * @param dtInterval        DateInterval to be formatted.
    626      * @param appendTo          Output parameter to receive result.
    627      *                          Result is appended to existing contents.
    628      * @param fieldPosition     On input: an alignment field, if desired.
    629      *                          On output: the offsets of the alignment field.
    630      *                          There may be multiple instances of a given field type
    631      *                          in an interval format; in this case the fieldPosition
    632      *                          offsets refer to the first instance.
    633      * @return                  Reference to 'appendTo' parameter.
    634      * @stable ICU 4.0
    635      */
    636     public final synchronized StringBuffer format(DateInterval dtInterval,
    637                                      StringBuffer appendTo,
    638                                      FieldPosition fieldPosition)
    639     {
    640         fFromCalendar.setTimeInMillis(dtInterval.getFromDate());
    641         fToCalendar.setTimeInMillis(dtInterval.getToDate());
    642         return format(fFromCalendar, fToCalendar, appendTo, fieldPosition);
    643     }
    644 
    645     /**
    646      * @internal
    647      * @deprecated This API is ICU internal only.
    648      */
    649     @Deprecated
    650     public String getPatterns(Calendar fromCalendar,
    651             Calendar toCalendar,
    652             Output<String> part2) {
    653         // First, find the largest different calendar field.
    654         int field;
    655         if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
    656             field = Calendar.ERA;
    657         } else if ( fromCalendar.get(Calendar.YEAR) !=
    658                     toCalendar.get(Calendar.YEAR) ) {
    659             field = Calendar.YEAR;
    660         } else if ( fromCalendar.get(Calendar.MONTH) !=
    661                     toCalendar.get(Calendar.MONTH) ) {
    662             field = Calendar.MONTH;
    663         } else if ( fromCalendar.get(Calendar.DATE) !=
    664                     toCalendar.get(Calendar.DATE) ) {
    665             field = Calendar.DATE;
    666         } else if ( fromCalendar.get(Calendar.AM_PM) !=
    667                     toCalendar.get(Calendar.AM_PM) ) {
    668             field = Calendar.AM_PM;
    669         } else if ( fromCalendar.get(Calendar.HOUR) !=
    670                     toCalendar.get(Calendar.HOUR) ) {
    671             field = Calendar.HOUR;
    672         } else if ( fromCalendar.get(Calendar.MINUTE) !=
    673                     toCalendar.get(Calendar.MINUTE) ) {
    674             field = Calendar.MINUTE;
    675         } else if ( fromCalendar.get(Calendar.SECOND) !=
    676                     toCalendar.get(Calendar.SECOND) ) {
    677             field = Calendar.SECOND;
    678         } else {
    679             return null;
    680         }
    681         PatternInfo intervalPattern = fIntervalPatterns.get(
    682                 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
    683         part2.value = intervalPattern.getSecondPart();
    684         return intervalPattern.getFirstPart();
    685     }
    686     /**
    687      * Format 2 Calendars to produce a string.
    688      *
    689      * @param fromCalendar      calendar set to the from date in date interval
    690      *                          to be formatted into date interval string
    691      * @param toCalendar        calendar set to the to date in date interval
    692      *                          to be formatted into date interval string
    693      * @param appendTo          Output parameter to receive result.
    694      *                          Result is appended to existing contents.
    695      * @param pos               On input: an alignment field, if desired.
    696      *                          On output: the offsets of the alignment field.
    697      *                          There may be multiple instances of a given field type
    698      *                          in an interval format; in this case the fieldPosition
    699      *                          offsets refer to the first instance.
    700      * @return                  Reference to 'appendTo' parameter.
    701      * @throws    IllegalArgumentException  if the two calendars are not equivalent.
    702      * @stable ICU 4.0
    703      */
    704     public final synchronized StringBuffer format(Calendar fromCalendar,
    705                                      Calendar toCalendar,
    706                                      StringBuffer appendTo,
    707                                      FieldPosition pos)
    708     {
    709         // not support different calendar types and time zones
    710         if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
    711             throw new IllegalArgumentException("can not format on two different calendars");
    712         }
    713 
    714         // First, find the largest different calendar field.
    715         int field = -1; //init with an invalid value.
    716 
    717         if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
    718             field = Calendar.ERA;
    719         } else if ( fromCalendar.get(Calendar.YEAR) !=
    720                     toCalendar.get(Calendar.YEAR) ) {
    721             field = Calendar.YEAR;
    722         } else if ( fromCalendar.get(Calendar.MONTH) !=
    723                     toCalendar.get(Calendar.MONTH) ) {
    724             field = Calendar.MONTH;
    725         } else if ( fromCalendar.get(Calendar.DATE) !=
    726                     toCalendar.get(Calendar.DATE) ) {
    727             field = Calendar.DATE;
    728         } else if ( fromCalendar.get(Calendar.AM_PM) !=
    729                     toCalendar.get(Calendar.AM_PM) ) {
    730             field = Calendar.AM_PM;
    731         } else if ( fromCalendar.get(Calendar.HOUR) !=
    732                     toCalendar.get(Calendar.HOUR) ) {
    733             field = Calendar.HOUR;
    734         } else if ( fromCalendar.get(Calendar.MINUTE) !=
    735                     toCalendar.get(Calendar.MINUTE) ) {
    736             field = Calendar.MINUTE;
    737          } else if ( fromCalendar.get(Calendar.SECOND) !=
    738                     toCalendar.get(Calendar.SECOND) ) {
    739             field = Calendar.SECOND;
    740        } else {
    741             /* ignore the millisecond etc. small fields' difference.
    742              * use single date when all the above are the same.
    743              */
    744             return fDateFormat.format(fromCalendar, appendTo, pos);
    745         }
    746         boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND);
    747 
    748         // get interval pattern
    749         PatternInfo intervalPattern = fIntervalPatterns.get(
    750               DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
    751 
    752         if ( intervalPattern == null ) {
    753             if ( fDateFormat.isFieldUnitIgnored(field) ) {
    754                 /* the largest different calendar field is small than
    755                  * the smallest calendar field in pattern,
    756                  * return single date format.
    757                  */
    758                 return fDateFormat.format(fromCalendar, appendTo, pos);
    759             }
    760 
    761             return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos);
    762         }
    763 
    764         // If the first part in interval pattern is empty,
    765         // the 2nd part of it saves the full-pattern used in fall-back.
    766         // For a 'real' interval pattern, the first part will never be empty.
    767         if ( intervalPattern.getFirstPart() == null ) {
    768             // fall back
    769             return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos,
    770                                     intervalPattern.getSecondPart());
    771         }
    772         Calendar firstCal;
    773         Calendar secondCal;
    774         if ( intervalPattern.firstDateInPtnIsLaterDate() ) {
    775             firstCal = toCalendar;
    776             secondCal = fromCalendar;
    777         } else {
    778             firstCal = fromCalendar;
    779             secondCal = toCalendar;
    780         }
    781         // break the interval pattern into 2 parts
    782         // first part should not be empty,
    783         String originalPattern = fDateFormat.toPattern();
    784         fDateFormat.applyPattern(intervalPattern.getFirstPart());
    785         fDateFormat.format(firstCal, appendTo, pos);
    786         if ( intervalPattern.getSecondPart() != null ) {
    787             fDateFormat.applyPattern(intervalPattern.getSecondPart());
    788             FieldPosition otherPos = new FieldPosition(pos.getField());
    789             fDateFormat.format(secondCal, appendTo, otherPos);
    790             if (pos.getEndIndex() == 0 && otherPos.getEndIndex() > 0) {
    791                 pos.setBeginIndex(otherPos.getBeginIndex());
    792                 pos.setEndIndex(otherPos.getEndIndex());
    793             }
    794         }
    795         fDateFormat.applyPattern(originalPattern);
    796         return appendTo;
    797     }
    798 
    799     private void adjustPosition(String combiningPattern, // has {0} and {1} in it
    800                                 String pat0, FieldPosition pos0, // pattern and pos corresponding to {0}
    801                                 String pat1, FieldPosition pos1, // pattern and pos corresponding to {1}
    802                                 FieldPosition posResult) {
    803         int index0 = combiningPattern.indexOf("{0}");
    804         int index1 = combiningPattern.indexOf("{1}");
    805         if (index0 < 0 || index1 < 0) {
    806             return;
    807         }
    808         int placeholderLen = 3; // length of "{0}" or "{1}"
    809         if (index0 < index1) {
    810             if (pos0.getEndIndex() > 0) {
    811                 posResult.setBeginIndex(pos0.getBeginIndex() + index0);
    812                 posResult.setEndIndex(pos0.getEndIndex() + index0);
    813             } else if (pos1.getEndIndex() > 0) {
    814                 // here index1 >= 3
    815                 index1 += pat0.length() - placeholderLen; // adjust for pat0 replacing {0}
    816                 posResult.setBeginIndex(pos1.getBeginIndex() + index1);
    817                 posResult.setEndIndex(pos1.getEndIndex() + index1);
    818             }
    819         } else {
    820             if (pos1.getEndIndex() > 0) {
    821                 posResult.setBeginIndex(pos1.getBeginIndex() + index1);
    822                 posResult.setEndIndex(pos1.getEndIndex() + index1);
    823             } else if (pos0.getEndIndex() > 0) {
    824                 // here index0 >= 3
    825                 index0 += pat1.length() - placeholderLen; // adjust for pat1 replacing {1}
    826                 posResult.setBeginIndex(pos0.getBeginIndex() + index0);
    827                 posResult.setEndIndex(pos0.getEndIndex() + index0);
    828             }
    829         }
    830     }
    831 
    832     /*
    833      * Format 2 Calendars to using fall-back interval pattern
    834      *
    835      * The full pattern used in this fall-back format is the
    836      * full pattern of the date formatter.
    837      *
    838      * @param fromCalendar      calendar set to the from date in date interval
    839      *                          to be formatted into date interval string
    840      * @param toCalendar        calendar set to the to date in date interval
    841      *                          to be formatted into date interval string
    842      * @param appendTo          Output parameter to receive result.
    843      *                          Result is appended to existing contents.
    844      * @param pos               On input: an alignment field, if desired.
    845      *                          On output: the offsets of the alignment field.
    846      * @return                  Reference to 'appendTo' parameter.
    847      */
    848     private final StringBuffer fallbackFormat(Calendar fromCalendar,
    849                                               Calendar toCalendar,
    850                                               boolean fromToOnSameDay,
    851                                               StringBuffer appendTo,
    852                                               FieldPosition pos)  {
    853             String fullPattern = null; // for saving the pattern in fDateFormat
    854             boolean formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern != null && fTimePattern != null);
    855             // the fall back
    856             if (formatDatePlusTimeRange) {
    857                 fullPattern = fDateFormat.toPattern(); // save current pattern, restore later
    858                 fDateFormat.applyPattern(fTimePattern);
    859             }
    860             FieldPosition otherPos = new FieldPosition(pos.getField());
    861             StringBuffer earlierDate = new StringBuffer(64);
    862             earlierDate = fDateFormat.format(fromCalendar, earlierDate, pos);
    863             StringBuffer laterDate = new StringBuffer(64);
    864             laterDate = fDateFormat.format(toCalendar, laterDate, otherPos);
    865             String fallbackPattern = fInfo.getFallbackIntervalPattern();
    866             adjustPosition(fallbackPattern, earlierDate.toString(), pos, laterDate.toString(), otherPos, pos);
    867             String fallbackRange = SimpleFormatterImpl.formatRawPattern(
    868                     fallbackPattern, 2, 2, earlierDate, laterDate);
    869             if (formatDatePlusTimeRange) {
    870                 // fallbackRange has just the time range, need to format the date part and combine that
    871                 fDateFormat.applyPattern(fDatePattern);
    872                 StringBuffer datePortion = new StringBuffer(64);
    873                 otherPos.setBeginIndex(0);
    874                 otherPos.setEndIndex(0);
    875                 datePortion = fDateFormat.format(fromCalendar, datePortion, otherPos);
    876                 adjustPosition(fDateTimeFormat, fallbackRange, pos, datePortion.toString(), otherPos, pos);
    877                 // Android patch (CLDR ticket #10321) begin.
    878                 MessageFormat msgFmt = new MessageFormat("");
    879                 msgFmt.applyPattern(fDateTimeFormat, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
    880                 StringBuffer fallbackRangeBuffer = new StringBuffer(128);
    881                 fallbackRange = msgFmt.format(new Object[] { fallbackRange, datePortion },
    882                     fallbackRangeBuffer, new FieldPosition(0)).toString();
    883                 // Android patch (CLDR ticket #10321) end.
    884             }
    885             appendTo.append(fallbackRange);
    886             if (formatDatePlusTimeRange) {
    887                 // restore full pattern
    888                 fDateFormat.applyPattern(fullPattern);
    889             }
    890             return appendTo;
    891     }
    892 
    893 
    894     /*
    895      * Format 2 Calendars to using fall-back interval pattern
    896      *
    897      * This fall-back pattern is generated on a given full pattern,
    898      * not the full pattern of the date formatter.
    899      *
    900      * @param fromCalendar      calendar set to the from date in date interval
    901      *                          to be formatted into date interval string
    902      * @param toCalendar        calendar set to the to date in date interval
    903      *                          to be formatted into date interval string
    904      * @param appendTo          Output parameter to receive result.
    905      *                          Result is appended to existing contents.
    906      * @param pos               On input: an alignment field, if desired.
    907      *                          On output: the offsets of the alignment field.
    908      * @param fullPattern       the full pattern need to apply to date formatter
    909      * @return                  Reference to 'appendTo' parameter.
    910      */
    911     private final StringBuffer fallbackFormat(Calendar fromCalendar,
    912                                               Calendar toCalendar,
    913                                               boolean fromToOnSameDay,
    914                                               StringBuffer appendTo,
    915                                               FieldPosition pos,
    916                                               String fullPattern)  {
    917             String originalPattern = fDateFormat.toPattern();
    918             fDateFormat.applyPattern(fullPattern);
    919             fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos);
    920             fDateFormat.applyPattern(originalPattern);
    921             return appendTo;
    922     }
    923 
    924 
    925     /**
    926      * Date interval parsing is not supported.
    927      * <P>
    928      * This method should handle parsing of
    929      * date time interval strings into Formattable objects with
    930      * DateInterval type, which is a pair of UDate.
    931      * <P>
    932      * Before calling, set parse_pos.index to the offset you want to start
    933      * parsing at in the source. After calling, parse_pos.index is the end of
    934      * the text you parsed. If error occurs, index is unchanged.
    935      * <P>
    936      * When parsing, leading whitespace is discarded (with a successful parse),
    937      * while trailing whitespace is left as is.
    938      * <P>
    939      * See Format.parseObject() for more.
    940      *
    941      * @param source    The string to be parsed into an object.
    942      * @param parse_pos The position to start parsing at. Since no parsing
    943      *                  is supported, upon return this param is unchanged.
    944      * @return          A newly created Formattable* object, or NULL
    945      *                  on failure.
    946      * @internal
    947      * @deprecated This API is ICU internal only.
    948      */
    949     @Deprecated
    950     public Object parseObject(String source, ParsePosition parse_pos)
    951     {
    952         throw new UnsupportedOperationException("parsing is not supported");
    953     }
    954 
    955 
    956     /**
    957      * Gets the date time interval patterns.
    958      * @return a copy of the date time interval patterns associated with
    959      * this date interval formatter.
    960      * @stable ICU 4.0
    961      */
    962     public DateIntervalInfo getDateIntervalInfo()
    963     {
    964         return (DateIntervalInfo)fInfo.clone();
    965     }
    966 
    967 
    968     /**
    969      * Set the date time interval patterns.
    970      * @param newItvPattern   the given interval patterns to copy.
    971      * @stable ICU 4.0
    972      */
    973     public void setDateIntervalInfo(DateIntervalInfo newItvPattern)
    974     {
    975         // clone it. If it is frozen, the clone returns itself.
    976         // Otherwise, clone returns a copy
    977         fInfo = (DateIntervalInfo)newItvPattern.clone();
    978         this.isDateIntervalInfoDefault = false;
    979         fInfo.freeze(); // freeze it
    980         if ( fDateFormat != null ) {
    981             initializePattern(null);
    982         }
    983     }
    984 
    985     /**
    986      * Get the TimeZone
    987      * @return A copy of the TimeZone associated with this date interval formatter.
    988      * @stable ICU 53
    989      */
    990     public TimeZone getTimeZone()
    991     {
    992         if ( fDateFormat != null ) {
    993             // Here we clone, like other getters here, but unlike
    994             // DateFormat.getTimeZone() and Calendar.getTimeZone()
    995             // which return the TimeZone from the Calendar's zone variable
    996             return (TimeZone)(fDateFormat.getTimeZone().clone());
    997         }
    998         // If fDateFormat is null (unexpected), return default timezone.
    999         return TimeZone.getDefault();
   1000     }
   1001 
   1002 
   1003     /**
   1004      * Set the TimeZone for the calendar used by this DateIntervalFormat object.
   1005      * @param zone The new TimeZone, will be cloned for use by this DateIntervalFormat.
   1006      * @stable ICU 53
   1007      */
   1008     public void setTimeZone(TimeZone zone)
   1009     {
   1010         // zone is cloned once for all three usages below:
   1011         TimeZone zoneToSet = (TimeZone)zone.clone();
   1012         if (fDateFormat != null) {
   1013             fDateFormat.setTimeZone(zoneToSet);
   1014         }
   1015         // fDateFormat has the master calendar for the DateIntervalFormat;
   1016         // fFromCalendar and fToCalendar are internal work clones of that calendar.
   1017         if (fFromCalendar != null) {
   1018             fFromCalendar.setTimeZone(zoneToSet);
   1019         }
   1020         if (fToCalendar != null) {
   1021             fToCalendar.setTimeZone(zoneToSet);
   1022         }
   1023     }
   1024 
   1025     /**
   1026      * Gets the date formatter
   1027      * @return a copy of the date formatter associated with
   1028      * this date interval formatter.
   1029      * @stable ICU 4.0
   1030      */
   1031     public synchronized DateFormat getDateFormat()
   1032     {
   1033         return (DateFormat)fDateFormat.clone();
   1034     }
   1035 
   1036 
   1037     /*
   1038      *  Below are for generating interval patterns locale to the formatter
   1039      */
   1040 
   1041     /*
   1042      * Initialize interval patterns locale to this formatter.
   1043      */
   1044     private void initializePattern(ICUCache<String, Map<String, PatternInfo>> cache) {
   1045         String fullPattern = fDateFormat.toPattern();
   1046         ULocale locale = fDateFormat.getLocale();
   1047         String key = null;
   1048         Map<String, PatternInfo> patterns = null;
   1049         if (cache != null) {
   1050             if ( fSkeleton != null ) {
   1051                 key = locale.toString() + "+" + fullPattern + "+" + fSkeleton;
   1052             } else {
   1053                 key = locale.toString() + "+" + fullPattern;
   1054             }
   1055             patterns = cache.get(key);
   1056         }
   1057         if (patterns == null) {
   1058             Map<String, PatternInfo> intervalPatterns = initializeIntervalPattern(fullPattern, locale);
   1059             patterns = Collections.unmodifiableMap(intervalPatterns);
   1060             if (cache != null) {
   1061                 cache.put(key, patterns);
   1062             }
   1063         }
   1064         fIntervalPatterns = patterns;
   1065     }
   1066 
   1067 
   1068 
   1069     /*
   1070      * Initialize interval patterns locale to this formatter
   1071      *
   1072      * This code is a bit complicated since
   1073      * 1. the interval patterns saved in resource bundle files are interval
   1074      *    patterns based on date or time only.
   1075      *    It does not have interval patterns based on both date and time.
   1076      *    Interval patterns on both date and time are algorithm generated.
   1077      *
   1078      *    For example, it has interval patterns on skeleton "dMy" and "hm",
   1079      *    but it does not have interval patterns on skeleton "dMyhm".
   1080      *
   1081      *    The rule to generate interval patterns for both date and time skeleton are
   1082      *    1) when the year, month, or day differs, concatenate the two original
   1083      *    expressions with a separator between,
   1084      *    For example, interval pattern from "Jan 10, 2007 10:10 am"
   1085      *    to "Jan 11, 2007 10:10am" is
   1086      *    "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
   1087      *
   1088      *    2) otherwise, present the date followed by the range expression
   1089      *    for the time.
   1090      *    For example, interval pattern from "Jan 10, 2007 10:10 am"
   1091      *    to "Jan 10, 2007 11:10am" is
   1092      *    "Jan 10, 2007 10:10 am - 11:10am"
   1093      *
   1094      * 2. even a pattern does not request a certain calendar field,
   1095      *    the interval pattern needs to include such field if such fields are
   1096      *    different between 2 dates.
   1097      *    For example, a pattern/skeleton is "hm", but the interval pattern
   1098      *    includes year, month, and date when year, month, and date differs.
   1099      *
   1100      *
   1101      * @param fullPattern  formatter's full pattern
   1102      * @param locale       the given locale.
   1103      * @return             interval patterns' hash map
   1104      */
   1105     private Map<String, PatternInfo> initializeIntervalPattern(String fullPattern, ULocale locale) {
   1106         DateTimePatternGenerator dtpng = DateTimePatternGenerator.getInstance(locale);
   1107         if ( fSkeleton == null ) {
   1108             // fSkeleton is already set by getDateIntervalInstance()
   1109             // or by getInstance(String skeleton, .... )
   1110             fSkeleton = dtpng.getSkeleton(fullPattern);
   1111         }
   1112         String skeleton = fSkeleton;
   1113 
   1114         HashMap<String, PatternInfo> intervalPatterns = new HashMap<String, PatternInfo>();
   1115 
   1116         /* Check whether the skeleton is a combination of date and time.
   1117          * For the complication reason 1 explained above.
   1118          */
   1119         StringBuilder date = new StringBuilder(skeleton.length());
   1120         StringBuilder normalizedDate = new StringBuilder(skeleton.length());
   1121         StringBuilder time = new StringBuilder(skeleton.length());
   1122         StringBuilder normalizedTime = new StringBuilder(skeleton.length());
   1123 
   1124         /* the difference between time skeleton and normalizedTimeSkeleton are:
   1125          * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
   1126          * 2. 'a' is omitted in normalized time skeleton.
   1127          * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized
   1128          *    time skeleton
   1129          *
   1130          * The difference between date skeleton and normalizedDateSkeleton are:
   1131          * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
   1132          * 2. 'E' and 'EE' are normalized into 'EEE'
   1133          * 3. 'MM' is normalized into 'M'
   1134          */
   1135         getDateTimeSkeleton(skeleton, date, normalizedDate,
   1136                             time, normalizedTime);
   1137 
   1138         String dateSkeleton = date.toString();
   1139         String timeSkeleton = time.toString();
   1140         String normalizedDateSkeleton = normalizedDate.toString();
   1141         String normalizedTimeSkeleton = normalizedTime.toString();
   1142 
   1143         // move this up here since we need it for fallbacks
   1144         if (time.length() != 0 && date.length() != 0) {
   1145             // Need the Date/Time pattern for concatenating the date with
   1146             // the time interval.
   1147             // The date/time pattern ( such as {0} {1} ) is saved in
   1148             // calendar, that is why need to get the CalendarData here.
   1149             fDateTimeFormat = getConcatenationPattern(locale);
   1150         }
   1151 
   1152         boolean found = genSeparateDateTimePtn(normalizedDateSkeleton,
   1153                                                normalizedTimeSkeleton,
   1154                                                intervalPatterns, dtpng);
   1155 
   1156         // for skeletons with seconds, found is false and we enter this block
   1157         if ( found == false ) {
   1158             // use fallback
   1159             // TODO: if user asks "m", but "d" differ
   1160             //StringBuffer skeleton = new StringBuffer(skeleton);
   1161             if ( time.length() != 0 ) {
   1162                 //genFallbackForNotFound(Calendar.MINUTE, skeleton);
   1163                 //genFallbackForNotFound(Calendar.HOUR, skeleton);
   1164                 //genFallbackForNotFound(Calendar.AM_PM, skeleton);
   1165                 if ( date.length() == 0 ) {
   1166                     // prefix with yMd
   1167                     timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
   1168                     String pattern =dtpng.getBestPattern(timeSkeleton);
   1169                     // for fall back interval patterns,
   1170                     // the first part of the pattern is empty,
   1171                     // the second part of the pattern is the full-pattern
   1172                     // should be used in fall-back.
   1173                     PatternInfo ptn = new PatternInfo(null, pattern,
   1174                                                      fInfo.getDefaultOrder());
   1175                     intervalPatterns.put(DateIntervalInfo.
   1176                         CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
   1177                     // share interval pattern
   1178                     intervalPatterns.put(DateIntervalInfo.
   1179                         CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
   1180                     // share interval pattern
   1181                     intervalPatterns.put(DateIntervalInfo.
   1182                         CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
   1183                 } else {
   1184                     //genFallbackForNotFound(Calendar.DATE, skeleton);
   1185                     //genFallbackForNotFound(Calendar.MONTH, skeleton);
   1186                     //genFallbackForNotFound(Calendar.YEAR, skeleton);
   1187                 }
   1188             } else {
   1189                     //genFallbackForNotFound(Calendar.DATE, skeleton);
   1190                     //genFallbackForNotFound(Calendar.MONTH, skeleton);
   1191                     //genFallbackForNotFound(Calendar.YEAR, skeleton);
   1192             }
   1193             return intervalPatterns;
   1194         } // end of skeleton not found
   1195         // interval patterns for skeleton are found in resource
   1196         if ( time.length() == 0 ) {
   1197             // done
   1198         } else if ( date.length() == 0 ) {
   1199             // need to set up patterns for y/M/d differ
   1200             /* result from following looks confusing.
   1201              * for example: 10 10:10 - 11 10:10, it is not
   1202              * clear that the first 10 is the 10th day
   1203             time.insert(0, 'd');
   1204             genFallbackPattern(Calendar.DATE, time);
   1205             time.insert(0, 'M');
   1206             genFallbackPattern(Calendar.MONTH, time);
   1207             time.insert(0, 'y');
   1208             genFallbackPattern(Calendar.YEAR, time);
   1209             */
   1210             // prefix with yMd
   1211             timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
   1212             String pattern =dtpng.getBestPattern(timeSkeleton);
   1213             // for fall back interval patterns,
   1214             // the first part of the pattern is empty,
   1215             // the second part of the pattern is the full-pattern
   1216             // should be used in fall-back.
   1217             PatternInfo ptn = new PatternInfo(
   1218                                     null, pattern, fInfo.getDefaultOrder());
   1219             intervalPatterns.put(DateIntervalInfo.
   1220                 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
   1221             intervalPatterns.put(DateIntervalInfo.
   1222                 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
   1223             intervalPatterns.put(DateIntervalInfo.
   1224                 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
   1225         } else {
   1226             /* if both present,
   1227              * 1) when the year, month, or day differs,
   1228              * concatenate the two original expressions with a separator between,
   1229              * 2) otherwise, present the date followed by the
   1230              * range expression for the time.
   1231              */
   1232             /*
   1233              * 1) when the year, month, or day differs,
   1234              * concatenate the two original expressions with a separator between,
   1235              */
   1236             // if field exists, use fall back
   1237             if ( !fieldExistsInSkeleton(Calendar.DATE, dateSkeleton) ) {
   1238                 // prefix skeleton with 'd'
   1239                 skeleton = DateIntervalInfo.
   1240                     CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE] + skeleton;
   1241                 genFallbackPattern(Calendar.DATE, skeleton, intervalPatterns, dtpng);
   1242             }
   1243             if ( !fieldExistsInSkeleton(Calendar.MONTH, dateSkeleton) ) {
   1244                 // then prefix skeleton with 'M'
   1245                 skeleton = DateIntervalInfo.
   1246                     CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH] + skeleton;
   1247                 genFallbackPattern(Calendar.MONTH, skeleton, intervalPatterns, dtpng);
   1248             }
   1249             if ( !fieldExistsInSkeleton(Calendar.YEAR, dateSkeleton) ) {
   1250                 // then prefix skeleton with 'y'
   1251                 skeleton = DateIntervalInfo.
   1252                     CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR] + skeleton;
   1253                 genFallbackPattern(Calendar.YEAR, skeleton, intervalPatterns, dtpng);
   1254             }
   1255 
   1256             /*
   1257              * 2) otherwise, present the date followed by the
   1258              * range expression for the time.
   1259              */
   1260             if (fDateTimeFormat == null) {
   1261                 fDateTimeFormat = "{1} {0}";
   1262             }
   1263             String datePattern =dtpng.getBestPattern(dateSkeleton);
   1264             concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.AM_PM, intervalPatterns);
   1265             concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.HOUR, intervalPatterns);
   1266             concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.MINUTE, intervalPatterns);
   1267         }
   1268 
   1269         return intervalPatterns;
   1270     }
   1271 
   1272     /**
   1273      * Retrieves the concatenation DateTime pattern from the resource bundle.
   1274      * @param locale Locale to retrieve.
   1275      * @return Concatenation DateTime pattern.
   1276      */
   1277     private String getConcatenationPattern(ULocale locale) {
   1278         ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
   1279         ICUResourceBundle dtPatternsRb = rb.getWithFallback("calendar/gregorian/DateTimePatterns");
   1280         ICUResourceBundle concatenationPatternRb = (ICUResourceBundle) dtPatternsRb.get(8);
   1281         if (concatenationPatternRb.getType() == UResourceBundle.STRING) {
   1282             return concatenationPatternRb.getString();
   1283         } else {
   1284             return concatenationPatternRb.getString(0);
   1285         }
   1286     }
   1287 
   1288     /*
   1289      * Generate fall back interval pattern given a calendar field,
   1290      * a skeleton, and a date time pattern generator
   1291      * @param field      the largest different calendar field
   1292      * @param skeleton   a skeleton
   1293      * @param dtpng      date time pattern generator
   1294      * @param intervalPatterns interval patterns
   1295      */
   1296     private void genFallbackPattern(int field, String skeleton,
   1297                                     Map<String, PatternInfo> intervalPatterns,
   1298                                     DateTimePatternGenerator dtpng) {
   1299         String pattern = dtpng.getBestPattern(skeleton);
   1300         // for fall back interval patterns,
   1301         // the first part of the pattern is empty,
   1302         // the second part of the pattern is the full-pattern
   1303         // should be used in fall-back.
   1304         PatternInfo ptn = new PatternInfo(
   1305                                     null, pattern, fInfo.getDefaultOrder());
   1306         intervalPatterns.put(
   1307             DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptn);
   1308     }
   1309 
   1310 
   1311 
   1312     /*
   1313     private void genFallbackForNotFound(String field, StringBuffer skeleton) {
   1314         if ( SimpleDateFormat.isFieldUnitIgnored(skeleton.toString(), field) ) {
   1315             // single date
   1316             DateIntervalInfo.PatternInfo ptnInfo =
   1317                 new DateIntervalInfo.PatternInfo(null, fDateFormat.toPattern(),
   1318                                                  fInfo.getDefaultOrder());
   1319             fIntervalPatterns.put(field, ptnInfo);
   1320             return;
   1321         } else if ( skeleton.indexOf(field) == -1 ) {
   1322             skeleton.insert(0,field);
   1323             genFallbackPattern(field, skeleton, dtpng);
   1324         }
   1325     }
   1326     */
   1327 
   1328     /*
   1329      * get separated date and time skeleton from a combined skeleton.
   1330      *
   1331      * The difference between date skeleton and normalizedDateSkeleton are:
   1332      * 1. both 'y' and 'd' are appeared only once in normalizeDateSkeleton
   1333      * 2. 'E' and 'EE' are normalized into 'EEE'
   1334      * 3. 'MM' is normalized into 'M'
   1335      *
   1336      ** the difference between time skeleton and normalizedTimeSkeleton are:
   1337      * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton,
   1338      * 2. 'a' is omitted in normalized time skeleton.
   1339      * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized time
   1340      *    skeleton
   1341      *
   1342      *
   1343      *  @param skeleton               given combined skeleton.
   1344      *  @param date                   Output parameter for date only skeleton.
   1345      *  @param normalizedDate         Output parameter for normalized date only
   1346      *
   1347      *  @param time                   Output parameter for time only skeleton.
   1348      *  @param normalizedTime         Output parameter for normalized time only
   1349      *                                skeleton.
   1350      */
   1351     private static void getDateTimeSkeleton(String skeleton,
   1352                                             StringBuilder dateSkeleton,
   1353                                             StringBuilder normalizedDateSkeleton,
   1354                                             StringBuilder timeSkeleton,
   1355                                             StringBuilder normalizedTimeSkeleton)
   1356     {
   1357         // dateSkeleton follows the sequence of y*M*E*d*
   1358         // timeSkeleton follows the sequence of hm*[v|z]?
   1359         int i;
   1360         int ECount = 0;
   1361         int dCount = 0;
   1362         int MCount = 0;
   1363         int yCount = 0;
   1364         int hCount = 0;
   1365         int HCount = 0;
   1366         int mCount = 0;
   1367         int vCount = 0;
   1368         int zCount = 0;
   1369 
   1370         for (i = 0; i < skeleton.length(); ++i) {
   1371             char ch = skeleton.charAt(i);
   1372             switch ( ch ) {
   1373               case 'E':
   1374                 dateSkeleton.append(ch);
   1375                 ++ECount;
   1376                 break;
   1377               case 'd':
   1378                 dateSkeleton.append(ch);
   1379                 ++dCount;
   1380                 break;
   1381               case 'M':
   1382                 dateSkeleton.append(ch);
   1383                 ++MCount;
   1384                 break;
   1385               case 'y':
   1386                 dateSkeleton.append(ch);
   1387                 ++yCount;
   1388                 break;
   1389               case 'G':
   1390               case 'Y':
   1391               case 'u':
   1392               case 'Q':
   1393               case 'q':
   1394               case 'L':
   1395               case 'l':
   1396               case 'W':
   1397               case 'w':
   1398               case 'D':
   1399               case 'F':
   1400               case 'g':
   1401               case 'e':
   1402               case 'c':
   1403               case 'U':
   1404               case 'r':
   1405                 normalizedDateSkeleton.append(ch);
   1406                 dateSkeleton.append(ch);
   1407                 break;
   1408               case 'a':
   1409                 // 'a' is implicitly handled
   1410                 timeSkeleton.append(ch);
   1411                 break;
   1412               case 'h':
   1413                 timeSkeleton.append(ch);
   1414                 ++hCount;
   1415                 break;
   1416               case 'H':
   1417                 timeSkeleton.append(ch);
   1418                 ++HCount;
   1419                 break;
   1420               case 'm':
   1421                 timeSkeleton.append(ch);
   1422                 ++mCount;
   1423                 break;
   1424               case 'z':
   1425                 ++zCount;
   1426                 timeSkeleton.append(ch);
   1427                 break;
   1428               case 'v':
   1429                 ++vCount;
   1430                 timeSkeleton.append(ch);
   1431                 break;
   1432               case 'V':
   1433               case 'Z':
   1434               case 'k':
   1435               case 'K':
   1436               case 'j':
   1437               case 's':
   1438               case 'S':
   1439               case 'A':
   1440                 timeSkeleton.append(ch);
   1441                 normalizedTimeSkeleton.append(ch);
   1442                 break;
   1443             }
   1444         }
   1445 
   1446         /* generate normalized form for date*/
   1447         if ( yCount != 0 ) {
   1448             for (i = 0; i < yCount; i++) {
   1449                 normalizedDateSkeleton.append('y');
   1450             }
   1451         }
   1452         if ( MCount != 0 ) {
   1453             if ( MCount < 3 ) {
   1454                 normalizedDateSkeleton.append('M');
   1455             } else {
   1456                 for ( i = 0; i < MCount && i < 5; ++i ) {
   1457                      normalizedDateSkeleton.append('M');
   1458                 }
   1459             }
   1460         }
   1461         if ( ECount != 0 ) {
   1462             if ( ECount <= 3 ) {
   1463                 normalizedDateSkeleton.append('E');
   1464             } else {
   1465                 for ( i = 0; i < ECount && i < 5; ++i ) {
   1466                      normalizedDateSkeleton.append('E');
   1467                 }
   1468             }
   1469         }
   1470         if ( dCount != 0 ) {
   1471             normalizedDateSkeleton.append('d');
   1472         }
   1473 
   1474         /* generate normalized form for time */
   1475         if ( HCount != 0 ) {
   1476             normalizedTimeSkeleton.append('H');
   1477         }
   1478         else if ( hCount != 0 ) {
   1479             normalizedTimeSkeleton.append('h');
   1480         }
   1481         if ( mCount != 0 ) {
   1482             normalizedTimeSkeleton.append('m');
   1483         }
   1484         if ( zCount != 0 ) {
   1485             normalizedTimeSkeleton.append('z');
   1486         }
   1487         if ( vCount != 0 ) {
   1488             normalizedTimeSkeleton.append('v');
   1489         }
   1490     }
   1491 
   1492 
   1493 
   1494     /*
   1495      * Generate date or time interval pattern from resource.
   1496      *
   1497      * It needs to handle the following:
   1498      * 1. need to adjust field width.
   1499      *    For example, the interval patterns saved in DateIntervalInfo
   1500      *    includes "dMMMy", but not "dMMMMy".
   1501      *    Need to get interval patterns for dMMMMy from dMMMy.
   1502      *    Another example, the interval patterns saved in DateIntervalInfo
   1503      *    includes "hmv", but not "hmz".
   1504      *    Need to get interval patterns for "hmz' from 'hmv'
   1505      *
   1506      * 2. there might be no pattern for 'y' differ for skeleton "Md",
   1507      *    in order to get interval patterns for 'y' differ,
   1508      *    need to look for it from skeleton 'yMd'
   1509      *
   1510      * @param dateSkeleton   normalized date skeleton
   1511      * @param timeSkeleton   normalized time skeleton
   1512      * @param intervalPatterns interval patterns
   1513      * @return whether there is interval patterns for the skeleton.
   1514      *         true if there is, false otherwise
   1515      */
   1516     private boolean genSeparateDateTimePtn(String dateSkeleton,
   1517                                            String timeSkeleton,
   1518                                            Map<String, PatternInfo> intervalPatterns,
   1519                                            DateTimePatternGenerator dtpng)
   1520     {
   1521         String skeleton;
   1522         // if both date and time skeleton present,
   1523         // the final interval pattern might include time interval patterns
   1524         // ( when, am_pm, hour, minute, second differ ),
   1525         // but not date interval patterns ( when year, month, day differ ).
   1526         // For year/month/day differ, it falls back to fall-back pattern.
   1527         if ( timeSkeleton.length() != 0  ) {
   1528             skeleton = timeSkeleton;
   1529         } else {
   1530             skeleton = dateSkeleton;
   1531         }
   1532 
   1533         /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
   1534          * are defined in resource,
   1535          * interval patterns for skeleton "dMMMMy" are calculated by
   1536          * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
   1537          * 2. get the interval patterns for "dMMMy",
   1538          * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
   1539          * getBestSkeleton() is step 1.
   1540          */
   1541         // best skeleton, and the difference information
   1542         BestMatchInfo retValue = fInfo.getBestSkeleton(skeleton);
   1543         String bestSkeleton = retValue.bestMatchSkeleton;
   1544         int differenceInfo =  retValue.bestMatchDistanceInfo;
   1545 
   1546         // Set patterns for fallback use, need to do this
   1547         // before returning if differenceInfo == -1
   1548         if (dateSkeleton.length() != 0  ) {
   1549             fDatePattern = dtpng.getBestPattern(dateSkeleton);
   1550         }
   1551         if (timeSkeleton.length() != 0  ) {
   1552             fTimePattern = dtpng.getBestPattern(timeSkeleton);
   1553         }
   1554 
   1555         // difference:
   1556         // 0 means the best matched skeleton is the same as input skeleton
   1557         // 1 means the fields are the same, but field width are different
   1558         // 2 means the only difference between fields are v/z,
   1559         // -1 means there are other fields difference
   1560         // (this will happen, for instance, if the supplied skeleton has seconds,
   1561         //  but no skeletons in the intervalFormats data do)
   1562         if ( differenceInfo == -1 ) {
   1563             // skeleton has different fields, not only  v/z difference
   1564             return false;
   1565         }
   1566 
   1567         if ( timeSkeleton.length() == 0 ) {
   1568             // only has date skeleton
   1569             genIntervalPattern(Calendar.DATE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
   1570             SkeletonAndItsBestMatch skeletons = genIntervalPattern(
   1571                                                   Calendar.MONTH, skeleton,
   1572                                                   bestSkeleton, differenceInfo,
   1573                                                   intervalPatterns);
   1574             if ( skeletons != null ) {
   1575                 bestSkeleton = skeletons.skeleton;
   1576                 skeleton = skeletons.bestMatchSkeleton;
   1577             }
   1578             genIntervalPattern(Calendar.YEAR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
   1579         } else {
   1580             genIntervalPattern(Calendar.MINUTE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
   1581             genIntervalPattern(Calendar.HOUR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
   1582             genIntervalPattern(Calendar.AM_PM, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
   1583         }
   1584         return true;
   1585 
   1586     }
   1587 
   1588 
   1589 
   1590     /*
   1591      * Generate interval pattern from existing resource
   1592      *
   1593      * It not only save the interval patterns,
   1594      * but also return the skeleton and its best match skeleton.
   1595      *
   1596      * @param field           largest different calendar field
   1597      * @param skeleton        skeleton
   1598      * @param bestSkeleton    the best match skeleton which has interval pattern
   1599      *                        defined in resource
   1600      * @param differenceInfo  the difference between skeleton and best skeleton
   1601      *         0 means the best matched skeleton is the same as input skeleton
   1602      *         1 means the fields are the same, but field width are different
   1603      *         2 means the only difference between fields are v/z,
   1604      *        -1 means there are other fields difference
   1605      *
   1606      * @param intervalPatterns interval patterns
   1607      *
   1608      * @return  an extended skeleton or extended best skeleton if applicable.
   1609      *          null otherwise.
   1610      */
   1611     private SkeletonAndItsBestMatch genIntervalPattern(
   1612                    int field, String skeleton, String bestSkeleton,
   1613                    int differenceInfo, Map<String, PatternInfo> intervalPatterns) {
   1614         SkeletonAndItsBestMatch retValue = null;
   1615         PatternInfo pattern = fInfo.getIntervalPattern(
   1616                                            bestSkeleton, field);
   1617         if ( pattern == null ) {
   1618             // single date
   1619             if ( SimpleDateFormat.isFieldUnitIgnored(bestSkeleton, field) ) {
   1620                 PatternInfo ptnInfo =
   1621                     new PatternInfo(fDateFormat.toPattern(),
   1622                                                      null,
   1623                                                      fInfo.getDefaultOrder());
   1624                 intervalPatterns.put(DateIntervalInfo.
   1625                     CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptnInfo);
   1626                 return null;
   1627             }
   1628 
   1629             // for 24 hour system, interval patterns in resource file
   1630             // might not include pattern when am_pm differ,
   1631             // which should be the same as hour differ.
   1632             // add it here for simplicity
   1633             if ( field == Calendar.AM_PM ) {
   1634                  pattern = fInfo.getIntervalPattern(bestSkeleton,
   1635                                                          Calendar.HOUR);
   1636                  if ( pattern != null ) {
   1637                       // share
   1638                       intervalPatterns.put(DateIntervalInfo.
   1639                           CALENDAR_FIELD_TO_PATTERN_LETTER[field],
   1640                           pattern);
   1641                  }
   1642                  return null;
   1643             }
   1644             // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
   1645             // first, get best match pattern "MMMd",
   1646             // since there is no pattern for 'y' differs for skeleton 'MMMd',
   1647             // need to look for it from skeleton 'yMMMd',
   1648             // if found, adjust field width in interval pattern from
   1649             // "MMM" to "MMMM".
   1650             String fieldLetter =
   1651                 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
   1652             bestSkeleton = fieldLetter + bestSkeleton;
   1653             skeleton = fieldLetter + skeleton;
   1654             // for example, looking for patterns when 'y' differ for
   1655             // skeleton "MMMM".
   1656             pattern = fInfo.getIntervalPattern(bestSkeleton, field);
   1657             if ( pattern == null && differenceInfo == 0 ) {
   1658                 // if there is no skeleton "yMMMM" defined,
   1659                 // look for the best match skeleton, for example: "yMMM"
   1660                 BestMatchInfo tmpRetValue = fInfo.getBestSkeleton(skeleton);
   1661                 String tmpBestSkeleton = tmpRetValue.bestMatchSkeleton;
   1662                 differenceInfo =  tmpRetValue.bestMatchDistanceInfo;
   1663                 if ( tmpBestSkeleton.length() != 0 && differenceInfo != -1 ) {
   1664                     pattern = fInfo.getIntervalPattern(tmpBestSkeleton, field);
   1665                     bestSkeleton = tmpBestSkeleton;
   1666                 }
   1667             }
   1668             if ( pattern != null ) {
   1669                 retValue = new SkeletonAndItsBestMatch(skeleton, bestSkeleton);
   1670             }
   1671         }
   1672         if ( pattern != null ) {
   1673             if ( differenceInfo != 0 ) {
   1674                 String part1 = adjustFieldWidth(skeleton, bestSkeleton,
   1675                                    pattern.getFirstPart(), differenceInfo);
   1676                 String part2 = adjustFieldWidth(skeleton, bestSkeleton,
   1677                                    pattern.getSecondPart(), differenceInfo);
   1678                 pattern =  new PatternInfo(part1, part2,
   1679                                            pattern.firstDateInPtnIsLaterDate());
   1680             } else {
   1681                 // pattern is immutable, no need to clone;
   1682                 // pattern = (PatternInfo)pattern.clone();
   1683             }
   1684             intervalPatterns.put(
   1685               DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], pattern);
   1686         }
   1687         return retValue;
   1688     }
   1689 
   1690     /*
   1691      * Adjust field width in best match interval pattern to match
   1692      * the field width in input skeleton.
   1693      *
   1694      * TODO (xji) make a general solution
   1695      * The adjusting rule can be:
   1696      * 1. always adjust
   1697      * 2. never adjust
   1698      * 3. default adjust, which means adjust according to the following rules
   1699      * 3.1 always adjust string, such as MMM and MMMM
   1700      * 3.2 never adjust between string and numeric, such as MM and MMM
   1701      * 3.3 always adjust year
   1702      * 3.4 do not adjust 'd', 'h', or 'm' if h presents
   1703      * 3.5 do not adjust 'M' if it is numeric(?)
   1704      *
   1705      * Since date interval format is well-formed format,
   1706      * date and time skeletons are normalized previously,
   1707      * till this stage, the adjust here is only "adjust strings, such as MMM
   1708      * and MMMM, EEE and EEEE.
   1709      *
   1710      * @param inputSkeleton            the input skeleton
   1711      * @param bestMatchSkeleton        the best match skeleton
   1712      * @param bestMatchIntervalpattern the best match interval pattern
   1713      * @param differenceInfo           the difference between 2 skeletons
   1714      *                                 1 means only field width differs
   1715      *                                 2 means v/z exchange
   1716      * @return the adjusted interval pattern
   1717      */
   1718     private static String adjustFieldWidth(String inputSkeleton,
   1719                                     String bestMatchSkeleton,
   1720                                     String bestMatchIntervalPattern,
   1721                                     int differenceInfo ) {
   1722 
   1723         if ( bestMatchIntervalPattern == null ) {
   1724             return null; // the 2nd part could be null
   1725         }
   1726         int[] inputSkeletonFieldWidth = new int[58];
   1727         int[] bestMatchSkeletonFieldWidth = new int[58];
   1728 
   1729         /* initialize as following
   1730         {
   1731         //       A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
   1732             0, 0, 0, 0, 0,  0, 0,  0,  0, 0, 0, 0, 0,  0, 0, 0,
   1733         //   P   Q   R   S   T   U   V   W   X   Y   Z
   1734             0, 0, 0, 0, 0,  0, 0,  0,  0, 0, 0, 0, 0,  0, 0, 0,
   1735         //       a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
   1736             0, 0, 0, 0, 0,  0, 0,  0,  0, 0, 0, 0, 0,  0, 0, 0,
   1737         //   p   q   r   s   t   u   v   w   x   y   z
   1738             0, 0, 0, 0, 0,  0, 0,  0,  0, 0, 0, 0, 0,  0, 0, 0,
   1739         };
   1740         */
   1741 
   1742 
   1743         DateIntervalInfo.parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
   1744         DateIntervalInfo.parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth);
   1745         if ( differenceInfo == 2 ) {
   1746             bestMatchIntervalPattern = bestMatchIntervalPattern.replace('v', 'z');
   1747         }
   1748 
   1749         StringBuilder adjustedPtn = new StringBuilder(bestMatchIntervalPattern);
   1750 
   1751         boolean inQuote = false;
   1752         char prevCh = 0;
   1753         int count = 0;
   1754 
   1755         int PATTERN_CHAR_BASE = 0x41;
   1756 
   1757         // loop through the pattern string character by character
   1758         int adjustedPtnLength = adjustedPtn.length();
   1759         for (int i = 0; i < adjustedPtnLength; ++i) {
   1760             char ch = adjustedPtn.charAt(i);
   1761             if (ch != prevCh && count > 0) {
   1762                 // check the repeativeness of pattern letter
   1763                 char skeletonChar = prevCh;
   1764                 if ( skeletonChar == 'L' ) {
   1765                     // for skeleton "M+", the pattern is "...L..."
   1766                     skeletonChar = 'M';
   1767                 }
   1768                 int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
   1769                 int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
   1770                 if ( fieldCount == count && inputFieldCount > fieldCount ) {
   1771                     count = inputFieldCount - fieldCount;
   1772                     for ( int j = 0; j < count; ++j ) {
   1773                         adjustedPtn.insert(i, prevCh);
   1774                     }
   1775                     i += count;
   1776                     adjustedPtnLength += count;
   1777                 }
   1778                 count = 0;
   1779             }
   1780             if (ch == '\'') {
   1781                 // Consecutive single quotes are a single quote literal,
   1782                 // either outside of quotes or between quotes
   1783                 if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == '\'') {
   1784                     ++i;
   1785                 } else {
   1786                     inQuote = ! inQuote;
   1787                 }
   1788             }
   1789             else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
   1790                         || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
   1791                 // ch is a date-time pattern character
   1792                 prevCh = ch;
   1793                 ++count;
   1794             }
   1795         }
   1796         if ( count > 0 ) {
   1797             // last item
   1798             // check the repeativeness of pattern letter
   1799             char skeletonChar = prevCh;
   1800             if ( skeletonChar == 'L' ) {
   1801                 // for skeleton "M+", the pattern is "...L..."
   1802                 skeletonChar = 'M';
   1803             }
   1804             int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
   1805             int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
   1806             if ( fieldCount == count && inputFieldCount > fieldCount ) {
   1807                 count = inputFieldCount - fieldCount;
   1808                 for ( int j = 0; j < count; ++j ) {
   1809                     adjustedPtn.append(prevCh);
   1810                 }
   1811             }
   1812         }
   1813         return adjustedPtn.toString();
   1814     }
   1815 
   1816 
   1817     /*
   1818      * Concat a single date pattern with a time interval pattern,
   1819      * set it into the intervalPatterns, while field is time field.
   1820      * This is used to handle time interval patterns on skeleton with
   1821      * both time and date. Present the date followed by
   1822      * the range expression for the time.
   1823      * @param dtfmt                  date and time format
   1824      * @param datePattern            date pattern
   1825      * @param field                  time calendar field: AM_PM, HOUR, MINUTE
   1826      * @param intervalPatterns       interval patterns
   1827      */
   1828     private void concatSingleDate2TimeInterval(String dtfmt,
   1829                                                String datePattern,
   1830                                                int field,
   1831                                                Map<String, PatternInfo> intervalPatterns)
   1832     {
   1833 
   1834         PatternInfo  timeItvPtnInfo =
   1835             intervalPatterns.get(DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
   1836         if ( timeItvPtnInfo != null ) {
   1837             String timeIntervalPattern = timeItvPtnInfo.getFirstPart() +
   1838                                          timeItvPtnInfo.getSecondPart();
   1839             String pattern = SimpleFormatterImpl.formatRawPattern(
   1840                     dtfmt, 2, 2, timeIntervalPattern, datePattern);
   1841             timeItvPtnInfo = DateIntervalInfo.genPatternInfo(pattern,
   1842                                 timeItvPtnInfo.firstDateInPtnIsLaterDate());
   1843             intervalPatterns.put(
   1844               DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], timeItvPtnInfo);
   1845         }
   1846         // else: fall back
   1847         // it should not happen if the interval format defined is valid
   1848     }
   1849 
   1850 
   1851     /*
   1852      * check whether a calendar field present in a skeleton.
   1853      * @param field      calendar field need to check
   1854      * @param skeleton   given skeleton on which to check the calendar field
   1855      * @return           true if field present in a skeleton.
   1856      */
   1857     private static boolean fieldExistsInSkeleton(int field, String skeleton)
   1858     {
   1859         String fieldChar = DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
   1860         return ( (skeleton.indexOf(fieldChar) == -1) ? false : true ) ;
   1861     }
   1862 
   1863 
   1864     /*
   1865      * readObject.
   1866      */
   1867     private void readObject(ObjectInputStream stream)
   1868         throws IOException, ClassNotFoundException {
   1869         stream.defaultReadObject();
   1870         initializePattern(isDateIntervalInfoDefault ? LOCAL_PATTERN_CACHE : null);
   1871     }
   1872 
   1873     /**
   1874      * Get the internal patterns for the skeleton
   1875      * @internal CLDR
   1876      * @deprecated This API is ICU internal only.
   1877      */
   1878     @Deprecated
   1879     public Map<String, PatternInfo> getRawPatterns() {
   1880         // this is unmodifiable, so ok to return directly
   1881         return fIntervalPatterns;
   1882     }
   1883 }
   1884