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