Home | History | Annotate | Download | only in text
      1 /* GENERATED SOURCE. DO NOT MODIFY. */
      2 //  2016 and later: Unicode, Inc. and others.
      3 // License & terms of use: http://www.unicode.org/copyright.html#License
      4 /*
      5  *******************************************************************************
      6  * Copyright (C) 2008-2016, International Business Machines Corporation and
      7  * others. All Rights Reserved.
      8  *******************************************************************************
      9  */
     10 
     11 package android.icu.text;
     12 
     13 import java.io.Serializable;
     14 import java.util.HashMap;
     15 import java.util.HashSet;
     16 import java.util.LinkedHashMap;
     17 import java.util.LinkedHashSet;
     18 import java.util.Locale;
     19 import java.util.Map;
     20 import java.util.Map.Entry;
     21 import java.util.MissingResourceException;
     22 import java.util.Set;
     23 
     24 import android.icu.impl.ICUCache;
     25 import android.icu.impl.ICUData;
     26 import android.icu.impl.ICUResourceBundle;
     27 import android.icu.impl.SimpleCache;
     28 import android.icu.impl.UResource;
     29 import android.icu.impl.UResource.Key;
     30 import android.icu.impl.UResource.Value;
     31 import android.icu.impl.Utility;
     32 import android.icu.util.Calendar;
     33 import android.icu.util.Freezable;
     34 import android.icu.util.ICUCloneNotSupportedException;
     35 import android.icu.util.ICUException;
     36 import android.icu.util.ULocale;
     37 import android.icu.util.UResourceBundle;
     38 
     39 /**
     40  * DateIntervalInfo is a public class for encapsulating localizable
     41  * date time interval patterns. It is used by DateIntervalFormat.
     42  *
     43  * <P>
     44  * For most users, ordinary use of DateIntervalFormat does not need to create
     45  * DateIntervalInfo object directly.
     46  * DateIntervalFormat will take care of it when creating a date interval
     47  * formatter when user pass in skeleton and locale.
     48  *
     49  * <P>
     50  * For power users, who want to create their own date interval patterns,
     51  * or want to re-set date interval patterns, they could do so by
     52  * directly creating DateIntervalInfo and manipulating it.
     53  *
     54  * <P>
     55  * Logically, the interval patterns are mappings
     56  * from (skeleton, the_largest_different_calendar_field)
     57  * to (date_interval_pattern).
     58  *
     59  * <P>
     60  * A skeleton
     61  * <ol>
     62  * <li>
     63  * only keeps the field pattern letter and ignores all other parts
     64  * in a pattern, such as space, punctuations, and string literals.
     65  * <li>
     66  * hides the order of fields.
     67  * <li>
     68  * might hide a field's pattern letter length.
     69  *
     70  * For those non-digit calendar fields, the pattern letter length is
     71  * important, such as MMM, MMMM, and MMMMM; EEE and EEEE,
     72  * and the field's pattern letter length is honored.
     73  *
     74  * For the digit calendar fields,  such as M or MM, d or dd, yy or yyyy,
     75  * the field pattern length is ignored and the best match, which is defined
     76  * in date time patterns, will be returned without honor the field pattern
     77  * letter length in skeleton.
     78  * </ol>
     79  *
     80  * <P>
     81  * The calendar fields we support for interval formatting are:
     82  * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and
     83  * second (though we do not currently have specific intervalFormat data for
     84  * skeletons with seconds).
     85  * Those calendar fields can be defined in the following order:
     86  * year &gt; month &gt; date &gt; am-pm &gt; hour &gt;  minute &gt; second
     87  *
     88  * The largest different calendar fields between 2 calendars is the
     89  * first different calendar field in above order.
     90  *
     91  * For example: the largest different calendar fields between "Jan 10, 2007"
     92  * and "Feb 20, 2008" is year.
     93  *
     94  * <P>
     95  * There is a set of pre-defined static skeleton strings.
     96  * There are pre-defined interval patterns for those pre-defined skeletons
     97  * in locales' resource files.
     98  * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is  "yMMMd",
     99  * in  en_US, if the largest different calendar field between date1 and date2
    100  * is "year", the date interval pattern  is "MMM d, yyyy - MMM d, yyyy",
    101  * such as "Jan 10, 2007 - Jan 10, 2008".
    102  * If the largest different calendar field between date1 and date2 is "month",
    103  * the date interval pattern is "MMM d - MMM d, yyyy",
    104  * such as "Jan 10 - Feb 10, 2007".
    105  * If the largest different calendar field between date1 and date2 is "day",
    106  * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".
    107  *
    108  * For date skeleton, the interval patterns when year, or month, or date is
    109  * different are defined in resource files.
    110  * For time skeleton, the interval patterns when am/pm, or hour, or minute is
    111  * different are defined in resource files.
    112  *
    113  *
    114  * <P>
    115  * There are 2 dates in interval pattern. For most locales, the first date
    116  * in an interval pattern is the earlier date. There might be a locale in which
    117  * the first date in an interval pattern is the later date.
    118  * We use fallback format for the default order for the locale.
    119  * For example, if the fallback format is "{0} - {1}", it means
    120  * the first date in the interval pattern for this locale is earlier date.
    121  * If the fallback format is "{1} - {0}", it means the first date is the
    122  * later date.
    123  * For a particular interval pattern, the default order can be overriden
    124  * by prefixing "latestFirst:" or "earliestFirst:" to the interval pattern.
    125  * For example, if the fallback format is "{0}-{1}",
    126  * but for skeleton "yMMMd", the interval pattern when day is different is
    127  * "latestFirst:d-d MMM yy", it means by default, the first date in interval
    128  * pattern is the earlier date. But for skeleton "yMMMd", when day is different,
    129  * the first date in "d-d MMM yy" is the later date.
    130  *
    131  * <P>
    132  * The recommended way to create a DateIntervalFormat object is to pass in
    133  * the locale.
    134  * By using a Locale parameter, the DateIntervalFormat object is
    135  * initialized with the pre-defined interval patterns for a given or
    136  * default locale.
    137  * <P>
    138  * Users can also create DateIntervalFormat object
    139  * by supplying their own interval patterns.
    140  * It provides flexibility for power usage.
    141  *
    142  * <P>
    143  * After a DateIntervalInfo object is created, clients may modify
    144  * the interval patterns using setIntervalPattern function as so desired.
    145  * Currently, users can only set interval patterns when the following
    146  * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH,
    147  * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE and SECOND.
    148  * Interval patterns when other calendar fields are different is not supported.
    149  * <P>
    150  * DateIntervalInfo objects are cloneable.
    151  * When clients obtain a DateIntervalInfo object,
    152  * they can feel free to modify it as necessary.
    153  * <P>
    154  * DateIntervalInfo are not expected to be subclassed.
    155  * Data for a calendar is loaded out of resource bundles.
    156  * Through ICU 4.4, date interval patterns are only supported in the Gregoria
    157  * calendar; non-Gregorian calendars are supported from ICU 4.4.1.
    158  */
    159 
    160 public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>, Serializable {
    161 
    162     /* Save the interval pattern information.
    163      * Interval pattern consists of 2 single date patterns and the separator.
    164      * For example, interval pattern "MMM d - MMM d, yyyy" consists
    165      * a single date pattern "MMM d", another single date pattern "MMM d, yyyy",
    166      * and a separator "-".
    167      * Also, the first date appears in an interval pattern could be
    168      * the earlier date or the later date.
    169      * And such information is saved in the interval pattern as well.
    170      */
    171     static final int currentSerialVersion = 1;
    172 
    173     /**
    174      * PatternInfo class saves the first and second part of interval pattern,
    175      * and whether the interval pattern is earlier date first.
    176      */
    177     public static final class PatternInfo implements Cloneable, Serializable {
    178         static final int currentSerialVersion = 1;
    179         private static final long serialVersionUID = 1;
    180         private final String fIntervalPatternFirstPart;
    181         private final String fIntervalPatternSecondPart;
    182         /*
    183          * Whether the first date in interval pattern is later date or not.
    184          * Fallback format set the default ordering.
    185          * And for a particular interval pattern, the order can be
    186          * overriden by prefixing the interval pattern with "latestFirst:" or
    187          * "earliestFirst:"
    188          * For example, given 2 date, Jan 10, 2007 to Feb 10, 2007.
    189          * if the fallback format is "{0} - {1}",
    190          * and the pattern is "d MMM - d MMM yyyy", the interval format is
    191          * "10 Jan - 10 Feb, 2007".
    192          * If the pattern is "latestFirst:d MMM - d MMM yyyy",
    193          * the interval format is "10 Feb - 10 Jan, 2007"
    194          */
    195         private final boolean fFirstDateInPtnIsLaterDate;
    196 
    197         /**
    198          * Constructs a <code>PatternInfo</code> object.
    199          * @param firstPart     The first part of interval pattern.
    200          * @param secondPart    The second part of interval pattern.
    201          * @param firstDateInPtnIsLaterDate Whether the first date in interval patter is later date or not.
    202          */
    203         public PatternInfo(String firstPart, String secondPart,
    204                            boolean firstDateInPtnIsLaterDate) {
    205             fIntervalPatternFirstPart = firstPart;
    206             fIntervalPatternSecondPart = secondPart;
    207             fFirstDateInPtnIsLaterDate = firstDateInPtnIsLaterDate;
    208         }
    209 
    210         /**
    211          * Returns the first part of interval pattern.
    212          * @return The first part of interval pattern.
    213          */
    214         public String getFirstPart() {
    215             return fIntervalPatternFirstPart;
    216         }
    217 
    218         /**
    219          * Returns the second part of interval pattern.
    220          * @return The second part of interval pattern.
    221          */
    222         public String getSecondPart() {
    223             return fIntervalPatternSecondPart;
    224         }
    225 
    226         /**
    227          * Returns whether the first date in interval patter is later date or not.
    228          * @return Whether the first date in interval patter is later date or not.
    229          */
    230         public boolean firstDateInPtnIsLaterDate() {
    231             return fFirstDateInPtnIsLaterDate;
    232         }
    233 
    234         /**
    235          * Compares the specified object with this <code>PatternInfo</code> for equality.
    236          * @param a The object to be compared.
    237          * @return <code>true</code> if the specified object is equal to this <code>PatternInfo</code>.
    238          */
    239         @Override
    240         public boolean equals(Object a) {
    241             if (a instanceof PatternInfo) {
    242                 PatternInfo patternInfo = (PatternInfo)a;
    243                 return Utility.objectEquals(fIntervalPatternFirstPart, patternInfo.fIntervalPatternFirstPart) &&
    244                        Utility.objectEquals(fIntervalPatternSecondPart, patternInfo.fIntervalPatternSecondPart) &&
    245                        fFirstDateInPtnIsLaterDate == patternInfo.fFirstDateInPtnIsLaterDate;
    246             }
    247             return false;
    248         }
    249 
    250         /**
    251          * Returns the hash code of this <code>PatternInfo</code>.
    252          * @return A hash code value for this object.
    253          */
    254         @Override
    255         public int hashCode() {
    256             int hash = fIntervalPatternFirstPart != null ? fIntervalPatternFirstPart.hashCode() : 0;
    257             if (fIntervalPatternSecondPart != null) {
    258                 hash ^= fIntervalPatternSecondPart.hashCode();
    259             }
    260             if (fFirstDateInPtnIsLaterDate) {
    261                 hash ^= -1;
    262             }
    263             return hash;
    264         }
    265 
    266         /**
    267          * {@inheritDoc}
    268          * @deprecated This API is ICU internal only.
    269          * @hide draft / provisional / internal are hidden on Android
    270          */
    271         @Deprecated
    272         @Override
    273         public String toString() {
    274             return "{first=" + fIntervalPatternFirstPart + ", second=" + fIntervalPatternSecondPart + ", reversed:" + fFirstDateInPtnIsLaterDate + "}";
    275         }
    276     }
    277 
    278     // Following is package protected since
    279     // it is shared with DateIntervalFormat.
    280     static final String[] CALENDAR_FIELD_TO_PATTERN_LETTER =
    281     {
    282         "G", "y", "M",
    283         "w", "W", "d",
    284         "D", "E", "F",
    285         "a", "h", "H",
    286         "m", "s", "S",  // MINUTE, SECOND, MILLISECOND
    287         "z", " ", "Y",  // ZONE_OFFSET, DST_OFFSET, YEAR_WOY
    288         "e", "u", "g",  // DOW_LOCAL, EXTENDED_YEAR, JULIAN_DAY
    289         "A", " ", " ",  // MILLISECONDS_IN_DAY, IS_LEAP_MONTH.
    290     };
    291 
    292 
    293     private static final long serialVersionUID = 1;
    294     private static final int MINIMUM_SUPPORTED_CALENDAR_FIELD =
    295                                                           Calendar.SECOND;
    296     //private static boolean DEBUG = true;
    297 
    298     private static String CALENDAR_KEY = "calendar";
    299     private static String INTERVAL_FORMATS_KEY = "intervalFormats";
    300     private static String FALLBACK_STRING = "fallback";
    301     private static String LATEST_FIRST_PREFIX = "latestFirst:";
    302     private static String EARLIEST_FIRST_PREFIX = "earliestFirst:";
    303 
    304     // DateIntervalInfo cache
    305     private final static ICUCache<String, DateIntervalInfo> DIICACHE = new SimpleCache<String, DateIntervalInfo>();
    306 
    307 
    308     // default interval pattern on the skeleton, {0} - {1}
    309     private String fFallbackIntervalPattern;
    310     // default order
    311     private boolean fFirstDateInPtnIsLaterDate = false;
    312 
    313     // HashMap( skeleton, HashMap(largest_different_field, pattern) )
    314     private Map<String, Map<String, PatternInfo>> fIntervalPatterns = null;
    315 
    316     private transient volatile boolean frozen = false;
    317 
    318     // If true, fIntervalPatterns should not be modified in-place because it
    319     // is shared with other objects. Unlike frozen which is always true once
    320     // set to true, this field can go from true to false as long as frozen is
    321     // false.
    322     private transient boolean fIntervalPatternsReadOnly = false;
    323 
    324 
    325     /**
    326      * Create empty instance.
    327      * It does not initialize any interval patterns except
    328      * that it initialize default fall-back pattern as "{0} - {1}",
    329      * which can be reset by setFallbackIntervalPattern().
    330      *
    331      * It should be followed by setFallbackIntervalPattern() and
    332      * setIntervalPattern(),
    333      * and is recommended to be used only for power users who
    334      * wants to create their own interval patterns and use them to create
    335      * date interval formatter.
    336      * @deprecated This API is ICU internal only.
    337      * @hide original deprecated declaration
    338      * @hide draft / provisional / internal are hidden on Android
    339      */
    340     @Deprecated
    341     public DateIntervalInfo()
    342     {
    343         fIntervalPatterns = new HashMap<String, Map<String, PatternInfo>>();
    344         fFallbackIntervalPattern = "{0} \u2013 {1}";
    345     }
    346 
    347 
    348     /**
    349      * Construct DateIntervalInfo for the given locale,
    350      * @param locale  the interval patterns are loaded from the appropriate
    351      *                calendar data (specified calendar or default calendar)
    352      *                in this locale.
    353      */
    354     public DateIntervalInfo(ULocale locale)
    355     {
    356         initializeData(locale);
    357     }
    358 
    359 
    360     /**
    361      * Construct DateIntervalInfo for the given {@link java.util.Locale}.
    362      * @param locale  the interval patterns are loaded from the appropriate
    363      *                calendar data (specified calendar or default calendar)
    364      *                in this locale.
    365      */
    366     public DateIntervalInfo(Locale locale)
    367     {
    368         this(ULocale.forLocale(locale));
    369     }
    370 
    371     /*
    372      * Initialize the DateIntervalInfo from locale
    373      * @param locale   the given locale.
    374      */
    375     private void initializeData(ULocale locale)
    376     {
    377         String key = locale.toString();
    378         DateIntervalInfo dii = DIICACHE.get(key);
    379         if ( dii == null ) {
    380             // initialize data from scratch
    381             setup(locale);
    382             // Marking fIntervalPatterns read-only makes cloning cheaper.
    383             fIntervalPatternsReadOnly = true;
    384             // We freeze what goes in the cache without freezing this object.
    385             DIICACHE.put(key, ((DateIntervalInfo) clone()).freeze());
    386         } else {
    387             initializeFromReadOnlyPatterns(dii);
    388         }
    389     }
    390 
    391 
    392 
    393     /**
    394      * Initialize this object
    395      * @param dii must have read-only fIntervalPatterns.
    396      */
    397     private void initializeFromReadOnlyPatterns(DateIntervalInfo dii) {
    398         fFallbackIntervalPattern = dii.fFallbackIntervalPattern;
    399         fFirstDateInPtnIsLaterDate = dii.fFirstDateInPtnIsLaterDate;
    400         fIntervalPatterns = dii.fIntervalPatterns;
    401         fIntervalPatternsReadOnly = true;
    402     }
    403 
    404 
    405 
    406     /**
    407      * Sink for enumerating all of the date interval skeletons.
    408      */
    409     private static final class DateIntervalSink extends UResource.Sink {
    410 
    411         /**
    412          * Accepted pattern letters:
    413          * Calendar.YEAR
    414          * Calendar.MONTH
    415          * Calendar.DATE
    416          * Calendar.AM_PM
    417          * Calendar.HOUR
    418          * Calendar.HOUR_OF_DAY
    419          * Calendar.MINUTE
    420          * Calendar.SECOND
    421          */
    422         private static final String ACCEPTED_PATTERN_LETTERS = "yMdahHms";
    423 
    424         // Output data
    425         DateIntervalInfo dateIntervalInfo;
    426 
    427         // Alias handling
    428         String nextCalendarType;
    429 
    430         // Constructor
    431         public DateIntervalSink(DateIntervalInfo dateIntervalInfo) {
    432             this.dateIntervalInfo = dateIntervalInfo;
    433         }
    434 
    435         @Override
    436         public void put(Key key, Value value, boolean noFallback) {
    437             // Iterate over all the calendar entries and only pick the 'intervalFormats' table.
    438             UResource.Table dateIntervalData = value.getTable();
    439             for (int i = 0; dateIntervalData.getKeyAndValue(i, key, value); i++) {
    440                 if (!key.contentEquals(INTERVAL_FORMATS_KEY)) {
    441                     continue;
    442                 }
    443 
    444                 // Handle aliases and tables. Ignore the rest.
    445                 if (value.getType() == ICUResourceBundle.ALIAS) {
    446                     // Get the calendar type from the alias path.
    447                     nextCalendarType = getCalendarTypeFromPath(value.getAliasString());
    448                     break;
    449 
    450                 } else if (value.getType() == ICUResourceBundle.TABLE) {
    451                     // Iterate over all the skeletons in the 'intervalFormat' table.
    452                     UResource.Table skeletonData = value.getTable();
    453                     for (int j = 0; skeletonData.getKeyAndValue(j, key, value); j++) {
    454                         if (value.getType() == ICUResourceBundle.TABLE) {
    455                             // Process the skeleton
    456                             processSkeletonTable(key, value);
    457                         }
    458                     }
    459                     break;
    460                 }
    461             }
    462         }
    463 
    464         /** Processes the patterns for a skeleton table. */
    465         public void processSkeletonTable(Key key, Value value) {
    466             // Iterate over all the patterns in the current skeleton table
    467             String currentSkeleton = key.toString();
    468             UResource.Table patternData = value.getTable();
    469             for (int k = 0; patternData.getKeyAndValue(k, key, value); k++) {
    470                 if (value.getType() == ICUResourceBundle.STRING) {
    471                     // Process the key
    472                     CharSequence patternLetter = validateAndProcessPatternLetter(key);
    473 
    474                     // If the calendar field has a valid value
    475                     if (patternLetter != null) {
    476                         // Get the largest different calendar unit
    477                         String lrgDiffCalUnit = patternLetter.toString();
    478 
    479                         // Set the interval pattern
    480                         setIntervalPatternIfAbsent(currentSkeleton, lrgDiffCalUnit, value);
    481                     }
    482                 }
    483             }
    484         }
    485 
    486         /**
    487          * Returns and resets the next calendar type.
    488          * @return Next calendar type
    489          */
    490         public String getAndResetNextCalendarType() {
    491             String tmpCalendarType = nextCalendarType;
    492             nextCalendarType = null;
    493             return tmpCalendarType;
    494         }
    495 
    496         // Alias' path prefix and suffix.
    497         private static final String DATE_INTERVAL_PATH_PREFIX =
    498             "/LOCALE/" + CALENDAR_KEY + "/";
    499         private static final String DATE_INTERVAL_PATH_SUFFIX =
    500             "/" + INTERVAL_FORMATS_KEY;
    501 
    502         /**
    503          * Extracts the calendar type from the path
    504          * @param path
    505          * @return Calendar Type
    506          */
    507         private String getCalendarTypeFromPath(String path) {
    508             if (path.startsWith(DATE_INTERVAL_PATH_PREFIX) &&
    509                     path.endsWith(DATE_INTERVAL_PATH_SUFFIX)) {
    510                 return path.substring(DATE_INTERVAL_PATH_PREFIX.length(),
    511                     path.length() - DATE_INTERVAL_PATH_SUFFIX.length());
    512             }
    513             throw new ICUException("Malformed 'intervalFormat' alias path: " + path);
    514         }
    515 
    516         /**
    517          * Processes the pattern letter
    518          * @param patternLetter
    519          * @return Pattern letter
    520          */
    521         private CharSequence validateAndProcessPatternLetter(CharSequence patternLetter) {
    522             // Check that patternLetter is just one letter
    523             if (patternLetter.length() != 1) { return null; }
    524 
    525             // Check that the pattern letter is accepted
    526             char letter = patternLetter.charAt(0);
    527             if (ACCEPTED_PATTERN_LETTERS.indexOf(letter) < 0) {
    528                 return null;
    529             }
    530 
    531             // Replace 'h' for 'H'
    532             if (letter == CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR_OF_DAY].charAt(0)) {
    533                 patternLetter = CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR];
    534             }
    535 
    536             return patternLetter;
    537         }
    538 
    539         /**
    540          * Stores the interval pattern for the current skeleton in the internal data structure
    541          * if it's not present.
    542          * @param lrgDiffCalUnit
    543          * @param intervalPattern
    544          */
    545         private void setIntervalPatternIfAbsent(String currentSkeleton, String lrgDiffCalUnit, Value intervalPattern) {
    546             // Check if the pattern has already been stored on the data structure.
    547             Map<String, PatternInfo> patternsOfOneSkeleton =
    548                     dateIntervalInfo.fIntervalPatterns.get(currentSkeleton);
    549             if (patternsOfOneSkeleton == null || !patternsOfOneSkeleton.containsKey(lrgDiffCalUnit)) {
    550                 // Store the pattern
    551                 dateIntervalInfo.setIntervalPatternInternally(currentSkeleton, lrgDiffCalUnit,
    552                         intervalPattern.toString());
    553             }
    554         }
    555     }
    556 
    557 
    558     /*
    559      * Initialize DateIntervalInfo from calendar data
    560      * @param calData  calendar data
    561      */
    562     private void setup(ULocale locale) {
    563         int DEFAULT_HASH_SIZE = 19;
    564         fIntervalPatterns = new HashMap<String, Map<String, PatternInfo>>(DEFAULT_HASH_SIZE);
    565         // initialize to guard if there is no interval date format defined in
    566         // resource files
    567         fFallbackIntervalPattern = "{0} \u2013 {1}";
    568 
    569         try {
    570             // Get the correct calendar type
    571             String calendarTypeToUse = locale.getKeywordValue("calendar");
    572             if ( calendarTypeToUse == null ) {
    573                 String[] preferredCalendarTypes =
    574                         Calendar.getKeywordValuesForLocale("calendar", locale, true);
    575                 calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar
    576             }
    577             if ( calendarTypeToUse == null ) {
    578                 calendarTypeToUse = "gregorian"; // fallback
    579             }
    580 
    581             // Instantiate the sink to process the data and the resource bundle
    582             DateIntervalSink sink = new DateIntervalSink(this);
    583             ICUResourceBundle resource =
    584                     (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
    585 
    586             // Get the fallback pattern
    587             String fallbackPattern = resource.getStringWithFallback(CALENDAR_KEY + "/" + calendarTypeToUse
    588                     + "/" + INTERVAL_FORMATS_KEY + "/" + FALLBACK_STRING);
    589             setFallbackIntervalPattern(fallbackPattern);
    590 
    591             // Already loaded calendar types
    592             Set<String> loadedCalendarTypes = new HashSet<String>();
    593 
    594             while (calendarTypeToUse != null) {
    595                 // Throw an exception when a loop is detected
    596                 if (loadedCalendarTypes.contains(calendarTypeToUse)) {
    597                     throw new ICUException("Loop in calendar type fallback: " + calendarTypeToUse);
    598                 }
    599 
    600                 // Register the calendar type to avoid loops
    601                 loadedCalendarTypes.add(calendarTypeToUse);
    602 
    603                 // Get all resources for this calendar type
    604                 String pathToIntervalFormats = CALENDAR_KEY + "/" + calendarTypeToUse;
    605                 resource.getAllItemsWithFallback(pathToIntervalFormats, sink);
    606 
    607                 // Get next calendar type to load if there was an alias pointing at it
    608                 calendarTypeToUse = sink.getAndResetNextCalendarType();
    609             }
    610         } catch ( MissingResourceException e) {
    611             // Will fallback to {data0} - {date1}
    612         }
    613     }
    614 
    615 
    616     /*
    617      * Split interval patterns into 2 part.
    618      * @param intervalPattern  interval pattern
    619      * @return the index in interval pattern which split the pattern into 2 part
    620      */
    621     private static int splitPatternInto2Part(String intervalPattern) {
    622         boolean inQuote = false;
    623         char prevCh = 0;
    624         int count = 0;
    625 
    626         /* repeatedPattern used to record whether a pattern has already seen.
    627            It is a pattern applies to first calendar if it is first time seen,
    628            otherwise, it is a pattern applies to the second calendar
    629          */
    630         int[] patternRepeated = new int[58];
    631 
    632         int PATTERN_CHAR_BASE = 0x41;
    633 
    634         /* loop through the pattern string character by character looking for
    635          * the first repeated pattern letter, which breaks the interval pattern
    636          * into 2 parts.
    637          */
    638         int i;
    639         boolean foundRepetition = false;
    640         for (i = 0; i < intervalPattern.length(); ++i) {
    641             char ch = intervalPattern.charAt(i);
    642 
    643             if (ch != prevCh && count > 0) {
    644                 // check the repeativeness of pattern letter
    645                 int repeated = patternRepeated[prevCh - PATTERN_CHAR_BASE];
    646                 if ( repeated == 0 ) {
    647                     patternRepeated[prevCh - PATTERN_CHAR_BASE] = 1;
    648                 } else {
    649                     foundRepetition = true;
    650                     break;
    651                 }
    652                 count = 0;
    653             }
    654             if (ch == '\'') {
    655                 // Consecutive single quotes are a single quote literal,
    656                 // either outside of quotes or between quotes
    657                 if ((i+1) < intervalPattern.length() &&
    658                     intervalPattern.charAt(i+1) == '\'') {
    659                     ++i;
    660                 } else {
    661                     inQuote = ! inQuote;
    662                 }
    663             }
    664             else if (!inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
    665                         || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
    666                 // ch is a date-time pattern character
    667                 prevCh = ch;
    668                 ++count;
    669             }
    670         }
    671         // check last pattern char, distinguish
    672         // "dd MM" ( no repetition ),
    673         // "d-d"(last char repeated ), and
    674         // "d-d MM" ( repetition found )
    675         if ( count > 0 && foundRepetition == false ) {
    676             if ( patternRepeated[prevCh - PATTERN_CHAR_BASE] == 0 ) {
    677                 count = 0;
    678             }
    679         }
    680         return (i - count);
    681     }
    682 
    683 
    684     /**
    685      * Provides a way for client to build interval patterns.
    686      * User could construct DateIntervalInfo by providing
    687      * a list of skeletons and their patterns.
    688      * <P>
    689      * For example:
    690      * <pre>
    691      * DateIntervalInfo dIntervalInfo = new DateIntervalInfo();
    692      * dIntervalInfo.setIntervalPattern("yMd", Calendar.YEAR, "'from' yyyy-M-d 'to' yyyy-M-d");
    693      * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.MONTH, "'from' yyyy MMM d 'to' MMM d");
    694      * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.DAY, "yyyy MMM d-d");
    695      * dIntervalInfo.setFallbackIntervalPattern("{0} ~ {1}");
    696      * </pre>
    697      *
    698      * Restriction:
    699      * Currently, users can only set interval patterns when the following
    700      * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH,
    701      * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE, and SECOND.
    702      * Interval patterns when other calendar fields are different are
    703      * not supported.
    704      *
    705      * @param skeleton         the skeleton on which interval pattern based
    706      * @param lrgDiffCalUnit   the largest different calendar unit.
    707      * @param intervalPattern  the interval pattern on the largest different
    708      *                         calendar unit.
    709      *                         For example, if lrgDiffCalUnit is
    710      *                         "year", the interval pattern for en_US when year
    711      *                         is different could be "'from' yyyy 'to' yyyy".
    712      * @throws IllegalArgumentException  if setting interval pattern on
    713      *                            a calendar field that is smaller
    714      *                            than the MINIMUM_SUPPORTED_CALENDAR_FIELD
    715      * @throws UnsupportedOperationException  if the object is frozen
    716      */
    717     public void setIntervalPattern(String skeleton,
    718                                    int lrgDiffCalUnit,
    719                                    String intervalPattern)
    720     {
    721         if ( frozen ) {
    722             throw new UnsupportedOperationException("no modification is allowed after DII is frozen");
    723         }
    724         if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
    725             throw new IllegalArgumentException("calendar field is larger than MINIMUM_SUPPORTED_CALENDAR_FIELD");
    726         }
    727         if (fIntervalPatternsReadOnly) {
    728             fIntervalPatterns = cloneIntervalPatterns(fIntervalPatterns);
    729             fIntervalPatternsReadOnly = false;
    730         }
    731         PatternInfo ptnInfo = setIntervalPatternInternally(skeleton,
    732                           CALENDAR_FIELD_TO_PATTERN_LETTER[lrgDiffCalUnit],
    733                           intervalPattern);
    734         if ( lrgDiffCalUnit == Calendar.HOUR_OF_DAY ) {
    735             setIntervalPattern(skeleton,
    736                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM],
    737                                ptnInfo);
    738             setIntervalPattern(skeleton,
    739                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR],
    740                                ptnInfo);
    741         } else if ( lrgDiffCalUnit == Calendar.DAY_OF_MONTH ||
    742                     lrgDiffCalUnit == Calendar.DAY_OF_WEEK ) {
    743             setIntervalPattern(skeleton,
    744                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE],
    745                                ptnInfo);
    746         }
    747     }
    748 
    749 
    750     /* Set Interval pattern.
    751      *
    752      * It generates the interval pattern info,
    753      * afer which, not only sets the interval pattern info into the hash map,
    754      * but also returns the interval pattern info to the caller
    755      * so that caller can re-use it.
    756      *
    757      * @param skeleton         skeleton on which the interval pattern based
    758      * @param lrgDiffCalUnit   the largest different calendar unit.
    759      * @param intervalPattern  the interval pattern on the largest different
    760      *                         calendar unit.
    761      * @return the interval pattern pattern information
    762      */
    763     private PatternInfo setIntervalPatternInternally(String skeleton,
    764                                                 String lrgDiffCalUnit,
    765                                                 String intervalPattern) {
    766         Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
    767         boolean emptyHash = false;
    768         if (patternsOfOneSkeleton == null) {
    769             patternsOfOneSkeleton = new HashMap<String, PatternInfo>();
    770             emptyHash = true;
    771         }
    772         boolean order = fFirstDateInPtnIsLaterDate;
    773         // check for "latestFirst:" or "earliestFirst:" prefix
    774         if ( intervalPattern.startsWith(LATEST_FIRST_PREFIX) ) {
    775             order = true;
    776             int prefixLength = LATEST_FIRST_PREFIX.length();
    777             intervalPattern = intervalPattern.substring(prefixLength, intervalPattern.length());
    778         } else if ( intervalPattern.startsWith(EARLIEST_FIRST_PREFIX) ) {
    779             order = false;
    780             int earliestFirstLength = EARLIEST_FIRST_PREFIX.length();
    781             intervalPattern = intervalPattern.substring(earliestFirstLength, intervalPattern.length());
    782         }
    783         PatternInfo itvPtnInfo = genPatternInfo(intervalPattern, order);
    784 
    785         patternsOfOneSkeleton.put(lrgDiffCalUnit, itvPtnInfo);
    786         if ( emptyHash == true ) {
    787             fIntervalPatterns.put(skeleton, patternsOfOneSkeleton);
    788         }
    789 
    790         return itvPtnInfo;
    791     }
    792 
    793 
    794     /* Set Interval pattern.
    795      *
    796      * @param skeleton         skeleton on which the interval pattern based
    797      * @param lrgDiffCalUnit   the largest different calendar unit.
    798      * @param ptnInfo          interval pattern infomration
    799      */
    800     private void setIntervalPattern(String skeleton,
    801                                     String lrgDiffCalUnit,
    802                                     PatternInfo ptnInfo) {
    803         Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
    804         patternsOfOneSkeleton.put(lrgDiffCalUnit, ptnInfo);
    805     }
    806 
    807 
    808     /**
    809      * Break interval patterns as 2 part and save them into pattern info.
    810      * @param intervalPattern  interval pattern
    811      * @param laterDateFirst   whether the first date in intervalPattern
    812      *                         is earlier date or later date
    813      * @return                 pattern info object
    814      * @deprecated This API is ICU internal only.
    815      * @hide original deprecated declaration
    816      * @hide draft / provisional / internal are hidden on Android
    817      */
    818     @Deprecated
    819     public static PatternInfo genPatternInfo(String intervalPattern,
    820                                       boolean laterDateFirst) {
    821         int splitPoint = splitPatternInto2Part(intervalPattern);
    822 
    823         String firstPart = intervalPattern.substring(0, splitPoint);
    824         String secondPart = null;
    825         if ( splitPoint < intervalPattern.length() ) {
    826             secondPart = intervalPattern.substring(splitPoint, intervalPattern.length());
    827         }
    828 
    829         return new PatternInfo(firstPart, secondPart, laterDateFirst);
    830     }
    831 
    832 
    833     /**
    834      * Get the interval pattern given the largest different calendar field.
    835      * @param skeleton   the skeleton
    836      * @param field      the largest different calendar field
    837      * @return interval pattern  return null if interval pattern is not found.
    838      * @throws IllegalArgumentException  if getting interval pattern on
    839      *                            a calendar field that is smaller
    840      *                            than the MINIMUM_SUPPORTED_CALENDAR_FIELD
    841      */
    842     public PatternInfo getIntervalPattern(String skeleton, int field)
    843     {
    844         if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
    845             throw new IllegalArgumentException("no support for field less than SECOND");
    846         }
    847         Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
    848         if ( patternsOfOneSkeleton != null ) {
    849             PatternInfo intervalPattern = patternsOfOneSkeleton.
    850                 get(CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
    851             if ( intervalPattern != null ) {
    852                 return intervalPattern;
    853             }
    854         }
    855         return null;
    856     }
    857 
    858 
    859 
    860     /**
    861      * Get the fallback interval pattern.
    862      * @return fallback interval pattern
    863      */
    864     public String getFallbackIntervalPattern()
    865     {
    866         return fFallbackIntervalPattern;
    867     }
    868 
    869 
    870     /**
    871      * Re-set the fallback interval pattern.
    872      *
    873      * In construction, default fallback pattern is set as "{0} - {1}".
    874      * And constructor taking locale as parameter will set the
    875      * fallback pattern as what defined in the locale resource file.
    876      *
    877      * This method provides a way for user to replace the fallback pattern.
    878      *
    879      * @param fallbackPattern                 fall-back interval pattern.
    880      * @throws UnsupportedOperationException  if the object is frozen
    881      * @throws IllegalArgumentException       if there is no pattern {0} or
    882      *                                        pattern {1} in fallbakckPattern
    883      */
    884     public void setFallbackIntervalPattern(String fallbackPattern)
    885     {
    886         if ( frozen ) {
    887             throw new UnsupportedOperationException("no modification is allowed after DII is frozen");
    888         }
    889         int firstPatternIndex = fallbackPattern.indexOf("{0}");
    890         int secondPatternIndex = fallbackPattern.indexOf("{1}");
    891         if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) {
    892             throw new IllegalArgumentException("no pattern {0} or pattern {1} in fallbackPattern");
    893         }
    894         if ( firstPatternIndex > secondPatternIndex ) {
    895             fFirstDateInPtnIsLaterDate = true;
    896         }
    897         fFallbackIntervalPattern = fallbackPattern;
    898     }
    899 
    900 
    901     /**
    902      * Get default order -- whether the first date in pattern is later date
    903      *                      or not.
    904      *
    905      * return default date ordering in interval pattern. TRUE if the first date
    906      *        in pattern is later date, FALSE otherwise.
    907      */
    908     public boolean getDefaultOrder()
    909     {
    910         return fFirstDateInPtnIsLaterDate;
    911     }
    912 
    913 
    914     /**
    915      * Clone this object.
    916      * @return     a copy of the object
    917      */
    918     @Override
    919     public Object clone()
    920     {
    921         if ( frozen ) {
    922             return this;
    923         }
    924         return cloneUnfrozenDII();
    925     }
    926 
    927 
    928     /*
    929      * Clone an unfrozen DateIntervalInfo object.
    930      * @return     a copy of the object
    931      */
    932     private Object cloneUnfrozenDII() //throws IllegalStateException
    933     {
    934         try {
    935             DateIntervalInfo other = (DateIntervalInfo) super.clone();
    936             other.fFallbackIntervalPattern=fFallbackIntervalPattern;
    937             other.fFirstDateInPtnIsLaterDate = fFirstDateInPtnIsLaterDate;
    938             if (fIntervalPatternsReadOnly) {
    939                 other.fIntervalPatterns = fIntervalPatterns;
    940                 other.fIntervalPatternsReadOnly = true;
    941             } else {
    942                 other.fIntervalPatterns = cloneIntervalPatterns(fIntervalPatterns);
    943                 other.fIntervalPatternsReadOnly = false;
    944             }
    945             other.frozen = false;
    946             return other;
    947         } catch ( CloneNotSupportedException e ) {
    948             ///CLOVER:OFF
    949             throw new  ICUCloneNotSupportedException("clone is not supported", e);
    950             ///CLOVER:ON
    951         }
    952     }
    953 
    954     private static Map<String, Map<String, PatternInfo>> cloneIntervalPatterns(
    955             Map<String, Map<String, PatternInfo>> patterns) {
    956         Map<String, Map<String, PatternInfo>> result = new HashMap<String, Map<String, PatternInfo>>();
    957         for (Entry<String, Map<String, PatternInfo>> skeletonEntry : patterns.entrySet()) {
    958             String skeleton = skeletonEntry.getKey();
    959             Map<String, PatternInfo> patternsOfOneSkeleton = skeletonEntry.getValue();
    960             Map<String, PatternInfo> oneSetPtn = new HashMap<String, PatternInfo>();
    961             for (Entry<String, PatternInfo> calEntry : patternsOfOneSkeleton.entrySet()) {
    962                 String calField = calEntry.getKey();
    963                 PatternInfo value = calEntry.getValue();
    964                 oneSetPtn.put(calField, value);
    965             }
    966             result.put(skeleton, oneSetPtn);
    967         }
    968         return result;
    969     }
    970 
    971 
    972 
    973     /**
    974      * {@inheritDoc}
    975      */
    976     @Override
    977     public boolean isFrozen() {
    978         return frozen;
    979     }
    980 
    981     /**
    982      * {@inheritDoc}
    983      */
    984     @Override
    985     public DateIntervalInfo freeze() {
    986         fIntervalPatternsReadOnly = true;
    987         frozen = true;
    988         return this;
    989     }
    990 
    991     /**
    992      * {@inheritDoc}
    993      */
    994     @Override
    995     public DateIntervalInfo cloneAsThawed() {
    996         DateIntervalInfo result = (DateIntervalInfo) (this.cloneUnfrozenDII());
    997         return result;
    998     }
    999 
   1000 
   1001     /**
   1002      * Parse skeleton, save each field's width.
   1003      * It is used for looking for best match skeleton,
   1004      * and adjust pattern field width.
   1005      * @param skeleton            skeleton to be parsed
   1006      * @param skeletonFieldWidth  parsed skeleton field width
   1007      */
   1008     static void parseSkeleton(String skeleton, int[] skeletonFieldWidth) {
   1009         int PATTERN_CHAR_BASE = 0x41;
   1010         for ( int i = 0; i < skeleton.length(); ++i ) {
   1011             ++skeletonFieldWidth[skeleton.charAt(i) - PATTERN_CHAR_BASE];
   1012         }
   1013     }
   1014 
   1015 
   1016 
   1017     /*
   1018      * Check whether one field width is numeric while the other is string.
   1019      *
   1020      * TODO (xji): make it general
   1021      *
   1022      * @param fieldWidth          one field width
   1023      * @param anotherFieldWidth   another field width
   1024      * @param patternLetter       pattern letter char
   1025      * @return true if one field width is numeric and the other is string,
   1026      *         false otherwise.
   1027      */
   1028     private static boolean stringNumeric(int fieldWidth,
   1029                                          int anotherFieldWidth,
   1030                                          char patternLetter) {
   1031         if ( patternLetter == 'M' ) {
   1032             if ( fieldWidth <= 2 && anotherFieldWidth > 2 ||
   1033                  fieldWidth > 2 && anotherFieldWidth <= 2 ) {
   1034                 return true;
   1035             }
   1036         }
   1037         return false;
   1038     }
   1039 
   1040 
   1041     /*
   1042      * given an input skeleton, get the best match skeleton
   1043      * which has pre-defined interval pattern in resource file.
   1044      *
   1045      * TODO (xji): set field weight or
   1046      *             isolate the funtionality in DateTimePatternGenerator
   1047      * @param  inputSkeleton        input skeleton
   1048      * @return 0, if there is exact match for input skeleton
   1049      *         1, if there is only field width difference between
   1050      *            the best match and the input skeleton
   1051      *         2, the only field difference is 'v' and 'z'
   1052      *        -1, if there is calendar field difference between
   1053      *            the best match and the input skeleton
   1054      */
   1055     DateIntervalFormat.BestMatchInfo getBestSkeleton(String inputSkeleton) {
   1056         String bestSkeleton = inputSkeleton;
   1057         int[] inputSkeletonFieldWidth = new int[58];
   1058         int[] skeletonFieldWidth = new int[58];
   1059 
   1060         final int DIFFERENT_FIELD = 0x1000;
   1061         final int STRING_NUMERIC_DIFFERENCE = 0x100;
   1062         final int BASE = 0x41;
   1063 
   1064         // TODO: this is a hack for 'v' and 'z'
   1065         // resource bundle only have time skeletons ending with 'v',
   1066         // but not for time skeletons ending with 'z'.
   1067         boolean replaceZWithV = false;
   1068         if ( inputSkeleton.indexOf('z') != -1 ) {
   1069             inputSkeleton = inputSkeleton.replace('z', 'v');
   1070             replaceZWithV = true;
   1071         }
   1072 
   1073         parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
   1074         int bestDistance = Integer.MAX_VALUE;
   1075         // 0 means exact the same skeletons;
   1076         // 1 means having the same field, but with different length,
   1077         // 2 means only z/v differs
   1078         // -1 means having different field.
   1079         int bestFieldDifference = 0;
   1080         for (String skeleton : fIntervalPatterns.keySet()) {
   1081             // clear skeleton field width
   1082             for ( int i = 0; i < skeletonFieldWidth.length; ++i ) {
   1083                 skeletonFieldWidth[i] = 0;
   1084             }
   1085             parseSkeleton(skeleton, skeletonFieldWidth);
   1086             // calculate distance
   1087             int distance = 0;
   1088             int fieldDifference = 1;
   1089             for ( int i = 0; i < inputSkeletonFieldWidth.length; ++i ) {
   1090                 int inputFieldWidth = inputSkeletonFieldWidth[i];
   1091                 int fieldWidth = skeletonFieldWidth[i];
   1092                 if ( inputFieldWidth == fieldWidth ) {
   1093                     continue;
   1094                 }
   1095                 if ( inputFieldWidth == 0 ) {
   1096                     fieldDifference = -1;
   1097                     distance += DIFFERENT_FIELD;
   1098                 } else if ( fieldWidth == 0 ) {
   1099                     fieldDifference = -1;
   1100                     distance += DIFFERENT_FIELD;
   1101                 } else if (stringNumeric(inputFieldWidth, fieldWidth,
   1102                                          (char)(i+BASE) ) ) {
   1103                     distance += STRING_NUMERIC_DIFFERENCE;
   1104                 } else {
   1105                     distance += Math.abs(inputFieldWidth - fieldWidth);
   1106                 }
   1107             }
   1108             if ( distance < bestDistance ) {
   1109                 bestSkeleton = skeleton;
   1110                 bestDistance = distance;
   1111                 bestFieldDifference = fieldDifference;
   1112             }
   1113             if ( distance == 0 ) {
   1114                 bestFieldDifference = 0;
   1115                 break;
   1116             }
   1117         }
   1118         if ( replaceZWithV && bestFieldDifference != -1 ) {
   1119             bestFieldDifference = 2;
   1120         }
   1121         return new DateIntervalFormat.BestMatchInfo(bestSkeleton, bestFieldDifference);
   1122     }
   1123 
   1124     /**
   1125      * Override equals
   1126      */
   1127     @Override
   1128     public boolean equals(Object a) {
   1129         if ( a instanceof DateIntervalInfo ) {
   1130             DateIntervalInfo dtInfo = (DateIntervalInfo)a;
   1131             return fIntervalPatterns.equals(dtInfo.fIntervalPatterns);
   1132         }
   1133         return false;
   1134     }
   1135 
   1136     /**
   1137      * Override hashcode
   1138      */
   1139     @Override
   1140     public int hashCode() {
   1141         return fIntervalPatterns.hashCode();
   1142     }
   1143 
   1144     /**
   1145      * @deprecated This API is ICU internal only.
   1146      * @hide original deprecated declaration
   1147      * @hide draft / provisional / internal are hidden on Android
   1148      */
   1149     @Deprecated
   1150     public Map<String,Set<String>> getPatterns() {
   1151         LinkedHashMap<String,Set<String>> result = new LinkedHashMap<String,Set<String>>();
   1152         for (Entry<String, Map<String, PatternInfo>> entry : fIntervalPatterns.entrySet()) {
   1153             result.put(entry.getKey(), new LinkedHashSet<String>(entry.getValue().keySet()));
   1154         }
   1155         return result;
   1156     }
   1157 
   1158     /**
   1159      * Get the internal patterns, with a deep clone for safety.
   1160      * @deprecated This API is ICU internal only.
   1161      * @hide original deprecated declaration
   1162      * @hide draft / provisional / internal are hidden on Android
   1163      */
   1164     @Deprecated
   1165     public Map<String, Map<String, PatternInfo>> getRawPatterns() {
   1166         LinkedHashMap<String, Map<String, PatternInfo>> result = new LinkedHashMap<String, Map<String, PatternInfo>>();
   1167         for (Entry<String, Map<String, PatternInfo>> entry : fIntervalPatterns.entrySet()) {
   1168             result.put(entry.getKey(), new LinkedHashMap<String, PatternInfo>(entry.getValue()));
   1169         }
   1170         return result;
   1171     }
   1172 }// end class DateIntervalInfo
   1173