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