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