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