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