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