• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 ohos.global.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.Objects;
23 import java.util.Set;
24 
25 import ohos.global.icu.impl.ICUCache;
26 import ohos.global.icu.impl.ICUData;
27 import ohos.global.icu.impl.ICUResourceBundle;
28 import ohos.global.icu.impl.SimpleCache;
29 import ohos.global.icu.impl.UResource;
30 import ohos.global.icu.impl.UResource.Key;
31 import ohos.global.icu.impl.UResource.Value;
32 import ohos.global.icu.util.Calendar;
33 import ohos.global.icu.util.Freezable;
34 import ohos.global.icu.util.ICUCloneNotSupportedException;
35 import ohos.global.icu.util.ICUException;
36 import ohos.global.icu.util.ULocale;
37 import ohos.global.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, SECOND, and MILLISECOND.
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          */
PatternInfo(String firstPart, String secondPart, boolean firstDateInPtnIsLaterDate)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          */
getFirstPart()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          */
getSecondPart()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          */
firstDateInPtnIsLaterDate()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
equals(Object a)240         public boolean equals(Object a) {
241             if (a instanceof PatternInfo) {
242                 PatternInfo patternInfo = (PatternInfo)a;
243                 return Objects.equals(fIntervalPatternFirstPart, patternInfo.fIntervalPatternFirstPart) &&
244                        Objects.equals(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
hashCode()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          */
269         @Override
toString()270         public String toString() {
271             return "{first=«" + fIntervalPatternFirstPart + "», second=«" + fIntervalPatternSecondPart + "», reversed:" + fFirstDateInPtnIsLaterDate + "}";
272         }
273     }
274 
275     // Following is package protected since
276     // it is shared with DateIntervalFormat.
277     static final String[] CALENDAR_FIELD_TO_PATTERN_LETTER =
278     {
279         "G", "y", "M",
280         "w", "W", "d",
281         "D", "E", "F",
282         "a", "h", "H",
283         "m", "s", "S",  // MINUTE, SECOND, MILLISECOND
284         "z", " ", "Y",  // ZONE_OFFSET, DST_OFFSET, YEAR_WOY
285         "e", "u", "g",  // DOW_LOCAL, EXTENDED_YEAR, JULIAN_DAY
286         "A", " ", " ",  // MILLISECONDS_IN_DAY, IS_LEAP_MONTH.
287     };
288 
289 
290     private static final long serialVersionUID = 1;
291     private static final int MINIMUM_SUPPORTED_CALENDAR_FIELD =
292                                                           Calendar.MILLISECOND;
293     //private static boolean DEBUG = true;
294 
295     private static String CALENDAR_KEY = "calendar";
296     private static String INTERVAL_FORMATS_KEY = "intervalFormats";
297     private static String FALLBACK_STRING = "fallback";
298     private static String LATEST_FIRST_PREFIX = "latestFirst:";
299     private static String EARLIEST_FIRST_PREFIX = "earliestFirst:";
300 
301     // DateIntervalInfo cache
302     private final static ICUCache<String, DateIntervalInfo> DIICACHE = new SimpleCache<>();
303 
304 
305     // default interval pattern on the skeleton, {0} - {1}
306     private String fFallbackIntervalPattern;
307     // default order
308     private boolean fFirstDateInPtnIsLaterDate = false;
309 
310     // HashMap( skeleton, HashMap(largest_different_field, pattern) )
311     private Map<String, Map<String, PatternInfo>> fIntervalPatterns = null;
312 
313     private transient volatile boolean frozen = false;
314 
315     // If true, fIntervalPatterns should not be modified in-place because it
316     // is shared with other objects. Unlike frozen which is always true once
317     // set to true, this field can go from true to false as long as frozen is
318     // false.
319     private transient boolean fIntervalPatternsReadOnly = false;
320 
321 
322     /**
323      * Create empty instance.
324      * It does not initialize any interval patterns except
325      * that it initialize default fall-back pattern as "{0} - {1}",
326      * which can be reset by setFallbackIntervalPattern().
327      *
328      * It should be followed by setFallbackIntervalPattern() and
329      * setIntervalPattern(),
330      * and is recommended to be used only for power users who
331      * wants to create their own interval patterns and use them to create
332      * date interval formatter.
333      * @deprecated This API is ICU internal only.
334      * @hide deprecated on icu4j-org
335      * @hide draft / provisional / internal are hidden on OHOS
336      */
337     @Deprecated
DateIntervalInfo()338     public DateIntervalInfo()
339     {
340         fIntervalPatterns = new HashMap<>();
341         fFallbackIntervalPattern = "{0} \u2013 {1}";
342     }
343 
344 
345     /**
346      * Construct DateIntervalInfo for the given locale,
347      * @param locale  the interval patterns are loaded from the appropriate
348      *                calendar data (specified calendar or default calendar)
349      *                in this locale.
350      */
DateIntervalInfo(ULocale locale)351     public DateIntervalInfo(ULocale locale)
352     {
353         initializeData(locale);
354     }
355 
356 
357     /**
358      * Construct DateIntervalInfo for the given {@link java.util.Locale}.
359      * @param locale  the interval patterns are loaded from the appropriate
360      *                calendar data (specified calendar or default calendar)
361      *                in this locale.
362      */
DateIntervalInfo(Locale locale)363     public DateIntervalInfo(Locale locale)
364     {
365         this(ULocale.forLocale(locale));
366     }
367 
368     /*
369      * Initialize the DateIntervalInfo from locale
370      * @param locale   the given locale.
371      */
initializeData(ULocale locale)372     private void initializeData(ULocale locale)
373     {
374         String key = locale.toString();
375         DateIntervalInfo dii = DIICACHE.get(key);
376         if ( dii == null ) {
377             // initialize data from scratch
378             setup(locale);
379             // Marking fIntervalPatterns read-only makes cloning cheaper.
380             fIntervalPatternsReadOnly = true;
381             // We freeze what goes in the cache without freezing this object.
382             DIICACHE.put(key, ((DateIntervalInfo) clone()).freeze());
383         } else {
384             initializeFromReadOnlyPatterns(dii);
385         }
386     }
387 
388 
389 
390     /**
391      * Initialize this object
392      * @param dii must have read-only fIntervalPatterns.
393      */
initializeFromReadOnlyPatterns(DateIntervalInfo dii)394     private void initializeFromReadOnlyPatterns(DateIntervalInfo dii) {
395         fFallbackIntervalPattern = dii.fFallbackIntervalPattern;
396         fFirstDateInPtnIsLaterDate = dii.fFirstDateInPtnIsLaterDate;
397         fIntervalPatterns = dii.fIntervalPatterns;
398         fIntervalPatternsReadOnly = true;
399     }
400 
401 
402 
403     /**
404      * Sink for enumerating all of the date interval skeletons.
405      */
406     private static final class DateIntervalSink extends UResource.Sink {
407 
408         /**
409          * Accepted pattern letters:
410          * Calendar.YEAR
411          * Calendar.MONTH
412          * Calendar.DATE
413          * Calendar.AM_PM
414          * Calendar.HOUR
415          * Calendar.HOUR_OF_DAY
416          * Calendar.MINUTE
417          * Calendar.SECOND
418          * Calendar.MILLISECOND
419          */
420         private static final String ACCEPTED_PATTERN_LETTERS = "GyMdahHmsS";
421 
422         // Output data
423         DateIntervalInfo dateIntervalInfo;
424 
425         // Alias handling
426         String nextCalendarType;
427 
428         // Constructor
DateIntervalSink(DateIntervalInfo dateIntervalInfo)429         public DateIntervalSink(DateIntervalInfo dateIntervalInfo) {
430             this.dateIntervalInfo = dateIntervalInfo;
431         }
432 
433         @Override
put(Key key, Value value, boolean noFallback)434         public void put(Key key, Value value, boolean noFallback) {
435             // Iterate over all the calendar entries and only pick the 'intervalFormats' table.
436             UResource.Table dateIntervalData = value.getTable();
437             for (int i = 0; dateIntervalData.getKeyAndValue(i, key, value); i++) {
438                 if (!key.contentEquals(INTERVAL_FORMATS_KEY)) {
439                     continue;
440                 }
441 
442                 // Handle aliases and tables. Ignore the rest.
443                 if (value.getType() == ICUResourceBundle.ALIAS) {
444                     // Get the calendar type from the alias path.
445                     nextCalendarType = getCalendarTypeFromPath(value.getAliasString());
446                     break;
447 
448                 } else if (value.getType() == ICUResourceBundle.TABLE) {
449                     // Iterate over all the skeletons in the 'intervalFormat' table.
450                     UResource.Table skeletonData = value.getTable();
451                     for (int j = 0; skeletonData.getKeyAndValue(j, key, value); j++) {
452                         if (value.getType() == ICUResourceBundle.TABLE) {
453                             // Process the skeleton
454                             processSkeletonTable(key, value);
455                         }
456                     }
457                     break;
458                 }
459             }
460         }
461 
462         /** Processes the patterns for a skeleton table. */
processSkeletonTable(Key key, Value value)463         public void processSkeletonTable(Key key, Value value) {
464             // Iterate over all the patterns in the current skeleton table
465             String currentSkeleton = key.toString();
466             UResource.Table patternData = value.getTable();
467             for (int k = 0; patternData.getKeyAndValue(k, key, value); k++) {
468                 if (value.getType() == ICUResourceBundle.STRING) {
469                     // Process the key
470                     CharSequence patternLetter = validateAndProcessPatternLetter(key);
471 
472                     // If the calendar field has a valid value
473                     if (patternLetter != null) {
474                         // Get the largest different calendar unit
475                         String lrgDiffCalUnit = patternLetter.toString();
476 
477                         // Set the interval pattern
478                         setIntervalPatternIfAbsent(currentSkeleton, lrgDiffCalUnit, value);
479                     }
480                 }
481             }
482         }
483 
484         /**
485          * Returns and resets the next calendar type.
486          * @return Next calendar type
487          */
getAndResetNextCalendarType()488         public String getAndResetNextCalendarType() {
489             String tmpCalendarType = nextCalendarType;
490             nextCalendarType = null;
491             return tmpCalendarType;
492         }
493 
494         // Alias' path prefix and suffix.
495         private static final String DATE_INTERVAL_PATH_PREFIX =
496             "/LOCALE/" + CALENDAR_KEY + "/";
497         private static final String DATE_INTERVAL_PATH_SUFFIX =
498             "/" + INTERVAL_FORMATS_KEY;
499 
500         /**
501          * Extracts the calendar type from the path
502          * @param path
503          * @return Calendar Type
504          */
getCalendarTypeFromPath(String path)505         private String getCalendarTypeFromPath(String path) {
506             if (path.startsWith(DATE_INTERVAL_PATH_PREFIX) &&
507                     path.endsWith(DATE_INTERVAL_PATH_SUFFIX)) {
508                 return path.substring(DATE_INTERVAL_PATH_PREFIX.length(),
509                     path.length() - DATE_INTERVAL_PATH_SUFFIX.length());
510             }
511             throw new ICUException("Malformed 'intervalFormat' alias path: " + path);
512         }
513 
514         /**
515          * Processes the pattern letter
516          * @param patternLetter
517          * @return Pattern letter
518          */
validateAndProcessPatternLetter(CharSequence patternLetter)519         private CharSequence validateAndProcessPatternLetter(CharSequence patternLetter) {
520             // Check that patternLetter is just one letter
521             if (patternLetter.length() != 1) { return null; }
522 
523             // Check that the pattern letter is accepted
524             char letter = patternLetter.charAt(0);
525             if (ACCEPTED_PATTERN_LETTERS.indexOf(letter) < 0) {
526                 return null;
527             }
528 
529             // Replace 'h' for 'H'
530             if (letter == CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR_OF_DAY].charAt(0)) {
531                 patternLetter = CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR];
532             }
533 
534             return patternLetter;
535         }
536 
537         /**
538          * Stores the interval pattern for the current skeleton in the internal data structure
539          * if it's not present.
540          * @param lrgDiffCalUnit
541          * @param intervalPattern
542          */
setIntervalPatternIfAbsent(String currentSkeleton, String lrgDiffCalUnit, Value intervalPattern)543         private void setIntervalPatternIfAbsent(String currentSkeleton, String lrgDiffCalUnit, Value intervalPattern) {
544             // Check if the pattern has already been stored on the data structure.
545             Map<String, PatternInfo> patternsOfOneSkeleton =
546                     dateIntervalInfo.fIntervalPatterns.get(currentSkeleton);
547             if (patternsOfOneSkeleton == null || !patternsOfOneSkeleton.containsKey(lrgDiffCalUnit)) {
548                 // Store the pattern
549                 dateIntervalInfo.setIntervalPatternInternally(currentSkeleton, lrgDiffCalUnit,
550                         intervalPattern.toString());
551             }
552         }
553     }
554 
555 
556     /*
557      * Initialize DateIntervalInfo from calendar data
558      * @param calData  calendar data
559      */
setup(ULocale locale)560     private void setup(ULocale locale) {
561         int DEFAULT_HASH_SIZE = 19;
562         fIntervalPatterns = new HashMap<>(DEFAULT_HASH_SIZE);
563         // initialize to guard if there is no interval date format defined in
564         // resource files
565         fFallbackIntervalPattern = "{0} \u2013 {1}";
566 
567         try {
568             // Get the correct calendar type
569             String calendarTypeToUse = locale.getKeywordValue("calendar");
570             if ( calendarTypeToUse == null ) {
571                 String[] preferredCalendarTypes =
572                         Calendar.getKeywordValuesForLocale("calendar", locale, true);
573                 calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar
574             }
575             if ( calendarTypeToUse == null ) {
576                 calendarTypeToUse = "gregorian"; // fallback
577             }
578 
579             // Instantiate the sink to process the data and the resource bundle
580             DateIntervalSink sink = new DateIntervalSink(this);
581             ICUResourceBundle resource =
582                     (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
583 
584             // Get the fallback pattern
585             String fallbackPattern = resource.getStringWithFallback(CALENDAR_KEY + "/" + calendarTypeToUse
586                     + "/" + INTERVAL_FORMATS_KEY + "/" + FALLBACK_STRING);
587             setFallbackIntervalPattern(fallbackPattern);
588 
589             // Already loaded calendar types
590             Set<String> loadedCalendarTypes = new HashSet<>();
591 
592             while (calendarTypeToUse != null) {
593                 // Throw an exception when a loop is detected
594                 if (loadedCalendarTypes.contains(calendarTypeToUse)) {
595                     throw new ICUException("Loop in calendar type fallback: " + calendarTypeToUse);
596                 }
597 
598                 // Register the calendar type to avoid loops
599                 loadedCalendarTypes.add(calendarTypeToUse);
600 
601                 // Get all resources for this calendar type
602                 String pathToIntervalFormats = CALENDAR_KEY + "/" + calendarTypeToUse;
603                 resource.getAllItemsWithFallback(pathToIntervalFormats, sink);
604 
605                 // Get next calendar type to load if there was an alias pointing at it
606                 calendarTypeToUse = sink.getAndResetNextCalendarType();
607             }
608         } catch ( MissingResourceException e) {
609             // Will fallback to {data0} - {date1}
610         }
611     }
612 
613 
614     /*
615      * Split interval patterns into 2 part.
616      * @param intervalPattern  interval pattern
617      * @return the index in interval pattern which split the pattern into 2 part
618      */
splitPatternInto2Part(String intervalPattern)619     private static int splitPatternInto2Part(String intervalPattern) {
620         boolean inQuote = false;
621         char prevCh = 0;
622         int count = 0;
623 
624         /* repeatedPattern used to record whether a pattern has already seen.
625            It is a pattern applies to first calendar if it is first time seen,
626            otherwise, it is a pattern applies to the second calendar
627          */
628         int[] patternRepeated = new int[58];
629 
630         int PATTERN_CHAR_BASE = 0x41;
631 
632         /* loop through the pattern string character by character looking for
633          * the first repeated pattern letter, which breaks the interval pattern
634          * into 2 parts.
635          */
636         int i;
637         boolean foundRepetition = false;
638         for (i = 0; i < intervalPattern.length(); ++i) {
639             char ch = intervalPattern.charAt(i);
640 
641             if (ch != prevCh && count > 0) {
642                 // check the repeativeness of pattern letter
643                 int repeated = patternRepeated[prevCh - PATTERN_CHAR_BASE];
644                 if ( repeated == 0 ) {
645                     patternRepeated[prevCh - PATTERN_CHAR_BASE] = 1;
646                 } else {
647                     foundRepetition = true;
648                     break;
649                 }
650                 count = 0;
651             }
652             if (ch == '\'') {
653                 // Consecutive single quotes are a single quote literal,
654                 // either outside of quotes or between quotes
655                 if ((i+1) < intervalPattern.length() &&
656                     intervalPattern.charAt(i+1) == '\'') {
657                     ++i;
658                 } else {
659                     inQuote = ! inQuote;
660                 }
661             }
662             else if (!inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
663                         || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
664                 // ch is a date-time pattern character
665                 prevCh = ch;
666                 ++count;
667             }
668         }
669         // check last pattern char, distinguish
670         // "dd MM" ( no repetition ),
671         // "d-d"(last char repeated ), and
672         // "d-d MM" ( repetition found )
673         if ( count > 0 && foundRepetition == false ) {
674             if ( patternRepeated[prevCh - PATTERN_CHAR_BASE] == 0 ) {
675                 count = 0;
676             }
677         }
678         return (i - count);
679     }
680 
681 
682     /**
683      * Provides a way for client to build interval patterns.
684      * User could construct DateIntervalInfo by providing
685      * a list of skeletons and their patterns.
686      * <P>
687      * For example:
688      * <pre>
689      * DateIntervalInfo dIntervalInfo = new DateIntervalInfo();
690      * dIntervalInfo.setIntervalPattern("yMd", Calendar.YEAR, "'from' yyyy-M-d 'to' yyyy-M-d");
691      * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.MONTH, "'from' yyyy MMM d 'to' MMM d");
692      * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.DAY, "yyyy MMM d-d");
693      * dIntervalInfo.setFallbackIntervalPattern("{0} ~ {1}");
694      * </pre>
695      *
696      * Restriction:
697      * Currently, users can only set interval patterns when the following
698      * calendar fields are different: ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH,
699      * DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND.
700      * Interval patterns when other calendar fields are different are
701      * not supported.
702      *
703      * @param skeleton         the skeleton on which interval pattern based
704      * @param lrgDiffCalUnit   the largest different calendar unit.
705      * @param intervalPattern  the interval pattern on the largest different
706      *                         calendar unit.
707      *                         For example, if lrgDiffCalUnit is
708      *                         "year", the interval pattern for en_US when year
709      *                         is different could be "'from' yyyy 'to' yyyy".
710      * @throws IllegalArgumentException  if setting interval pattern on
711      *                            a calendar field that is smaller
712      *                            than the MINIMUM_SUPPORTED_CALENDAR_FIELD
713      * @throws UnsupportedOperationException  if the object is frozen
714      */
setIntervalPattern(String skeleton, int lrgDiffCalUnit, String intervalPattern)715     public void setIntervalPattern(String skeleton,
716                                    int lrgDiffCalUnit,
717                                    String intervalPattern)
718     {
719         if ( frozen ) {
720             throw new UnsupportedOperationException("no modification is allowed after DII is frozen");
721         }
722         if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
723             throw new IllegalArgumentException("calendar field is larger than MINIMUM_SUPPORTED_CALENDAR_FIELD");
724         }
725         if (fIntervalPatternsReadOnly) {
726             fIntervalPatterns = cloneIntervalPatterns(fIntervalPatterns);
727             fIntervalPatternsReadOnly = false;
728         }
729         PatternInfo ptnInfo = setIntervalPatternInternally(skeleton,
730                           CALENDAR_FIELD_TO_PATTERN_LETTER[lrgDiffCalUnit],
731                           intervalPattern);
732         if ( lrgDiffCalUnit == Calendar.HOUR_OF_DAY ) {
733             setIntervalPattern(skeleton,
734                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM],
735                                ptnInfo);
736             setIntervalPattern(skeleton,
737                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR],
738                                ptnInfo);
739         } else if ( lrgDiffCalUnit == Calendar.DAY_OF_MONTH ||
740                     lrgDiffCalUnit == Calendar.DAY_OF_WEEK ) {
741             setIntervalPattern(skeleton,
742                                CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE],
743                                ptnInfo);
744         }
745     }
746 
747 
748     /* Set Interval pattern.
749      *
750      * It generates the interval pattern info,
751      * afer which, not only sets the interval pattern info into the hash map,
752      * but also returns the interval pattern info to the caller
753      * so that caller can re-use it.
754      *
755      * @param skeleton         skeleton on which the interval pattern based
756      * @param lrgDiffCalUnit   the largest different calendar unit.
757      * @param intervalPattern  the interval pattern on the largest different
758      *                         calendar unit.
759      * @return the interval pattern pattern information
760      */
setIntervalPatternInternally(String skeleton, String lrgDiffCalUnit, String intervalPattern)761     private PatternInfo setIntervalPatternInternally(String skeleton,
762                                                 String lrgDiffCalUnit,
763                                                 String intervalPattern) {
764         Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
765         boolean emptyHash = false;
766         if (patternsOfOneSkeleton == null) {
767             patternsOfOneSkeleton = new HashMap<>();
768             emptyHash = true;
769         }
770         boolean order = fFirstDateInPtnIsLaterDate;
771         // check for "latestFirst:" or "earliestFirst:" prefix
772         if ( intervalPattern.startsWith(LATEST_FIRST_PREFIX) ) {
773             order = true;
774             int prefixLength = LATEST_FIRST_PREFIX.length();
775             intervalPattern = intervalPattern.substring(prefixLength, intervalPattern.length());
776         } else if ( intervalPattern.startsWith(EARLIEST_FIRST_PREFIX) ) {
777             order = false;
778             int earliestFirstLength = EARLIEST_FIRST_PREFIX.length();
779             intervalPattern = intervalPattern.substring(earliestFirstLength, intervalPattern.length());
780         }
781         PatternInfo itvPtnInfo = genPatternInfo(intervalPattern, order);
782 
783         patternsOfOneSkeleton.put(lrgDiffCalUnit, itvPtnInfo);
784         if ( emptyHash == true ) {
785             fIntervalPatterns.put(skeleton, patternsOfOneSkeleton);
786         }
787 
788         return itvPtnInfo;
789     }
790 
791 
792     /* Set Interval pattern.
793      *
794      * @param skeleton         skeleton on which the interval pattern based
795      * @param lrgDiffCalUnit   the largest different calendar unit.
796      * @param ptnInfo          interval pattern infomration
797      */
setIntervalPattern(String skeleton, String lrgDiffCalUnit, PatternInfo ptnInfo)798     private void setIntervalPattern(String skeleton,
799                                     String lrgDiffCalUnit,
800                                     PatternInfo ptnInfo) {
801         Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
802         patternsOfOneSkeleton.put(lrgDiffCalUnit, ptnInfo);
803     }
804 
805 
806     /**
807      * Break interval patterns as 2 part and save them into pattern info.
808      * @param intervalPattern  interval pattern
809      * @param laterDateFirst   whether the first date in intervalPattern
810      *                         is earlier date or later date
811      * @return                 pattern info object
812      * @deprecated This API is ICU internal only.
813      * @hide deprecated on icu4j-org
814      * @hide draft / provisional / internal are hidden on OHOS
815      */
816     @Deprecated
genPatternInfo(String intervalPattern, boolean laterDateFirst)817     public static PatternInfo genPatternInfo(String intervalPattern,
818                                       boolean laterDateFirst) {
819         int splitPoint = splitPatternInto2Part(intervalPattern);
820 
821         String firstPart = intervalPattern.substring(0, splitPoint);
822         String secondPart = null;
823         if ( splitPoint < intervalPattern.length() ) {
824             secondPart = intervalPattern.substring(splitPoint, intervalPattern.length());
825         }
826 
827         return new PatternInfo(firstPart, secondPart, laterDateFirst);
828     }
829 
830 
831     /**
832      * Get the interval pattern given the largest different calendar field.
833      * @param skeleton   the skeleton
834      * @param field      the largest different calendar field
835      * @return interval pattern  return null if interval pattern is not found.
836      * @throws IllegalArgumentException  if getting interval pattern on
837      *                            a calendar field that is smaller
838      *                            than the MINIMUM_SUPPORTED_CALENDAR_FIELD
839      */
getIntervalPattern(String skeleton, int field)840     public PatternInfo getIntervalPattern(String skeleton, int field)
841     {
842         if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
843             throw new IllegalArgumentException("no support for field less than MILLISECOND");
844         }
845         Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
846         if ( patternsOfOneSkeleton != null ) {
847             PatternInfo intervalPattern = patternsOfOneSkeleton.
848                 get(CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
849             if ( intervalPattern != null ) {
850                 return intervalPattern;
851             }
852         }
853         return null;
854     }
855 
856 
857 
858     /**
859      * Get the fallback interval pattern.
860      * @return fallback interval pattern
861      */
getFallbackIntervalPattern()862     public String getFallbackIntervalPattern()
863     {
864         return fFallbackIntervalPattern;
865     }
866 
867 
868     /**
869      * Re-set the fallback interval pattern.
870      *
871      * In construction, default fallback pattern is set as "{0} - {1}".
872      * And constructor taking locale as parameter will set the
873      * fallback pattern as what defined in the locale resource file.
874      *
875      * This method provides a way for user to replace the fallback pattern.
876      *
877      * @param fallbackPattern                 fall-back interval pattern.
878      * @throws UnsupportedOperationException  if the object is frozen
879      * @throws IllegalArgumentException       if there is no pattern {0} or
880      *                                        pattern {1} in fallbakckPattern
881      */
setFallbackIntervalPattern(String fallbackPattern)882     public void setFallbackIntervalPattern(String fallbackPattern)
883     {
884         if ( frozen ) {
885             throw new UnsupportedOperationException("no modification is allowed after DII is frozen");
886         }
887         int firstPatternIndex = fallbackPattern.indexOf("{0}");
888         int secondPatternIndex = fallbackPattern.indexOf("{1}");
889         if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) {
890             throw new IllegalArgumentException("no pattern {0} or pattern {1} in fallbackPattern");
891         }
892         if ( firstPatternIndex > secondPatternIndex ) {
893             fFirstDateInPtnIsLaterDate = true;
894         }
895         fFallbackIntervalPattern = fallbackPattern;
896     }
897 
898 
899     /**
900      * Get default order -- whether the first date in pattern is later date
901      *                      or not.
902      *
903      * return default date ordering in interval pattern. TRUE if the first date
904      *        in pattern is later date, FALSE otherwise.
905      */
getDefaultOrder()906     public boolean getDefaultOrder()
907     {
908         return fFirstDateInPtnIsLaterDate;
909     }
910 
911 
912     /**
913      * Clone this object.
914      * @return     a copy of the object
915      */
916     @Override
clone()917     public Object clone()
918     {
919         if ( frozen ) {
920             return this;
921         }
922         return cloneUnfrozenDII();
923     }
924 
925 
926     /*
927      * Clone an unfrozen DateIntervalInfo object.
928      * @return     a copy of the object
929      */
cloneUnfrozenDII()930     private Object cloneUnfrozenDII() //throws IllegalStateException
931     {
932         try {
933             DateIntervalInfo other = (DateIntervalInfo) super.clone();
934             other.fFallbackIntervalPattern=fFallbackIntervalPattern;
935             other.fFirstDateInPtnIsLaterDate = fFirstDateInPtnIsLaterDate;
936             if (fIntervalPatternsReadOnly) {
937                 other.fIntervalPatterns = fIntervalPatterns;
938                 other.fIntervalPatternsReadOnly = true;
939             } else {
940                 other.fIntervalPatterns = cloneIntervalPatterns(fIntervalPatterns);
941                 other.fIntervalPatternsReadOnly = false;
942             }
943             other.frozen = false;
944             return other;
945         } catch ( CloneNotSupportedException e ) {
946             ///CLOVER:OFF
947             throw new  ICUCloneNotSupportedException("clone is not supported", e);
948             ///CLOVER:ON
949         }
950     }
951 
cloneIntervalPatterns( Map<String, Map<String, PatternInfo>> patterns)952     private static Map<String, Map<String, PatternInfo>> cloneIntervalPatterns(
953             Map<String, Map<String, PatternInfo>> patterns) {
954         Map<String, Map<String, PatternInfo>> result = new HashMap<>();
955         for (Entry<String, Map<String, PatternInfo>> skeletonEntry : patterns.entrySet()) {
956             String skeleton = skeletonEntry.getKey();
957             Map<String, PatternInfo> patternsOfOneSkeleton = skeletonEntry.getValue();
958             Map<String, PatternInfo> oneSetPtn = new HashMap<>();
959             for (Entry<String, PatternInfo> calEntry : patternsOfOneSkeleton.entrySet()) {
960                 String calField = calEntry.getKey();
961                 PatternInfo value = calEntry.getValue();
962                 oneSetPtn.put(calField, value);
963             }
964             result.put(skeleton, oneSetPtn);
965         }
966         return result;
967     }
968 
969 
970 
971     /**
972      * {@inheritDoc}
973      */
974     @Override
isFrozen()975     public boolean isFrozen() {
976         return frozen;
977     }
978 
979     /**
980      * {@inheritDoc}
981      */
982     @Override
freeze()983     public DateIntervalInfo freeze() {
984         fIntervalPatternsReadOnly = true;
985         frozen = true;
986         return this;
987     }
988 
989     /**
990      * {@inheritDoc}
991      */
992     @Override
cloneAsThawed()993     public DateIntervalInfo cloneAsThawed() {
994         DateIntervalInfo result = (DateIntervalInfo) (this.cloneUnfrozenDII());
995         return result;
996     }
997 
998 
999     /**
1000      * Parse skeleton, save each field's width.
1001      * It is used for looking for best match skeleton,
1002      * and adjust pattern field width.
1003      * @param skeleton            skeleton to be parsed
1004      * @param skeletonFieldWidth  parsed skeleton field width
1005      */
parseSkeleton(String skeleton, int[] skeletonFieldWidth)1006     static void parseSkeleton(String skeleton, int[] skeletonFieldWidth) {
1007         int PATTERN_CHAR_BASE = 0x41;
1008         for ( int i = 0; i < skeleton.length(); ++i ) {
1009             ++skeletonFieldWidth[skeleton.charAt(i) - PATTERN_CHAR_BASE];
1010         }
1011     }
1012 
1013 
1014 
1015     /*
1016      * Check whether one field width is numeric while the other is string.
1017      *
1018      * TODO (xji): make it general
1019      *
1020      * @param fieldWidth          one field width
1021      * @param anotherFieldWidth   another field width
1022      * @param patternLetter       pattern letter char
1023      * @return true if one field width is numeric and the other is string,
1024      *         false otherwise.
1025      */
stringNumeric(int fieldWidth, int anotherFieldWidth, char patternLetter)1026     private static boolean stringNumeric(int fieldWidth,
1027                                          int anotherFieldWidth,
1028                                          char patternLetter) {
1029         if ( patternLetter == 'M' ) {
1030             if ( fieldWidth <= 2 && anotherFieldWidth > 2 ||
1031                  fieldWidth > 2 && anotherFieldWidth <= 2 ) {
1032                 return true;
1033             }
1034         }
1035         return false;
1036     }
1037 
1038 
1039     /*
1040      * given an input skeleton, get the best match skeleton
1041      * which has pre-defined interval pattern in resource file.
1042      *
1043      * TODO (xji): set field weight or
1044      *             isolate the funtionality in DateTimePatternGenerator
1045      * @param  inputSkeleton        input skeleton
1046      * @return 0, if there is exact match for input skeleton
1047      *         1, if there is only field width difference between
1048      *            the best match and the input skeleton
1049      *         2, the only field difference is 'v' and 'z'
1050      *        -1, if there is calendar field difference between
1051      *            the best match and the input skeleton
1052      */
getBestSkeleton(String inputSkeleton)1053     DateIntervalFormat.BestMatchInfo getBestSkeleton(String inputSkeleton) {
1054         String bestSkeleton = inputSkeleton;
1055         int[] inputSkeletonFieldWidth = new int[58];
1056         int[] skeletonFieldWidth = new int[58];
1057 
1058         final int DIFFERENT_FIELD = 0x1000;
1059         final int STRING_NUMERIC_DIFFERENCE = 0x100;
1060         final int BASE = 0x41;
1061 
1062         // TODO: this is a hack for 'v' and 'z'
1063         // resource bundle only have time skeletons ending with 'v',
1064         // but not for time skeletons ending with 'z'.
1065         boolean replaceZWithV = false;
1066         if ( inputSkeleton.indexOf('z') != -1 ) {
1067             inputSkeleton = inputSkeleton.replace('z', 'v');
1068             replaceZWithV = true;
1069         }
1070 
1071         parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
1072         int bestDistance = Integer.MAX_VALUE;
1073         // 0 means exact the same skeletons;
1074         // 1 means having the same field, but with different length,
1075         // 2 means only z/v differs
1076         // -1 means having different field.
1077         int bestFieldDifference = 0;
1078         for (String skeleton : fIntervalPatterns.keySet()) {
1079             // clear skeleton field width
1080             for ( int i = 0; i < skeletonFieldWidth.length; ++i ) {
1081                 skeletonFieldWidth[i] = 0;
1082             }
1083             parseSkeleton(skeleton, skeletonFieldWidth);
1084             // calculate distance
1085             int distance = 0;
1086             int fieldDifference = 1;
1087             for ( int i = 0; i < inputSkeletonFieldWidth.length; ++i ) {
1088                 int inputFieldWidth = inputSkeletonFieldWidth[i];
1089                 int fieldWidth = skeletonFieldWidth[i];
1090                 if ( inputFieldWidth == fieldWidth ) {
1091                     continue;
1092                 }
1093                 if ( inputFieldWidth == 0 ) {
1094                     fieldDifference = -1;
1095                     distance += DIFFERENT_FIELD;
1096                 } else if ( fieldWidth == 0 ) {
1097                     fieldDifference = -1;
1098                     distance += DIFFERENT_FIELD;
1099                 } else if (stringNumeric(inputFieldWidth, fieldWidth,
1100                                          (char)(i+BASE) ) ) {
1101                     distance += STRING_NUMERIC_DIFFERENCE;
1102                 } else {
1103                     distance += Math.abs(inputFieldWidth - fieldWidth);
1104                 }
1105             }
1106             if ( distance < bestDistance ) {
1107                 bestSkeleton = skeleton;
1108                 bestDistance = distance;
1109                 bestFieldDifference = fieldDifference;
1110             }
1111             if ( distance == 0 ) {
1112                 bestFieldDifference = 0;
1113                 break;
1114             }
1115         }
1116         if ( replaceZWithV && bestFieldDifference != -1 ) {
1117             bestFieldDifference = 2;
1118         }
1119         return new DateIntervalFormat.BestMatchInfo(bestSkeleton, bestFieldDifference);
1120     }
1121 
1122     /**
1123      * Override equals
1124      */
1125     @Override
equals(Object a)1126     public boolean equals(Object a) {
1127         if ( a instanceof DateIntervalInfo ) {
1128             DateIntervalInfo dtInfo = (DateIntervalInfo)a;
1129             return fIntervalPatterns.equals(dtInfo.fIntervalPatterns);
1130         }
1131         return false;
1132     }
1133 
1134     /**
1135      * Override hashcode
1136      */
1137     @Override
hashCode()1138     public int hashCode() {
1139         return fIntervalPatterns.hashCode();
1140     }
1141 
1142     /**
1143      * @deprecated This API is ICU internal only.
1144      * @hide deprecated on icu4j-org
1145      * @hide draft / provisional / internal are hidden on OHOS
1146      */
1147     @Deprecated
getPatterns()1148     public Map<String,Set<String>> getPatterns() {
1149         LinkedHashMap<String,Set<String>> result = new LinkedHashMap<>();
1150         for (Entry<String, Map<String, PatternInfo>> entry : fIntervalPatterns.entrySet()) {
1151             result.put(entry.getKey(), new LinkedHashSet<>(entry.getValue().keySet()));
1152         }
1153         return result;
1154     }
1155 
1156     /**
1157      * Get the internal patterns, with a deep clone for safety.
1158      * @deprecated This API is ICU internal only.
1159      * @hide deprecated on icu4j-org
1160      * @hide draft / provisional / internal are hidden on OHOS
1161      */
1162     @Deprecated
getRawPatterns()1163     public Map<String, Map<String, PatternInfo>> getRawPatterns() {
1164         LinkedHashMap<String, Map<String, PatternInfo>> result = new LinkedHashMap<>();
1165         for (Entry<String, Map<String, PatternInfo>> entry : fIntervalPatterns.entrySet()) {
1166             result.put(entry.getKey(), new LinkedHashMap<>(entry.getValue()));
1167         }
1168         return result;
1169     }
1170 }// end class DateIntervalInfo
1171