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