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