/*
*******************************************************************************
- * Copyright (C) 2013-2015, International Business Machines Corporation and
+ * Copyright (C) 2013-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
import java.util.Locale;
import com.ibm.icu.impl.CalendarData;
+import com.ibm.icu.impl.DontCareFieldPosition;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
+import com.ibm.icu.impl.SimplePatternFormatter;
+import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.lang.UCharacter;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.ICUException;
import com.ibm.icu.util.ULocale;
+import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.UResourceBundle;
* Use single letters when possible.
* @stable ICU 54
*/
- NARROW,
+ NARROW;
+
+ private static final int INDEX_COUNT = 3; // NARROW.ordinal() + 1
}
-
+
/**
* Represents the unit for formatting a relative date. e.g "in 5 days"
* or "in 3 months"
* @stable ICU 53
*/
YEARS,
+
+ /**
+ * Quarters
+ * @internal TODO: propose for addition in ICU 57
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ QUARTERS,
}
/**
* @stable ICU 53
*/
NOW,
+
+ /*
+ * Quarter
+ * @internal TODO: propose for addition in ICU 57
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ QUARTER,
}
/**
* @stable ICU 53
*/
public static enum Direction {
-
/**
* Two before. Not fully supported in every locale
* @stable ICU 53
}
return new RelativeDateTimeFormatter(
data.qualitativeUnitMap,
- data.quantitativeUnitMap,
+ data.relUnitPatternMap,
new MessageFormat(data.dateTimePattern),
PluralRules.forLocale(locale),
nf,
capitalizationContext == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ?
BreakIterator.getSentenceInstance(locale) : null,
locale);
-
}
-
+
/**
* Returns a RelativeDateTimeFormatter for a particular JDK locale that uses a particular
* NumberFormat object.
throw new IllegalArgumentException("direction must be NEXT or LAST");
}
String result;
+ int pastFutureIndex = (direction == Direction.NEXT ? 1 : 0);
+
// This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this
// class we must guarantee that only one thread at a time uses our numberFormat.
synchronized (numberFormat) {
- result = getQuantity(
- unit, direction == Direction.NEXT).format(
- quantity, numberFormat, pluralRules);
+ StringBuffer formatStr = new StringBuffer();
+ DontCareFieldPosition fieldPosition = DontCareFieldPosition.INSTANCE;
+ StandardPlural pluralForm = QuantityFormatter.selectPlural(quantity,
+ numberFormat, pluralRules, formatStr, fieldPosition);
+
+ String formatter = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
+ result = SimplePatternFormatter.formatCompiledPattern(formatter, formatStr);
}
return adjustForContext(result);
+
}
-
+
+ private int[] styleToDateFormatSymbolsWidth = {
+ DateFormatSymbols.WIDE, DateFormatSymbols.SHORT, DateFormatSymbols.NARROW
+ };
/**
* Formats a relative date without a quantity.
if (unit == AbsoluteUnit.NOW && direction != Direction.PLAIN) {
throw new IllegalArgumentException("NOW can only accept direction PLAIN.");
}
- String result = this.qualitativeUnitMap.get(style).get(unit).get(direction);
+ String result;
+ // Get plain day of week names from DateFormatSymbols.
+ if ((direction == Direction.PLAIN) && (AbsoluteUnit.SUNDAY.ordinal() <= unit.ordinal() &&
+ unit.ordinal() <= AbsoluteUnit.SATURDAY.ordinal())) {
+ // Convert from AbsoluteUnit days to Calendar class indexing.
+ int dateSymbolsDayOrdinal = (unit.ordinal() - AbsoluteUnit.SUNDAY.ordinal()) + Calendar.SUNDAY;
+ String[] dayNames =
+ dateFormatSymbols.getWeekdays(DateFormatSymbols.STANDALONE,
+ styleToDateFormatSymbolsWidth[style.ordinal()]);
+ result = dayNames[dateSymbolsDayOrdinal];
+ } else {
+ // Not PLAIN, or not a weekday.
+ result = getAbsoluteUnitString(style, unit, direction);
+ }
return result != null ? adjustForContext(result) : null;
}
+ /**
+ * Gets the string value from qualitativeUnitMap with fallback based on style.
+ * @param style
+ * @param unit
+ * @param direction
+ * @return
+ */
+ private String getAbsoluteUnitString(Style style, AbsoluteUnit unit, Direction direction) {
+ EnumMap<AbsoluteUnit, EnumMap<Direction, String>> unitMap;
+ EnumMap<Direction, String> dirMap;
+
+ do {
+ unitMap = qualitativeUnitMap.get(style);
+ if (unitMap != null) {
+ dirMap = unitMap.get(unit);
+ if (dirMap != null) {
+ String result = dirMap.get(direction);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ }
+
+ // Consider other styles from alias fallback.
+ // Data loading guaranteed no endless loops.
+ } while ((style = fallbackCache[style.ordinal()]) != null);
+ return null;
+ }
+
/**
* Combines a relative date string and a time string in this object's
* locale. This is done with the same date-time separator used for the
return this.combinedDateAndTime.format(
new Object[]{timeString, relativeDateString}, new StringBuffer(), null).toString();
}
-
+
/**
* Returns a copy of the NumberFormat this object is using.
* @return A copy of the NumberFormat.
return (NumberFormat) numberFormat.clone();
}
}
-
+
/**
* Return capitalization context.
*
public Style getFormatStyle() {
return style;
}
-
+
private String adjustForContext(String originalFormattedString) {
if (breakIterator == null || originalFormattedString.length() == 0
|| !UCharacter.isLowerCase(UCharacter.codePointAt(originalFormattedString, 0))) {
UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
}
}
-
- private static void addQualitativeUnit(
- EnumMap<AbsoluteUnit, EnumMap<Direction, String>> qualitativeUnits,
- AbsoluteUnit unit,
- String current) {
- EnumMap<Direction, String> unitStrings =
- new EnumMap<Direction, String>(Direction.class);
- unitStrings.put(Direction.PLAIN, current);
- qualitativeUnits.put(unit, unitStrings);
- }
-
- private static void addQualitativeUnit(
- EnumMap<AbsoluteUnit, EnumMap<Direction, String>> qualitativeUnits,
- AbsoluteUnit unit, ICUResourceBundle bundle, String plain) {
- EnumMap<Direction, String> unitStrings =
- new EnumMap<Direction, String>(Direction.class);
- unitStrings.put(Direction.LAST, bundle.getStringWithFallback("-1"));
- unitStrings.put(Direction.THIS, bundle.getStringWithFallback("0"));
- unitStrings.put(Direction.NEXT, bundle.getStringWithFallback("1"));
- addOptionalDirection(unitStrings, Direction.LAST_2, bundle, "-2");
- addOptionalDirection(unitStrings, Direction.NEXT_2, bundle, "2");
- unitStrings.put(Direction.PLAIN, plain);
- qualitativeUnits.put(unit, unitStrings);
- }
-
- private static void addOptionalDirection(
- EnumMap<Direction, String> unitStrings,
- Direction direction,
- ICUResourceBundle bundle,
- String key) {
- String s = bundle.findStringWithFallback(key);
- if (s != null) {
- unitStrings.put(direction, s);
- }
- }
private RelativeDateTimeFormatter(
EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap,
- EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap,
+ EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap,
MessageFormat combinedDateAndTime,
PluralRules pluralRules,
NumberFormat numberFormat,
BreakIterator breakIterator,
ULocale locale) {
this.qualitativeUnitMap = qualitativeUnitMap;
- this.quantitativeUnitMap = quantitativeUnitMap;
+ this.patternMap = patternMap;
this.combinedDateAndTime = combinedDateAndTime;
this.pluralRules = pluralRules;
this.numberFormat = numberFormat;
this.capitalizationContext = capitalizationContext;
this.breakIterator = breakIterator;
this.locale = locale;
+ this.dateFormatSymbols = new DateFormatSymbols(locale);
}
-
- private QuantityFormatter getQuantity(RelativeUnit unit, boolean isFuture) {
- QuantityFormatter[] quantities = quantitativeUnitMap.get(style).get(unit);
- return isFuture ? quantities[1] : quantities[0];
+
+ private String getRelativeUnitPluralPattern(
+ Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm) {
+ if (pluralForm != StandardPlural.OTHER) {
+ String formatter = getRelativeUnitPattern(style, unit, pastFutureIndex, pluralForm);
+ if (formatter != null) {
+ return formatter;
+ }
+ }
+ return getRelativeUnitPattern(style, unit, pastFutureIndex, StandardPlural.OTHER);
}
-
+
+ private String getRelativeUnitPattern(
+ Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm) {
+ int pluralIndex = pluralForm.ordinal();
+ do {
+ EnumMap<RelativeUnit, String[][]> unitMap = patternMap.get(style);
+ if (unitMap != null) {
+ String[][] spfCompiledPatterns = unitMap.get(unit);
+ if (spfCompiledPatterns != null) {
+ if (spfCompiledPatterns[pastFutureIndex][pluralIndex] != null) {
+ return spfCompiledPatterns[pastFutureIndex][pluralIndex];
+ }
+ }
+
+ }
+
+ // Consider other styles from alias fallback.
+ // Data loading guaranteed no endless loops.
+ } while ((style = fallbackCache[style.ordinal()]) != null);
+ return null;
+ }
+
private final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap;
- private final EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap;
+ private final EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap;
+
private final MessageFormat combinedDateAndTime;
private final PluralRules pluralRules;
private final NumberFormat numberFormat;
+
private final Style style;
private final DisplayContext capitalizationContext;
private final BreakIterator breakIterator;
private final ULocale locale;
-
+
+ private final DateFormatSymbols dateFormatSymbols;
+
+ private static final Style fallbackCache[] = new Style[Style.INDEX_COUNT];
+
private static class RelativeDateTimeFormatterData {
public RelativeDateTimeFormatterData(
EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap,
- EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap,
+ EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap,
String dateTimePattern) {
this.qualitativeUnitMap = qualitativeUnitMap;
- this.quantitativeUnitMap = quantitativeUnitMap;
+ this.relUnitPatternMap = relUnitPatternMap;
+
this.dateTimePattern = dateTimePattern;
}
-
+
public final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap;
- public final EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap;
+ EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap;
public final String dateTimePattern; // Example: "{1}, {0}"
}
-
+
private static class Cache {
private final ICUCache<String, RelativeDateTimeFormatterData> cache =
new SimpleCache<String, RelativeDateTimeFormatterData>();
return result;
}
}
-
- private static class Loader {
- private final ULocale ulocale;
-
- public Loader(ULocale ulocale) {
- this.ulocale = ulocale;
+
+ private static Direction keyToDirection(UResource.Key key) {
+ if (key.contentEquals("-2")) {
+ return Direction.LAST_2;
+ }
+ if (key.contentEquals("-1")) {
+ return Direction.LAST;
+ }
+ if (key.contentEquals("0")) {
+ return Direction.THIS;
+ }
+ if (key.contentEquals("1")) {
+ return Direction.NEXT;
+ }
+ if (key.contentEquals("2")) {
+ return Direction.NEXT_2;
}
+ return null;
+ }
- public RelativeDateTimeFormatterData load() {
- EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap =
- new EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>>(Style.class);
-
- EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap =
- new EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>>(Style.class);
-
- for (Style style : Style.values()) {
- qualitativeUnitMap.put(style, new EnumMap<AbsoluteUnit, EnumMap<Direction, String>>(AbsoluteUnit.class));
- quantitativeUnitMap.put(style, new EnumMap<RelativeUnit, QuantityFormatter[]>(RelativeUnit.class));
+ /**
+ * Sink for enumerating all of the relative data time formatter names.
+ * Contains inner sink classes, each one corresponding to a type of resource table.
+ * The outer sink handles the top-level 'fields'.
+ *
+ * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
+ * Only store a value if it is still missing, that is, it has not been overridden.
+ *
+ * C++: Each inner sink class has a reference to the main outer sink.
+ * Java: Use non-static inner classes instead.
+ */
+ private static final class RelDateTimeFmtDataSink extends UResource.TableSink {
+ // For white list of units to handle in RelativeDateTimeFormatter.
+ private static enum DateTimeUnit {
+ SECOND(RelativeUnit.SECONDS, null),
+ MINUTE(RelativeUnit.MINUTES, null),
+ HOUR(RelativeUnit.HOURS, null),
+ DAY(RelativeUnit.DAYS, AbsoluteUnit.DAY),
+ WEEK(RelativeUnit.WEEKS, AbsoluteUnit.WEEK),
+ MONTH(RelativeUnit.MONTHS, AbsoluteUnit.MONTH),
+ QUARTER(RelativeUnit.QUARTERS, AbsoluteUnit.QUARTER),
+ YEAR(RelativeUnit.YEARS, AbsoluteUnit.YEAR),
+ SUNDAY(null, AbsoluteUnit.SUNDAY),
+ MONDAY(null, AbsoluteUnit.MONDAY),
+ TUESDAY(null, AbsoluteUnit.TUESDAY),
+ WEDNESDAY(null, AbsoluteUnit.WEDNESDAY),
+ THURSDAY(null, AbsoluteUnit.THURSDAY),
+ FRIDAY(null, AbsoluteUnit.FRIDAY),
+ SATURDAY(null, AbsoluteUnit.SATURDAY);
+
+ RelativeUnit relUnit;
+ AbsoluteUnit absUnit;
+
+ DateTimeUnit(RelativeUnit relUnit, AbsoluteUnit absUnit) {
+ this.relUnit = relUnit;
+ this.absUnit = absUnit;
+ }
+
+ private static final DateTimeUnit orNullFromString(CharSequence keyword) {
+ // Quick check from string to enum.
+ switch (keyword.length()) {
+ case 3:
+ if ("day".contentEquals(keyword)) {
+ return DAY;
+ } else if ("sun".contentEquals(keyword)) {
+ return SUNDAY;
+ } else if ("mon".contentEquals(keyword)) {
+ return MONDAY;
+ } else if ("tue".contentEquals(keyword)) {
+ return TUESDAY;
+ } else if ("wed".contentEquals(keyword)) {
+ return WEDNESDAY;
+ } else if ("thu".contentEquals(keyword)) {
+ return THURSDAY;
+ } else if ("fri".contentEquals(keyword)) {
+ return FRIDAY;
+ } else if ("sat".contentEquals(keyword)) {
+ return SATURDAY;
+ }
+ break;
+ case 4:
+ if ("hour".contentEquals(keyword)) {
+ return HOUR;
+ } else if ("week".contentEquals(keyword)) {
+ return WEEK;
+ } else if ("year".contentEquals(keyword)) {
+ return YEAR;
+ }
+ break;
+ case 5:
+ if ("month".contentEquals(keyword)) {
+ return MONTH;
+ }
+ break;
+ case 6:
+ if ("minute".contentEquals(keyword)) {
+ return MINUTE;
+ }else if ("second".contentEquals(keyword)) {
+ return SECOND;
+ }
+ break;
+ case 7:
+ if ("quarter".contentEquals(keyword)) {
+ return QUARTER; // TODO: Check @provisional
+ }
+ break;
+ default:
+ break;
+ }
+ return null;
}
-
- ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
- getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale);
- addTimeUnits(
- r,
- "fields/day", "fields/day-short", "fields/day-narrow",
- RelativeUnit.DAYS,
- AbsoluteUnit.DAY,
- quantitativeUnitMap,
- qualitativeUnitMap);
- addTimeUnits(
- r,
- "fields/week", "fields/week-short", "fields/week-narrow",
- RelativeUnit.WEEKS,
- AbsoluteUnit.WEEK,
- quantitativeUnitMap,
- qualitativeUnitMap);
- addTimeUnits(
- r,
- "fields/month", "fields/month-short", "fields/month-narrow",
- RelativeUnit.MONTHS,
- AbsoluteUnit.MONTH,
- quantitativeUnitMap,
- qualitativeUnitMap);
- addTimeUnits(
- r,
- "fields/year", "fields/year-short", "fields/year-narrow",
- RelativeUnit.YEARS,
- AbsoluteUnit.YEAR,
- quantitativeUnitMap,
- qualitativeUnitMap);
- initRelativeUnits(
- r,
- "fields/second", "fields/second-short", "fields/second-narrow",
- RelativeUnit.SECONDS,
- quantitativeUnitMap);
- initRelativeUnits(
- r,
- "fields/minute", "fields/minute-short", "fields/minute-narrow",
- RelativeUnit.MINUTES,
- quantitativeUnitMap);
- initRelativeUnits(
- r,
- "fields/hour", "fields/hour-short", "fields/hour-narrow",
- RelativeUnit.HOURS,
- quantitativeUnitMap);
-
- addQualitativeUnit(
- qualitativeUnitMap.get(Style.LONG),
- AbsoluteUnit.NOW,
- r.getStringWithFallback("fields/second/relative/0"));
- addQualitativeUnit(
- qualitativeUnitMap.get(Style.SHORT),
- AbsoluteUnit.NOW,
- r.getStringWithFallback("fields/second-short/relative/0"));
- addQualitativeUnit(
- qualitativeUnitMap.get(Style.NARROW),
- AbsoluteUnit.NOW,
- r.getStringWithFallback("fields/second-narrow/relative/0"));
-
- EnumMap<Style, EnumMap<AbsoluteUnit, String>> dayOfWeekMap =
- new EnumMap<Style, EnumMap<AbsoluteUnit, String>>(Style.class);
- dayOfWeekMap.put(Style.LONG, readDaysOfWeek(
- r.getWithFallback("calendar/gregorian/dayNames/stand-alone/wide")));
- dayOfWeekMap.put(Style.SHORT, readDaysOfWeek(
- r.getWithFallback("calendar/gregorian/dayNames/stand-alone/short")));
- dayOfWeekMap.put(Style.NARROW, readDaysOfWeek(
- r.getWithFallback("calendar/gregorian/dayNames/stand-alone/narrow")));
-
- addWeekDays(
- r,
- "fields/mon/relative",
- "fields/mon-short/relative",
- "fields/mon-narrow/relative",
- dayOfWeekMap,
- AbsoluteUnit.MONDAY,
- qualitativeUnitMap);
- addWeekDays(
- r,
- "fields/tue/relative",
- "fields/tue-short/relative",
- "fields/tue-narrow/relative",
- dayOfWeekMap,
- AbsoluteUnit.TUESDAY,
- qualitativeUnitMap);
- addWeekDays(
- r,
- "fields/wed/relative",
- "fields/wed-short/relative",
- "fields/wed-narrow/relative",
- dayOfWeekMap,
- AbsoluteUnit.WEDNESDAY,
- qualitativeUnitMap);
- addWeekDays(
- r,
- "fields/thu/relative",
- "fields/thu-short/relative",
- "fields/thu-narrow/relative",
- dayOfWeekMap,
- AbsoluteUnit.THURSDAY,
- qualitativeUnitMap);
- addWeekDays(
- r,
- "fields/fri/relative",
- "fields/fri-short/relative",
- "fields/fri-narrow/relative",
- dayOfWeekMap,
- AbsoluteUnit.FRIDAY,
- qualitativeUnitMap);
- addWeekDays(
- r,
- "fields/sat/relative",
- "fields/sat-short/relative",
- "fields/sat-narrow/relative",
- dayOfWeekMap,
- AbsoluteUnit.SATURDAY,
- qualitativeUnitMap);
- addWeekDays(
- r,
- "fields/sun/relative",
- "fields/sun-short/relative",
- "fields/sun-narrow/relative",
- dayOfWeekMap,
- AbsoluteUnit.SUNDAY,
- qualitativeUnitMap);
- CalendarData calData = new CalendarData(
- ulocale, r.getStringWithFallback("calendar/default"));
- return new RelativeDateTimeFormatterData(
- qualitativeUnitMap, quantitativeUnitMap, calData.getDateTimePattern());
}
- private void addTimeUnits(
- ICUResourceBundle r,
- String path, String pathShort, String pathNarrow,
- RelativeUnit relativeUnit,
- AbsoluteUnit absoluteUnit,
- EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap,
- EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap) {
- addTimeUnit(
- r.getWithFallback(path),
- relativeUnit,
- absoluteUnit,
- quantitativeUnitMap.get(Style.LONG),
- qualitativeUnitMap.get(Style.LONG));
- addTimeUnit(
- r.getWithFallback(pathShort),
- relativeUnit,
- absoluteUnit,
- quantitativeUnitMap.get(Style.SHORT),
- qualitativeUnitMap.get(Style.SHORT));
- addTimeUnit(
- r.getWithFallback(pathNarrow),
- relativeUnit,
- absoluteUnit,
- quantitativeUnitMap.get(Style.NARROW),
- qualitativeUnitMap.get(Style.NARROW));
-
+ EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap =
+ new EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>>(Style.class);
+ EnumMap<Style, EnumMap<RelativeUnit, String[][]>> styleRelUnitPatterns =
+ new EnumMap<Style, EnumMap<RelativeUnit, String[][]>>(Style.class);
+
+ private ULocale ulocale = null;
+
+ StringBuilder sb = new StringBuilder();
+
+ public RelDateTimeFmtDataSink(ULocale locale) {
+ ulocale = locale;
}
- private void addTimeUnit(
- ICUResourceBundle timeUnitBundle,
- RelativeUnit relativeUnit,
- AbsoluteUnit absoluteUnit,
- EnumMap<RelativeUnit, QuantityFormatter[]> quantitativeUnitMap,
- EnumMap<AbsoluteUnit, EnumMap<Direction, String>> qualitativeUnitMap) {
- addTimeUnit(timeUnitBundle, relativeUnit, quantitativeUnitMap);
- String unitName = timeUnitBundle.getStringWithFallback("dn");
- // TODO(Travis Keep): This is a hack to get around CLDR bug 6818.
- if (ulocale.getLanguage().equals("en")) {
- unitName = unitName.toLowerCase();
+ // Values keep between levels of parsing the CLDR data.
+ int pastFutureIndex;
+ Style style; // {LONG, SHORT, NARROW} Derived from unit key string.
+ DateTimeUnit unit; // From the unit key string, with the style (e.g., "-short") separated out.
+
+ private Style styleFromKey(UResource.Key key) {
+ if (key.endsWith("-short")) {
+ return Style.SHORT;
+ } else if (key.endsWith("-narrow")) {
+ return Style.NARROW;
+ } else {
+ return Style.LONG;
}
- timeUnitBundle = timeUnitBundle.getWithFallback("relative");
- addQualitativeUnit(
- qualitativeUnitMap,
- absoluteUnit,
- timeUnitBundle,
- unitName);
}
-
- private void initRelativeUnits(
- ICUResourceBundle r,
- String path,
- String pathShort,
- String pathNarrow,
- RelativeUnit relativeUnit,
- EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap) {
- addTimeUnit(
- r.getWithFallback(path),
- relativeUnit,
- quantitativeUnitMap.get(Style.LONG));
- addTimeUnit(
- r.getWithFallback(pathShort),
- relativeUnit,
- quantitativeUnitMap.get(Style.SHORT));
- addTimeUnit(
- r.getWithFallback(pathNarrow),
- relativeUnit,
- quantitativeUnitMap.get(Style.NARROW));
+
+ private Style styleFromAlias(UResource.Value value) {
+ String s = value.getAliasString();
+ if (s.endsWith("-short")) {
+ return Style.SHORT;
+ } else if (s.endsWith("-narrow")) {
+ return Style.NARROW;
+ } else {
+ return Style.LONG;
+ }
}
- private static void addTimeUnit(
- ICUResourceBundle timeUnitBundle,
- RelativeUnit relativeUnit,
- EnumMap<RelativeUnit, QuantityFormatter[]> quantitativeUnitMap) {
- QuantityFormatter future = new QuantityFormatter();
- QuantityFormatter past = new QuantityFormatter();
- timeUnitBundle = timeUnitBundle.getWithFallback("relativeTime");
- addTimeUnit(
- timeUnitBundle.getWithFallback("future"),
- future);
- addTimeUnit(
- timeUnitBundle.getWithFallback("past"),
- past);
- quantitativeUnitMap.put(
- relativeUnit, new QuantityFormatter[] { past, future });
+ private static int styleSuffixLength(Style style) {
+ switch (style) {
+ case SHORT: return 6;
+ case NARROW: return 7;
+ default: return 0;
+ }
+ }
+
+ @Override
+ public void put(UResource.Key key, UResource.Value value) {
+ // Parse and store aliases.
+ if (value.getType() != ICUResourceBundle.ALIAS) { return; }
+
+ Style sourceStyle = styleFromKey(key);
+ int limit = key.length() - styleSuffixLength(sourceStyle);
+ DateTimeUnit unit = DateTimeUnit.orNullFromString(key.substring(0, limit));
+ if (unit != null) {
+ // Record the fallback chain for the values.
+ // At formatting time, limit to 2 levels of fallback.
+ Style targetStyle = styleFromAlias(value);
+ if (sourceStyle == targetStyle) {
+ throw new ICUException("Invalid style fallback from " + sourceStyle + " to itself");
+ }
+
+ // Check for inconsistent fallbacks.
+ if (fallbackCache[sourceStyle.ordinal()] == null) {
+ fallbackCache[sourceStyle.ordinal()] = targetStyle;
+ } else if (fallbackCache[sourceStyle.ordinal()] != targetStyle) {
+ throw new ICUException(
+ "Inconsistent style fallback for style " + sourceStyle + " to " + targetStyle);
+ }
+ }
}
- private static void addTimeUnit(
- ICUResourceBundle pastOrFuture, QuantityFormatter qf) {
- int size = pastOrFuture.getSize();
- for (int i = 0; i < size; i++) {
- UResourceBundle r = pastOrFuture.get(i);
- qf.addIfAbsent(r.getKey(), r.getString());
+ @Override
+ public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) {
+ // Get base unit and style from the key value.
+ style = styleFromKey(key);
+ int limit = key.length() - styleSuffixLength(style);
+ String unitString = key.substring(0, limit);
+
+ // Process only if unitString is in the white list.
+ unit = DateTimeUnit.orNullFromString(unitString);
+ if (unit == null) {
+ return null;
}
+ return unitSink; // Continue parsing this path.
}
-
- private void addWeekDays(
- ICUResourceBundle r,
- String path,
- String pathShort,
- String pathNarrow,
- EnumMap<Style, EnumMap<AbsoluteUnit, String>> dayOfWeekMap,
- AbsoluteUnit weekDay,
- EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap) {
- addQualitativeUnit(
- qualitativeUnitMap.get(Style.LONG),
- weekDay,
- r.findWithFallback(path),
- dayOfWeekMap.get(Style.LONG).get(weekDay));
- addQualitativeUnit(
- qualitativeUnitMap.get(Style.SHORT),
- weekDay,
- r.findWithFallback(pathShort),
- dayOfWeekMap.get(Style.SHORT).get(weekDay));
- addQualitativeUnit(
- qualitativeUnitMap.get(Style.NARROW),
- weekDay,
- r.findWithFallback(pathNarrow),
- dayOfWeekMap.get(Style.NARROW).get(weekDay));
-
+
+ // Sinks for additional levels under /fields/*/relative/ and /fields/*/relativeTime/
+
+ // Sets values under relativeTime paths, e.g., "hour/relativeTime/future/one"
+ class RelativeTimeDetailSink extends UResource.TableSink {
+ @Override
+ public void put(UResource.Key key, UResource.Value value) {
+ /* Make two lists of simplePatternFmtList, one for past and one for future.
+ * Set a SimplePatternFormatter for the <style, relative unit, plurality>
+ *
+ * Fill in values for the particular plural given, e.g., ONE, FEW, OTHER, etc.
+ */
+ EnumMap<RelativeUnit, String[][]> unitPatterns =
+ styleRelUnitPatterns.get(style);
+ if (unitPatterns == null) {
+ unitPatterns = new EnumMap<RelativeUnit, String[][]>(RelativeUnit.class);
+ styleRelUnitPatterns.put(style, unitPatterns);
+ }
+ String[][] patterns = unitPatterns.get(unit.relUnit);
+ if (patterns == null) {
+ patterns = new String[2][StandardPlural.COUNT];
+ unitPatterns.put(unit.relUnit, patterns);
+ }
+ int pluralIndex = StandardPlural.indexFromString(key.toString());
+ if (patterns[pastFutureIndex][pluralIndex] == null) {
+ patterns[pastFutureIndex][pluralIndex] =
+ SimplePatternFormatter.compileToStringMinMaxPlaceholders(value.getString(),
+ sb, 0, 1);
+ }
+ }
}
+ RelativeTimeDetailSink relativeTimeDetailSink = new RelativeTimeDetailSink();
- private static EnumMap<AbsoluteUnit, String> readDaysOfWeek(ICUResourceBundle daysOfWeekBundle) {
- EnumMap<AbsoluteUnit, String> dayOfWeekMap = new EnumMap<AbsoluteUnit, String>(AbsoluteUnit.class);
- if (daysOfWeekBundle.getSize() != 7) {
- throw new IllegalStateException(String.format("Expect 7 days in a week, got %d", daysOfWeekBundle.getSize()));
+ // Handles "relativeTime" entries, e.g., under "day", "hour", "minute", "minute-short", etc.
+ class RelativeTimeSink extends UResource.TableSink {
+ @Override
+ public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) {
+ if (key.contentEquals("past")) {
+ pastFutureIndex = 0;
+ } else if (key.contentEquals("future")) {
+ pastFutureIndex = 1;
+ } else {
+ return null;
+ }
+ if (unit.relUnit == null) {
+ return null;
+ }
+ return relativeTimeDetailSink;
}
- // Sunday always comes first in CLDR data.
- int idx = 0;
- dayOfWeekMap.put(AbsoluteUnit.SUNDAY, daysOfWeekBundle.getString(idx++));
- dayOfWeekMap.put(AbsoluteUnit.MONDAY, daysOfWeekBundle.getString(idx++));
- dayOfWeekMap.put(AbsoluteUnit.TUESDAY, daysOfWeekBundle.getString(idx++));
- dayOfWeekMap.put(AbsoluteUnit.WEDNESDAY, daysOfWeekBundle.getString(idx++));
- dayOfWeekMap.put(AbsoluteUnit.THURSDAY, daysOfWeekBundle.getString(idx++));
- dayOfWeekMap.put(AbsoluteUnit.FRIDAY, daysOfWeekBundle.getString(idx++));
- dayOfWeekMap.put(AbsoluteUnit.SATURDAY, daysOfWeekBundle.getString(idx++));
- return dayOfWeekMap;
+ }
+ RelativeTimeSink relativeTimeSink = new RelativeTimeSink();
+
+ // Handles "relative" entries, e.g., under "day", "day-short", "fri", "fri-narrow", "fri-short", etc.
+ class RelativeSink extends UResource.TableSink {
+ @Override
+ public void put(UResource.Key key, UResource.Value value) {
+
+ EnumMap<AbsoluteUnit, EnumMap<Direction, String>> absMap = qualitativeUnitMap.get(style);
+
+ if (unit.relUnit == RelativeUnit.SECONDS) {
+ if (key.contentEquals("0")) {
+ // Handle Zero seconds for "now".
+ EnumMap<Direction, String> unitStrings = absMap.get(AbsoluteUnit.NOW);
+ if (unitStrings == null) {
+ unitStrings = new EnumMap<Direction, String>(Direction.class);
+ absMap.put(AbsoluteUnit.NOW, unitStrings);
+ }
+ if (unitStrings.get(Direction.PLAIN) == null) {
+ unitStrings.put(Direction.PLAIN, value.getString());
+ }
+ return;
+ }
+ }
+ Direction keyDirection = keyToDirection(key);
+ if (keyDirection == null) {
+ return;
+ }
+ AbsoluteUnit absUnit = unit.absUnit;
+ if (absUnit == null) {
+ return;
+ }
+
+ if (absMap == null) {
+ absMap = new EnumMap<AbsoluteUnit, EnumMap<Direction, String>>(AbsoluteUnit.class);
+ qualitativeUnitMap.put(style, absMap);
+ }
+ EnumMap<Direction, String> dirMap = absMap.get(absUnit);
+ if (dirMap == null) {
+ dirMap = new EnumMap<Direction, String>(Direction.class);
+ absMap.put(absUnit, dirMap);
+ }
+ if (dirMap.get(keyDirection) == null) {
+ // Do not override values already entered.
+ dirMap.put(keyDirection, value.getString());
+ }
+ }
+ }
+ RelativeSink relativeSink = new RelativeSink();
+
+ // Handles entries under units, recognizing "relative" and "relativeTime" entries.
+ class UnitSink extends UResource.TableSink {
+ @Override
+ public void put(UResource.Key key, UResource.Value value) {
+ if (key.contentEquals("dn")) {
+ // Handle Display Name for PLAIN direction for some units.
+ AbsoluteUnit absUnit = unit.absUnit;
+ if (absUnit == null) {
+ return; // Not interesting.
+ }
+ EnumMap<AbsoluteUnit, EnumMap<Direction, String>> unitMap =
+ qualitativeUnitMap.get(style);
+ if (unitMap == null) {
+ unitMap = new EnumMap<AbsoluteUnit, EnumMap<Direction, String>>(AbsoluteUnit.class);
+ qualitativeUnitMap.put(style, unitMap);
+ }
+ EnumMap<Direction,String> dirMap = unitMap.get(absUnit);
+ if (dirMap == null) {
+ dirMap = new EnumMap<Direction,String>(Direction.class);
+ unitMap.put(absUnit, dirMap);
+ }
+ if (dirMap.get(Direction.PLAIN) == null) {
+ String displayName = value.toString();
+ // TODO(Travis Keep): This is a hack to get around CLDR bug 6818.
+ if (ulocale.getLanguage().equals("en")) {
+ displayName = displayName.toLowerCase(Locale.ROOT);
+ }
+ dirMap.put(Direction.PLAIN, displayName);
+ }
+ }
+ }
+
+ @Override
+ public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) {
+ if (key.contentEquals("relative")) {
+ return relativeSink;
+ } else if (key.contentEquals("relativeTime")) {
+ return relativeTimeSink;
+ }
+ return null;
+ }
+ }
+ UnitSink unitSink = new UnitSink();
+ }
+
+ private static class Loader {
+ private final ULocale ulocale;
+
+ public Loader(ULocale ulocale) {
+ this.ulocale = ulocale;
+ }
+
+ public RelativeDateTimeFormatterData load() {
+ // Sink for traversing data.
+ RelDateTimeFmtDataSink sink = new RelDateTimeFmtDataSink(ulocale);
+ ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
+ getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale);
+
+ // Use sink mechanism to traverse data structure.
+ r.getAllTableItemsWithFallback("fields", sink);
+
+ // Check fallbacks array for loops or too many levels.
+ for (Style testStyle : Style.values()) {
+ Style newStyle1 = fallbackCache[testStyle.ordinal()];
+ // Data loading guaranteed newStyle1 != testStyle.
+ if (newStyle1 != null) {
+ Style newStyle2 = fallbackCache[newStyle1.ordinal()];
+ if (newStyle2 != null) {
+ // No fallback should take more than 2 steps.
+ if (fallbackCache[newStyle2.ordinal()] != null) {
+ throw new IllegalStateException("Style fallback too deep");
+ }
+ }
+ }
+ }
+
+ // TODO: Replace this use of CalendarData.
+ CalendarData calData = new CalendarData(
+ ulocale, r.getStringWithFallback("calendar/default"));
+
+ return new RelativeDateTimeFormatterData(
+ sink.qualitativeUnitMap, sink.styleRelUnitPatterns,
+ calData.getDateTimePattern());
}
}