• 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 *   Copyright (C) 2008-2016, International Business Machines
6 *   Corporation and others.  All Rights Reserved.
7 */
8 
9 package ohos.global.icu.text;
10 
11 import java.io.IOException;
12 import java.io.InvalidObjectException;
13 import java.io.ObjectInputStream;
14 import java.text.AttributedCharacterIterator;
15 import java.text.FieldPosition;
16 import java.text.ParsePosition;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Locale;
22 import java.util.Map;
23 
24 import ohos.global.icu.impl.FormattedValueFieldPositionIteratorImpl;
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.SimpleFormatterImpl;
30 import ohos.global.icu.impl.Utility;
31 import ohos.global.icu.text.DateIntervalInfo.PatternInfo;
32 import ohos.global.icu.util.Calendar;
33 import ohos.global.icu.util.DateInterval;
34 import ohos.global.icu.util.Output;
35 import ohos.global.icu.util.TimeZone;
36 import ohos.global.icu.util.ULocale;
37 import ohos.global.icu.util.ULocale.Category;
38 import ohos.global.icu.util.UResourceBundle;
39 
40 
41 /**
42  * DateIntervalFormat is a class for formatting and parsing date
43  * intervals in a language-independent manner.
44  * Only formatting is supported. Parsing is not supported.
45  *
46  * <P>
47  * Date interval means from one date to another date,
48  * for example, from "Jan 11, 2008" to "Jan 18, 2008".
49  * We introduced class DateInterval to represent it.
50  * DateInterval is a pair of UDate, which is
51  * the standard milliseconds since 24:00 GMT, Jan 1, 1970.
52  *
53  * <P>
54  * DateIntervalFormat formats a DateInterval into
55  * text as compactly as possible.
56  * For example, the date interval format from "Jan 11, 2008" to "Jan 18,. 2008"
57  * is "Jan 11-18, 2008" for English.
58  * And it parses text into DateInterval,
59  * although initially, parsing is not supported.
60  *
61  * <P>
62  * There is no structural information in date time patterns.
63  * For any punctuations and string literals inside a date time pattern,
64  * we do not know whether it is just a separator, or a prefix, or a suffix.
65  * Without such information, so, it is difficult to generate a sub-pattern
66  * (or super-pattern) by algorithm.
67  * So, formatting a DateInterval is pattern-driven. It is very
68  * similar to formatting in SimpleDateFormat.
69  * We introduce class DateIntervalInfo to save date interval
70  * patterns, similar to date time pattern in SimpleDateFormat.
71  *
72  * <P>
73  * Logically, the interval patterns are mappings
74  * from (skeleton, the_largest_different_calendar_field)
75  * to (date_interval_pattern).
76  *
77  * <P>
78  * A skeleton
79  * <ol>
80  * <li>
81  * only keeps the field pattern letter and ignores all other parts
82  * in a pattern, such as space, punctuations, and string literals.
83  * <li>
84  * hides the order of fields.
85  * <li>
86  * might hide a field's pattern letter length.
87  *
88  * For those non-digit calendar fields, the pattern letter length is
89  * important, such as MMM, MMMM, and MMMMM; EEE and EEEE,
90  * and the field's pattern letter length is honored.
91  *
92  * For the digit calendar fields,  such as M or MM, d or dd, yy or yyyy,
93  * the field pattern length is ignored and the best match, which is defined
94  * in date time patterns, will be returned without honor the field pattern
95  * letter length in skeleton.
96  * </ol>
97  *
98  * <P>
99  * The calendar fields we support for interval formatting are:
100  * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and
101  * second (though we do not currently have specific intervalFormat data for
102  * skeletons with seconds).
103  * Those calendar fields can be defined in the following order:
104  * year &gt; month &gt; date &gt; hour (in day) &gt; minute &gt; second
105  *
106  * The largest different calendar fields between 2 calendars is the
107  * first different calendar field in above order.
108  *
109  * For example: the largest different calendar fields between "Jan 10, 2007"
110  * and "Feb 20, 2008" is year.
111  *
112  * <P>
113  * For other calendar fields, the compact interval formatting is not
114  * supported. And the interval format will be fall back to fall-back
115  * patterns, which is mostly "{date0} - {date1}".
116  *
117  * <P>
118  * There is a set of pre-defined static skeleton strings in DateFormat,
119  * There are pre-defined interval patterns for those pre-defined skeletons
120  * in locales' resource files.
121  * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is  "yMMMd",
122  * in  en_US, if the largest different calendar field between date1 and date2
123  * is "year", the date interval pattern  is "MMM d, yyyy - MMM d, yyyy",
124  * such as "Jan 10, 2007 - Jan 10, 2008".
125  * If the largest different calendar field between date1 and date2 is "month",
126  * the date interval pattern is "MMM d - MMM d, yyyy",
127  * such as "Jan 10 - Feb 10, 2007".
128  * If the largest different calendar field between date1 and date2 is "day",
129  * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".
130  *
131  * For date skeleton, the interval patterns when year, or month, or date is
132  * different are defined in resource files.
133  * For time skeleton, the interval patterns when am/pm, or hour, or minute is
134  * different are defined in resource files.
135  *
136  * <P>
137  * If a skeleton is not found in a locale's DateIntervalInfo, which means
138  * the interval patterns for the skeleton is not defined in resource file,
139  * the interval pattern will falls back to the interval "fallback" pattern
140  * defined in resource file.
141  * If the interval "fallback" pattern is not defined, the default fall-back
142  * is "{date0} - {data1}".
143  *
144  * <P>
145  * For the combination of date and time,
146  * The rule to genearte interval patterns are:
147  * <ol>
148  * <li>
149  *    when the year, month, or day differs, falls back to fall-back
150  *    interval pattern, which mostly is the concatenate the two original
151  *    expressions with a separator between,
152  *    For example, interval pattern from "Jan 10, 2007 10:10 am"
153  *    to "Jan 11, 2007 10:10am" is
154  *    "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
155  * <li>
156  *    otherwise, present the date followed by the range expression
157  *    for the time.
158  *    For example, interval pattern from "Jan 10, 2007 10:10 am"
159  *    to "Jan 10, 2007 11:10am" is "Jan 10, 2007 10:10 am - 11:10am"
160  * </ol>
161  *
162  *
163  * <P>
164  * If two dates are the same, the interval pattern is the single date pattern.
165  * For example, interval pattern from "Jan 10, 2007" to "Jan 10, 2007" is
166  * "Jan 10, 2007".
167  *
168  * Or if the presenting fields between 2 dates have the exact same values,
169  * the interval pattern is the  single date pattern.
170  * For example, if user only requests year and month,
171  * the interval pattern from "Jan 10, 2007" to "Jan 20, 2007" is "Jan 2007".
172  *
173  * <P>
174  * DateIntervalFormat needs the following information for correct
175  * formatting: time zone, calendar type, pattern, date format symbols,
176  * and date interval patterns.
177  * It can be instantiated in several ways:
178  * <ol>
179  * <li>
180  *    create an instance using default or given locale plus given skeleton.
181  *    Users are encouraged to created date interval formatter this way and
182  *    to use the pre-defined skeleton macros, such as
183  *    YEAR_NUM_MONTH, which consists the calendar fields and
184  *    the format style.
185  * </li>
186  * <li>
187  *    create an instance using default or given locale plus given skeleton
188  *    plus a given DateIntervalInfo.
189  *    This factory method is for powerful users who want to provide their own
190  *    interval patterns.
191  *    Locale provides the timezone, calendar, and format symbols information.
192  *    Local plus skeleton provides full pattern information.
193  *    DateIntervalInfo provides the date interval patterns.
194  * </li>
195  * </ol>
196  *
197  * <P>
198  * For the calendar field pattern letter, such as G, y, M, d, a, h, H, m, s etc.
199  * DateIntervalFormat uses the same syntax as that of
200  * DateTime format.
201  *
202  * <P>
203  * Code Sample: general usage
204  * <pre>
205  *
206  *   // the date interval object which the DateIntervalFormat formats on
207  *   // and parses into
208  *   DateInterval dtInterval = new DateInterval(1000*3600*24L, 1000*3600*24*2L);
209  *   DateIntervalFormat dtIntervalFmt = DateIntervalFormat.getInstance(
210  *                   YEAR_MONTH_DAY, Locale("en", "GB", ""));
211  *   StringBuffer str = new StringBuffer("");
212  *   FieldPosition pos = new FieldPosition(0);
213  *   // formatting
214  *   dtIntervalFmt.format(dtInterval, dateIntervalString, pos);
215  *
216  * </pre>
217  *
218  * <P>
219  * Code Sample: for powerful users who wants to use their own interval pattern
220  * <pre>
221  *
222  *     import ohos.global.icu.text.DateIntervalInfo;
223  *     import ohos.global.icu.text.DateIntervalFormat;
224  *     ....................
225  *
226  *     // Get DateIntervalFormat instance using default locale
227  *     DateIntervalFormat dtitvfmt = DateIntervalFormat.getInstance(YEAR_MONTH_DAY);
228  *
229  *     // Create an empty DateIntervalInfo object, which does not have any interval patterns inside.
230  *     dtitvinf = new DateIntervalInfo();
231  *
232  *     // a series of set interval patterns.
233  *     // Only ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY,
234  *     MINUTE, SECOND and MILLISECOND are supported.
235  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'");
236  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d");
237  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d");
238  *     dtitvinf.setIntervalPattern("yMMMd", Calendar.HOUR_OF_DAY, "yyyy MMM d HH:mm ~ HH:mm");
239  *
240  *     // Set fallback interval pattern. Fallback pattern is used when interval pattern is not found.
241  *     // If the fall-back pattern is not set,  falls back to {date0} - {date1} if interval pattern is not found.
242  *     dtitvinf.setFallbackIntervalPattern("{0} - {1}");
243  *
244  *     // Set above DateIntervalInfo object as the interval patterns of date interval formatter
245  *     dtitvfmt.setDateIntervalInfo(dtitvinf);
246  *
247  *     // Prepare to format
248  *     pos = new FieldPosition(0);
249  *     str = new StringBuffer("");
250  *
251  *     // The 2 calendars should be equivalent, otherwise,  IllegalArgumentException will be thrown by format()
252  *     Calendar fromCalendar = (Calendar) dtfmt.getCalendar().clone();
253  *     Calendar toCalendar = (Calendar) dtfmt.getCalendar().clone();
254  *     fromCalendar.setTimeInMillis(....);
255  *     toCalendar.setTimeInMillis(...);
256  *
257  *     //Formatting given 2 calendars
258  *     dtitvfmt.format(fromCalendar, toCalendar, str, pos);
259  *
260  *
261  * </pre>
262  * <h3>Synchronization</h3>
263  *
264  * The format methods of DateIntervalFormat may be used concurrently from multiple threads.
265  * Functions that alter the state of a DateIntervalFormat object (setters)
266  * may not be used concurrently with any other functions.
267  */
268 
269 public class DateIntervalFormat extends UFormat {
270 
271     /**
272      * An immutable class containing the result of a date interval formatting operation.
273      *
274      * Instances of this class are immutable and thread-safe.
275      *
276      * Not intended for public subclassing.
277      */
278     public static final class FormattedDateInterval implements FormattedValue {
279         private final String string;
280         private final List<FieldPosition> attributes;
281 
FormattedDateInterval(CharSequence cs, List<FieldPosition> attributes)282         FormattedDateInterval(CharSequence cs, List<FieldPosition> attributes) {
283             this.string = cs.toString();
284             this.attributes = Collections.unmodifiableList(attributes);
285         }
286 
287         /**
288          * {@inheritDoc}
289          */
290         @Override
toString()291         public String toString() {
292             return string;
293         }
294 
295         /**
296          * {@inheritDoc}
297          */
298         @Override
length()299         public int length() {
300             return string.length();
301         }
302 
303         /**
304          * {@inheritDoc}
305          */
306         @Override
charAt(int index)307         public char charAt(int index) {
308             return string.charAt(index);
309         }
310 
311         /**
312          * {@inheritDoc}
313          */
314         @Override
subSequence(int start, int end)315         public CharSequence subSequence(int start, int end) {
316             return string.subSequence(start, end);
317         }
318 
319         /**
320          * {@inheritDoc}
321          */
322         @Override
appendTo(A appendable)323         public <A extends Appendable> A appendTo(A appendable) {
324             return Utility.appendTo(string, appendable);
325         }
326 
327         /**
328          * {@inheritDoc}
329          */
330         @Override
nextPosition(ConstrainedFieldPosition cfpos)331         public boolean nextPosition(ConstrainedFieldPosition cfpos) {
332             return FormattedValueFieldPositionIteratorImpl.nextPosition(attributes, cfpos);
333         }
334 
335         /**
336          * {@inheritDoc}
337          */
338         @Override
toCharacterIterator()339         public AttributedCharacterIterator toCharacterIterator() {
340             return FormattedValueFieldPositionIteratorImpl.toCharacterIterator(string, attributes);
341         }
342     }
343 
344     /**
345      * Class for span fields in FormattedDateInterval.
346      *
347      * @hide exposed on OHOS
348      */
349     public static final class SpanField extends UFormat.SpanField {
350         private static final long serialVersionUID = -6330879259553618133L;
351 
352         /**
353          * The concrete field used for spans in FormattedDateInterval.
354          *
355          * Instances of DATE_INTERVAL_SPAN should have an associated value. If
356          * 0, the date fields within the span are for the "from" date; if 1,
357          * the date fields within the span are for the "to" date.
358          */
359         public static final SpanField DATE_INTERVAL_SPAN = new SpanField("date-interval-span");
360 
SpanField(String name)361         private SpanField(String name) {
362             super(name);
363         }
364 
365         /**
366          * serialization method resolve instances to the constant
367          * DateIntervalFormat.SpanField values
368          * @hide draft / provisional / internal are hidden on OHOS
369          */
370         @Override
readResolve()371         protected Object readResolve() throws InvalidObjectException {
372             if (this.getName().equals(DATE_INTERVAL_SPAN.getName()))
373                 return DATE_INTERVAL_SPAN;
374 
375             throw new InvalidObjectException("An invalid object.");
376         }
377     }
378 
379     private static final long serialVersionUID = 1;
380 
381     /**
382      * Used to save the information for a skeleton's best match skeleton.
383      * It is package accessible since it is used in DateIntervalInfo too.
384      */
385     static final class BestMatchInfo {
386         // the best match skeleton
387         final String bestMatchSkeleton;
388         // 0 means the best matched skeleton is the same as input skeleton
389         // 1 means the fields are the same, but field width are different
390         // 2 means the only difference between fields are v/z,
391         // -1 means there are other fields difference
392         final int    bestMatchDistanceInfo;
BestMatchInfo(String bestSkeleton, int difference)393         BestMatchInfo(String bestSkeleton, int difference) {
394             bestMatchSkeleton = bestSkeleton;
395             bestMatchDistanceInfo = difference;
396         }
397     }
398 
399 
400     /*
401      * Used to save the information on a skeleton and its best match.
402      */
403     private static final class SkeletonAndItsBestMatch {
404         final String skeleton;
405         final String bestMatchSkeleton;
SkeletonAndItsBestMatch(String skeleton, String bestMatch)406         SkeletonAndItsBestMatch(String skeleton, String bestMatch) {
407             this.skeleton = skeleton;
408             bestMatchSkeleton = bestMatch;
409         }
410     }
411 
412     /** Used to output information during formatting. */
413     private static final class FormatOutput {
414         int firstIndex = -1;
415 
register(int i)416         public void register(int i) {
417             if (firstIndex == -1) {
418                 firstIndex = i;
419             }
420         }
421     }
422 
423 
424     // Cache for the locale interval pattern
425     private static ICUCache<String, Map<String, PatternInfo>> LOCAL_PATTERN_CACHE =
426         new SimpleCache<>();
427 
428     /*
429      * The interval patterns for this locale.
430      */
431     private DateIntervalInfo     fInfo;
432 
433     /*
434      * The DateFormat object used to format single pattern.
435      * Because fDateFormat is modified during format operations, all
436      * access to it from logically const, thread safe functions must be synchronized.
437      */
438     private SimpleDateFormat     fDateFormat;
439 
440     /*
441      * The 2 calendars with the from and to date.
442      * could re-use the calendar in fDateFormat,
443      * but keeping 2 calendars make it clear and clean.
444      * Because these Calendars are modified during format operations, all
445      * access to them from logically const, thread safe functions must be synchronized.
446      */
447     private Calendar fFromCalendar;
448     private Calendar fToCalendar;
449 
450     /*
451      * Following are transient interval information
452      * relevant (locale) to this formatter.
453      */
454     private String fSkeleton = null;
455 
456     /*
457      * Needed for efficient deserialization. If set, it means we can use the
458      * cache to initialize fIntervalPatterns.
459      */
460     private boolean isDateIntervalInfoDefault;
461 
462     /**
463      *  Interval patterns for this instance's locale.
464      */
465     private transient Map<String, PatternInfo> fIntervalPatterns = null;
466 
467     /*
468      * Patterns for fallback formatting.
469      */
470     private String fDatePattern = null;
471     private String fTimePattern = null;
472     private String fDateTimeFormat = null;
473 
474 
475     /*
476      * default constructor; private because we don't want anyone to use
477      */
478     @SuppressWarnings("unused")
DateIntervalFormat()479     private DateIntervalFormat() {
480     }
481 
482     /**
483      * Construct a DateIntervalFormat from DateFormat,
484      * a DateIntervalInfo, and skeleton.
485      * DateFormat provides the timezone, calendar,
486      * full pattern, and date format symbols information.
487      * It should be a SimpleDateFormat object which
488      * has a pattern in it.
489      * the DateIntervalInfo provides the interval patterns.
490      *
491      * @param skeleton  the skeleton of the date formatter
492      * @param dtItvInfo  the DateIntervalInfo object to be adopted.
493      * @param simpleDateFormat will be used for formatting
494      *
495      * @deprecated This API is ICU internal only.
496      * @hide deprecated on icu4j-org
497      * @hide draft / provisional / internal are hidden on OHOS
498      */
499     @Deprecated
DateIntervalFormat(String skeleton, DateIntervalInfo dtItvInfo, SimpleDateFormat simpleDateFormat)500     public DateIntervalFormat(String skeleton, DateIntervalInfo dtItvInfo,
501                                SimpleDateFormat simpleDateFormat)
502     {
503         fDateFormat = simpleDateFormat;
504         // freeze date interval info
505         dtItvInfo.freeze();
506         fSkeleton = skeleton;
507         fInfo = dtItvInfo;
508         isDateIntervalInfoDefault = false;
509         fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
510         fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
511         initializePattern(null);
512     }
513 
DateIntervalFormat(String skeleton, ULocale locale, SimpleDateFormat simpleDateFormat)514     private DateIntervalFormat(String skeleton, ULocale locale,
515             SimpleDateFormat simpleDateFormat)
516     {
517         fDateFormat = simpleDateFormat;
518         fSkeleton = skeleton;
519         fInfo = new DateIntervalInfo(locale).freeze();
520         isDateIntervalInfoDefault = true;
521         fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
522         fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
523         initializePattern(LOCAL_PATTERN_CACHE);
524 }
525 
526 
527     /**
528      * Construct a DateIntervalFormat from skeleton and  the default <code>FORMAT</code> locale.
529      *
530      * This is a convenient override of
531      * getInstance(String skeleton, ULocale locale)
532      * with the value of locale as default <code>FORMAT</code> locale.
533      *
534      * @param skeleton  the skeleton on which interval format based.
535      * @return          a date time interval formatter.
536      * @see Category#FORMAT
537      */
538     public static final DateIntervalFormat
getInstance(String skeleton)539         getInstance(String skeleton)
540 
541     {
542         return getInstance(skeleton, ULocale.getDefault(Category.FORMAT));
543     }
544 
545 
546     /**
547      * Construct a DateIntervalFormat from skeleton and a given locale.
548      *
549      * This is a convenient override of
550      * getInstance(String skeleton, ULocale locale)
551      *
552      * <p>Example code:{@sample external/icu/ohos_icu4j/src/samples/java/ohos/global/icu/samples/text/dateintervalformat/DateIntervalFormatSample.java dtitvfmtPreDefinedExample}
553      * @param skeleton  the skeleton on which interval format based.
554      * @param locale    the given locale
555      * @return          a date time interval formatter.
556      */
557     public static final DateIntervalFormat
getInstance(String skeleton, Locale locale)558         getInstance(String skeleton, Locale locale)
559     {
560         return getInstance(skeleton, ULocale.forLocale(locale));
561     }
562 
563 
564     /**
565      * Construct a DateIntervalFormat from skeleton and a given locale.
566      * <P>
567      * In this factory method,
568      * the date interval pattern information is load from resource files.
569      * Users are encouraged to created date interval formatter this way and
570      * to use the pre-defined skeleton macros.
571      *
572      * <P>
573      * There are pre-defined skeletons in DateFormat,
574      * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
575      *
576      * Those skeletons have pre-defined interval patterns in resource files.
577      * Users are encouraged to use them.
578      * For example:
579      * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc);
580      *
581      * The given Locale provides the interval patterns.
582      * For example, for en_GB, if skeleton is YEAR_ABBR_MONTH_WEEKDAY_DAY,
583      * which is "yMMMEEEd",
584      * the interval patterns defined in resource file to above skeleton are:
585      * "EEE, d MMM, yyyy - EEE, d MMM, yyyy" for year differs,
586      * "EEE, d MMM - EEE, d MMM, yyyy" for month differs,
587      * "EEE, d - EEE, d MMM, yyyy" for day differs,
588      * @param skeleton  the skeleton on which interval format based.
589      * @param locale    the given locale
590      * @return          a date time interval formatter.
591      */
592     public static final DateIntervalFormat
getInstance(String skeleton, ULocale locale)593         getInstance(String skeleton, ULocale locale)
594     {
595         DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
596         return new DateIntervalFormat(skeleton, locale, new SimpleDateFormat(generator.getBestPattern(skeleton), locale));
597     }
598 
599 
600 
601     /**
602      * Construct a DateIntervalFormat from skeleton
603      *  DateIntervalInfo, and the default <code>FORMAT</code> locale.
604      *
605      * This is a convenient override of
606      * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
607      * with the locale value as default <code>FORMAT</code> locale.
608      *
609      * @param skeleton  the skeleton on which interval format based.
610      * @param dtitvinf  the DateIntervalInfo object to be adopted.
611      * @return          a date time interval formatter.
612      * @see Category#FORMAT
613      */
getInstance(String skeleton, DateIntervalInfo dtitvinf)614     public static final DateIntervalFormat getInstance(String skeleton,
615                                                    DateIntervalInfo dtitvinf)
616     {
617         return getInstance(skeleton, ULocale.getDefault(Category.FORMAT), dtitvinf);
618     }
619 
620 
621 
622     /**
623      * Construct a DateIntervalFormat from skeleton
624      * a DateIntervalInfo, and the given locale.
625      *
626      * This is a convenient override of
627      * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
628      *
629      * <p>Example code:{@sample external/icu/ohos_icu4j/src/samples/java/ohos/global/icu/samples/text/dateintervalformat/DateIntervalFormatSample.java dtitvfmtCustomizedExample}
630      * @param skeleton  the skeleton on which interval format based.
631      * @param locale    the given locale
632      * @param dtitvinf  the DateIntervalInfo object to be adopted.
633      * @return          a date time interval formatter.
634      */
getInstance(String skeleton, Locale locale, DateIntervalInfo dtitvinf)635     public static final DateIntervalFormat getInstance(String skeleton,
636                                                  Locale locale,
637                                                  DateIntervalInfo dtitvinf)
638     {
639         return getInstance(skeleton, ULocale.forLocale(locale), dtitvinf);
640     }
641 
642 
643 
644     /**
645      * Construct a DateIntervalFormat from skeleton
646      * a DateIntervalInfo, and the given locale.
647      *
648      * <P>
649      * In this factory method, user provides its own date interval pattern
650      * information, instead of using those pre-defined data in resource file.
651      * This factory method is for powerful users who want to provide their own
652      * interval patterns.
653      *
654      * <P>
655      * There are pre-defined skeleton in DateFormat,
656      * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
657      *
658      * Those skeletons have pre-defined interval patterns in resource files.
659      * Users are encouraged to use them.
660      * For example:
661      * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc,itvinf);
662      *
663      * the DateIntervalInfo provides the interval patterns.
664      *
665      * User are encouraged to set default interval pattern in DateIntervalInfo
666      * as well, if they want to set other interval patterns ( instead of
667      * reading the interval patterns from resource files).
668      * When the corresponding interval pattern for a largest calendar different
669      * field is not found ( if user not set it ), interval format fallback to
670      * the default interval pattern.
671      * If user does not provide default interval pattern, it fallback to
672      * "{date0} - {date1}"
673      *
674      * @param skeleton  the skeleton on which interval format based.
675      * @param locale    the given locale
676      * @param dtitvinf  the DateIntervalInfo object to be adopted.
677      * @return          a date time interval formatter.
678      */
getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)679     public static final DateIntervalFormat getInstance(String skeleton,
680                                                  ULocale locale,
681                                                  DateIntervalInfo dtitvinf)
682     {
683         // clone. If it is frozen, clone returns itself, otherwise, clone
684         // returns a copy.
685         dtitvinf = (DateIntervalInfo)dtitvinf.clone();
686         DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
687         return new DateIntervalFormat(skeleton, dtitvinf, new SimpleDateFormat(generator.getBestPattern(skeleton), locale));
688     }
689 
690 
691     /**
692      * Clone this Format object polymorphically.
693      * @return    A copy of the object.
694      */
695     @Override
clone()696     public synchronized Object clone()
697     {
698         DateIntervalFormat other = (DateIntervalFormat) super.clone();
699         other.fDateFormat = (SimpleDateFormat) fDateFormat.clone();
700         other.fInfo = (DateIntervalInfo) fInfo.clone();
701         other.fFromCalendar = (Calendar) fFromCalendar.clone();
702         other.fToCalendar = (Calendar) fToCalendar.clone();
703         other.fDatePattern = fDatePattern;
704         other.fTimePattern = fTimePattern;
705         other.fDateTimeFormat = fDateTimeFormat;
706         return other;
707     }
708 
709 
710     /**
711      * Format an object to produce a string. This method handles Formattable
712      * objects with a DateInterval type.
713      * If a the Formattable object type is not a DateInterval,
714      * IllegalArgumentException is thrown.
715      *
716      * @param obj               The object to format.
717      *                          Must be a DateInterval.
718      * @param appendTo          Output parameter to receive result.
719      *                          Result is appended to existing contents.
720      * @param fieldPosition     On input: an alignment field, if desired.
721      *                          On output: the offsets of the alignment field.
722      *                          There may be multiple instances of a given field type
723      *                          in an interval format; in this case the fieldPosition
724      *                          offsets refer to the first instance.
725      * @return                  Reference to 'appendTo' parameter.
726      * @throws    IllegalArgumentException  if the formatted object is not
727      *                                      DateInterval object
728      */
729     @Override
730     public final StringBuffer
format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition)731         format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition)
732     {
733         if ( obj instanceof DateInterval ) {
734             return format( (DateInterval)obj, appendTo, fieldPosition);
735         }
736         else {
737             throw new IllegalArgumentException("Cannot format given Object (" + obj.getClass().getName() + ") as a DateInterval");
738         }
739     }
740 
741     /**
742      * Format a DateInterval to produce a string.
743      *
744      * @param dtInterval        DateInterval to be formatted.
745      * @param appendTo          Output parameter to receive result.
746      *                          Result is appended to existing contents.
747      * @param fieldPosition     On input: an alignment field, if desired.
748      *                          On output: the offsets of the alignment field.
749      *                          There may be multiple instances of a given field type
750      *                          in an interval format; in this case the fieldPosition
751      *                          offsets refer to the first instance.
752      * @return                  Reference to 'appendTo' parameter.
753      */
format(DateInterval dtInterval, StringBuffer appendTo, FieldPosition fieldPosition)754     public final StringBuffer format(DateInterval dtInterval,
755                                      StringBuffer appendTo,
756                                      FieldPosition fieldPosition) {
757         return formatIntervalImpl(dtInterval, appendTo, fieldPosition, null, null);
758     }
759 
760     /**
761      * Format a DateInterval to produce a FormattedDateInterval.
762      *
763      * The FormattedDateInterval exposes field information about the formatted string.
764      *
765      * @param dtInterval        DateInterval to be formatted.
766      * @return                  A FormattedDateInterval containing the format result.
767      */
formatToValue(DateInterval dtInterval)768     public FormattedDateInterval formatToValue(DateInterval dtInterval) {
769         StringBuffer sb = new StringBuffer();
770         FieldPosition ignore = new FieldPosition(0);
771         FormatOutput output = new FormatOutput();
772         List<FieldPosition> attributes = new ArrayList<>();
773         formatIntervalImpl(dtInterval, sb, ignore, output, attributes);
774         if (output.firstIndex != -1) {
775             FormattedValueFieldPositionIteratorImpl.addOverlapSpans(
776                     attributes, SpanField.DATE_INTERVAL_SPAN, output.firstIndex);
777             FormattedValueFieldPositionIteratorImpl.sort(attributes);
778         }
779         return new FormattedDateInterval(sb, attributes);
780     }
781 
formatIntervalImpl( DateInterval dtInterval, StringBuffer appendTo, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes)782     private synchronized StringBuffer formatIntervalImpl(
783             DateInterval dtInterval,
784             StringBuffer appendTo,
785             FieldPosition pos,
786             FormatOutput output,
787             List<FieldPosition> attributes) {
788         fFromCalendar.setTimeInMillis(dtInterval.getFromDate());
789         fToCalendar.setTimeInMillis(dtInterval.getToDate());
790         return formatImpl(fFromCalendar, fToCalendar, appendTo, pos, output, attributes);
791     }
792 
793     /**
794      * @deprecated This API is ICU internal only.
795      * @hide deprecated on icu4j-org
796      * @hide draft / provisional / internal are hidden on OHOS
797      */
798     @Deprecated
getPatterns(Calendar fromCalendar, Calendar toCalendar, Output<String> part2)799     public String getPatterns(Calendar fromCalendar,
800             Calendar toCalendar,
801             Output<String> part2) {
802         // First, find the largest different calendar field.
803         int field;
804         if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
805             field = Calendar.ERA;
806         } else if ( fromCalendar.get(Calendar.YEAR) !=
807                     toCalendar.get(Calendar.YEAR) ) {
808             field = Calendar.YEAR;
809         } else if ( fromCalendar.get(Calendar.MONTH) !=
810                     toCalendar.get(Calendar.MONTH) ) {
811             field = Calendar.MONTH;
812         } else if ( fromCalendar.get(Calendar.DATE) !=
813                     toCalendar.get(Calendar.DATE) ) {
814             field = Calendar.DATE;
815         } else if ( fromCalendar.get(Calendar.AM_PM) !=
816                     toCalendar.get(Calendar.AM_PM) ) {
817             field = Calendar.AM_PM;
818         } else if ( fromCalendar.get(Calendar.HOUR) !=
819                     toCalendar.get(Calendar.HOUR) ) {
820             field = Calendar.HOUR;
821         } else if ( fromCalendar.get(Calendar.MINUTE) !=
822                     toCalendar.get(Calendar.MINUTE) ) {
823             field = Calendar.MINUTE;
824         } else if ( fromCalendar.get(Calendar.SECOND) !=
825                     toCalendar.get(Calendar.SECOND) ) {
826             field = Calendar.SECOND;
827         } else if ( fromCalendar.get(Calendar.MILLISECOND) !=
828                     toCalendar.get(Calendar.MILLISECOND) ) {
829             field = Calendar.MILLISECOND;
830         } else {
831             return null;
832         }
833         PatternInfo intervalPattern = fIntervalPatterns.get(
834                 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
835         part2.value = intervalPattern.getSecondPart();
836         return intervalPattern.getFirstPart();
837     }
838 
839     /**
840      * Format 2 Calendars to produce a string.
841      *
842      * @param fromCalendar      calendar set to the from date in date interval
843      *                          to be formatted into date interval string
844      * @param toCalendar        calendar set to the to date in date interval
845      *                          to be formatted into date interval string
846      * @param appendTo          Output parameter to receive result.
847      *                          Result is appended to existing contents.
848      * @param pos               On input: an alignment field, if desired.
849      *                          On output: the offsets of the alignment field.
850      *                          There may be multiple instances of a given field type
851      *                          in an interval format; in this case the fieldPosition
852      *                          offsets refer to the first instance.
853      * @return                  Reference to 'appendTo' parameter.
854      * @throws    IllegalArgumentException  if the two calendars are not equivalent.
855      */
format(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos)856     public final StringBuffer format(Calendar fromCalendar,
857             Calendar toCalendar,
858             StringBuffer appendTo,
859             FieldPosition pos) {
860         return formatImpl(fromCalendar, toCalendar, appendTo, pos, null, null);
861     }
862 
863     /**
864      * Format 2 Calendars to produce a FormattedDateInterval.
865      *
866      * The FormattedDateInterval exposes field information about the formatted string.
867      *
868      * @param fromCalendar      calendar set to the from date in date interval
869      *                          to be formatted into date interval string
870      * @param toCalendar        calendar set to the to date in date interval
871      *                          to be formatted into date interval string
872      * @return                  A FormattedDateInterval containing the format result.
873      */
formatToValue(Calendar fromCalendar, Calendar toCalendar)874     public FormattedDateInterval formatToValue(Calendar fromCalendar, Calendar toCalendar) {
875         StringBuffer sb = new StringBuffer();
876         FieldPosition ignore = new FieldPosition(0);
877         FormatOutput output = new FormatOutput();
878         List<FieldPosition> attributes = new ArrayList<>();
879         formatImpl(fromCalendar, toCalendar, sb, ignore, output, attributes);
880         if (output.firstIndex != -1) {
881             FormattedValueFieldPositionIteratorImpl.addOverlapSpans(
882                     attributes, SpanField.DATE_INTERVAL_SPAN, output.firstIndex);
883             FormattedValueFieldPositionIteratorImpl.sort(attributes);
884         }
885         return new FormattedDateInterval(sb, attributes);
886     }
887 
formatImpl(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes)888     private synchronized StringBuffer formatImpl(Calendar fromCalendar,
889                                      Calendar toCalendar,
890                                      StringBuffer appendTo,
891                                      FieldPosition pos,
892                                      FormatOutput output,
893                                      List<FieldPosition> attributes)
894     {
895         // not support different calendar types and time zones
896         if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
897             throw new IllegalArgumentException("can not format on two different calendars");
898         }
899 
900         // First, find the largest different calendar field.
901         int field = -1; //init with an invalid value.
902 
903         if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
904             field = Calendar.ERA;
905         } else if ( fromCalendar.get(Calendar.YEAR) !=
906                     toCalendar.get(Calendar.YEAR) ) {
907             field = Calendar.YEAR;
908         } else if ( fromCalendar.get(Calendar.MONTH) !=
909                     toCalendar.get(Calendar.MONTH) ) {
910             field = Calendar.MONTH;
911         } else if ( fromCalendar.get(Calendar.DATE) !=
912                     toCalendar.get(Calendar.DATE) ) {
913             field = Calendar.DATE;
914         } else if ( fromCalendar.get(Calendar.AM_PM) !=
915                     toCalendar.get(Calendar.AM_PM) ) {
916             field = Calendar.AM_PM;
917         } else if ( fromCalendar.get(Calendar.HOUR) !=
918                     toCalendar.get(Calendar.HOUR) ) {
919             field = Calendar.HOUR;
920         } else if ( fromCalendar.get(Calendar.MINUTE) !=
921                     toCalendar.get(Calendar.MINUTE) ) {
922             field = Calendar.MINUTE;
923         } else if ( fromCalendar.get(Calendar.SECOND) !=
924                     toCalendar.get(Calendar.SECOND) ) {
925             field = Calendar.SECOND;
926         } else if ( fromCalendar.get(Calendar.MILLISECOND) !=
927                     toCalendar.get(Calendar.MILLISECOND) ) {
928             field = Calendar.MILLISECOND;
929         } else {
930             /* ignore the millisecond etc. small fields' difference.
931              * use single date when all the above are the same.
932              */
933             return fDateFormat.format(fromCalendar, appendTo, pos, attributes);
934         }
935         boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND || field==Calendar.MILLISECOND);
936 
937         // get interval pattern
938         PatternInfo intervalPattern = fIntervalPatterns.get(
939               DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
940 
941         if ( intervalPattern == null ) {
942             if ( fDateFormat.isFieldUnitIgnored(field) ) {
943                 /* the largest different calendar field is small than
944                  * the smallest calendar field in pattern,
945                  * return single date format.
946                  */
947                 return fDateFormat.format(fromCalendar, appendTo, pos, attributes);
948             }
949 
950             return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos,
951                     output, attributes);
952         }
953 
954         // If the first part in interval pattern is empty,
955         // the 2nd part of it saves the full-pattern used in fall-back.
956         // For a 'real' interval pattern, the first part will never be empty.
957         if ( intervalPattern.getFirstPart() == null ) {
958             // fall back
959             return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos,
960                     output, attributes, intervalPattern.getSecondPart());
961         }
962         Calendar firstCal;
963         Calendar secondCal;
964         if ( intervalPattern.firstDateInPtnIsLaterDate() ) {
965             if (output != null) {
966                 output.register(1);
967             }
968             firstCal = toCalendar;
969             secondCal = fromCalendar;
970         } else {
971             if (output != null) {
972                 output.register(0);
973             }
974             firstCal = fromCalendar;
975             secondCal = toCalendar;
976         }
977         // break the interval pattern into 2 parts
978         // first part should not be empty,
979         String originalPattern = fDateFormat.toPattern();
980         fDateFormat.applyPattern(intervalPattern.getFirstPart());
981         fDateFormat.format(firstCal, appendTo, pos, attributes);
982         // Only accept the first instance of the field
983         if (pos.getEndIndex() > 0) {
984             pos = new FieldPosition(0);
985         }
986         if ( intervalPattern.getSecondPart() != null ) {
987             fDateFormat.applyPattern(intervalPattern.getSecondPart());
988             fDateFormat.format(secondCal, appendTo, pos, attributes);
989         }
990         fDateFormat.applyPattern(originalPattern);
991         return appendTo;
992     }
993 
994     /** Like fallbackFormat, but specifically for ranges. */
fallbackFormatRange(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, StringBuilder patternSB, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes)995     private final void fallbackFormatRange(Calendar fromCalendar,
996             Calendar toCalendar,
997             StringBuffer appendTo,
998             StringBuilder patternSB,
999             FieldPosition pos,
1000             FormatOutput output,
1001             List<FieldPosition> attributes) {
1002         String compiledPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(
1003                 fInfo.getFallbackIntervalPattern(), patternSB, 2, 2);
1004         long state = 0;
1005         while (true) {
1006             state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo);
1007             if (state == SimpleFormatterImpl.IterInternal.DONE) {
1008                 break;
1009             }
1010             if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) {
1011                 if (output != null) {
1012                     output.register(0);
1013                 }
1014                 fDateFormat.format(fromCalendar, appendTo, pos, attributes);
1015             } else {
1016                 if (output != null) {
1017                     output.register(1);
1018                 }
1019                 fDateFormat.format(toCalendar, appendTo, pos, attributes);
1020             }
1021             // Only accept the first instance of the field
1022             if (pos.getEndIndex() > 0) {
1023                 pos = new FieldPosition(0);
1024             }
1025         }
1026     }
1027 
1028     /*
1029      * Format 2 Calendars to using fall-back interval pattern
1030      *
1031      * The full pattern used in this fall-back format is the
1032      * full pattern of the date formatter.
1033      *
1034      * @param fromCalendar      calendar set to the from date in date interval
1035      *                          to be formatted into date interval string
1036      * @param toCalendar        calendar set to the to date in date interval
1037      *                          to be formatted into date interval string
1038      * @param appendTo          Output parameter to receive result.
1039      *                          Result is appended to existing contents.
1040      * @param pos               On input: an alignment field, if desired.
1041      *                          On output: the offsets of the alignment field.
1042      * @return                  Reference to 'appendTo' parameter.
1043      */
fallbackFormat(Calendar fromCalendar, Calendar toCalendar, boolean fromToOnSameDay, StringBuffer appendTo, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes)1044     private final StringBuffer fallbackFormat(Calendar fromCalendar,
1045                                               Calendar toCalendar,
1046                                               boolean fromToOnSameDay,
1047                                               StringBuffer appendTo,
1048                                               FieldPosition pos,
1049                                               FormatOutput output,
1050                                               List<FieldPosition> attributes)  {
1051         StringBuilder patternSB = new StringBuilder();
1052         boolean formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern != null && fTimePattern != null);
1053         if (formatDatePlusTimeRange) {
1054             String compiledPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(
1055                     fDateTimeFormat, patternSB, 2, 2);
1056 
1057             String fullPattern; // for saving the pattern in fDateFormat
1058             fullPattern = fDateFormat.toPattern(); // save current pattern, restore later
1059 
1060             // {0} is time range
1061             // {1} is single date portion
1062             long state = 0;
1063             while (true) {
1064                 state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo);
1065                 if (state == SimpleFormatterImpl.IterInternal.DONE) {
1066                     break;
1067                 }
1068                 if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) {
1069                     fDateFormat.applyPattern(fTimePattern);
1070                     fallbackFormatRange(fromCalendar, toCalendar, appendTo, patternSB, pos, output, attributes);
1071                 } else {
1072                     fDateFormat.applyPattern(fDatePattern);
1073                     fDateFormat.format(fromCalendar, appendTo, pos, attributes);
1074                 }
1075                 // Only accept the first instance of the field
1076                 if (pos.getEndIndex() > 0) {
1077                     pos = new FieldPosition(0);
1078                 }
1079             }
1080 
1081             // restore full pattern
1082             fDateFormat.applyPattern(fullPattern);
1083         } else {
1084             fallbackFormatRange(fromCalendar, toCalendar, appendTo, patternSB, pos, output, attributes);
1085         }
1086         return appendTo;
1087     }
1088 
1089 
1090     /*
1091      * Format 2 Calendars to using fall-back interval pattern
1092      *
1093      * This fall-back pattern is generated on a given full pattern,
1094      * not the full pattern of the date formatter.
1095      *
1096      * @param fromCalendar      calendar set to the from date in date interval
1097      *                          to be formatted into date interval string
1098      * @param toCalendar        calendar set to the to date in date interval
1099      *                          to be formatted into date interval string
1100      * @param appendTo          Output parameter to receive result.
1101      *                          Result is appended to existing contents.
1102      * @param pos               On input: an alignment field, if desired.
1103      *                          On output: the offsets of the alignment field.
1104      * @param fullPattern       the full pattern need to apply to date formatter
1105      * @return                  Reference to 'appendTo' parameter.
1106      */
fallbackFormat(Calendar fromCalendar, Calendar toCalendar, boolean fromToOnSameDay, StringBuffer appendTo, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes, String fullPattern)1107     private final StringBuffer fallbackFormat(Calendar fromCalendar,
1108                                               Calendar toCalendar,
1109                                               boolean fromToOnSameDay,
1110                                               StringBuffer appendTo,
1111                                               FieldPosition pos,
1112                                               FormatOutput output,
1113                                               List<FieldPosition> attributes,
1114                                               String fullPattern)  {
1115             String originalPattern = fDateFormat.toPattern();
1116             fDateFormat.applyPattern(fullPattern);
1117             fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, output, attributes);
1118             fDateFormat.applyPattern(originalPattern);
1119             return appendTo;
1120     }
1121 
1122 
1123     /**
1124      * Date interval parsing is not supported.
1125      * <P>
1126      * This method should handle parsing of
1127      * date time interval strings into Formattable objects with
1128      * DateInterval type, which is a pair of UDate.
1129      * <P>
1130      * Before calling, set parse_pos.index to the offset you want to start
1131      * parsing at in the source. After calling, parse_pos.index is the end of
1132      * the text you parsed. If error occurs, index is unchanged.
1133      * <P>
1134      * When parsing, leading whitespace is discarded (with a successful parse),
1135      * while trailing whitespace is left as is.
1136      * <P>
1137      * See Format.parseObject() for more.
1138      *
1139      * @param source    The string to be parsed into an object.
1140      * @param parse_pos The position to start parsing at. Since no parsing
1141      *                  is supported, upon return this param is unchanged.
1142      * @return          A newly created Formattable* object, or NULL
1143      *                  on failure.
1144      * @deprecated This API is ICU internal only.
1145      * @hide deprecated on icu4j-org
1146      * @hide draft / provisional / internal are hidden on OHOS
1147      */
1148     @Override
1149     @Deprecated
parseObject(String source, ParsePosition parse_pos)1150     public Object parseObject(String source, ParsePosition parse_pos)
1151     {
1152         throw new UnsupportedOperationException("parsing is not supported");
1153     }
1154 
1155 
1156     /**
1157      * Gets the date time interval patterns.
1158      * @return a copy of the date time interval patterns associated with
1159      * this date interval formatter.
1160      */
getDateIntervalInfo()1161     public DateIntervalInfo getDateIntervalInfo()
1162     {
1163         return (DateIntervalInfo)fInfo.clone();
1164     }
1165 
1166 
1167     /**
1168      * Set the date time interval patterns.
1169      * @param newItvPattern   the given interval patterns to copy.
1170      */
setDateIntervalInfo(DateIntervalInfo newItvPattern)1171     public void setDateIntervalInfo(DateIntervalInfo newItvPattern)
1172     {
1173         // clone it. If it is frozen, the clone returns itself.
1174         // Otherwise, clone returns a copy
1175         fInfo = (DateIntervalInfo)newItvPattern.clone();
1176         this.isDateIntervalInfoDefault = false;
1177         fInfo.freeze(); // freeze it
1178         if ( fDateFormat != null ) {
1179             initializePattern(null);
1180         }
1181     }
1182 
1183     /**
1184      * Get the TimeZone
1185      * @return A copy of the TimeZone associated with this date interval formatter.
1186      */
getTimeZone()1187     public TimeZone getTimeZone()
1188     {
1189         if ( fDateFormat != null ) {
1190             // Here we clone, like other getters here, but unlike
1191             // DateFormat.getTimeZone() and Calendar.getTimeZone()
1192             // which return the TimeZone from the Calendar's zone variable
1193             return (TimeZone)(fDateFormat.getTimeZone().clone());
1194         }
1195         // If fDateFormat is null (unexpected), return default timezone.
1196         return TimeZone.getDefault();
1197     }
1198 
1199 
1200     /**
1201      * Set the TimeZone for the calendar used by this DateIntervalFormat object.
1202      * @param zone The new TimeZone, will be cloned for use by this DateIntervalFormat.
1203      */
setTimeZone(TimeZone zone)1204     public void setTimeZone(TimeZone zone)
1205     {
1206         // zone is cloned once for all three usages below:
1207         TimeZone zoneToSet = (TimeZone)zone.clone();
1208         if (fDateFormat != null) {
1209             fDateFormat.setTimeZone(zoneToSet);
1210         }
1211         // fDateFormat has the master calendar for the DateIntervalFormat;
1212         // fFromCalendar and fToCalendar are internal work clones of that calendar.
1213         if (fFromCalendar != null) {
1214             fFromCalendar.setTimeZone(zoneToSet);
1215         }
1216         if (fToCalendar != null) {
1217             fToCalendar.setTimeZone(zoneToSet);
1218         }
1219     }
1220 
1221     /**
1222      * Gets the date formatter
1223      * @return a copy of the date formatter associated with
1224      * this date interval formatter.
1225      */
getDateFormat()1226     public synchronized DateFormat getDateFormat()
1227     {
1228         return (DateFormat)fDateFormat.clone();
1229     }
1230 
1231 
1232     /*
1233      *  Below are for generating interval patterns locale to the formatter
1234      */
1235 
1236     /*
1237      * Initialize interval patterns locale to this formatter.
1238      */
initializePattern(ICUCache<String, Map<String, PatternInfo>> cache)1239     private void initializePattern(ICUCache<String, Map<String, PatternInfo>> cache) {
1240         String fullPattern = fDateFormat.toPattern();
1241         ULocale locale = fDateFormat.getLocale();
1242         String key = null;
1243         Map<String, PatternInfo> patterns = null;
1244         if (cache != null) {
1245             if ( fSkeleton != null ) {
1246                 key = locale.toString() + "+" + fullPattern + "+" + fSkeleton;
1247             } else {
1248                 key = locale.toString() + "+" + fullPattern;
1249             }
1250             patterns = cache.get(key);
1251         }
1252         if (patterns == null) {
1253             Map<String, PatternInfo> intervalPatterns = initializeIntervalPattern(fullPattern, locale);
1254             patterns = Collections.unmodifiableMap(intervalPatterns);
1255             if (cache != null) {
1256                 cache.put(key, patterns);
1257             }
1258         }
1259         fIntervalPatterns = patterns;
1260     }
1261 
1262 
1263 
1264     /*
1265      * Initialize interval patterns locale to this formatter
1266      *
1267      * This code is a bit complicated since
1268      * 1. the interval patterns saved in resource bundle files are interval
1269      *    patterns based on date or time only.
1270      *    It does not have interval patterns based on both date and time.
1271      *    Interval patterns on both date and time are algorithm generated.
1272      *
1273      *    For example, it has interval patterns on skeleton "dMy" and "hm",
1274      *    but it does not have interval patterns on skeleton "dMyhm".
1275      *
1276      *    The rule to generate interval patterns for both date and time skeleton are
1277      *    1) when the year, month, or day differs, concatenate the two original
1278      *    expressions with a separator between,
1279      *    For example, interval pattern from "Jan 10, 2007 10:10 am"
1280      *    to "Jan 11, 2007 10:10am" is
1281      *    "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
1282      *
1283      *    2) otherwise, present the date followed by the range expression
1284      *    for the time.
1285      *    For example, interval pattern from "Jan 10, 2007 10:10 am"
1286      *    to "Jan 10, 2007 11:10am" is
1287      *    "Jan 10, 2007 10:10 am - 11:10am"
1288      *
1289      * 2. even a pattern does not request a certain calendar field,
1290      *    the interval pattern needs to include such field if such fields are
1291      *    different between 2 dates.
1292      *    For example, a pattern/skeleton is "hm", but the interval pattern
1293      *    includes year, month, and date when year, month, and date differs.
1294      *
1295      *
1296      * @param fullPattern  formatter's full pattern
1297      * @param locale       the given locale.
1298      * @return             interval patterns' hash map
1299      */
initializeIntervalPattern(String fullPattern, ULocale locale)1300     private Map<String, PatternInfo> initializeIntervalPattern(String fullPattern, ULocale locale) {
1301         DateTimePatternGenerator dtpng = DateTimePatternGenerator.getInstance(locale);
1302         if ( fSkeleton == null ) {
1303             // fSkeleton is already set by getDateIntervalInstance()
1304             // or by getInstance(String skeleton, .... )
1305             fSkeleton = dtpng.getSkeleton(fullPattern);
1306         }
1307         String skeleton = fSkeleton;
1308 
1309         HashMap<String, PatternInfo> intervalPatterns = new HashMap<>();
1310 
1311         /* Check whether the skeleton is a combination of date and time.
1312          * For the complication reason 1 explained above.
1313          */
1314         StringBuilder date = new StringBuilder(skeleton.length());
1315         StringBuilder normalizedDate = new StringBuilder(skeleton.length());
1316         StringBuilder time = new StringBuilder(skeleton.length());
1317         StringBuilder normalizedTime = new StringBuilder(skeleton.length());
1318 
1319         /* the difference between time skeleton and normalizedTimeSkeleton are:
1320          * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
1321          * 2. 'a' is omitted in normalized time skeleton.
1322          * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized
1323          *    time skeleton
1324          *
1325          * The difference between date skeleton and normalizedDateSkeleton are:
1326          * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
1327          * 2. 'E' and 'EE' are normalized into 'EEE'
1328          * 3. 'MM' is normalized into 'M'
1329          */
1330         getDateTimeSkeleton(skeleton, date, normalizedDate,
1331                             time, normalizedTime);
1332 
1333         String dateSkeleton = date.toString();
1334         String timeSkeleton = time.toString();
1335         String normalizedDateSkeleton = normalizedDate.toString();
1336         String normalizedTimeSkeleton = normalizedTime.toString();
1337 
1338         // move this up here since we need it for fallbacks
1339         if (time.length() != 0 && date.length() != 0) {
1340             // Need the Date/Time pattern for concatenating the date with
1341             // the time interval.
1342             // The date/time pattern ( such as {0} {1} ) is saved in
1343             // calendar, that is why need to get the CalendarData here.
1344             fDateTimeFormat = getConcatenationPattern(locale);
1345         }
1346 
1347         boolean found = genSeparateDateTimePtn(normalizedDateSkeleton,
1348                                                normalizedTimeSkeleton,
1349                                                intervalPatterns, dtpng);
1350 
1351         // for skeletons with seconds, found is false and we enter this block
1352         if ( found == false ) {
1353             // use fallback
1354             // TODO: if user asks "m", but "d" differ
1355             //StringBuffer skeleton = new StringBuffer(skeleton);
1356             if ( time.length() != 0 ) {
1357                 //genFallbackForNotFound(Calendar.MINUTE, skeleton);
1358                 //genFallbackForNotFound(Calendar.HOUR, skeleton);
1359                 //genFallbackForNotFound(Calendar.AM_PM, skeleton);
1360                 if ( date.length() == 0 ) {
1361                     // prefix with yMd
1362                     timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
1363                     String pattern =dtpng.getBestPattern(timeSkeleton);
1364                     // for fall back interval patterns,
1365                     // the first part of the pattern is empty,
1366                     // the second part of the pattern is the full-pattern
1367                     // should be used in fall-back.
1368                     PatternInfo ptn = new PatternInfo(null, pattern,
1369                                                      fInfo.getDefaultOrder());
1370                     intervalPatterns.put(DateIntervalInfo.
1371                         CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
1372                     // share interval pattern
1373                     intervalPatterns.put(DateIntervalInfo.
1374                         CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
1375                     // share interval pattern
1376                     intervalPatterns.put(DateIntervalInfo.
1377                         CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
1378                 } else {
1379                     //genFallbackForNotFound(Calendar.DATE, skeleton);
1380                     //genFallbackForNotFound(Calendar.MONTH, skeleton);
1381                     //genFallbackForNotFound(Calendar.YEAR, skeleton);
1382                 }
1383             } else {
1384                     //genFallbackForNotFound(Calendar.DATE, skeleton);
1385                     //genFallbackForNotFound(Calendar.MONTH, skeleton);
1386                     //genFallbackForNotFound(Calendar.YEAR, skeleton);
1387             }
1388             return intervalPatterns;
1389         } // end of skeleton not found
1390         // interval patterns for skeleton are found in resource
1391         if ( time.length() == 0 ) {
1392             // done
1393         } else if ( date.length() == 0 ) {
1394             // need to set up patterns for y/M/d differ
1395             /* result from following looks confusing.
1396              * for example: 10 10:10 - 11 10:10, it is not
1397              * clear that the first 10 is the 10th day
1398             time.insert(0, 'd');
1399             genFallbackPattern(Calendar.DATE, time);
1400             time.insert(0, 'M');
1401             genFallbackPattern(Calendar.MONTH, time);
1402             time.insert(0, 'y');
1403             genFallbackPattern(Calendar.YEAR, time);
1404             */
1405             // prefix with yMd
1406             timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
1407             String pattern =dtpng.getBestPattern(timeSkeleton);
1408             // for fall back interval patterns,
1409             // the first part of the pattern is empty,
1410             // the second part of the pattern is the full-pattern
1411             // should be used in fall-back.
1412             PatternInfo ptn = new PatternInfo(
1413                                     null, pattern, fInfo.getDefaultOrder());
1414             intervalPatterns.put(DateIntervalInfo.
1415                 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
1416             intervalPatterns.put(DateIntervalInfo.
1417                 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
1418             intervalPatterns.put(DateIntervalInfo.
1419                 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
1420         } else {
1421             /* if both present,
1422              * 1) when the year, month, or day differs,
1423              * concatenate the two original expressions with a separator between,
1424              * 2) otherwise, present the date followed by the
1425              * range expression for the time.
1426              */
1427             /*
1428              * 1) when the year, month, or day differs,
1429              * concatenate the two original expressions with a separator between,
1430              */
1431             // if field exists, use fall back
1432             if ( !fieldExistsInSkeleton(Calendar.DATE, dateSkeleton) ) {
1433                 // prefix skeleton with 'd'
1434                 skeleton = DateIntervalInfo.
1435                     CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE] + skeleton;
1436                 genFallbackPattern(Calendar.DATE, skeleton, intervalPatterns, dtpng);
1437             }
1438             if ( !fieldExistsInSkeleton(Calendar.MONTH, dateSkeleton) ) {
1439                 // then prefix skeleton with 'M'
1440                 skeleton = DateIntervalInfo.
1441                     CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH] + skeleton;
1442                 genFallbackPattern(Calendar.MONTH, skeleton, intervalPatterns, dtpng);
1443             }
1444             if ( !fieldExistsInSkeleton(Calendar.YEAR, dateSkeleton) ) {
1445                 // then prefix skeleton with 'y'
1446                 skeleton = DateIntervalInfo.
1447                     CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR] + skeleton;
1448                 genFallbackPattern(Calendar.YEAR, skeleton, intervalPatterns, dtpng);
1449             }
1450 
1451             /*
1452              * 2) otherwise, present the date followed by the
1453              * range expression for the time.
1454              */
1455             if (fDateTimeFormat == null) {
1456                 fDateTimeFormat = "{1} {0}";
1457             }
1458             String datePattern =dtpng.getBestPattern(dateSkeleton);
1459             concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.AM_PM, intervalPatterns);
1460             concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.HOUR, intervalPatterns);
1461             concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.MINUTE, intervalPatterns);
1462         }
1463 
1464         return intervalPatterns;
1465     }
1466 
1467     /**
1468      * Retrieves the concatenation DateTime pattern from the resource bundle.
1469      * @param locale Locale to retrieve.
1470      * @return Concatenation DateTime pattern.
1471      */
getConcatenationPattern(ULocale locale)1472     private String getConcatenationPattern(ULocale locale) {
1473         ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
1474         ICUResourceBundle dtPatternsRb = rb.getWithFallback("calendar/gregorian/DateTimePatterns");
1475         ICUResourceBundle concatenationPatternRb = (ICUResourceBundle) dtPatternsRb.get(8);
1476         if (concatenationPatternRb.getType() == UResourceBundle.STRING) {
1477             return concatenationPatternRb.getString();
1478         } else {
1479             return concatenationPatternRb.getString(0);
1480         }
1481     }
1482 
1483     /*
1484      * Generate fall back interval pattern given a calendar field,
1485      * a skeleton, and a date time pattern generator
1486      * @param field      the largest different calendar field
1487      * @param skeleton   a skeleton
1488      * @param dtpng      date time pattern generator
1489      * @param intervalPatterns interval patterns
1490      */
genFallbackPattern(int field, String skeleton, Map<String, PatternInfo> intervalPatterns, DateTimePatternGenerator dtpng)1491     private void genFallbackPattern(int field, String skeleton,
1492                                     Map<String, PatternInfo> intervalPatterns,
1493                                     DateTimePatternGenerator dtpng) {
1494         String pattern = dtpng.getBestPattern(skeleton);
1495         // for fall back interval patterns,
1496         // the first part of the pattern is empty,
1497         // the second part of the pattern is the full-pattern
1498         // should be used in fall-back.
1499         PatternInfo ptn = new PatternInfo(
1500                                     null, pattern, fInfo.getDefaultOrder());
1501         intervalPatterns.put(
1502             DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptn);
1503     }
1504 
1505 
1506 
1507     /*
1508     private void genFallbackForNotFound(String field, StringBuffer skeleton) {
1509         if ( SimpleDateFormat.isFieldUnitIgnored(skeleton.toString(), field) ) {
1510             // single date
1511             DateIntervalInfo.PatternInfo ptnInfo =
1512                 new DateIntervalInfo.PatternInfo(null, fDateFormat.toPattern(),
1513                                                  fInfo.getDefaultOrder());
1514             fIntervalPatterns.put(field, ptnInfo);
1515             return;
1516         } else if ( skeleton.indexOf(field) == -1 ) {
1517             skeleton.insert(0,field);
1518             genFallbackPattern(field, skeleton, dtpng);
1519         }
1520     }
1521     */
1522 
1523     /*
1524      * get separated date and time skeleton from a combined skeleton.
1525      *
1526      * The difference between date skeleton and normalizedDateSkeleton are:
1527      * 1. both 'y' and 'd' are appeared only once in normalizeDateSkeleton
1528      * 2. 'E' and 'EE' are normalized into 'EEE'
1529      * 3. 'MM' is normalized into 'M'
1530      *
1531      ** the difference between time skeleton and normalizedTimeSkeleton are:
1532      * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton,
1533      * 2. 'a' is omitted in normalized time skeleton.
1534      * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized time
1535      *    skeleton
1536      *
1537      *
1538      *  @param skeleton               given combined skeleton.
1539      *  @param date                   Output parameter for date only skeleton.
1540      *  @param normalizedDate         Output parameter for normalized date only
1541      *
1542      *  @param time                   Output parameter for time only skeleton.
1543      *  @param normalizedTime         Output parameter for normalized time only
1544      *                                skeleton.
1545      */
getDateTimeSkeleton(String skeleton, StringBuilder dateSkeleton, StringBuilder normalizedDateSkeleton, StringBuilder timeSkeleton, StringBuilder normalizedTimeSkeleton)1546     private static void getDateTimeSkeleton(String skeleton,
1547                                             StringBuilder dateSkeleton,
1548                                             StringBuilder normalizedDateSkeleton,
1549                                             StringBuilder timeSkeleton,
1550                                             StringBuilder normalizedTimeSkeleton)
1551     {
1552         // dateSkeleton follows the sequence of y*M*E*d*
1553         // timeSkeleton follows the sequence of hm*[v|z]?
1554         int i;
1555         int ECount = 0;
1556         int dCount = 0;
1557         int MCount = 0;
1558         int yCount = 0;
1559         int hCount = 0;
1560         int HCount = 0;
1561         int mCount = 0;
1562         int vCount = 0;
1563         int zCount = 0;
1564 
1565         for (i = 0; i < skeleton.length(); ++i) {
1566             char ch = skeleton.charAt(i);
1567             switch ( ch ) {
1568               case 'E':
1569                 dateSkeleton.append(ch);
1570                 ++ECount;
1571                 break;
1572               case 'd':
1573                 dateSkeleton.append(ch);
1574                 ++dCount;
1575                 break;
1576               case 'M':
1577                 dateSkeleton.append(ch);
1578                 ++MCount;
1579                 break;
1580               case 'y':
1581                 dateSkeleton.append(ch);
1582                 ++yCount;
1583                 break;
1584               case 'G':
1585               case 'Y':
1586               case 'u':
1587               case 'Q':
1588               case 'q':
1589               case 'L':
1590               case 'l':
1591               case 'W':
1592               case 'w':
1593               case 'D':
1594               case 'F':
1595               case 'g':
1596               case 'e':
1597               case 'c':
1598               case 'U':
1599               case 'r':
1600                 normalizedDateSkeleton.append(ch);
1601                 dateSkeleton.append(ch);
1602                 break;
1603               case 'a':
1604                 // 'a' is implicitly handled
1605                 timeSkeleton.append(ch);
1606                 break;
1607               case 'h':
1608                 timeSkeleton.append(ch);
1609                 ++hCount;
1610                 break;
1611               case 'H':
1612                 timeSkeleton.append(ch);
1613                 ++HCount;
1614                 break;
1615               case 'm':
1616                 timeSkeleton.append(ch);
1617                 ++mCount;
1618                 break;
1619               case 'z':
1620                 ++zCount;
1621                 timeSkeleton.append(ch);
1622                 break;
1623               case 'v':
1624                 ++vCount;
1625                 timeSkeleton.append(ch);
1626                 break;
1627               case 'V':
1628               case 'Z':
1629               case 'k':
1630               case 'K':
1631               case 'j':
1632               case 's':
1633               case 'S':
1634               case 'A':
1635                 timeSkeleton.append(ch);
1636                 normalizedTimeSkeleton.append(ch);
1637                 break;
1638             }
1639         }
1640 
1641         /* generate normalized form for date*/
1642         if ( yCount != 0 ) {
1643             for (i = 0; i < yCount; i++) {
1644                 normalizedDateSkeleton.append('y');
1645             }
1646         }
1647         if ( MCount != 0 ) {
1648             if ( MCount < 3 ) {
1649                 normalizedDateSkeleton.append('M');
1650             } else {
1651                 for ( i = 0; i < MCount && i < 5; ++i ) {
1652                      normalizedDateSkeleton.append('M');
1653                 }
1654             }
1655         }
1656         if ( ECount != 0 ) {
1657             if ( ECount <= 3 ) {
1658                 normalizedDateSkeleton.append('E');
1659             } else {
1660                 for ( i = 0; i < ECount && i < 5; ++i ) {
1661                      normalizedDateSkeleton.append('E');
1662                 }
1663             }
1664         }
1665         if ( dCount != 0 ) {
1666             normalizedDateSkeleton.append('d');
1667         }
1668 
1669         /* generate normalized form for time */
1670         if ( HCount != 0 ) {
1671             normalizedTimeSkeleton.append('H');
1672         }
1673         else if ( hCount != 0 ) {
1674             normalizedTimeSkeleton.append('h');
1675         }
1676         if ( mCount != 0 ) {
1677             normalizedTimeSkeleton.append('m');
1678         }
1679         if ( zCount != 0 ) {
1680             normalizedTimeSkeleton.append('z');
1681         }
1682         if ( vCount != 0 ) {
1683             normalizedTimeSkeleton.append('v');
1684         }
1685     }
1686 
1687 
1688 
1689     /*
1690      * Generate date or time interval pattern from resource.
1691      *
1692      * It needs to handle the following:
1693      * 1. need to adjust field width.
1694      *    For example, the interval patterns saved in DateIntervalInfo
1695      *    includes "dMMMy", but not "dMMMMy".
1696      *    Need to get interval patterns for dMMMMy from dMMMy.
1697      *    Another example, the interval patterns saved in DateIntervalInfo
1698      *    includes "hmv", but not "hmz".
1699      *    Need to get interval patterns for "hmz' from 'hmv'
1700      *
1701      * 2. there might be no pattern for 'y' differ for skeleton "Md",
1702      *    in order to get interval patterns for 'y' differ,
1703      *    need to look for it from skeleton 'yMd'
1704      *
1705      * @param dateSkeleton   normalized date skeleton
1706      * @param timeSkeleton   normalized time skeleton
1707      * @param intervalPatterns interval patterns
1708      * @return whether there is interval patterns for the skeleton.
1709      *         true if there is, false otherwise
1710      */
genSeparateDateTimePtn(String dateSkeleton, String timeSkeleton, Map<String, PatternInfo> intervalPatterns, DateTimePatternGenerator dtpng)1711     private boolean genSeparateDateTimePtn(String dateSkeleton,
1712                                            String timeSkeleton,
1713                                            Map<String, PatternInfo> intervalPatterns,
1714                                            DateTimePatternGenerator dtpng)
1715     {
1716         String skeleton;
1717         // if both date and time skeleton present,
1718         // the final interval pattern might include time interval patterns
1719         // ( when, am_pm, hour, minute, second differ ),
1720         // but not date interval patterns ( when year, month, day differ ).
1721         // For year/month/day differ, it falls back to fall-back pattern.
1722         if ( timeSkeleton.length() != 0  ) {
1723             skeleton = timeSkeleton;
1724         } else {
1725             skeleton = dateSkeleton;
1726         }
1727 
1728         /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
1729          * are defined in resource,
1730          * interval patterns for skeleton "dMMMMy" are calculated by
1731          * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
1732          * 2. get the interval patterns for "dMMMy",
1733          * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
1734          * getBestSkeleton() is step 1.
1735          */
1736         // best skeleton, and the difference information
1737         BestMatchInfo retValue = fInfo.getBestSkeleton(skeleton);
1738         String bestSkeleton = retValue.bestMatchSkeleton;
1739         int differenceInfo =  retValue.bestMatchDistanceInfo;
1740 
1741         // Set patterns for fallback use, need to do this
1742         // before returning if differenceInfo == -1
1743         if (dateSkeleton.length() != 0  ) {
1744             fDatePattern = dtpng.getBestPattern(dateSkeleton);
1745         }
1746         if (timeSkeleton.length() != 0  ) {
1747             fTimePattern = dtpng.getBestPattern(timeSkeleton);
1748         }
1749 
1750         // difference:
1751         // 0 means the best matched skeleton is the same as input skeleton
1752         // 1 means the fields are the same, but field width are different
1753         // 2 means the only difference between fields are v/z,
1754         // -1 means there are other fields difference
1755         // (this will happen, for instance, if the supplied skeleton has seconds,
1756         //  but no skeletons in the intervalFormats data do)
1757         if ( differenceInfo == -1 ) {
1758             // skeleton has different fields, not only  v/z difference
1759             return false;
1760         }
1761 
1762         if ( timeSkeleton.length() == 0 ) {
1763             // only has date skeleton
1764             genIntervalPattern(Calendar.DATE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1765             SkeletonAndItsBestMatch skeletons = genIntervalPattern(
1766                                                   Calendar.MONTH, skeleton,
1767                                                   bestSkeleton, differenceInfo,
1768                                                   intervalPatterns);
1769             if ( skeletons != null ) {
1770                 bestSkeleton = skeletons.skeleton;
1771                 skeleton = skeletons.bestMatchSkeleton;
1772             }
1773             genIntervalPattern(Calendar.YEAR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1774             genIntervalPattern(Calendar.ERA, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1775         } else {
1776             genIntervalPattern(Calendar.MINUTE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1777             genIntervalPattern(Calendar.HOUR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1778             genIntervalPattern(Calendar.AM_PM, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1779         }
1780         return true;
1781 
1782     }
1783 
1784 
1785 
1786     /*
1787      * Generate interval pattern from existing resource
1788      *
1789      * It not only save the interval patterns,
1790      * but also return the skeleton and its best match skeleton.
1791      *
1792      * @param field           largest different calendar field
1793      * @param skeleton        skeleton
1794      * @param bestSkeleton    the best match skeleton which has interval pattern
1795      *                        defined in resource
1796      * @param differenceInfo  the difference between skeleton and best skeleton
1797      *         0 means the best matched skeleton is the same as input skeleton
1798      *         1 means the fields are the same, but field width are different
1799      *         2 means the only difference between fields are v/z,
1800      *        -1 means there are other fields difference
1801      *
1802      * @param intervalPatterns interval patterns
1803      *
1804      * @return  an extended skeleton or extended best skeleton if applicable.
1805      *          null otherwise.
1806      */
genIntervalPattern( int field, String skeleton, String bestSkeleton, int differenceInfo, Map<String, PatternInfo> intervalPatterns)1807     private SkeletonAndItsBestMatch genIntervalPattern(
1808                    int field, String skeleton, String bestSkeleton,
1809                    int differenceInfo, Map<String, PatternInfo> intervalPatterns) {
1810         SkeletonAndItsBestMatch retValue = null;
1811         PatternInfo pattern = fInfo.getIntervalPattern(
1812                                            bestSkeleton, field);
1813         if ( pattern == null ) {
1814             // single date
1815             if ( SimpleDateFormat.isFieldUnitIgnored(bestSkeleton, field) ) {
1816                 PatternInfo ptnInfo =
1817                     new PatternInfo(fDateFormat.toPattern(),
1818                                                      null,
1819                                                      fInfo.getDefaultOrder());
1820                 intervalPatterns.put(DateIntervalInfo.
1821                     CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptnInfo);
1822                 return null;
1823             }
1824 
1825             // for 24 hour system, interval patterns in resource file
1826             // might not include pattern when am_pm differ,
1827             // which should be the same as hour differ.
1828             // add it here for simplicity
1829             if ( field == Calendar.AM_PM ) {
1830                  pattern = fInfo.getIntervalPattern(bestSkeleton,
1831                                                          Calendar.HOUR);
1832                  if ( pattern != null ) {
1833                       // share
1834                       intervalPatterns.put(DateIntervalInfo.
1835                           CALENDAR_FIELD_TO_PATTERN_LETTER[field],
1836                           pattern);
1837                  }
1838                  return null;
1839             }
1840             // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1841             // first, get best match pattern "MMMd",
1842             // since there is no pattern for 'y' differs for skeleton 'MMMd',
1843             // need to look for it from skeleton 'yMMMd',
1844             // if found, adjust field width in interval pattern from
1845             // "MMM" to "MMMM".
1846             String fieldLetter =
1847                 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
1848             bestSkeleton = fieldLetter + bestSkeleton;
1849             skeleton = fieldLetter + skeleton;
1850             // for example, looking for patterns when 'y' differ for
1851             // skeleton "MMMM".
1852             pattern = fInfo.getIntervalPattern(bestSkeleton, field);
1853             if ( pattern == null && differenceInfo == 0 ) {
1854                 // if there is no skeleton "yMMMM" defined,
1855                 // look for the best match skeleton, for example: "yMMM"
1856                 BestMatchInfo tmpRetValue = fInfo.getBestSkeleton(skeleton);
1857                 String tmpBestSkeleton = tmpRetValue.bestMatchSkeleton;
1858                 differenceInfo =  tmpRetValue.bestMatchDistanceInfo;
1859                 if ( tmpBestSkeleton.length() != 0 && differenceInfo != -1 ) {
1860                     pattern = fInfo.getIntervalPattern(tmpBestSkeleton, field);
1861                     bestSkeleton = tmpBestSkeleton;
1862                 }
1863             }
1864             if ( pattern != null ) {
1865                 retValue = new SkeletonAndItsBestMatch(skeleton, bestSkeleton);
1866             }
1867         }
1868         if ( pattern != null ) {
1869             if ( differenceInfo != 0 ) {
1870                 String part1 = adjustFieldWidth(skeleton, bestSkeleton,
1871                                    pattern.getFirstPart(), differenceInfo);
1872                 String part2 = adjustFieldWidth(skeleton, bestSkeleton,
1873                                    pattern.getSecondPart(), differenceInfo);
1874                 pattern =  new PatternInfo(part1, part2,
1875                                            pattern.firstDateInPtnIsLaterDate());
1876             } else {
1877                 // pattern is immutable, no need to clone;
1878                 // pattern = (PatternInfo)pattern.clone();
1879             }
1880             intervalPatterns.put(
1881               DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], pattern);
1882         }
1883         return retValue;
1884     }
1885 
1886     /*
1887      * Adjust field width in best match interval pattern to match
1888      * the field width in input skeleton.
1889      *
1890      * TODO (xji) make a general solution
1891      * The adjusting rule can be:
1892      * 1. always adjust
1893      * 2. never adjust
1894      * 3. default adjust, which means adjust according to the following rules
1895      * 3.1 always adjust string, such as MMM and MMMM
1896      * 3.2 never adjust between string and numeric, such as MM and MMM
1897      * 3.3 always adjust year
1898      * 3.4 do not adjust 'd', 'h', or 'm' if h presents
1899      * 3.5 do not adjust 'M' if it is numeric(?)
1900      *
1901      * Since date interval format is well-formed format,
1902      * date and time skeletons are normalized previously,
1903      * till this stage, the adjust here is only "adjust strings, such as MMM
1904      * and MMMM, EEE and EEEE.
1905      *
1906      * @param inputSkeleton            the input skeleton
1907      * @param bestMatchSkeleton        the best match skeleton
1908      * @param bestMatchIntervalpattern the best match interval pattern
1909      * @param differenceInfo           the difference between 2 skeletons
1910      *                                 1 means only field width differs
1911      *                                 2 means v/z exchange
1912      * @return the adjusted interval pattern
1913      */
adjustFieldWidth(String inputSkeleton, String bestMatchSkeleton, String bestMatchIntervalPattern, int differenceInfo )1914     private static String adjustFieldWidth(String inputSkeleton,
1915                                     String bestMatchSkeleton,
1916                                     String bestMatchIntervalPattern,
1917                                     int differenceInfo ) {
1918 
1919         if ( bestMatchIntervalPattern == null ) {
1920             return null; // the 2nd part could be null
1921         }
1922         int[] inputSkeletonFieldWidth = new int[58];
1923         int[] bestMatchSkeletonFieldWidth = new int[58];
1924 
1925         /* initialize as following
1926         {
1927         //       A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
1928             0, 0, 0, 0, 0,  0, 0,  0,  0, 0, 0, 0, 0,  0, 0, 0,
1929         //   P   Q   R   S   T   U   V   W   X   Y   Z
1930             0, 0, 0, 0, 0,  0, 0,  0,  0, 0, 0, 0, 0,  0, 0, 0,
1931         //       a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
1932             0, 0, 0, 0, 0,  0, 0,  0,  0, 0, 0, 0, 0,  0, 0, 0,
1933         //   p   q   r   s   t   u   v   w   x   y   z
1934             0, 0, 0, 0, 0,  0, 0,  0,  0, 0, 0, 0, 0,  0, 0, 0,
1935         };
1936         */
1937 
1938 
1939         DateIntervalInfo.parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
1940         DateIntervalInfo.parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth);
1941         if ( differenceInfo == 2 ) {
1942             bestMatchIntervalPattern = bestMatchIntervalPattern.replace('v', 'z');
1943         }
1944 
1945         StringBuilder adjustedPtn = new StringBuilder(bestMatchIntervalPattern);
1946 
1947         boolean inQuote = false;
1948         char prevCh = 0;
1949         int count = 0;
1950 
1951         int PATTERN_CHAR_BASE = 0x41;
1952 
1953         // loop through the pattern string character by character
1954         int adjustedPtnLength = adjustedPtn.length();
1955         for (int i = 0; i < adjustedPtnLength; ++i) {
1956             char ch = adjustedPtn.charAt(i);
1957             if (ch != prevCh && count > 0) {
1958                 // check the repeativeness of pattern letter
1959                 char skeletonChar = prevCh;
1960                 if ( skeletonChar == 'L' ) {
1961                     // for skeleton "M+", the pattern is "...L..."
1962                     skeletonChar = 'M';
1963                 }
1964                 int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
1965                 int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
1966                 if ( fieldCount == count && inputFieldCount > fieldCount ) {
1967                     count = inputFieldCount - fieldCount;
1968                     for ( int j = 0; j < count; ++j ) {
1969                         adjustedPtn.insert(i, prevCh);
1970                     }
1971                     i += count;
1972                     adjustedPtnLength += count;
1973                 }
1974                 count = 0;
1975             }
1976             if (ch == '\'') {
1977                 // Consecutive single quotes are a single quote literal,
1978                 // either outside of quotes or between quotes
1979                 if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == '\'') {
1980                     ++i;
1981                 } else {
1982                     inQuote = ! inQuote;
1983                 }
1984             }
1985             else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
1986                         || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
1987                 // ch is a date-time pattern character
1988                 prevCh = ch;
1989                 ++count;
1990             }
1991         }
1992         if ( count > 0 ) {
1993             // last item
1994             // check the repeativeness of pattern letter
1995             char skeletonChar = prevCh;
1996             if ( skeletonChar == 'L' ) {
1997                 // for skeleton "M+", the pattern is "...L..."
1998                 skeletonChar = 'M';
1999             }
2000             int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
2001             int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
2002             if ( fieldCount == count && inputFieldCount > fieldCount ) {
2003                 count = inputFieldCount - fieldCount;
2004                 for ( int j = 0; j < count; ++j ) {
2005                     adjustedPtn.append(prevCh);
2006                 }
2007             }
2008         }
2009         return adjustedPtn.toString();
2010     }
2011 
2012 
2013     /*
2014      * Concat a single date pattern with a time interval pattern,
2015      * set it into the intervalPatterns, while field is time field.
2016      * This is used to handle time interval patterns on skeleton with
2017      * both time and date. Present the date followed by
2018      * the range expression for the time.
2019      * @param dtfmt                  date and time format
2020      * @param datePattern            date pattern
2021      * @param field                  time calendar field: AM_PM, HOUR, MINUTE
2022      * @param intervalPatterns       interval patterns
2023      */
concatSingleDate2TimeInterval(String dtfmt, String datePattern, int field, Map<String, PatternInfo> intervalPatterns)2024     private void concatSingleDate2TimeInterval(String dtfmt,
2025                                                String datePattern,
2026                                                int field,
2027                                                Map<String, PatternInfo> intervalPatterns)
2028     {
2029 
2030         PatternInfo  timeItvPtnInfo =
2031             intervalPatterns.get(DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
2032         if ( timeItvPtnInfo != null ) {
2033             String timeIntervalPattern = timeItvPtnInfo.getFirstPart() +
2034                                          timeItvPtnInfo.getSecondPart();
2035             String pattern = SimpleFormatterImpl.formatRawPattern(
2036                     dtfmt, 2, 2, timeIntervalPattern, datePattern);
2037             timeItvPtnInfo = DateIntervalInfo.genPatternInfo(pattern,
2038                                 timeItvPtnInfo.firstDateInPtnIsLaterDate());
2039             intervalPatterns.put(
2040               DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], timeItvPtnInfo);
2041         }
2042         // else: fall back
2043         // it should not happen if the interval format defined is valid
2044     }
2045 
2046 
2047     /*
2048      * check whether a calendar field present in a skeleton.
2049      * @param field      calendar field need to check
2050      * @param skeleton   given skeleton on which to check the calendar field
2051      * @return           true if field present in a skeleton.
2052      */
fieldExistsInSkeleton(int field, String skeleton)2053     private static boolean fieldExistsInSkeleton(int field, String skeleton)
2054     {
2055         String fieldChar = DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
2056         return ( (skeleton.indexOf(fieldChar) == -1) ? false : true ) ;
2057     }
2058 
2059 
2060     /*
2061      * readObject.
2062      */
readObject(ObjectInputStream stream)2063     private void readObject(ObjectInputStream stream)
2064         throws IOException, ClassNotFoundException {
2065         stream.defaultReadObject();
2066         initializePattern(isDateIntervalInfoDefault ? LOCAL_PATTERN_CACHE : null);
2067     }
2068 
2069     /**
2070      * Get the internal patterns for the skeleton
2071      * @deprecated This API is ICU internal only.
2072      * @hide deprecated on icu4j-org
2073      * @hide draft / provisional / internal are hidden on OHOS
2074      */
2075     @Deprecated
getRawPatterns()2076     public Map<String, PatternInfo> getRawPatterns() {
2077         // this is unmodifiable, so ok to return directly
2078         return fIntervalPatterns;
2079     }
2080 }
2081