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