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