]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/DateIntervalInfo.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / DateIntervalInfo.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 2008-2009, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 \r
8 package com.ibm.icu.text;\r
9 \r
10 import java.io.Serializable;\r
11 \r
12 import java.util.MissingResourceException;\r
13 import java.util.Iterator;\r
14 import java.util.HashMap;\r
15 import java.util.HashSet;\r
16 \r
17 import com.ibm.icu.impl.ICUResourceBundle;\r
18 import com.ibm.icu.impl.ICUCache;\r
19 import com.ibm.icu.impl.SimpleCache;\r
20 import com.ibm.icu.impl.Utility;\r
21 import com.ibm.icu.util.Calendar;\r
22 import com.ibm.icu.util.ULocale;\r
23 import com.ibm.icu.util.Freezable;\r
24 import com.ibm.icu.util.UResourceBundle;\r
25 \r
26 \r
27 /**\r
28  * DateIntervalInfo is a public class for encapsulating localizable\r
29  * date time interval patterns. It is used by DateIntervalFormat.\r
30  *\r
31  * <P>\r
32  * For most users, ordinary use of DateIntervalFormat does not need to create\r
33  * DateIntervalInfo object directly.\r
34  * DateIntervalFormat will take care of it when creating a date interval\r
35  * formatter when user pass in skeleton and locale.\r
36  *\r
37  * <P>\r
38  * For power users, who want to create their own date interval patterns,\r
39  * or want to re-set date interval patterns, they could do so by\r
40  * directly creating DateIntervalInfo and manupulating it.\r
41  *\r
42  * <P>\r
43  * Logically, the interval patterns are mappings\r
44  * from (skeleton, the_largest_different_calendar_field)\r
45  * to (date_interval_pattern).\r
46  *\r
47  * <P>\r
48  * A skeleton \r
49  * <ol>\r
50  * <li>\r
51  * only keeps the field pattern letter and ignores all other parts \r
52  * in a pattern, such as space, punctuations, and string literals.\r
53  * <li>\r
54  * hides the order of fields. \r
55  * <li>\r
56  * might hide a field's pattern letter length.\r
57  *\r
58  * For those non-digit calendar fields, the pattern letter length is \r
59  * important, such as MMM, MMMM, and MMMMM; EEE and EEEE, \r
60  * and the field's pattern letter length is honored.\r
61  *    \r
62  * For the digit calendar fields,  such as M or MM, d or dd, yy or yyyy, \r
63  * the field pattern length is ignored and the best match, which is defined \r
64  * in date time patterns, will be returned without honor the field pattern\r
65  * letter length in skeleton.\r
66  * </ol>\r
67  *\r
68  * <P>\r
69  * The calendar fields we support for interval formatting are:\r
70  * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.\r
71  * Those calendar fields can be defined in the following order:\r
72  * year >  month > date > am-pm > hour >  minute \r
73  *  \r
74  * The largest different calendar fields between 2 calendars is the\r
75  * first different calendar field in above order.\r
76  *\r
77  * For example: the largest different calendar fields between "Jan 10, 2007" \r
78  * and "Feb 20, 2008" is year.\r
79  *   \r
80  * <P>\r
81  * There is a set of pre-defined static skeleton strings.\r
82  * There are pre-defined interval patterns for those pre-defined skeletons\r
83  * in locales' resource files.\r
84  * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is  "yMMMd",\r
85  * in  en_US, if the largest different calendar field between date1 and date2 \r
86  * is "year", the date interval pattern  is "MMM d, yyyy - MMM d, yyyy", \r
87  * such as "Jan 10, 2007 - Jan 10, 2008".\r
88  * If the largest different calendar field between date1 and date2 is "month",\r
89  * the date interval pattern is "MMM d - MMM d, yyyy",\r
90  * such as "Jan 10 - Feb 10, 2007".\r
91  * If the largest different calendar field between date1 and date2 is "day",\r
92  * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".\r
93  *\r
94  * For date skeleton, the interval patterns when year, or month, or date is \r
95  * different are defined in resource files.\r
96  * For time skeleton, the interval patterns when am/pm, or hour, or minute is\r
97  * different are defined in resource files.\r
98  *\r
99  *\r
100  * <P>\r
101  * There are 2 dates in interval pattern. For most locales, the first date\r
102  * in an interval pattern is the earlier date. There might be a locale in which\r
103  * the first date in an interval pattern is the later date.\r
104  * We use fallback format for the default order for the locale.\r
105  * For example, if the fallback format is "{0} - {1}", it means\r
106  * the first date in the interval pattern for this locale is earlier date.\r
107  * If the fallback format is "{1} - {0}", it means the first date is the \r
108  * later date.\r
109  * For a particular interval pattern, the default order can be overriden\r
110  * by prefixing "latestFirst:" or "earliestFirst:" to the interval pattern.\r
111  * For example, if the fallback format is "{0}-{1}",\r
112  * but for skeleton "yMMMd", the interval pattern when day is different is \r
113  * "latestFirst:d-d MMM yy", it means by default, the first date in interval\r
114  * pattern is the earlier date. But for skeleton "yMMMd", when day is different,\r
115  * the first date in "d-d MMM yy" is the later date.\r
116  * \r
117  * <P>\r
118  * The recommended way to create a DateIntervalFormat object is to pass in \r
119  * the locale. \r
120  * By using a Locale parameter, the DateIntervalFormat object is \r
121  * initialized with the pre-defined interval patterns for a given or \r
122  * default locale.\r
123  * <P>\r
124  * Users can also create DateIntervalFormat object \r
125  * by supplying their own interval patterns.\r
126  * It provides flexibility for power usage.\r
127  *\r
128  * <P>\r
129  * After a DateIntervalInfo object is created, clients may modify\r
130  * the interval patterns using setIntervalPattern function as so desired.\r
131  * Currently, users can only set interval patterns when the following \r
132  * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, \r
133  * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, and MINUTE.\r
134  * Interval patterns when other calendar fields are different is not supported.\r
135  * <P>\r
136  * DateIntervalInfo objects are cloneable. \r
137  * When clients obtain a DateIntervalInfo object, \r
138  * they can feel free to modify it as necessary.\r
139  * <P>\r
140  * DateIntervalInfo are not expected to be subclassed. \r
141  * Data for a calendar is loaded out of resource bundles. \r
142  * To ICU 4.0, date interval patterns are only supported in Gregorian calendar.\r
143  * \r
144  * @stable ICU 4.0\r
145  */\r
146 \r
147 public class DateIntervalInfo implements Cloneable, Freezable, Serializable {\r
148     /* Save the interval pattern information.\r
149      * Interval pattern consists of 2 single date patterns and the separator.\r
150      * For example, interval pattern "MMM d - MMM d, yyyy" consists\r
151      * a single date pattern "MMM d", another single date pattern "MMM d, yyyy",\r
152      * and a separator "-".\r
153      * Also, the first date appears in an interval pattern could be \r
154      * the earlier date or the later date.\r
155      * And such information is saved in the interval pattern as well.\r
156      */\r
157     static final int currentSerialVersion = 1;\r
158 \r
159     /**\r
160      * PatternInfo class saves the first and second part of interval pattern,\r
161      * and whether the interval pattern is earlier date first.\r
162      * @stable ICU 4.0\r
163      */\r
164     public static final class PatternInfo implements Cloneable, Serializable {\r
165         static final int currentSerialVersion = 1;\r
166         private static final long serialVersionUID = 1;\r
167         private final String fIntervalPatternFirstPart;\r
168         private final String fIntervalPatternSecondPart;\r
169         /*\r
170          * Whether the first date in interval pattern is later date or not.\r
171          * Fallback format set the default ordering.\r
172          * And for a particular interval pattern, the order can be \r
173          * overriden by prefixing the interval pattern with "latestFirst:" or \r
174          * "earliestFirst:"\r
175          * For example, given 2 date, Jan 10, 2007 to Feb 10, 2007.\r
176          * if the fallback format is "{0} - {1}", \r
177          * and the pattern is "d MMM - d MMM yyyy", the interval format is\r
178          * "10 Jan - 10 Feb, 2007".\r
179          * If the pattern is "latestFirst:d MMM - d MMM yyyy", \r
180          * the interval format is "10 Feb - 10 Jan, 2007"\r
181          */\r
182         private final boolean fFirstDateInPtnIsLaterDate;\r
183 \r
184         /**\r
185          * constructor\r
186          * @stable ICU 4.0\r
187          */\r
188         public PatternInfo(String firstPart, String secondPart,\r
189                            boolean firstDateInPtnIsLaterDate) {\r
190             fIntervalPatternFirstPart = firstPart;\r
191             fIntervalPatternSecondPart = secondPart;\r
192             fFirstDateInPtnIsLaterDate = firstDateInPtnIsLaterDate;\r
193         }\r
194 \r
195         /**\r
196          * accessor\r
197          * @stable ICU 4.0\r
198          */\r
199         public String getFirstPart() {\r
200             return fIntervalPatternFirstPart;\r
201         }\r
202 \r
203         /**\r
204          * accessor\r
205          * @stable ICU 4.0\r
206          */\r
207         public String getSecondPart() {\r
208             return fIntervalPatternSecondPart;\r
209         }\r
210 \r
211         /**\r
212          * accessor\r
213          * @stable ICU 4.0\r
214          */\r
215         public boolean firstDateInPtnIsLaterDate() {\r
216             return fFirstDateInPtnIsLaterDate;\r
217         }\r
218 \r
219         /**\r
220          * Override equals\r
221          * @stable ICU 4.0\r
222          */\r
223         public boolean equals(Object a) {\r
224             if ( a instanceof PatternInfo ) {\r
225                 PatternInfo patternInfo = (PatternInfo)a;\r
226                 return Utility.objectEquals(fIntervalPatternFirstPart, patternInfo.fIntervalPatternFirstPart) && \r
227                        Utility.objectEquals(fIntervalPatternSecondPart, fIntervalPatternSecondPart) &&\r
228                        fFirstDateInPtnIsLaterDate == patternInfo.fFirstDateInPtnIsLaterDate;\r
229             }\r
230             return false;\r
231         }\r
232 \r
233         /**\r
234          * Override hashcode\r
235          * @stable ICU 4.0\r
236          */\r
237         public int hashCode() {\r
238             int hash = fIntervalPatternFirstPart != null ? fIntervalPatternFirstPart.hashCode() : 0;\r
239             if (fIntervalPatternSecondPart != null) {\r
240                 hash ^= fIntervalPatternSecondPart.hashCode();\r
241             }\r
242             if (fFirstDateInPtnIsLaterDate) {\r
243                 hash ^= -1;\r
244             }\r
245             return hash;\r
246         }\r
247     }\r
248 \r
249     // Following is package protected since \r
250     // it is shared with DateIntervalFormat.\r
251     static final String[] CALENDAR_FIELD_TO_PATTERN_LETTER = \r
252     {\r
253         "G", "y", "M",\r
254         "w", "W", "d", \r
255         "D", "E", "F",\r
256         "a", "h", "H",\r
257         "m",\r
258     };\r
259 \r
260 \r
261     private static final long serialVersionUID = 1;\r
262     private static final int MINIMUM_SUPPORTED_CALENDAR_FIELD = \r
263                                                           Calendar.MINUTE;\r
264     //private static boolean DEBUG = true;\r
265 \r
266     private static String FALLBACK_STRING = "fallback";\r
267     private static String LATEST_FIRST_PREFIX = "latestFirst:";\r
268     private static String EARLIEST_FIRST_PREFIX = "earliestFirst:";\r
269 \r
270     // DateIntervalInfo cache\r
271     private final static ICUCache DIICACHE = new SimpleCache();\r
272 \r
273     // default interval pattern on the skeleton, {0} - {1}\r
274     private String fFallbackIntervalPattern;\r
275     // default order\r
276     private boolean fFirstDateInPtnIsLaterDate = false;\r
277 \r
278     // HashMap<String, HashMap<String, PatternInfo> >\r
279     // HashMap( skeleton, HashMap(largest_different_field, pattern) )\r
280     private HashMap fIntervalPatterns = null;\r
281 \r
282     private transient boolean frozen = false;\r
283 \r
284 \r
285     /**\r
286      * Create empty instance.\r
287      * It does not initialize any interval patterns except\r
288      * that it initialize default fall-back pattern as "{0} - {1}",\r
289      * which can be reset by setFallbackIntervalPattern().\r
290      *\r
291      * It should be followed by setFallbackIntervalPattern() and \r
292      * setIntervalPattern(), \r
293      * and is recommended to be used only for power users who\r
294      * wants to create their own interval patterns and use them to create\r
295      * date interval formatter.\r
296      * @internal ICU 4.0\r
297      * @deprecated This API is ICU internal only.\r
298      */\r
299     public DateIntervalInfo() \r
300     {\r
301         fIntervalPatterns = new HashMap();\r
302         fFallbackIntervalPattern = "{0} \u2013 {1}";\r
303     }\r
304 \r
305 \r
306     /** \r
307      * Construct DateIntervalInfo for the given locale,\r
308      * @param locale  the interval patterns are loaded from the Gregorian \r
309      *                calendar data in this locale.\r
310      * @stable ICU 4.0\r
311      */\r
312     public DateIntervalInfo(ULocale locale) \r
313     {\r
314         initializeData(locale);\r
315     }\r
316 \r
317 \r
318     /*\r
319      * Initialize the DateIntervalInfo from locale\r
320      * @param locale   the given locale.\r
321      */\r
322     private void initializeData(ULocale locale)\r
323     {\r
324         String key = locale.toString();\r
325         DateIntervalInfo dii = (DateIntervalInfo) DIICACHE.get(key);\r
326         if ( dii == null ) {\r
327             // initialize data from scratch\r
328             setup(locale);\r
329             // TODO: should put a clone in cache?\r
330             // or put itself in cache?\r
331             // DIICACHE.put(key, this);\r
332             dii = (DateIntervalInfo)this.clone();\r
333             DIICACHE.put(key, dii);\r
334         } else {\r
335             initializeData(dii);\r
336         }\r
337     }\r
338 \r
339  \r
340 \r
341     /*\r
342      * Initialize DateIntervalInfo from another instance\r
343      * @param dii  an DateIntervalInfo instance\r
344      */\r
345     private void initializeData(DateIntervalInfo dii) {\r
346         fFallbackIntervalPattern = dii.fFallbackIntervalPattern;\r
347         fFirstDateInPtnIsLaterDate = dii.fFirstDateInPtnIsLaterDate;\r
348         fIntervalPatterns = dii.fIntervalPatterns;\r
349     }\r
350 \r
351 \r
352     /*\r
353      * Initialize DateIntervalInfo from calendar data\r
354      * @param calData  calendar data\r
355      */\r
356     private void setup(ULocale locale) {\r
357         if ( locale == null ) {\r
358             return;\r
359         }\r
360     \r
361         int DEFAULT_HASH_SIZE = 19;\r
362         fIntervalPatterns = new HashMap(DEFAULT_HASH_SIZE);\r
363         // initialize to guard if there is no interval date format defined in \r
364         // resource files\r
365         fFallbackIntervalPattern = "{0} \u2013 {1}";\r
366         HashSet skeletonSet = new HashSet();\r
367         try {\r
368             // loop through all locales to get all available skeletons'\r
369             // interval format\r
370             ULocale parentLocale = locale;\r
371             do {\r
372                 String name = parentLocale.getName();\r
373                 if ( name.length() == 0 ) {\r
374                     break;\r
375                 }\r
376 \r
377                 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.\r
378                   getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);\r
379                 rb = rb.getWithFallback("calendar");\r
380                 ICUResourceBundle gregorianBundle = rb.getWithFallback(\r
381                                                               "gregorian");\r
382                 ICUResourceBundle itvDtPtnResource =gregorianBundle.\r
383                                       getWithFallback("intervalFormats");\r
384                 // look for fallback first, since it establishes the default order\r
385                 String fallback = itvDtPtnResource.getStringWithFallback(\r
386                                                           FALLBACK_STRING);\r
387                 setFallbackIntervalPattern(fallback);\r
388                 int size = itvDtPtnResource.getSize();\r
389                 for ( int index = 0; index < size; ++index ) {\r
390                     String skeleton = itvDtPtnResource.get(index).getKey();\r
391                     if ( skeletonSet.contains(skeleton) ) {\r
392                         continue;\r
393                     }\r
394                     skeletonSet.add(skeleton);\r
395                     if ( skeleton.compareTo(FALLBACK_STRING) == 0 ) {\r
396                         continue;\r
397                     }\r
398                     ICUResourceBundle intervalPatterns =\r
399                         itvDtPtnResource.getWithFallback(skeleton);\r
400                     int ptnNum = intervalPatterns.getSize();\r
401                     for ( int ptnIndex = 0; ptnIndex < ptnNum; ++ptnIndex) {\r
402                         String key = intervalPatterns.get(ptnIndex).getKey();\r
403                         String pattern = intervalPatterns.get(ptnIndex).getString();\r
404     \r
405                         int calendarField = -1; // initialize with an invalid value.\r
406                         if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR]) == 0 ) {\r
407                             calendarField = Calendar.YEAR;    \r
408                         } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH]) == 0 ) {\r
409                             calendarField = Calendar.MONTH;\r
410                         } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE]) == 0 ) {\r
411                             calendarField = Calendar.DATE;\r
412                         } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM]) == 0 ) {\r
413                             calendarField = Calendar.AM_PM;    \r
414                         } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR]) == 0 ) {\r
415                             calendarField = Calendar.HOUR;    \r
416                         } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MINUTE]) == 0 ) {\r
417                             calendarField = Calendar.MINUTE;    \r
418                         }\r
419              \r
420                         if ( calendarField != -1 ) {\r
421                             setIntervalPatternInternally(skeleton, key, pattern);\r
422                         }\r
423                     }\r
424                 }\r
425                 parentLocale = parentLocale.getFallback();\r
426             } while (parentLocale != null && !parentLocale.equals(ULocale.ROOT));\r
427         } catch ( MissingResourceException e) {\r
428             // ok, will fallback to {data0} - {date1}\r
429         }\r
430     }\r
431 \r
432 \r
433     /*\r
434      * Split interval patterns into 2 part.\r
435      * @param intervalPattern  interval pattern\r
436      * @return the index in interval pattern which split the pattern into 2 part\r
437      */\r
438     private static int splitPatternInto2Part(String intervalPattern) {\r
439         boolean inQuote = false;\r
440         char prevCh = 0;\r
441         int count = 0;\r
442     \r
443         /* repeatedPattern used to record whether a pattern has already seen.\r
444            It is a pattern applies to first calendar if it is first time seen,\r
445            otherwise, it is a pattern applies to the second calendar\r
446          */\r
447         int[] patternRepeated = new int[58];\r
448 \r
449         int PATTERN_CHAR_BASE = 0x41;\r
450         \r
451         /* loop through the pattern string character by character looking for\r
452          * the first repeated pattern letter, which breaks the interval pattern\r
453          * into 2 parts. \r
454          */\r
455         int i;\r
456         boolean foundRepetition = false;\r
457         for (i = 0; i < intervalPattern.length(); ++i) {\r
458             char ch = intervalPattern.charAt(i);\r
459             \r
460             if (ch != prevCh && count > 0) {\r
461                 // check the repeativeness of pattern letter\r
462                 int repeated = patternRepeated[(int)(prevCh - PATTERN_CHAR_BASE)];\r
463                 if ( repeated == 0 ) {\r
464                     patternRepeated[prevCh - PATTERN_CHAR_BASE] = 1;\r
465                 } else {\r
466                     foundRepetition = true;\r
467                     break;\r
468                 }\r
469                 count = 0;\r
470             }\r
471             if (ch == '\'') {\r
472                 // Consecutive single quotes are a single quote literal,\r
473                 // either outside of quotes or between quotes\r
474                 if ((i+1) < intervalPattern.length() && \r
475                     intervalPattern.charAt(i+1) == '\'') {\r
476                     ++i;\r
477                 } else {\r
478                     inQuote = ! inQuote;\r
479                 }\r
480             } \r
481             else if (!inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)\r
482                         || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {\r
483                 // ch is a date-time pattern character \r
484                 prevCh = ch;\r
485                 ++count;\r
486             }\r
487         }\r
488         // check last pattern char, distinguish\r
489         // "dd MM" ( no repetition ), \r
490         // "d-d"(last char repeated ), and \r
491         // "d-d MM" ( repetition found )\r
492         if ( count > 0 && foundRepetition == false ) {\r
493             if ( patternRepeated[(int)(prevCh - PATTERN_CHAR_BASE)] == 0 ) {\r
494                 count = 0;\r
495             }\r
496         }\r
497         return (i - count);\r
498     }\r
499 \r
500 \r
501     /** \r
502      * Provides a way for client to build interval patterns.\r
503      * User could construct DateIntervalInfo by providing \r
504      * a list of skeletons and their patterns.\r
505      * <P>\r
506      * For example:\r
507      * <pre>\r
508      * DateIntervalInfo dIntervalInfo = new DateIntervalInfo();\r
509      * dIntervalInfo.setIntervalPattern("yMd", Calendar.YEAR, "'from' yyyy-M-d 'to' yyyy-M-d"); \r
510      * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.MONTH, "'from' yyyy MMM d 'to' MMM d");\r
511      * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.DAY, "yyyy MMM d-d");\r
512      * dIntervalInfo.setFallbackIntervalPattern("{0} ~ {1}");\r
513      * </pre>\r
514      *\r
515      * Restriction: \r
516      * Currently, users can only set interval patterns when the following \r
517      * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, \r
518      * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, and MINUTE.\r
519      * Interval patterns when other calendar fields are different are \r
520      * not supported.\r
521      *\r
522      * @param skeleton         the skeleton on which interval pattern based\r
523      * @param lrgDiffCalUnit   the largest different calendar unit.\r
524      * @param intervalPattern  the interval pattern on the largest different\r
525      *                         calendar unit.\r
526      *                         For example, if lrgDiffCalUnit is \r
527      *                         "year", the interval pattern for en_US when year\r
528      *                         is different could be "'from' yyyy 'to' yyyy".\r
529      * @throws IllegalArgumentException  if setting interval pattern on \r
530      *                            a calendar field that is smaller\r
531      *                            than the MINIMUM_SUPPORTED_CALENDAR_FIELD \r
532      * @throws UnsupportedOperationException  if the object is frozen\r
533      * @stable ICU 4.0\r
534      */\r
535     public void setIntervalPattern(String skeleton, \r
536                                    int lrgDiffCalUnit, \r
537                                    String intervalPattern)\r
538     {\r
539         if ( frozen ) {\r
540             throw new UnsupportedOperationException("no modification is allowed after DII is frozen");\r
541         }\r
542         if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {\r
543             throw new IllegalArgumentException("calendar field is larger than MINIMUM_SUPPORTED_CALENDAR_FIELD");\r
544         }\r
545 \r
546         PatternInfo ptnInfo = setIntervalPatternInternally(skeleton,\r
547                           CALENDAR_FIELD_TO_PATTERN_LETTER[lrgDiffCalUnit], \r
548                           intervalPattern);\r
549         if ( lrgDiffCalUnit == Calendar.HOUR_OF_DAY ) {\r
550             setIntervalPattern(skeleton, \r
551                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM],\r
552                                ptnInfo);\r
553             setIntervalPattern(skeleton, \r
554                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR],\r
555                                ptnInfo);\r
556         } else if ( lrgDiffCalUnit == Calendar.DAY_OF_MONTH ||\r
557                     lrgDiffCalUnit == Calendar.DAY_OF_WEEK ) {\r
558             setIntervalPattern(skeleton, \r
559                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE],\r
560                                ptnInfo);\r
561         }\r
562     }\r
563 \r
564 \r
565     /* Set Interval pattern.\r
566      *\r
567      * It generates the interval pattern info, \r
568      * afer which, not only sets the interval pattern info into the hash map,\r
569      * but also returns the interval pattern info to the caller\r
570      * so that caller can re-use it.\r
571      *\r
572      * @param skeleton         skeleton on which the interval pattern based\r
573      * @param lrgDiffCalUnit   the largest different calendar unit.\r
574      * @param intervalPattern  the interval pattern on the largest different\r
575      *                         calendar unit.\r
576      * @return the interval pattern pattern information\r
577      */\r
578     private PatternInfo setIntervalPatternInternally(String skeleton,\r
579                                                 String lrgDiffCalUnit,\r
580                                                 String intervalPattern) {\r
581         HashMap patternsOfOneSkeleton = (HashMap)fIntervalPatterns.get(skeleton);\r
582         boolean emptyHash = false;\r
583         if ( patternsOfOneSkeleton == null ) {\r
584             patternsOfOneSkeleton = new HashMap();\r
585             emptyHash = true;\r
586         }\r
587         boolean order = fFirstDateInPtnIsLaterDate;\r
588         // check for "latestFirst:" or "earliestFirst:" prefix\r
589         if ( intervalPattern.startsWith(LATEST_FIRST_PREFIX) ) {\r
590             order = true;\r
591             int prefixLength = LATEST_FIRST_PREFIX.length();\r
592             intervalPattern = intervalPattern.substring(prefixLength, intervalPattern.length());\r
593         } else if ( intervalPattern.startsWith(EARLIEST_FIRST_PREFIX) ) {\r
594             order = false;\r
595             int earliestFirstLength = EARLIEST_FIRST_PREFIX.length();\r
596             intervalPattern = intervalPattern.substring(earliestFirstLength, intervalPattern.length());\r
597         }\r
598         PatternInfo itvPtnInfo = genPatternInfo(intervalPattern, order);\r
599         \r
600         patternsOfOneSkeleton.put(lrgDiffCalUnit, itvPtnInfo);\r
601         if ( emptyHash == true ) {\r
602             fIntervalPatterns.put(skeleton, patternsOfOneSkeleton);\r
603         }\r
604 \r
605         return itvPtnInfo;\r
606     }\r
607 \r
608 \r
609     /* Set Interval pattern.\r
610      *\r
611      * @param skeleton         skeleton on which the interval pattern based\r
612      * @param lrgDiffCalUnit   the largest different calendar unit.\r
613      * @param ptnInfo          interval pattern infomration \r
614      */\r
615     private void setIntervalPattern(String skeleton,\r
616                                     String lrgDiffCalUnit,\r
617                                     PatternInfo ptnInfo) {\r
618         HashMap patternsOfOneSkeleton = (HashMap)fIntervalPatterns.get(skeleton);\r
619         patternsOfOneSkeleton.put(lrgDiffCalUnit, ptnInfo);\r
620     }\r
621 \r
622 \r
623     /**\r
624      * Break interval patterns as 2 part and save them into pattern info.\r
625      * @param intervalPattern  interval pattern\r
626      * @param laterDateFirst   whether the first date in intervalPattern\r
627      *                         is earlier date or later date\r
628      * @return                 pattern info object\r
629      * @internal ICU 4.0\r
630      * @deprecated This API is ICU internal only.\r
631      */\r
632     static PatternInfo genPatternInfo(String intervalPattern, \r
633                                       boolean laterDateFirst) {\r
634         int splitPoint = splitPatternInto2Part(intervalPattern);\r
635         \r
636         String firstPart = intervalPattern.substring(0, splitPoint);\r
637         String secondPart = null;\r
638         if ( splitPoint < intervalPattern.length() ) {\r
639             secondPart = intervalPattern.substring(splitPoint, intervalPattern.length());\r
640         }\r
641 \r
642         return new PatternInfo(firstPart, secondPart, laterDateFirst);\r
643     }\r
644 \r
645 \r
646     /**\r
647      * Get the interval pattern given the largest different calendar field.\r
648      * @param skeleton   the skeleton\r
649      * @param field      the largest different calendar field\r
650      * @return interval pattern  return null if interval pattern is not found.\r
651      * @throws IllegalArgumentException  if getting interval pattern on \r
652      *                            a calendar field that is smaller\r
653      *                            than the MINIMUM_SUPPORTED_CALENDAR_FIELD \r
654      * @stable ICU 4.0\r
655      */\r
656     public PatternInfo getIntervalPattern(String skeleton, int field) \r
657     {\r
658         if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {\r
659             throw new IllegalArgumentException("no support for field less than MINUTE");\r
660         }\r
661         HashMap patternsOfOneSkeleton = (HashMap) fIntervalPatterns.get(skeleton);\r
662         if ( patternsOfOneSkeleton != null ) {\r
663             PatternInfo intervalPattern = (PatternInfo) patternsOfOneSkeleton.\r
664                 get(CALENDAR_FIELD_TO_PATTERN_LETTER[field]);\r
665             if ( intervalPattern != null ) {\r
666                 return intervalPattern;\r
667             }\r
668         }\r
669         return null;\r
670     }\r
671 \r
672 \r
673 \r
674     /**\r
675      * Get the fallback interval pattern.\r
676      * @return fallback interval pattern\r
677      * @stable ICU 4.0\r
678      */\r
679     public String getFallbackIntervalPattern()\r
680     {\r
681         return fFallbackIntervalPattern;\r
682     }\r
683 \r
684 \r
685     /**\r
686      * Re-set the fallback interval pattern.\r
687      *\r
688      * In construction, default fallback pattern is set as "{0} - {1}".\r
689      * And constructor taking locale as parameter will set the\r
690      * fallback pattern as what defined in the locale resource file.\r
691      *\r
692      * This method provides a way for user to replace the fallback pattern.\r
693      *\r
694      * @param fallbackPattern                 fall-back interval pattern.\r
695      * @throws UnsupportedOperationException  if the object is frozen\r
696      * @throws IllegalArgumentException       if there is no pattern {0} or \r
697      *                                        pattern {1} in fallbakckPattern\r
698      *                   \r
699      * @stable ICU 4.0\r
700      */\r
701     public void setFallbackIntervalPattern(String fallbackPattern)\r
702     {\r
703         if ( frozen ) {\r
704             throw new UnsupportedOperationException("no modification is allowed after DII is frozen");\r
705         }\r
706         int firstPatternIndex = fallbackPattern.indexOf("{0}");\r
707         int secondPatternIndex = fallbackPattern.indexOf("{1}");\r
708         if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) {\r
709             throw new IllegalArgumentException("no pattern {0} or pattern {1} in fallbackPattern");\r
710         }\r
711         if ( firstPatternIndex > secondPatternIndex ) {\r
712             fFirstDateInPtnIsLaterDate = true;\r
713         }\r
714         fFallbackIntervalPattern = fallbackPattern;\r
715     }\r
716 \r
717 \r
718     /**\r
719      * Get default order -- whether the first date in pattern is later date\r
720      *                      or not.\r
721      *\r
722      * return default date ordering in interval pattern. TRUE if the first date \r
723      *        in pattern is later date, FALSE otherwise.\r
724      * @stable ICU 4.0\r
725      */\r
726     public boolean getDefaultOrder()\r
727     {\r
728         return fFirstDateInPtnIsLaterDate;\r
729     }\r
730 \r
731 \r
732     /**\r
733      * Boilerplate. Clone this object.\r
734      * @return     a copy of the object\r
735      * @stable ICU4.0\r
736      */\r
737     public Object clone() \r
738     {\r
739         if ( frozen ) {\r
740             return this;\r
741         }\r
742         return cloneUnfrozenDII();\r
743     }\r
744 \r
745 \r
746     /*\r
747      * Clone an unfrozen DateIntervalInfo object.\r
748      * @return     a copy of the object\r
749      */\r
750     private Object cloneUnfrozenDII() //throws IllegalStateException\r
751     {\r
752         try {\r
753             DateIntervalInfo other = (DateIntervalInfo) super.clone();\r
754             other.fFallbackIntervalPattern=fFallbackIntervalPattern;\r
755             other.fFirstDateInPtnIsLaterDate = fFirstDateInPtnIsLaterDate;\r
756             other.fIntervalPatterns = new HashMap();\r
757             Iterator iter = fIntervalPatterns.keySet().iterator();\r
758             while ( iter.hasNext() ) {\r
759                 String skeleton = (String) iter.next();\r
760                 HashMap patternsOfOneSkeleton = (HashMap)fIntervalPatterns.get(skeleton);\r
761                 HashMap oneSetPtn = new HashMap();\r
762                 Iterator patternIter = patternsOfOneSkeleton.keySet().iterator();\r
763                 while ( patternIter.hasNext() ) {\r
764                     String calField = (String) patternIter.next();\r
765                     PatternInfo value = (PatternInfo) patternsOfOneSkeleton.get(calField);\r
766                     oneSetPtn.put(calField, value);\r
767                 }\r
768                 other.fIntervalPatterns.put(skeleton, oneSetPtn);    \r
769             }\r
770             other.frozen = false;\r
771             return other;\r
772         } catch ( CloneNotSupportedException e ) {\r
773             throw new  IllegalStateException("clone is not supported");\r
774         }\r
775     }\r
776 \r
777     \r
778     /**\r
779      * Boilerplate for Freezable\r
780      * @stable ICU 4.0\r
781      */\r
782     public boolean isFrozen() {\r
783         return frozen;\r
784     }\r
785     \r
786     /**\r
787      * Boilerplate for Freezable\r
788      * @stable ICU 4.0\r
789      */\r
790     public Object freeze() {\r
791         frozen = true;\r
792         return this;\r
793     }\r
794     \r
795     /**\r
796      * Boilerplate for Freezable\r
797      * @stable ICU 4.0\r
798      */\r
799     public Object cloneAsThawed() {\r
800         DateIntervalInfo result = (DateIntervalInfo) (this.cloneUnfrozenDII());\r
801         return result;\r
802     }\r
803 \r
804 \r
805     /**\r
806      * Parse skeleton, save each field's width.\r
807      * It is used for looking for best match skeleton,\r
808      * and adjust pattern field width.\r
809      * @param skeleton            skeleton to be parsed\r
810      * @param skeletonFieldWidth  parsed skeleton field width\r
811      * @internal ICU 4.0\r
812      * @deprecated This API is ICU internal only.\r
813      */\r
814     static void parseSkeleton(String skeleton, int[] skeletonFieldWidth) {\r
815         int PATTERN_CHAR_BASE = 0x41;\r
816         for ( int i = 0; i < skeleton.length(); ++i ) {\r
817             ++skeletonFieldWidth[(int)(skeleton.charAt(i) - PATTERN_CHAR_BASE)];\r
818         }\r
819     }\r
820 \r
821 \r
822 \r
823     /*\r
824      * Check whether one field width is numeric while the other is string.\r
825      *\r
826      * TODO (xji): make it general\r
827      *\r
828      * @param fieldWidth          one field width\r
829      * @param anotherFieldWidth   another field width\r
830      * @param patternLetter       pattern letter char\r
831      * @return true if one field width is numeric and the other is string,\r
832      *         false otherwise.\r
833      */\r
834     private static boolean stringNumeric(int fieldWidth,\r
835                                          int anotherFieldWidth,\r
836                                          char patternLetter) {\r
837         if ( patternLetter == 'M' ) {\r
838             if ( fieldWidth <= 2 && anotherFieldWidth > 2 ||\r
839                  fieldWidth > 2 && anotherFieldWidth <= 2 ) {\r
840                 return true;\r
841             }\r
842         }        \r
843         return false;\r
844     }\r
845 \r
846 \r
847     /*\r
848      * given an input skeleton, get the best match skeleton \r
849      * which has pre-defined interval pattern in resource file.\r
850      *\r
851      * TODO (xji): set field weight or\r
852      *             isolate the funtionality in DateTimePatternGenerator\r
853      * @param  inputSkeleton        input skeleton\r
854      * @return 0, if there is exact match for input skeleton\r
855      *         1, if there is only field width difference between \r
856      *            the best match and the input skeleton\r
857      *         2, the only field difference is 'v' and 'z'\r
858      *        -1, if there is calendar field difference between\r
859      *            the best match and the input skeleton\r
860      */\r
861     DateIntervalFormat.BestMatchInfo getBestSkeleton(String inputSkeleton) {\r
862         String bestSkeleton = inputSkeleton;\r
863         int[] inputSkeletonFieldWidth = new int[58];\r
864         int[] skeletonFieldWidth = new int[58];\r
865 \r
866         final int DIFFERENT_FIELD = 0x1000;\r
867         final int STRING_NUMERIC_DIFFERENCE = 0x100;\r
868         final int BASE = 0x41;\r
869 \r
870         // TODO: this is a hack for 'v' and 'z'\r
871         // resource bundle only have time skeletons ending with 'v',\r
872         // but not for time skeletons ending with 'z'.\r
873         boolean replaceZWithV = false;\r
874         if ( inputSkeleton.indexOf('z') != -1 ) {\r
875             inputSkeleton = inputSkeleton.replace('z', 'v');\r
876             replaceZWithV = true;\r
877         }\r
878 \r
879         parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);\r
880         int bestDistance = Integer.MAX_VALUE;\r
881         // 0 means exact the same skeletons;\r
882         // 1 means having the same field, but with different length,\r
883         // 2 means only z/v differs\r
884         // -1 means having different field.\r
885         int bestFieldDifference = 0;\r
886         Iterator iter = fIntervalPatterns.keySet().iterator();\r
887         while ( iter.hasNext() ) {\r
888             String skeleton = (String)iter.next();\r
889             // clear skeleton field width\r
890             for ( int i = 0; i < skeletonFieldWidth.length; ++i ) {\r
891                 skeletonFieldWidth[i] = 0;    \r
892             }\r
893             parseSkeleton(skeleton, skeletonFieldWidth);\r
894             // calculate distance\r
895             int distance = 0;\r
896             int fieldDifference = 1;\r
897             for ( int i = 0; i < inputSkeletonFieldWidth.length; ++i ) {\r
898                 int inputFieldWidth = inputSkeletonFieldWidth[i];\r
899                 int fieldWidth = skeletonFieldWidth[i];\r
900                 if ( inputFieldWidth == fieldWidth ) {\r
901                     continue;\r
902                 }\r
903                 if ( inputFieldWidth == 0 ) {\r
904                     fieldDifference = -1;\r
905                     distance += DIFFERENT_FIELD;\r
906                 } else if ( fieldWidth == 0 ) {\r
907                     fieldDifference = -1;\r
908                     distance += DIFFERENT_FIELD;\r
909                 } else if (stringNumeric(inputFieldWidth, fieldWidth, \r
910                                          (char)(i+BASE) ) ) {\r
911                     distance += STRING_NUMERIC_DIFFERENCE;\r
912                 } else {\r
913                     distance += Math.abs(inputFieldWidth - fieldWidth);\r
914                 }\r
915             }\r
916             if ( distance < bestDistance ) {\r
917                 bestSkeleton = skeleton;\r
918                 bestDistance = distance;\r
919                 bestFieldDifference = fieldDifference;\r
920             }\r
921             if ( distance == 0 ) {\r
922                 bestFieldDifference = 0;\r
923                 break;\r
924             }\r
925         }\r
926         if ( replaceZWithV && bestFieldDifference != -1 ) {\r
927             bestFieldDifference = 2;\r
928         }\r
929         return new DateIntervalFormat.BestMatchInfo(bestSkeleton, bestFieldDifference);\r
930     }\r
931 \r
932     /**\r
933      * Override equals\r
934      * @stable ICU 4.0\r
935      */\r
936     public boolean equals(Object a) {\r
937         if ( a instanceof DateIntervalInfo ) {\r
938             DateIntervalInfo dtInfo = (DateIntervalInfo)a;\r
939             return fIntervalPatterns.equals(dtInfo.fIntervalPatterns);\r
940         }\r
941         return false;\r
942     }\r
943 \r
944     /**\r
945      * Override hashcode\r
946      * @stable ICU 4.0\r
947      */\r
948     public int hashCode() {\r
949         return fIntervalPatterns.hashCode();\r
950     }\r
951     \r
952 }// end class DateIntervalInfo\r