• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4  *******************************************************************************
5  * Copyright (C) 2013-2016, International Business Machines Corporation and
6  * others. All Rights Reserved.
7  *******************************************************************************
8  */
9 package com.ibm.icu.text;
10 
11 import java.io.InvalidObjectException;
12 import java.text.AttributedCharacterIterator;
13 import java.text.Format;
14 import java.text.FieldPosition;
15 import java.util.EnumMap;
16 import java.util.Locale;
17 
18 import com.ibm.icu.impl.CacheBase;
19 import com.ibm.icu.impl.FormattedStringBuilder;
20 import com.ibm.icu.impl.FormattedValueStringBuilderImpl;
21 import com.ibm.icu.impl.ICUData;
22 import com.ibm.icu.impl.ICUResourceBundle;
23 import com.ibm.icu.impl.SimpleFormatterImpl;
24 import com.ibm.icu.impl.SoftCache;
25 import com.ibm.icu.impl.StandardPlural;
26 import com.ibm.icu.impl.UResource;
27 import com.ibm.icu.impl.Utility;
28 import com.ibm.icu.impl.number.DecimalQuantity;
29 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
30 import com.ibm.icu.lang.UCharacter;
31 import com.ibm.icu.util.Calendar;
32 import com.ibm.icu.util.ICUException;
33 import com.ibm.icu.util.ULocale;
34 import com.ibm.icu.util.UResourceBundle;
35 
36 
37 /**
38  * Formats simple relative dates. There are two types of relative dates that
39  * it handles:
40  * <ul>
41  *   <li>relative dates with a quantity e.g "in 5 days"</li>
42  *   <li>relative dates without a quantity e.g "next Tuesday"</li>
43  * </ul>
44  * <p>
45  * This API is very basic and is intended to be a building block for more
46  * fancy APIs. The caller tells it exactly what to display in a locale
47  * independent way. While this class automatically provides the correct plural
48  * forms, the grammatical form is otherwise as neutral as possible. It is the
49  * caller's responsibility to handle cut-off logic such as deciding between
50  * displaying "in 7 days" or "in 1 week." This API supports relative dates
51  * involving one single unit. This API does not support relative dates
52  * involving compound units.
53  * e.g "in 5 days and 4 hours" nor does it support parsing.
54  * This class is both immutable and thread-safe.
55  * <p>
56  * Here are some examples of use:
57  * <blockquote>
58  * <pre>
59  * RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance();
60  * fmt.format(1, Direction.NEXT, RelativeUnit.DAYS); // "in 1 day"
61  * fmt.format(3, Direction.NEXT, RelativeUnit.DAYS); // "in 3 days"
62  * fmt.format(3.2, Direction.LAST, RelativeUnit.YEARS); // "3.2 years ago"
63  *
64  * fmt.format(Direction.LAST, AbsoluteUnit.SUNDAY); // "last Sunday"
65  * fmt.format(Direction.THIS, AbsoluteUnit.SUNDAY); // "this Sunday"
66  * fmt.format(Direction.NEXT, AbsoluteUnit.SUNDAY); // "next Sunday"
67  * fmt.format(Direction.PLAIN, AbsoluteUnit.SUNDAY); // "Sunday"
68  *
69  * fmt.format(Direction.LAST, AbsoluteUnit.DAY); // "yesterday"
70  * fmt.format(Direction.THIS, AbsoluteUnit.DAY); // "today"
71  * fmt.format(Direction.NEXT, AbsoluteUnit.DAY); // "tomorrow"
72  *
73  * fmt.format(Direction.PLAIN, AbsoluteUnit.NOW); // "now"
74  * </pre>
75  * </blockquote>
76  * <p>
77  * In the future, we may add more forms, such as abbreviated/short forms
78  * (3 secs ago), and relative day periods ("yesterday afternoon"), etc.
79  *
80  * @stable ICU 53
81  */
82 public final class RelativeDateTimeFormatter {
83 
84     /**
85      * The formatting style
86      * @stable ICU 54
87      *
88      */
89     public static enum Style {
90 
91         /**
92          * Everything spelled out.
93          * @stable ICU 54
94          */
95         LONG,
96 
97         /**
98          * Abbreviations used when possible.
99          * @stable ICU 54
100          */
101         SHORT,
102 
103         /**
104          * Use single letters when possible.
105          * @stable ICU 54
106          */
107         NARROW;
108 
109         private static final int INDEX_COUNT = 3;  // NARROW.ordinal() + 1
110     }
111 
112     /**
113      * Represents the unit for formatting a relative date. e.g "in 5 days"
114      * or "in 3 months"
115      * @stable ICU 53
116      */
117     public static enum RelativeUnit {
118 
119         /**
120          * Seconds
121          * @stable ICU 53
122          */
123         SECONDS,
124 
125         /**
126          * Minutes
127          * @stable ICU 53
128          */
129         MINUTES,
130 
131        /**
132         * Hours
133         * @stable ICU 53
134         */
135         HOURS,
136 
137         /**
138          * Days
139          * @stable ICU 53
140          */
141         DAYS,
142 
143         /**
144          * Weeks
145          * @stable ICU 53
146          */
147         WEEKS,
148 
149         /**
150          * Months
151          * @stable ICU 53
152          */
153         MONTHS,
154 
155         /**
156          * Years
157          * @stable ICU 53
158          */
159         YEARS,
160 
161         /**
162          * Quarters
163          * @internal TODO: propose for addition in ICU 57
164          * @deprecated This API is ICU internal only.
165          */
166         @Deprecated
167         QUARTERS,
168     }
169 
170     /**
171      * Represents an absolute unit.
172      * @stable ICU 53
173      */
174     public static enum AbsoluteUnit {
175 
176        /**
177         * Sunday
178         * @stable ICU 53
179         */
180         SUNDAY,
181 
182         /**
183          * Monday
184          * @stable ICU 53
185          */
186         MONDAY,
187 
188         /**
189          * Tuesday
190          * @stable ICU 53
191          */
192         TUESDAY,
193 
194         /**
195          * Wednesday
196          * @stable ICU 53
197          */
198         WEDNESDAY,
199 
200         /**
201          * Thursday
202          * @stable ICU 53
203          */
204         THURSDAY,
205 
206         /**
207          * Friday
208          * @stable ICU 53
209          */
210         FRIDAY,
211 
212         /**
213          * Saturday
214          * @stable ICU 53
215          */
216         SATURDAY,
217 
218         /**
219          * Day
220          * @stable ICU 53
221          */
222         DAY,
223 
224         /**
225          * Week
226          * @stable ICU 53
227          */
228         WEEK,
229 
230         /**
231          * Month
232          * @stable ICU 53
233          */
234         MONTH,
235 
236         /**
237          * Year
238          * @stable ICU 53
239          */
240         YEAR,
241 
242         /**
243          * Now
244          * @stable ICU 53
245          */
246         NOW,
247 
248         /**
249          * Quarter
250          * @stable ICU 64
251          */
252         QUARTER,
253 
254         /**
255          * Hour
256          * @stable ICU 65
257          */
258         HOUR,
259 
260         /**
261          * Minute
262          * @stable ICU 65
263          */
264         MINUTE,
265     }
266 
267     /**
268      * Represents a direction for an absolute unit e.g "Next Tuesday"
269      * or "Last Tuesday"
270      * @stable ICU 53
271      */
272     public static enum Direction {
273           /**
274            * Two before. Not fully supported in every locale
275            * @stable ICU 53
276            */
277           LAST_2,
278 
279           /**
280            * Last
281            * @stable ICU 53
282            */
283           LAST,
284 
285           /**
286            * This
287            * @stable ICU 53
288            */
289           THIS,
290 
291           /**
292            * Next
293            * @stable ICU 53
294            */
295           NEXT,
296 
297           /**
298            * Two after. Not fully supported in every locale
299            * @stable ICU 53
300            */
301           NEXT_2,
302 
303           /**
304            * Plain, which means the absence of a qualifier
305            * @stable ICU 53
306            */
307           PLAIN,
308     }
309 
310     /**
311      * Represents the unit for formatting a relative date. e.g "in 5 days"
312      * or "next year"
313      * @stable ICU 57
314      */
315     public static enum RelativeDateTimeUnit {
316         /**
317          * Specifies that relative unit is year, e.g. "last year",
318          * "in 5 years".
319          * @stable ICU 57
320          */
321         YEAR,
322         /**
323          * Specifies that relative unit is quarter, e.g. "last quarter",
324          * "in 5 quarters".
325          * @stable ICU 57
326          */
327         QUARTER,
328         /**
329          * Specifies that relative unit is month, e.g. "last month",
330          * "in 5 months".
331          * @stable ICU 57
332          */
333         MONTH,
334         /**
335          * Specifies that relative unit is week, e.g. "last week",
336          * "in 5 weeks".
337          * @stable ICU 57
338          */
339         WEEK,
340         /**
341          * Specifies that relative unit is day, e.g. "yesterday",
342          * "in 5 days".
343          * @stable ICU 57
344          */
345         DAY,
346         /**
347          * Specifies that relative unit is hour, e.g. "1 hour ago",
348          * "in 5 hours".
349          * @stable ICU 57
350          */
351         HOUR,
352         /**
353          * Specifies that relative unit is minute, e.g. "1 minute ago",
354          * "in 5 minutes".
355          * @stable ICU 57
356          */
357         MINUTE,
358         /**
359          * Specifies that relative unit is second, e.g. "1 second ago",
360          * "in 5 seconds".
361          * @stable ICU 57
362          */
363         SECOND,
364         /**
365          * Specifies that relative unit is Sunday, e.g. "last Sunday",
366          * "this Sunday", "next Sunday", "in 5 Sundays".
367          * @stable ICU 57
368          */
369         SUNDAY,
370         /**
371          * Specifies that relative unit is Monday, e.g. "last Monday",
372          * "this Monday", "next Monday", "in 5 Mondays".
373          * @stable ICU 57
374          */
375         MONDAY,
376         /**
377          * Specifies that relative unit is Tuesday, e.g. "last Tuesday",
378          * "this Tuesday", "next Tuesday", "in 5 Tuesdays".
379          * @stable ICU 57
380          */
381         TUESDAY,
382         /**
383          * Specifies that relative unit is Wednesday, e.g. "last Wednesday",
384          * "this Wednesday", "next Wednesday", "in 5 Wednesdays".
385          * @stable ICU 57
386          */
387         WEDNESDAY,
388         /**
389          * Specifies that relative unit is Thursday, e.g. "last Thursday",
390          * "this Thursday", "next Thursday", "in 5 Thursdays".
391          * @stable ICU 57
392          */
393         THURSDAY,
394         /**
395          * Specifies that relative unit is Friday, e.g. "last Friday",
396          * "this Friday", "next Friday", "in 5 Fridays".
397          * @stable ICU 57
398          */
399         FRIDAY,
400         /**
401          * Specifies that relative unit is Saturday, e.g. "last Saturday",
402          * "this Saturday", "next Saturday", "in 5 Saturdays".
403          * @stable ICU 57
404          */
405         SATURDAY,
406     }
407 
408     /**
409      * Field constants used when accessing field information for relative
410      * datetime strings in FormattedValue.
411      * <p>
412      * There is no public constructor to this class; the only instances are the
413      * constants defined here.
414      * <p>
415      * @stable ICU 64
416      */
417     public static class Field extends Format.Field {
418         private static final long serialVersionUID = -5327685528663492325L;
419 
420         /**
421          * Represents a literal text string, like "tomorrow" or "days ago".
422          *
423          * @stable ICU 64
424          */
425         public static final Field LITERAL = new Field("literal");
426 
427         /**
428          * Represents a number quantity, like "3" in "3 days ago".
429          *
430          * @stable ICU 64
431          */
432         public static final Field NUMERIC = new Field("numeric");
433 
Field(String fieldName)434         private Field(String fieldName) {
435             super(fieldName);
436         }
437 
438         /**
439          * Serizalization method resolve instances to the constant Field values
440          *
441          * @internal
442          * @deprecated This API is ICU internal only.
443          */
444         @Deprecated
445         @Override
readResolve()446         protected Object readResolve() throws InvalidObjectException {
447             if (this.getName().equals(LITERAL.getName()))
448                 return LITERAL;
449             if (this.getName().equals(NUMERIC.getName()))
450                 return NUMERIC;
451 
452             throw new InvalidObjectException("An invalid object.");
453         }
454     }
455 
456     /**
457      * Represents the result of a formatting operation of a relative datetime.
458      * Access the string value or field information.
459      *
460      * Instances of this class are immutable and thread-safe.
461      *
462      * Not intended for public subclassing.
463      *
464      * @author sffc
465      * @stable ICU 64
466      */
467     public static class FormattedRelativeDateTime implements FormattedValue {
468 
469         private final FormattedStringBuilder string;
470 
FormattedRelativeDateTime(FormattedStringBuilder string)471         private FormattedRelativeDateTime(FormattedStringBuilder string) {
472             this.string = string;
473         }
474 
475         /**
476          * {@inheritDoc}
477          *
478          * @stable ICU 64
479          */
480         @Override
toString()481         public String toString() {
482             return string.toString();
483         }
484 
485         /**
486          * {@inheritDoc}
487          *
488          * @stable ICU 64
489          */
490         @Override
length()491         public int length() {
492             return string.length();
493         }
494 
495         /**
496          * {@inheritDoc}
497          *
498          * @stable ICU 64
499          */
500         @Override
charAt(int index)501         public char charAt(int index) {
502             return string.charAt(index);
503         }
504 
505         /**
506          * {@inheritDoc}
507          *
508          * @stable ICU 64
509          */
510         @Override
subSequence(int start, int end)511         public CharSequence subSequence(int start, int end) {
512             return string.subString(start, end);
513         }
514 
515         /**
516          * {@inheritDoc}
517          *
518          * @stable ICU 64
519          */
520         @Override
appendTo(A appendable)521         public <A extends Appendable> A appendTo(A appendable) {
522             return Utility.appendTo(string, appendable);
523         }
524 
525         /**
526          * {@inheritDoc}
527          *
528          * @stable ICU 64
529          */
530         @Override
nextPosition(ConstrainedFieldPosition cfpos)531         public boolean nextPosition(ConstrainedFieldPosition cfpos) {
532             return FormattedValueStringBuilderImpl.nextPosition(string, cfpos, Field.NUMERIC);
533         }
534 
535         /**
536          * {@inheritDoc}
537          *
538          * @stable ICU 64
539          */
540         @Override
toCharacterIterator()541         public AttributedCharacterIterator toCharacterIterator() {
542             return FormattedValueStringBuilderImpl.toCharacterIterator(string, Field.NUMERIC);
543         }
544     }
545 
546     /**
547      * Returns a RelativeDateTimeFormatter for the default locale.
548      * @stable ICU 53
549      */
getInstance()550     public static RelativeDateTimeFormatter getInstance() {
551         return getInstance(ULocale.getDefault(), null, Style.LONG, DisplayContext.CAPITALIZATION_NONE);
552     }
553 
554     /**
555      * Returns a RelativeDateTimeFormatter for a particular locale.
556      *
557      * @param locale the locale.
558      * @return An instance of RelativeDateTimeFormatter.
559      * @stable ICU 53
560      */
getInstance(ULocale locale)561     public static RelativeDateTimeFormatter getInstance(ULocale locale) {
562         return getInstance(locale, null, Style.LONG, DisplayContext.CAPITALIZATION_NONE);
563     }
564 
565     /**
566      * Returns a RelativeDateTimeFormatter for a particular {@link java.util.Locale}.
567      *
568      * @param locale the {@link java.util.Locale}.
569      * @return An instance of RelativeDateTimeFormatter.
570      * @stable ICU 54
571      */
getInstance(Locale locale)572     public static RelativeDateTimeFormatter getInstance(Locale locale) {
573         return getInstance(ULocale.forLocale(locale));
574     }
575 
576     /**
577      * Returns a RelativeDateTimeFormatter for a particular locale that uses a particular
578      * NumberFormat object.
579      *
580      * @param locale the locale
581      * @param nf the number format object. It is defensively copied to ensure thread-safety
582      * and immutability of this class.
583      * @return An instance of RelativeDateTimeFormatter.
584      * @stable ICU 53
585      */
getInstance(ULocale locale, NumberFormat nf)586     public static RelativeDateTimeFormatter getInstance(ULocale locale, NumberFormat nf) {
587         return getInstance(locale, nf, Style.LONG, DisplayContext.CAPITALIZATION_NONE);
588     }
589 
590     /**
591      * Returns a RelativeDateTimeFormatter for a particular locale that uses a particular
592      * NumberFormat object, style, and capitalization context
593      *
594      * @param locale the locale
595      * @param nf the number format object. It is defensively copied to ensure thread-safety
596      * and immutability of this class. May be null.
597      * @param style the style.
598      * @param capitalizationContext the capitalization context.
599      * @stable ICU 54
600      */
getInstance( ULocale locale, NumberFormat nf, Style style, DisplayContext capitalizationContext)601     public static RelativeDateTimeFormatter getInstance(
602             ULocale locale,
603             NumberFormat nf,
604             Style style,
605             DisplayContext capitalizationContext) {
606         RelativeDateTimeFormatterData data = cache.get(locale);
607         if (nf == null) {
608             nf = NumberFormat.getInstance(locale);
609         } else {
610             nf = (NumberFormat) nf.clone();
611         }
612         return new RelativeDateTimeFormatter(
613                 data.qualitativeUnitMap,
614                 data.relUnitPatternMap,
615                 // Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717).
616                 data.dateTimePattern,
617                 PluralRules.forLocale(locale),
618                 nf,
619                 style,
620                 capitalizationContext,
621                 capitalizationContext == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ?
622                     BreakIterator.getSentenceInstance(locale) : null,
623                 locale);
624     }
625 
626     /**
627      * Returns a RelativeDateTimeFormatter for a particular {@link java.util.Locale} that uses a
628      * particular NumberFormat object.
629      *
630      * @param locale the {@link java.util.Locale}
631      * @param nf the number format object. It is defensively copied to ensure thread-safety
632      * and immutability of this class.
633      * @return An instance of RelativeDateTimeFormatter.
634      * @stable ICU 54
635      */
getInstance(Locale locale, NumberFormat nf)636     public static RelativeDateTimeFormatter getInstance(Locale locale, NumberFormat nf) {
637         return getInstance(ULocale.forLocale(locale), nf);
638     }
639 
640     /**
641      * Formats a relative date with a quantity such as "in 5 days" or
642      * "3 months ago".
643      *
644      * This method returns a String. To get more information about the
645      * formatting result, use formatToValue().
646      *
647      * @param quantity The numerical amount e.g 5. This value is formatted
648      * according to this object's {@link NumberFormat} object.
649      * @param direction NEXT means a future relative date; LAST means a past
650      * relative date.
651      * @param unit the unit e.g day? month? year?
652      * @return the formatted string
653      * @throws IllegalArgumentException if direction is something other than
654      * NEXT or LAST.
655      * @stable ICU 53
656      */
format(double quantity, Direction direction, RelativeUnit unit)657     public String format(double quantity, Direction direction, RelativeUnit unit) {
658         FormattedStringBuilder output = formatImpl(quantity, direction, unit);
659         return adjustForContext(output.toString());
660     }
661 
662     /**
663      * Formats a relative date with a quantity such as "in 5 days" or
664      * "3 months ago".
665      *
666      * This method returns a FormattedRelativeDateTime, which exposes more
667      * information than the String returned by format().
668      *
669      * @param quantity The numerical amount e.g 5. This value is formatted
670      * according to this object's {@link NumberFormat} object.
671      * @param direction NEXT means a future relative date; LAST means a past
672      * relative date.
673      * @param unit the unit e.g day? month? year?
674      * @return the formatted relative datetime
675      * @throws IllegalArgumentException if direction is something other than
676      * NEXT or LAST.
677      * @stable ICU 64
678      */
formatToValue(double quantity, Direction direction, RelativeUnit unit)679     public FormattedRelativeDateTime formatToValue(double quantity, Direction direction, RelativeUnit unit) {
680         checkNoAdjustForContext();
681         return new FormattedRelativeDateTime(formatImpl(quantity, direction, unit));
682     }
683 
684     /** Implementation method for format and formatToValue with RelativeUnit */
formatImpl(double quantity, Direction direction, RelativeUnit unit)685     private FormattedStringBuilder formatImpl(double quantity, Direction direction, RelativeUnit unit) {
686         if (direction != Direction.LAST && direction != Direction.NEXT) {
687             throw new IllegalArgumentException("direction must be NEXT or LAST");
688         }
689         int pastFutureIndex = (direction == Direction.NEXT ? 1 : 0);
690 
691         FormattedStringBuilder output = new FormattedStringBuilder();
692         String pluralKeyword;
693         if (numberFormat instanceof DecimalFormat) {
694             DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(quantity);
695             ((DecimalFormat) numberFormat).toNumberFormatter().formatImpl(dq, output);
696             pluralKeyword = pluralRules.select(dq);
697         } else {
698             String result = numberFormat.format(quantity);
699             output.append(result, null);
700             pluralKeyword = pluralRules.select(quantity);
701         }
702         StandardPlural pluralForm = StandardPlural.orOtherFromString(pluralKeyword);
703 
704         String compiledPattern = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
705         SimpleFormatterImpl.formatPrefixSuffix(compiledPattern, Field.LITERAL, 0, output.length(), output);
706         return output;
707     }
708 
709     /**
710      * Format a combination of RelativeDateTimeUnit and numeric offset
711      * using a numeric style, e.g. "1 week ago", "in 1 week",
712      * "5 weeks ago", "in 5 weeks".
713      *
714      * This method returns a String. To get more information about the
715      * formatting result, use formatNumericToValue().
716      *
717      * @param offset    The signed offset for the specified unit. This
718      *                  will be formatted according to this object's
719      *                  NumberFormat object.
720      * @param unit      The unit to use when formatting the relative
721      *                  date, e.g. RelativeDateTimeUnit.WEEK,
722      *                  RelativeDateTimeUnit.FRIDAY.
723      * @return          The formatted string (may be empty in case of error)
724      * @stable ICU 57
725      */
formatNumeric(double offset, RelativeDateTimeUnit unit)726     public String formatNumeric(double offset, RelativeDateTimeUnit unit) {
727         FormattedStringBuilder output = formatNumericImpl(offset, unit);
728         return adjustForContext(output.toString());
729     }
730 
731     /**
732      * Format a combination of RelativeDateTimeUnit and numeric offset
733      * using a numeric style, e.g. "1 week ago", "in 1 week",
734      * "5 weeks ago", "in 5 weeks".
735      *
736      * This method returns a FormattedRelativeDateTime, which exposes more
737      * information than the String returned by formatNumeric().
738      *
739      * @param offset    The signed offset for the specified unit. This
740      *                  will be formatted according to this object's
741      *                  NumberFormat object.
742      * @param unit      The unit to use when formatting the relative
743      *                  date, e.g. RelativeDateTimeUnit.WEEK,
744      *                  RelativeDateTimeUnit.FRIDAY.
745      * @return          The formatted string (may be empty in case of error)
746      * @stable ICU 64
747      */
formatNumericToValue(double offset, RelativeDateTimeUnit unit)748     public FormattedRelativeDateTime formatNumericToValue(double offset, RelativeDateTimeUnit unit) {
749         checkNoAdjustForContext();
750         return new FormattedRelativeDateTime(formatNumericImpl(offset, unit));
751     }
752 
753     /** Implementation method for formatNumeric and formatNumericToValue */
formatNumericImpl(double offset, RelativeDateTimeUnit unit)754     private FormattedStringBuilder formatNumericImpl(double offset, RelativeDateTimeUnit unit) {
755         // TODO:
756         // The full implementation of this depends on CLDR data that is not yet available,
757         // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data.
758         // In the meantime do a quick bring-up by calling the old format method. When the
759         // new CLDR data is available, update the data storage accordingly, rewrite this
760         // to use it directly, and rewrite the old format method to call this new one;
761         // that is covered by https://unicode-org.atlassian.net/browse/ICU-12171.
762         RelativeUnit relunit = RelativeUnit.SECONDS;
763         switch (unit) {
764             case YEAR:      relunit = RelativeUnit.YEARS; break;
765             case QUARTER:   relunit = RelativeUnit.QUARTERS; break;
766             case MONTH:     relunit = RelativeUnit.MONTHS; break;
767             case WEEK:      relunit = RelativeUnit.WEEKS; break;
768             case DAY:       relunit = RelativeUnit.DAYS; break;
769             case HOUR:      relunit = RelativeUnit.HOURS; break;
770             case MINUTE:    relunit = RelativeUnit.MINUTES; break;
771             case SECOND:    break; // set above
772             default: // SUNDAY..SATURDAY
773                 throw new UnsupportedOperationException("formatNumeric does not currently support RelativeUnit.SUNDAY..SATURDAY");
774         }
775         Direction direction = Direction.NEXT;
776         if (Double.compare(offset,0.0) < 0) { // needed to handle -0.0
777             direction = Direction.LAST;
778             offset = -offset;
779         }
780         return formatImpl(offset, direction, relunit);
781     }
782 
783     private int[] styleToDateFormatSymbolsWidth = {
784                 DateFormatSymbols.WIDE, DateFormatSymbols.SHORT, DateFormatSymbols.NARROW
785     };
786 
787     /**
788      * Formats a relative date without a quantity.
789      *
790      * This method returns a String. To get more information about the
791      * formatting result, use formatToValue().
792      *
793      * @param direction NEXT, LAST, THIS, etc.
794      * @param unit e.g SATURDAY, DAY, MONTH
795      * @return the formatted string. If direction has a value that is documented as not being
796      *  fully supported in every locale (for example NEXT_2 or LAST_2) then this function may
797      *  return null to signal that no formatted string is available.
798      * @throws IllegalArgumentException if the direction is incompatible with
799      * unit this can occur with NOW which can only take PLAIN.
800      * @stable ICU 53
801      */
format(Direction direction, AbsoluteUnit unit)802     public String format(Direction direction, AbsoluteUnit unit) {
803         String result = formatAbsoluteImpl(direction, unit);
804         return result != null ? adjustForContext(result) : null;
805     }
806 
807     /**
808      * Formats a relative date without a quantity.
809      *
810      * This method returns a FormattedRelativeDateTime, which exposes more
811      * information than the String returned by format().
812      *
813      * @param direction NEXT, LAST, THIS, etc.
814      * @param unit e.g SATURDAY, DAY, MONTH
815      * @return the formatted string. If direction has a value that is documented as not being
816      *  fully supported in every locale (for example NEXT_2 or LAST_2) then this function may
817      *  return null to signal that no formatted string is available.
818      * @throws IllegalArgumentException if the direction is incompatible with
819      * unit this can occur with NOW which can only take PLAIN.
820      * @stable ICU 64
821      */
formatToValue(Direction direction, AbsoluteUnit unit)822     public FormattedRelativeDateTime formatToValue(Direction direction, AbsoluteUnit unit) {
823         checkNoAdjustForContext();
824         String string = formatAbsoluteImpl(direction, unit);
825         if (string == null) {
826             return null;
827         }
828         FormattedStringBuilder nsb = new FormattedStringBuilder();
829         nsb.append(string, Field.LITERAL);
830         return new FormattedRelativeDateTime(nsb);
831     }
832 
833     /** Implementation method for format and formatToValue with AbsoluteUnit */
formatAbsoluteImpl(Direction direction, AbsoluteUnit unit)834     private String formatAbsoluteImpl(Direction direction, AbsoluteUnit unit) {
835         if (unit == AbsoluteUnit.NOW && direction != Direction.PLAIN) {
836             throw new IllegalArgumentException("NOW can only accept direction PLAIN.");
837         }
838         String result;
839         // Get plain day of week names from DateFormatSymbols.
840         if ((direction == Direction.PLAIN) &&  (AbsoluteUnit.SUNDAY.ordinal() <= unit.ordinal() &&
841                 unit.ordinal() <= AbsoluteUnit.SATURDAY.ordinal())) {
842             // Convert from AbsoluteUnit days to Calendar class indexing.
843             int dateSymbolsDayOrdinal = (unit.ordinal() - AbsoluteUnit.SUNDAY.ordinal()) + Calendar.SUNDAY;
844             String[] dayNames =
845                     dateFormatSymbols.getWeekdays(DateFormatSymbols.STANDALONE,
846                     styleToDateFormatSymbolsWidth[style.ordinal()]);
847             result = dayNames[dateSymbolsDayOrdinal];
848         } else {
849             // Not PLAIN, or not a weekday.
850             result = getAbsoluteUnitString(style, unit, direction);
851         }
852         return result;
853     }
854 
855     /**
856      * Format a combination of RelativeDateTimeUnit and numeric offset
857      * using a text style if possible, e.g. "last week", "this week",
858      * "next week", "yesterday", "tomorrow". Falls back to numeric
859      * style if no appropriate text term is available for the specified
860      * offset in the object’s locale.
861      *
862      * This method returns a String. To get more information about the
863      * formatting result, use formatToValue().
864      *
865      * @param offset    The signed offset for the specified field.
866      * @param unit      The unit to use when formatting the relative
867      *                  date, e.g. RelativeDateTimeUnit.WEEK,
868      *                  RelativeDateTimeUnit.FRIDAY.
869      * @return          The formatted string (may be empty in case of error)
870      * @stable ICU 57
871      */
format(double offset, RelativeDateTimeUnit unit)872     public String format(double offset, RelativeDateTimeUnit unit) {
873         return adjustForContext(formatRelativeImpl(offset, unit).toString());
874     }
875 
876     /**
877      * Format a combination of RelativeDateTimeUnit and numeric offset
878      * using a text style if possible, e.g. "last week", "this week",
879      * "next week", "yesterday", "tomorrow". Falls back to numeric
880      * style if no appropriate text term is available for the specified
881      * offset in the object’s locale.
882      *
883      * This method returns a FormattedRelativeDateTime, which exposes more
884      * information than the String returned by format().
885      *
886      * @param offset    The signed offset for the specified field.
887      * @param unit      The unit to use when formatting the relative
888      *                  date, e.g. RelativeDateTimeUnit.WEEK,
889      *                  RelativeDateTimeUnit.FRIDAY.
890      * @return          The formatted string (may be empty in case of error)
891      * @stable ICU 64
892      */
formatToValue(double offset, RelativeDateTimeUnit unit)893     public FormattedRelativeDateTime formatToValue(double offset, RelativeDateTimeUnit unit) {
894         checkNoAdjustForContext();
895         CharSequence cs = formatRelativeImpl(offset, unit);
896         FormattedStringBuilder nsb;
897         if (cs instanceof FormattedStringBuilder) {
898             nsb = (FormattedStringBuilder) cs;
899         } else {
900             nsb = new FormattedStringBuilder();
901             nsb.append(cs, Field.LITERAL);
902         }
903         return new FormattedRelativeDateTime(nsb);
904     }
905 
906 
907     /** Implementation method for format and formatToValue with RelativeDateTimeUnit. */
formatRelativeImpl(double offset, RelativeDateTimeUnit unit)908     private CharSequence formatRelativeImpl(double offset, RelativeDateTimeUnit unit) {
909         // TODO:
910         // The full implementation of this depends on CLDR data that is not yet available,
911         // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data.
912         // In the meantime do a quick bring-up by calling the old format method. When the
913         // new CLDR data is available, update the data storage accordingly, rewrite this
914         // to use it directly, and rewrite the old format method to call this new one;
915         // that is covered by https://unicode-org.atlassian.net/browse/ICU-12171.
916         boolean useNumeric = true;
917         Direction direction = Direction.THIS;
918         if (offset > -2.1 && offset < 2.1) {
919             // Allow a 1% epsilon, so offsets in -1.01..-0.99 map to LAST
920             double offsetx100 = offset * 100.0;
921             int intoffsetx100 = (offsetx100 < 0)? (int)(offsetx100-0.5) : (int)(offsetx100+0.5);
922             switch (intoffsetx100) {
923                 case -200/*-2*/: direction = Direction.LAST_2; useNumeric = false; break;
924                 case -100/*-1*/: direction = Direction.LAST;   useNumeric = false; break;
925                 case    0/* 0*/: useNumeric = false; break; // direction = Direction.THIS was set above
926                 case  100/* 1*/: direction = Direction.NEXT;   useNumeric = false; break;
927                 case  200/* 2*/: direction = Direction.NEXT_2; useNumeric = false; break;
928                 default: break;
929             }
930         }
931         AbsoluteUnit absunit = AbsoluteUnit.NOW;
932         switch (unit) {
933             case YEAR:      absunit = AbsoluteUnit.YEAR;    break;
934             case QUARTER:   absunit = AbsoluteUnit.QUARTER; break;
935             case MONTH:     absunit = AbsoluteUnit.MONTH;   break;
936             case WEEK:      absunit = AbsoluteUnit.WEEK;    break;
937             case DAY:       absunit = AbsoluteUnit.DAY;     break;
938             case SUNDAY:    absunit = AbsoluteUnit.SUNDAY;  break;
939             case MONDAY:    absunit = AbsoluteUnit.MONDAY;  break;
940             case TUESDAY:   absunit = AbsoluteUnit.TUESDAY; break;
941             case WEDNESDAY: absunit = AbsoluteUnit.WEDNESDAY; break;
942             case THURSDAY:  absunit = AbsoluteUnit.THURSDAY; break;
943             case FRIDAY:    absunit = AbsoluteUnit.FRIDAY;  break;
944             case SATURDAY:  absunit = AbsoluteUnit.SATURDAY; break;
945             case HOUR:      absunit = AbsoluteUnit.HOUR;    break;
946             case MINUTE:    absunit = AbsoluteUnit.MINUTE;  break;
947             case SECOND:
948                 if (direction == Direction.THIS) {
949                     // absunit = AbsoluteUnit.NOW was set above
950                     direction = Direction.PLAIN;
951                     break;
952                 }
953                 // could just fall through here but that produces warnings
954                 useNumeric = true;
955                 break;
956             default:
957                 useNumeric = true;
958                 break;
959         }
960         if (!useNumeric) {
961             String result = formatAbsoluteImpl(direction, absunit);
962             if (result != null && result.length() > 0) {
963                 return result;
964             }
965         }
966         // otherwise fallback to formatNumeric
967         return formatNumericImpl(offset, unit);
968     }
969 
970     /**
971      * Gets the string value from qualitativeUnitMap with fallback based on style.
972      */
getAbsoluteUnitString(Style style, AbsoluteUnit unit, Direction direction)973     private String getAbsoluteUnitString(Style style, AbsoluteUnit unit, Direction direction) {
974         EnumMap<AbsoluteUnit, EnumMap<Direction, String>> unitMap;
975         EnumMap<Direction, String> dirMap;
976 
977         do {
978             unitMap = qualitativeUnitMap.get(style);
979             if (unitMap != null) {
980                 dirMap = unitMap.get(unit);
981                 if (dirMap != null) {
982                     String result = dirMap.get(direction);
983                     if (result != null) {
984                         return result;
985                     }
986                 }
987 
988             }
989 
990             // Consider other styles from alias fallback.
991             // Data loading guaranteed no endless loops.
992         } while ((style = fallbackCache[style.ordinal()]) != null);
993         return null;
994     }
995 
996     /**
997      * Combines a relative date string and a time string in this object's
998      * locale. This is done with the same date-time separator used for the
999      * default calendar in this locale.
1000      * @param relativeDateString the relative date e.g 'yesterday'
1001      * @param timeString the time e.g '3:45'
1002      * @return the date and time concatenated according to the default
1003      * calendar in this locale e.g 'yesterday, 3:45'
1004      * @stable ICU 53
1005      */
combineDateAndTime(String relativeDateString, String timeString)1006     public String combineDateAndTime(String relativeDateString, String timeString) {
1007         // BEGIN Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717).
1008         MessageFormat msgFmt = new MessageFormat("");
1009         msgFmt.applyPattern(combinedDateAndTime, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
1010         StringBuffer combinedDateTimeBuffer = new StringBuffer(128);
1011         return msgFmt.format(new Object[] { timeString, relativeDateString},
1012                 combinedDateTimeBuffer, new FieldPosition(0)).toString();
1013         // END Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717).
1014     }
1015 
1016     /**
1017      * Returns a copy of the NumberFormat this object is using.
1018      * @return A copy of the NumberFormat.
1019      * @stable ICU 53
1020      */
getNumberFormat()1021     public NumberFormat getNumberFormat() {
1022         // This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this
1023         // class we must guarantee that only one thread at a time uses our numberFormat.
1024         synchronized (numberFormat) {
1025             return (NumberFormat) numberFormat.clone();
1026         }
1027     }
1028 
1029     /**
1030      * Return capitalization context.
1031      * @return The capitalization context.
1032      * @stable ICU 54
1033      */
getCapitalizationContext()1034     public DisplayContext getCapitalizationContext() {
1035         return capitalizationContext;
1036     }
1037 
1038     /**
1039      * Return style
1040      * @return The formatting style.
1041      * @stable ICU 54
1042      */
getFormatStyle()1043     public Style getFormatStyle() {
1044         return style;
1045     }
1046 
adjustForContext(String originalFormattedString)1047     private String adjustForContext(String originalFormattedString) {
1048         if (breakIterator == null || originalFormattedString.length() == 0
1049                 || !UCharacter.isLowerCase(UCharacter.codePointAt(originalFormattedString, 0))) {
1050             return originalFormattedString;
1051         }
1052         synchronized (breakIterator) {
1053             return UCharacter.toTitleCase(
1054                     locale,
1055                     originalFormattedString,
1056                     breakIterator,
1057                     UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
1058         }
1059     }
1060 
checkNoAdjustForContext()1061     private void checkNoAdjustForContext() {
1062         if (breakIterator != null) {
1063             throw new UnsupportedOperationException("Capitalization context is not supported in formatV");
1064         }
1065     }
1066 
RelativeDateTimeFormatter( EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap, String combinedDateAndTime, PluralRules pluralRules, NumberFormat numberFormat, Style style, DisplayContext capitalizationContext, BreakIterator breakIterator, ULocale locale)1067     private RelativeDateTimeFormatter(
1068             EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap,
1069             EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap,
1070             String combinedDateAndTime,
1071             PluralRules pluralRules,
1072             NumberFormat numberFormat,
1073             Style style,
1074             DisplayContext capitalizationContext,
1075             BreakIterator breakIterator,
1076             ULocale locale) {
1077         this.qualitativeUnitMap = qualitativeUnitMap;
1078         this.patternMap = patternMap;
1079         this.combinedDateAndTime = combinedDateAndTime;
1080         this.pluralRules = pluralRules;
1081         this.numberFormat = numberFormat;
1082         this.style = style;
1083         if (capitalizationContext.type() != DisplayContext.Type.CAPITALIZATION) {
1084             throw new IllegalArgumentException(capitalizationContext.toString());
1085         }
1086         this.capitalizationContext = capitalizationContext;
1087         this.breakIterator = breakIterator;
1088         this.locale = locale;
1089         this.dateFormatSymbols = new DateFormatSymbols(locale);
1090     }
1091 
getRelativeUnitPluralPattern( Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm)1092     private String getRelativeUnitPluralPattern(
1093             Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm) {
1094         if (pluralForm != StandardPlural.OTHER) {
1095             String formatter = getRelativeUnitPattern(style, unit, pastFutureIndex, pluralForm);
1096             if (formatter != null) {
1097                 return formatter;
1098             }
1099         }
1100         return getRelativeUnitPattern(style, unit, pastFutureIndex, StandardPlural.OTHER);
1101     }
1102 
getRelativeUnitPattern( Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm)1103     private String getRelativeUnitPattern(
1104             Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm) {
1105         int pluralIndex = pluralForm.ordinal();
1106         do {
1107             EnumMap<RelativeUnit, String[][]> unitMap = patternMap.get(style);
1108             if (unitMap != null) {
1109                 String[][] spfCompiledPatterns = unitMap.get(unit);
1110                 if (spfCompiledPatterns != null) {
1111                     if (spfCompiledPatterns[pastFutureIndex][pluralIndex] != null) {
1112                         return spfCompiledPatterns[pastFutureIndex][pluralIndex];
1113                     }
1114                 }
1115 
1116             }
1117 
1118             // Consider other styles from alias fallback.
1119             // Data loading guaranteed no endless loops.
1120         } while ((style = fallbackCache[style.ordinal()]) != null);
1121         return null;
1122     }
1123 
1124     private final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap;
1125     private final EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap;
1126 
1127     // Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717).
1128     private final String combinedDateAndTime;  // MessageFormat pattern for combining date and time.
1129     private final PluralRules pluralRules;
1130     private final NumberFormat numberFormat;
1131 
1132     private final Style style;
1133     private final DisplayContext capitalizationContext;
1134     private final BreakIterator breakIterator;
1135     private final ULocale locale;
1136 
1137     private final DateFormatSymbols dateFormatSymbols;
1138 
1139     private static final Style fallbackCache[] = new Style[Style.INDEX_COUNT];
1140 
1141     private static class RelativeDateTimeFormatterData {
RelativeDateTimeFormatterData( EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap, String dateTimePattern)1142         public RelativeDateTimeFormatterData(
1143                 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap,
1144                 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap,
1145                 String dateTimePattern) {
1146             this.qualitativeUnitMap = qualitativeUnitMap;
1147             this.relUnitPatternMap = relUnitPatternMap;
1148 
1149             this.dateTimePattern = dateTimePattern;
1150         }
1151 
1152         public final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap;
1153         EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap;
1154         public final String dateTimePattern;  // Example: "{1}, {0}"
1155     }
1156 
1157     private static class Cache {
1158         private final CacheBase<String, RelativeDateTimeFormatterData, ULocale> cache =
1159             new SoftCache<String, RelativeDateTimeFormatterData, ULocale>() {
1160                 @Override
1161                 protected RelativeDateTimeFormatterData createInstance(String key, ULocale locale) {
1162                     return new Loader(locale).load();
1163                 }
1164             };
1165 
get(ULocale locale)1166         public RelativeDateTimeFormatterData get(ULocale locale) {
1167             String key = locale.toString();
1168             return cache.getInstance(key, locale);
1169         }
1170     }
1171 
keyToDirection(UResource.Key key)1172     private static Direction keyToDirection(UResource.Key key) {
1173         if (key.contentEquals("-2")) {
1174             return Direction.LAST_2;
1175         }
1176         if (key.contentEquals("-1")) {
1177             return Direction.LAST;
1178         }
1179         if (key.contentEquals("0")) {
1180             return Direction.THIS;
1181         }
1182         if (key.contentEquals("1")) {
1183             return Direction.NEXT;
1184         }
1185         if (key.contentEquals("2")) {
1186             return Direction.NEXT_2;
1187         }
1188         return null;
1189     }
1190 
1191     /**
1192      * Sink for enumerating all of the relative data time formatter names.
1193      *
1194      * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
1195      * Only store a value if it is still missing, that is, it has not been overridden.
1196      */
1197     private static final class RelDateTimeDataSink extends UResource.Sink {
1198 
1199         // For white list of units to handle in RelativeDateTimeFormatter.
1200         private enum DateTimeUnit {
1201             SECOND(RelativeUnit.SECONDS, null),
1202             MINUTE(RelativeUnit.MINUTES, AbsoluteUnit.MINUTE),
1203             HOUR(RelativeUnit.HOURS, AbsoluteUnit.HOUR),
1204             DAY(RelativeUnit.DAYS, AbsoluteUnit.DAY),
1205             WEEK(RelativeUnit.WEEKS, AbsoluteUnit.WEEK),
1206             MONTH(RelativeUnit.MONTHS, AbsoluteUnit.MONTH),
1207             QUARTER(RelativeUnit.QUARTERS, AbsoluteUnit.QUARTER),
1208             YEAR(RelativeUnit.YEARS, AbsoluteUnit.YEAR),
1209             SUNDAY(null, AbsoluteUnit.SUNDAY),
1210             MONDAY(null, AbsoluteUnit.MONDAY),
1211             TUESDAY(null, AbsoluteUnit.TUESDAY),
1212             WEDNESDAY(null, AbsoluteUnit.WEDNESDAY),
1213             THURSDAY(null, AbsoluteUnit.THURSDAY),
1214             FRIDAY(null, AbsoluteUnit.FRIDAY),
1215             SATURDAY(null, AbsoluteUnit.SATURDAY);
1216 
1217             RelativeUnit relUnit;
1218             AbsoluteUnit absUnit;
1219 
DateTimeUnit(RelativeUnit relUnit, AbsoluteUnit absUnit)1220             DateTimeUnit(RelativeUnit relUnit, AbsoluteUnit absUnit) {
1221                 this.relUnit = relUnit;
1222                 this.absUnit = absUnit;
1223             }
1224 
orNullFromString(CharSequence keyword)1225             private static final DateTimeUnit orNullFromString(CharSequence keyword) {
1226                 // Quick check from string to enum.
1227                 switch (keyword.length()) {
1228                 case 3:
1229                     if ("day".contentEquals(keyword)) {
1230                         return DAY;
1231                     } else if ("sun".contentEquals(keyword)) {
1232                         return SUNDAY;
1233                     } else if ("mon".contentEquals(keyword)) {
1234                         return MONDAY;
1235                     } else if ("tue".contentEquals(keyword)) {
1236                         return TUESDAY;
1237                     } else if ("wed".contentEquals(keyword)) {
1238                         return WEDNESDAY;
1239                     } else if ("thu".contentEquals(keyword)) {
1240                         return THURSDAY;
1241                     }    else if ("fri".contentEquals(keyword)) {
1242                         return FRIDAY;
1243                     } else if ("sat".contentEquals(keyword)) {
1244                         return SATURDAY;
1245                     }
1246                     break;
1247                 case 4:
1248                     if ("hour".contentEquals(keyword)) {
1249                         return HOUR;
1250                     } else if ("week".contentEquals(keyword)) {
1251                         return WEEK;
1252                     } else if ("year".contentEquals(keyword)) {
1253                         return YEAR;
1254                     }
1255                     break;
1256                 case 5:
1257                     if ("month".contentEquals(keyword)) {
1258                         return MONTH;
1259                     }
1260                     break;
1261                 case 6:
1262                     if ("minute".contentEquals(keyword)) {
1263                         return MINUTE;
1264                     }else if ("second".contentEquals(keyword)) {
1265                         return SECOND;
1266                     }
1267                     break;
1268                 case 7:
1269                     if ("quarter".contentEquals(keyword)) {
1270                         return QUARTER;  // RelativeUnit.QUARTERS is deprecated
1271                     }
1272                     break;
1273                 default:
1274                     break;
1275                 }
1276                 return null;
1277             }
1278         }
1279 
1280         EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap =
1281                 new EnumMap<>(Style.class);
1282         EnumMap<Style, EnumMap<RelativeUnit, String[][]>> styleRelUnitPatterns =
1283                 new EnumMap<>(Style.class);
1284 
1285         StringBuilder sb = new StringBuilder();
1286 
1287         // Values keep between levels of parsing the CLDR data.
1288         int pastFutureIndex;
1289         Style style;                        // {LONG, SHORT, NARROW} Derived from unit key string.
1290         DateTimeUnit unit;                  // From the unit key string, with the style (e.g., "-short") separated out.
1291 
styleFromKey(UResource.Key key)1292         private Style styleFromKey(UResource.Key key) {
1293             if (key.endsWith("-short")) {
1294                 return Style.SHORT;
1295             } else if (key.endsWith("-narrow")) {
1296                 return Style.NARROW;
1297             } else {
1298                 return Style.LONG;
1299             }
1300         }
1301 
styleFromAlias(UResource.Value value)1302         private Style styleFromAlias(UResource.Value value) {
1303                 String s = value.getAliasString();
1304                 if (s.endsWith("-short")) {
1305                     return Style.SHORT;
1306                 } else if (s.endsWith("-narrow")) {
1307                     return Style.NARROW;
1308                 } else {
1309                     return Style.LONG;
1310                 }
1311         }
1312 
styleSuffixLength(Style style)1313         private static int styleSuffixLength(Style style) {
1314             switch (style) {
1315             case SHORT: return 6;
1316             case NARROW: return 7;
1317             default: return 0;
1318             }
1319         }
1320 
consumeTableRelative(UResource.Key key, UResource.Value value)1321         public void consumeTableRelative(UResource.Key key, UResource.Value value) {
1322             UResource.Table unitTypesTable = value.getTable();
1323             for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
1324                 if (value.getType() == ICUResourceBundle.STRING) {
1325                     String valueString = value.getString();
1326 
1327                     EnumMap<AbsoluteUnit, EnumMap<Direction, String>> absMap = qualitativeUnitMap.get(style);
1328 
1329                     if (unit.relUnit == RelativeUnit.SECONDS) {
1330                         if (key.contentEquals("0")) {
1331                             // Handle Zero seconds for "now".
1332                             EnumMap<Direction, String> unitStrings = absMap.get(AbsoluteUnit.NOW);
1333                             if (unitStrings == null) {
1334                                 unitStrings = new EnumMap<>(Direction.class);
1335                                 absMap.put(AbsoluteUnit.NOW, unitStrings);
1336                             }
1337                             if (unitStrings.get(Direction.PLAIN) == null) {
1338                                 unitStrings.put(Direction.PLAIN, valueString);
1339                             }
1340                             continue;
1341                         }
1342                     }
1343                     Direction keyDirection = keyToDirection(key);
1344                     if (keyDirection == null) {
1345                         continue;
1346                     }
1347                     AbsoluteUnit absUnit = unit.absUnit;
1348                     if (absUnit == null) {
1349                         continue;
1350                     }
1351 
1352                     if (absMap == null) {
1353                         absMap = new EnumMap<>(AbsoluteUnit.class);
1354                         qualitativeUnitMap.put(style, absMap);
1355                     }
1356                     EnumMap<Direction, String> dirMap = absMap.get(absUnit);
1357                     if (dirMap == null) {
1358                         dirMap = new EnumMap<>(Direction.class);
1359                         absMap.put(absUnit, dirMap);
1360                     }
1361                     if (dirMap.get(keyDirection) == null) {
1362                         // Do not override values already entered.
1363                         dirMap.put(keyDirection, value.getString());
1364                     }
1365                 }
1366             }
1367         }
1368 
1369         // Record past or future and
consumeTableRelativeTime(UResource.Key key, UResource.Value value)1370         public void consumeTableRelativeTime(UResource.Key key, UResource.Value value) {
1371             if (unit.relUnit == null) {
1372                 return;
1373             }
1374             UResource.Table unitTypesTable = value.getTable();
1375             for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
1376                 if (key.contentEquals("past")) {
1377                     pastFutureIndex = 0;
1378                 } else if (key.contentEquals("future")) {
1379                     pastFutureIndex = 1;
1380                 } else {
1381                     continue;
1382                 }
1383                 // Get the details of the relative time.
1384                 consumeTimeDetail(key, value);
1385             }
1386         }
1387 
consumeTimeDetail(UResource.Key key, UResource.Value value)1388         public void consumeTimeDetail(UResource.Key key, UResource.Value value) {
1389             UResource.Table unitTypesTable = value.getTable();
1390 
1391             EnumMap<RelativeUnit, String[][]> unitPatterns  = styleRelUnitPatterns.get(style);
1392             if (unitPatterns == null) {
1393                 unitPatterns = new EnumMap<>(RelativeUnit.class);
1394                 styleRelUnitPatterns.put(style, unitPatterns);
1395             }
1396             String[][] patterns = unitPatterns.get(unit.relUnit);
1397             if (patterns == null) {
1398                 patterns = new String[2][StandardPlural.COUNT];
1399                 unitPatterns.put(unit.relUnit, patterns);
1400             }
1401 
1402             // Stuff the pattern for the correct plural index with a simple formatter.
1403             for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
1404                 if (value.getType() == ICUResourceBundle.STRING) {
1405                     int pluralIndex = StandardPlural.indexFromString(key.toString());
1406                     if (patterns[pastFutureIndex][pluralIndex] == null) {
1407                         patterns[pastFutureIndex][pluralIndex] =
1408                                 SimpleFormatterImpl.compileToStringMinMaxArguments(
1409                                         value.getString(), sb, 0, 1);
1410                     }
1411                 }
1412             }
1413         }
1414 
handlePlainDirection(UResource.Key key, UResource.Value value)1415         private void handlePlainDirection(UResource.Key key, UResource.Value value) {
1416             AbsoluteUnit absUnit = unit.absUnit;
1417             if (absUnit == null) {
1418                 return;  // Not interesting.
1419             }
1420             EnumMap<AbsoluteUnit, EnumMap<Direction, String>> unitMap =
1421                     qualitativeUnitMap.get(style);
1422             if (unitMap == null) {
1423                 unitMap = new EnumMap<>(AbsoluteUnit.class);
1424                 qualitativeUnitMap.put(style, unitMap);
1425             }
1426             EnumMap<Direction,String> dirMap = unitMap.get(absUnit);
1427             if (dirMap == null) {
1428                 dirMap = new EnumMap<>(Direction.class);
1429                 unitMap.put(absUnit, dirMap);
1430             }
1431             if (dirMap.get(Direction.PLAIN) == null) {
1432                 dirMap.put(Direction.PLAIN, value.toString());
1433             }
1434         }
1435 
1436         // Handle at the Unit level,
consumeTimeUnit(UResource.Key key, UResource.Value value)1437         public void consumeTimeUnit(UResource.Key key, UResource.Value value) {
1438             UResource.Table unitTypesTable = value.getTable();
1439             for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
1440                 if (key.contentEquals("dn") && value.getType() == ICUResourceBundle.STRING) {
1441                     handlePlainDirection(key, value);
1442                 }
1443                 if (value.getType() == ICUResourceBundle.TABLE) {
1444                     if (key.contentEquals("relative")) {
1445                         consumeTableRelative(key, value);
1446                     } else if (key.contentEquals("relativeTime")) {
1447                         consumeTableRelativeTime(key, value);
1448                     }
1449                 }
1450             }
1451         }
1452 
handleAlias(UResource.Key key, UResource.Value value, boolean noFallback)1453         private void handleAlias(UResource.Key key, UResource.Value value, boolean noFallback) {
1454             Style sourceStyle = styleFromKey(key);
1455             int limit = key.length() - styleSuffixLength(sourceStyle);
1456             DateTimeUnit unit = DateTimeUnit.orNullFromString(key.substring(0, limit));
1457             if (unit != null) {
1458                 // Record the fallback chain for the values.
1459                 // At formatting time, limit to 2 levels of fallback.
1460                 Style targetStyle = styleFromAlias(value);
1461                 if (sourceStyle == targetStyle) {
1462                     throw new ICUException("Invalid style fallback from " + sourceStyle + " to itself");
1463                 }
1464 
1465                 // Check for inconsistent fallbacks.
1466                 if (fallbackCache[sourceStyle.ordinal()] == null) {
1467                     fallbackCache[sourceStyle.ordinal()] = targetStyle;
1468                 } else if (fallbackCache[sourceStyle.ordinal()] != targetStyle) {
1469                     throw new ICUException(
1470                             "Inconsistent style fallback for style " + sourceStyle + " to " + targetStyle);
1471                 }
1472                 return;
1473             }
1474         }
1475 
1476         @Override
put(UResource.Key key, UResource.Value value, boolean noFallback)1477         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
1478             // Main entry point to sink
1479             if (value.getType() == ICUResourceBundle.ALIAS) {
1480                 return;
1481             }
1482 
1483             UResource.Table table = value.getTable();
1484             // Process each key / value in this table.
1485             for (int i = 0; table.getKeyAndValue(i, key, value); i++) {
1486                 if (value.getType() == ICUResourceBundle.ALIAS) {
1487                     handleAlias(key, value, noFallback);
1488                 } else {
1489                     // Remember style and unit for deeper levels.
1490                     style = styleFromKey(key);
1491                     int limit = key.length() - styleSuffixLength(style);
1492                     unit = DateTimeUnit.orNullFromString(key.substring(0, limit));
1493                     if (unit != null) {
1494                         // Process only if unitString is in the white list.
1495                         consumeTimeUnit(key, value);
1496                     }
1497                 }
1498             }
1499         }
1500 
RelDateTimeDataSink()1501         RelDateTimeDataSink() {
1502         }
1503     }
1504 
1505     private static class Loader {
1506         private final ULocale ulocale;
1507 
Loader(ULocale ulocale)1508         public Loader(ULocale ulocale) {
1509             this.ulocale = ulocale;
1510         }
1511 
getDateTimePattern(ICUResourceBundle r)1512         private String getDateTimePattern(ICUResourceBundle r) {
1513             String calType = r.getStringWithFallback("calendar/default");
1514             if (calType == null || calType.equals("")) {
1515                 calType = "gregorian";
1516             }
1517             String resourcePath = "calendar/" + calType + "/DateTimePatterns";
1518             ICUResourceBundle patternsRb = r.findWithFallback(resourcePath);
1519             if (patternsRb == null && calType.equals("gregorian")) {
1520                 // Try with gregorian.
1521                 patternsRb = r.findWithFallback("calendar/gregorian/DateTimePatterns");
1522             }
1523             if (patternsRb == null || patternsRb.getSize() < 9) {
1524                 // Undefined or too few elements.
1525                 return "{1} {0}";
1526             } else {
1527                 int elementType = patternsRb.get(8).getType();
1528                 if (elementType == UResourceBundle.ARRAY) {
1529                     return patternsRb.get(8).getString(0);
1530                 } else {
1531                     return patternsRb.getString(8);
1532                 }
1533             }
1534         }
1535 
load()1536         public RelativeDateTimeFormatterData load() {
1537             // Sink for traversing data.
1538             RelDateTimeDataSink sink = new RelDateTimeDataSink();
1539 
1540             ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
1541                     getBundleInstance(ICUData.ICU_BASE_NAME, ulocale);
1542             r.getAllItemsWithFallback("fields", sink);
1543 
1544             // Check fallbacks array for loops or too many levels.
1545             for (Style testStyle : Style.values()) {
1546                 Style newStyle1 = fallbackCache[testStyle.ordinal()];
1547                 // Data loading guaranteed newStyle1 != testStyle.
1548                 if (newStyle1 != null) {
1549                     Style newStyle2 = fallbackCache[newStyle1.ordinal()];
1550                     if (newStyle2 != null) {
1551                         // No fallback should take more than 2 steps.
1552                         if (fallbackCache[newStyle2.ordinal()] != null) {
1553                             throw new IllegalStateException("Style fallback too deep");
1554                         }
1555                     }
1556                 }
1557             }
1558 
1559             return new RelativeDateTimeFormatterData(
1560                     sink.qualitativeUnitMap, sink.styleRelUnitPatterns,
1561                     getDateTimePattern(r));
1562         }
1563     }
1564 
1565     private static final Cache cache = new Cache();
1566 }
1567