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