script-astra/Android/Sdk/sources/android-35/android/icu/text/RelativeDateTimeFormatter.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

1459 lines
54 KiB
Java
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2013-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package android.icu.text;
import java.io.InvalidObjectException;
import java.text.AttributedCharacterIterator;
import java.text.Format;
import java.text.FieldPosition;
import java.util.EnumMap;
import java.util.Locale;
import android.icu.impl.CacheBase;
import android.icu.impl.FormattedStringBuilder;
import android.icu.impl.FormattedValueStringBuilderImpl;
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
import android.icu.impl.SimpleFormatterImpl;
import android.icu.impl.SoftCache;
import android.icu.impl.StandardPlural;
import android.icu.impl.UResource;
import android.icu.impl.Utility;
import android.icu.impl.number.DecimalQuantity;
import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
import android.icu.lang.UCharacter;
import android.icu.util.Calendar;
import android.icu.util.ICUException;
import android.icu.util.ULocale;
import android.icu.util.UResourceBundle;
/**
* Formats simple relative dates. There are two types of relative dates that
* it handles:
* <ul>
* <li>relative dates with a quantity e.g "in 5 days"</li>
* <li>relative dates without a quantity e.g "next Tuesday"</li>
* </ul>
* <p>
* This API is very basic and is intended to be a building block for more
* fancy APIs. The caller tells it exactly what to display in a locale
* independent way. While this class automatically provides the correct plural
* forms, the grammatical form is otherwise as neutral as possible. It is the
* caller's responsibility to handle cut-off logic such as deciding between
* displaying "in 7 days" or "in 1 week." This API supports relative dates
* involving one single unit. This API does not support relative dates
* involving compound units.
* e.g "in 5 days and 4 hours" nor does it support parsing.
* This class is both immutable and thread-safe.
* <p>
* Here are some examples of use:
* <blockquote>
* <pre>
* RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance();
* fmt.format(1, Direction.NEXT, RelativeUnit.DAYS); // "in 1 day"
* fmt.format(3, Direction.NEXT, RelativeUnit.DAYS); // "in 3 days"
* fmt.format(3.2, Direction.LAST, RelativeUnit.YEARS); // "3.2 years ago"
*
* fmt.format(Direction.LAST, AbsoluteUnit.SUNDAY); // "last Sunday"
* fmt.format(Direction.THIS, AbsoluteUnit.SUNDAY); // "this Sunday"
* fmt.format(Direction.NEXT, AbsoluteUnit.SUNDAY); // "next Sunday"
* fmt.format(Direction.PLAIN, AbsoluteUnit.SUNDAY); // "Sunday"
*
* fmt.format(Direction.LAST, AbsoluteUnit.DAY); // "yesterday"
* fmt.format(Direction.THIS, AbsoluteUnit.DAY); // "today"
* fmt.format(Direction.NEXT, AbsoluteUnit.DAY); // "tomorrow"
*
* fmt.format(Direction.PLAIN, AbsoluteUnit.NOW); // "now"
* </pre>
* </blockquote>
* <p>
* In the future, we may add more forms, such as abbreviated/short forms
* (3 secs ago), and relative day periods ("yesterday afternoon"), etc.
*/
public final class RelativeDateTimeFormatter {
/**
* The formatting style
*
*/
public static enum Style {
/**
* Everything spelled out.
*/
LONG,
/**
* Abbreviations used when possible.
*/
SHORT,
/**
* Use single letters when possible.
*/
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"
*/
public static enum RelativeUnit {
/**
* Seconds
*/
SECONDS,
/**
* Minutes
*/
MINUTES,
/**
* Hours
*/
HOURS,
/**
* Days
*/
DAYS,
/**
* Weeks
*/
WEEKS,
/**
* Months
*/
MONTHS,
/**
* Years
*/
YEARS,
/**
* Quarters
* @deprecated This API is ICU internal only.
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
QUARTERS,
}
/**
* Represents an absolute unit.
*/
public static enum AbsoluteUnit {
/**
* Sunday
*/
SUNDAY,
/**
* Monday
*/
MONDAY,
/**
* Tuesday
*/
TUESDAY,
/**
* Wednesday
*/
WEDNESDAY,
/**
* Thursday
*/
THURSDAY,
/**
* Friday
*/
FRIDAY,
/**
* Saturday
*/
SATURDAY,
/**
* Day
*/
DAY,
/**
* Week
*/
WEEK,
/**
* Month
*/
MONTH,
/**
* Year
*/
YEAR,
/**
* Now
*/
NOW,
/**
* Quarter
*/
QUARTER,
/**
* Hour
*/
HOUR,
/**
* Minute
*/
MINUTE,
}
/**
* Represents a direction for an absolute unit e.g "Next Tuesday"
* or "Last Tuesday"
*/
public static enum Direction {
/**
* Two before. Not fully supported in every locale
*/
LAST_2,
/**
* Last
*/
LAST,
/**
* This
*/
THIS,
/**
* Next
*/
NEXT,
/**
* Two after. Not fully supported in every locale
*/
NEXT_2,
/**
* Plain, which means the absence of a qualifier
*/
PLAIN,
}
/**
* Represents the unit for formatting a relative date. e.g "in 5 days"
* or "next year"
*/
public static enum RelativeDateTimeUnit {
/**
* Specifies that relative unit is year, e.g. "last year",
* "in 5 years".
*/
YEAR,
/**
* Specifies that relative unit is quarter, e.g. "last quarter",
* "in 5 quarters".
*/
QUARTER,
/**
* Specifies that relative unit is month, e.g. "last month",
* "in 5 months".
*/
MONTH,
/**
* Specifies that relative unit is week, e.g. "last week",
* "in 5 weeks".
*/
WEEK,
/**
* Specifies that relative unit is day, e.g. "yesterday",
* "in 5 days".
*/
DAY,
/**
* Specifies that relative unit is hour, e.g. "1 hour ago",
* "in 5 hours".
*/
HOUR,
/**
* Specifies that relative unit is minute, e.g. "1 minute ago",
* "in 5 minutes".
*/
MINUTE,
/**
* Specifies that relative unit is second, e.g. "1 second ago",
* "in 5 seconds".
*/
SECOND,
/**
* Specifies that relative unit is Sunday, e.g. "last Sunday",
* "this Sunday", "next Sunday", "in 5 Sundays".
*/
SUNDAY,
/**
* Specifies that relative unit is Monday, e.g. "last Monday",
* "this Monday", "next Monday", "in 5 Mondays".
*/
MONDAY,
/**
* Specifies that relative unit is Tuesday, e.g. "last Tuesday",
* "this Tuesday", "next Tuesday", "in 5 Tuesdays".
*/
TUESDAY,
/**
* Specifies that relative unit is Wednesday, e.g. "last Wednesday",
* "this Wednesday", "next Wednesday", "in 5 Wednesdays".
*/
WEDNESDAY,
/**
* Specifies that relative unit is Thursday, e.g. "last Thursday",
* "this Thursday", "next Thursday", "in 5 Thursdays".
*/
THURSDAY,
/**
* Specifies that relative unit is Friday, e.g. "last Friday",
* "this Friday", "next Friday", "in 5 Fridays".
*/
FRIDAY,
/**
* Specifies that relative unit is Saturday, e.g. "last Saturday",
* "this Saturday", "next Saturday", "in 5 Saturdays".
*/
SATURDAY,
}
/**
* Field constants used when accessing field information for relative
* datetime strings in FormattedValue.
* <p>
* There is no public constructor to this class; the only instances are the
* constants defined here.
* <p>
* @hide Only a subset of ICU is exposed in Android
*/
public static class Field extends Format.Field {
private static final long serialVersionUID = -5327685528663492325L;
/**
* Represents a literal text string, like "tomorrow" or "days ago".
*/
public static final Field LITERAL = new Field("literal");
/**
* Represents a number quantity, like "3" in "3 days ago".
*/
public static final Field NUMERIC = new Field("numeric");
private Field(String fieldName) {
super(fieldName);
}
/**
* Serizalization method resolve instances to the constant Field values
*
* @deprecated This API is ICU internal only.
* @hide draft / provisional / internal are hidden on Android
*/
@Deprecated
@Override
protected Object readResolve() throws InvalidObjectException {
if (this.getName().equals(LITERAL.getName()))
return LITERAL;
if (this.getName().equals(NUMERIC.getName()))
return NUMERIC;
throw new InvalidObjectException("An invalid object.");
}
}
/**
* Represents the result of a formatting operation of a relative datetime.
* Access the string value or field information.
*
* Instances of this class are immutable and thread-safe.
*
* Not intended for public subclassing.
*
* @author sffc
*/
public static class FormattedRelativeDateTime implements FormattedValue {
private final FormattedStringBuilder string;
private FormattedRelativeDateTime(FormattedStringBuilder string) {
this.string = string;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return string.toString();
}
/**
* {@inheritDoc}
*/
@Override
public int length() {
return string.length();
}
/**
* {@inheritDoc}
*/
@Override
public char charAt(int index) {
return string.charAt(index);
}
/**
* {@inheritDoc}
*/
@Override
public CharSequence subSequence(int start, int end) {
return string.subString(start, end);
}
/**
* {@inheritDoc}
*/
@Override
public <A extends Appendable> A appendTo(A appendable) {
return Utility.appendTo(string, appendable);
}
/**
* {@inheritDoc}
*/
@Override
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
return FormattedValueStringBuilderImpl.nextPosition(string, cfpos, Field.NUMERIC);
}
/**
* {@inheritDoc}
*/
@Override
public AttributedCharacterIterator toCharacterIterator() {
return FormattedValueStringBuilderImpl.toCharacterIterator(string, Field.NUMERIC);
}
}
/**
* Returns a RelativeDateTimeFormatter for the default locale.
*/
public static RelativeDateTimeFormatter getInstance() {
return getInstance(ULocale.getDefault(), null, Style.LONG, DisplayContext.CAPITALIZATION_NONE);
}
/**
* Returns a RelativeDateTimeFormatter for a particular locale.
*
* @param locale the locale.
* @return An instance of RelativeDateTimeFormatter.
*/
public static RelativeDateTimeFormatter getInstance(ULocale locale) {
return getInstance(locale, null, Style.LONG, DisplayContext.CAPITALIZATION_NONE);
}
/**
* Returns a RelativeDateTimeFormatter for a particular {@link java.util.Locale}.
*
* @param locale the {@link java.util.Locale}.
* @return An instance of RelativeDateTimeFormatter.
*/
public static RelativeDateTimeFormatter getInstance(Locale locale) {
return getInstance(ULocale.forLocale(locale));
}
/**
* Returns a RelativeDateTimeFormatter for a particular locale that uses a particular
* NumberFormat object.
*
* @param locale the locale
* @param nf the number format object. It is defensively copied to ensure thread-safety
* and immutability of this class.
* @return An instance of RelativeDateTimeFormatter.
*/
public static RelativeDateTimeFormatter getInstance(ULocale locale, NumberFormat nf) {
return getInstance(locale, nf, Style.LONG, DisplayContext.CAPITALIZATION_NONE);
}
/**
* Returns a RelativeDateTimeFormatter for a particular locale that uses a particular
* NumberFormat object, style, and capitalization context
*
* @param locale the locale
* @param nf the number format object. It is defensively copied to ensure thread-safety
* and immutability of this class. May be null.
* @param style the style.
* @param capitalizationContext the capitalization context.
*/
public static RelativeDateTimeFormatter getInstance(
ULocale locale,
NumberFormat nf,
Style style,
DisplayContext capitalizationContext) {
RelativeDateTimeFormatterData data = cache.get(locale);
if (nf == null) {
nf = NumberFormat.getInstance(locale);
} else {
nf = (NumberFormat) nf.clone();
}
return new RelativeDateTimeFormatter(
data.qualitativeUnitMap,
data.relUnitPatternMap,
// Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717).
data.dateTimePattern,
PluralRules.forLocale(locale),
nf,
style,
capitalizationContext,
capitalizationContext == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ?
BreakIterator.getSentenceInstance(locale) : null,
locale);
}
/**
* Returns a RelativeDateTimeFormatter for a particular {@link java.util.Locale} that uses a
* particular NumberFormat object.
*
* @param locale the {@link java.util.Locale}
* @param nf the number format object. It is defensively copied to ensure thread-safety
* and immutability of this class.
* @return An instance of RelativeDateTimeFormatter.
*/
public static RelativeDateTimeFormatter getInstance(Locale locale, NumberFormat nf) {
return getInstance(ULocale.forLocale(locale), nf);
}
/**
* Formats a relative date with a quantity such as "in 5 days" or
* "3 months ago".
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param quantity The numerical amount e.g 5. This value is formatted
* according to this object's {@link NumberFormat} object.
* @param direction NEXT means a future relative date; LAST means a past
* relative date.
* @param unit the unit e.g day? month? year?
* @return the formatted string
* @throws IllegalArgumentException if direction is something other than
* NEXT or LAST.
*/
public String format(double quantity, Direction direction, RelativeUnit unit) {
FormattedStringBuilder output = formatImpl(quantity, direction, unit);
return adjustForContext(output.toString());
}
/**
* Formats a relative date with a quantity such as "in 5 days" or
* "3 months ago".
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* @param quantity The numerical amount e.g 5. This value is formatted
* according to this object's {@link NumberFormat} object.
* @param direction NEXT means a future relative date; LAST means a past
* relative date.
* @param unit the unit e.g day? month? year?
* @return the formatted relative datetime
* @throws IllegalArgumentException if direction is something other than
* NEXT or LAST.
*/
public FormattedRelativeDateTime formatToValue(double quantity, Direction direction, RelativeUnit unit) {
checkNoAdjustForContext();
return new FormattedRelativeDateTime(formatImpl(quantity, direction, unit));
}
/** Implementation method for format and formatToValue with RelativeUnit */
private FormattedStringBuilder formatImpl(double quantity, Direction direction, RelativeUnit unit) {
if (direction != Direction.LAST && direction != Direction.NEXT) {
throw new IllegalArgumentException("direction must be NEXT or LAST");
}
int pastFutureIndex = (direction == Direction.NEXT ? 1 : 0);
FormattedStringBuilder output = new FormattedStringBuilder();
String pluralKeyword;
if (numberFormat instanceof DecimalFormat) {
DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(quantity);
((DecimalFormat) numberFormat).toNumberFormatter().formatImpl(dq, output);
pluralKeyword = pluralRules.select(dq);
} else {
String result = numberFormat.format(quantity);
output.append(result, null);
pluralKeyword = pluralRules.select(quantity);
}
StandardPlural pluralForm = StandardPlural.orOtherFromString(pluralKeyword);
String compiledPattern = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm);
SimpleFormatterImpl.formatPrefixSuffix(compiledPattern, Field.LITERAL, 0, output.length(), output);
return output;
}
/**
* Format a combination of RelativeDateTimeUnit and numeric offset
* using a numeric style, e.g. "1 week ago", "in 1 week",
* "5 weeks ago", "in 5 weeks".
*
* This method returns a String. To get more information about the
* formatting result, use formatNumericToValue().
*
* @param offset The signed offset for the specified unit. This
* will be formatted according to this object's
* NumberFormat object.
* @param unit The unit to use when formatting the relative
* date, e.g. RelativeDateTimeUnit.WEEK,
* RelativeDateTimeUnit.FRIDAY.
* @return The formatted string (may be empty in case of error)
*/
public String formatNumeric(double offset, RelativeDateTimeUnit unit) {
FormattedStringBuilder output = formatNumericImpl(offset, unit);
return adjustForContext(output.toString());
}
/**
* Format a combination of RelativeDateTimeUnit and numeric offset
* using a numeric style, e.g. "1 week ago", "in 1 week",
* "5 weeks ago", "in 5 weeks".
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by formatNumeric().
*
* @param offset The signed offset for the specified unit. This
* will be formatted according to this object's
* NumberFormat object.
* @param unit The unit to use when formatting the relative
* date, e.g. RelativeDateTimeUnit.WEEK,
* RelativeDateTimeUnit.FRIDAY.
* @return The formatted string (may be empty in case of error)
*/
public FormattedRelativeDateTime formatNumericToValue(double offset, RelativeDateTimeUnit unit) {
checkNoAdjustForContext();
return new FormattedRelativeDateTime(formatNumericImpl(offset, unit));
}
/** Implementation method for formatNumeric and formatNumericToValue */
private FormattedStringBuilder formatNumericImpl(double offset, RelativeDateTimeUnit unit) {
// TODO:
// The full implementation of this depends on CLDR data that is not yet available,
// see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data.
// In the meantime do a quick bring-up by calling the old format method. When the
// new CLDR data is available, update the data storage accordingly, rewrite this
// to use it directly, and rewrite the old format method to call this new one;
// that is covered by https://unicode-org.atlassian.net/browse/ICU-12171.
RelativeUnit relunit = RelativeUnit.SECONDS;
switch (unit) {
case YEAR: relunit = RelativeUnit.YEARS; break;
case QUARTER: relunit = RelativeUnit.QUARTERS; break;
case MONTH: relunit = RelativeUnit.MONTHS; break;
case WEEK: relunit = RelativeUnit.WEEKS; break;
case DAY: relunit = RelativeUnit.DAYS; break;
case HOUR: relunit = RelativeUnit.HOURS; break;
case MINUTE: relunit = RelativeUnit.MINUTES; break;
case SECOND: break; // set above
default: // SUNDAY..SATURDAY
throw new UnsupportedOperationException("formatNumeric does not currently support RelativeUnit.SUNDAY..SATURDAY");
}
Direction direction = Direction.NEXT;
if (Double.compare(offset,0.0) < 0) { // needed to handle -0.0
direction = Direction.LAST;
offset = -offset;
}
return formatImpl(offset, direction, relunit);
}
private int[] styleToDateFormatSymbolsWidth = {
DateFormatSymbols.WIDE, DateFormatSymbols.SHORT, DateFormatSymbols.NARROW
};
/**
* Formats a relative date without a quantity.
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param direction NEXT, LAST, THIS, etc.
* @param unit e.g SATURDAY, DAY, MONTH
* @return the formatted string. If direction has a value that is documented as not being
* fully supported in every locale (for example NEXT_2 or LAST_2) then this function may
* return null to signal that no formatted string is available.
* @throws IllegalArgumentException if the direction is incompatible with
* unit this can occur with NOW which can only take PLAIN.
*/
public String format(Direction direction, AbsoluteUnit unit) {
String result = formatAbsoluteImpl(direction, unit);
return result != null ? adjustForContext(result) : null;
}
/**
* Formats a relative date without a quantity.
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* @param direction NEXT, LAST, THIS, etc.
* @param unit e.g SATURDAY, DAY, MONTH
* @return the formatted string. If direction has a value that is documented as not being
* fully supported in every locale (for example NEXT_2 or LAST_2) then this function may
* return null to signal that no formatted string is available.
* @throws IllegalArgumentException if the direction is incompatible with
* unit this can occur with NOW which can only take PLAIN.
*/
public FormattedRelativeDateTime formatToValue(Direction direction, AbsoluteUnit unit) {
checkNoAdjustForContext();
String string = formatAbsoluteImpl(direction, unit);
if (string == null) {
return null;
}
FormattedStringBuilder nsb = new FormattedStringBuilder();
nsb.append(string, Field.LITERAL);
return new FormattedRelativeDateTime(nsb);
}
/** Implementation method for format and formatToValue with AbsoluteUnit */
private String formatAbsoluteImpl(Direction direction, AbsoluteUnit unit) {
if (unit == AbsoluteUnit.NOW && direction != Direction.PLAIN) {
throw new IllegalArgumentException("NOW can only accept direction PLAIN.");
}
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;
}
/**
* Format a combination of RelativeDateTimeUnit and numeric offset
* using a text style if possible, e.g. "last week", "this week",
* "next week", "yesterday", "tomorrow". Falls back to numeric
* style if no appropriate text term is available for the specified
* offset in the objects locale.
*
* This method returns a String. To get more information about the
* formatting result, use formatToValue().
*
* @param offset The signed offset for the specified field.
* @param unit The unit to use when formatting the relative
* date, e.g. RelativeDateTimeUnit.WEEK,
* RelativeDateTimeUnit.FRIDAY.
* @return The formatted string (may be empty in case of error)
*/
public String format(double offset, RelativeDateTimeUnit unit) {
return adjustForContext(formatRelativeImpl(offset, unit).toString());
}
/**
* Format a combination of RelativeDateTimeUnit and numeric offset
* using a text style if possible, e.g. "last week", "this week",
* "next week", "yesterday", "tomorrow". Falls back to numeric
* style if no appropriate text term is available for the specified
* offset in the objects locale.
*
* This method returns a FormattedRelativeDateTime, which exposes more
* information than the String returned by format().
*
* @param offset The signed offset for the specified field.
* @param unit The unit to use when formatting the relative
* date, e.g. RelativeDateTimeUnit.WEEK,
* RelativeDateTimeUnit.FRIDAY.
* @return The formatted string (may be empty in case of error)
*/
public FormattedRelativeDateTime formatToValue(double offset, RelativeDateTimeUnit unit) {
checkNoAdjustForContext();
CharSequence cs = formatRelativeImpl(offset, unit);
FormattedStringBuilder nsb;
if (cs instanceof FormattedStringBuilder) {
nsb = (FormattedStringBuilder) cs;
} else {
nsb = new FormattedStringBuilder();
nsb.append(cs, Field.LITERAL);
}
return new FormattedRelativeDateTime(nsb);
}
/** Implementation method for format and formatToValue with RelativeDateTimeUnit. */
private CharSequence formatRelativeImpl(double offset, RelativeDateTimeUnit unit) {
// TODO:
// The full implementation of this depends on CLDR data that is not yet available,
// see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data.
// In the meantime do a quick bring-up by calling the old format method. When the
// new CLDR data is available, update the data storage accordingly, rewrite this
// to use it directly, and rewrite the old format method to call this new one;
// that is covered by https://unicode-org.atlassian.net/browse/ICU-12171.
boolean useNumeric = true;
Direction direction = Direction.THIS;
if (offset > -2.1 && offset < 2.1) {
// Allow a 1% epsilon, so offsets in -1.01..-0.99 map to LAST
double offsetx100 = offset * 100.0;
int intoffsetx100 = (offsetx100 < 0)? (int)(offsetx100-0.5) : (int)(offsetx100+0.5);
switch (intoffsetx100) {
case -200/*-2*/: direction = Direction.LAST_2; useNumeric = false; break;
case -100/*-1*/: direction = Direction.LAST; useNumeric = false; break;
case 0/* 0*/: useNumeric = false; break; // direction = Direction.THIS was set above
case 100/* 1*/: direction = Direction.NEXT; useNumeric = false; break;
case 200/* 2*/: direction = Direction.NEXT_2; useNumeric = false; break;
default: break;
}
}
AbsoluteUnit absunit = AbsoluteUnit.NOW;
switch (unit) {
case YEAR: absunit = AbsoluteUnit.YEAR; break;
case QUARTER: absunit = AbsoluteUnit.QUARTER; break;
case MONTH: absunit = AbsoluteUnit.MONTH; break;
case WEEK: absunit = AbsoluteUnit.WEEK; break;
case DAY: absunit = AbsoluteUnit.DAY; break;
case SUNDAY: absunit = AbsoluteUnit.SUNDAY; break;
case MONDAY: absunit = AbsoluteUnit.MONDAY; break;
case TUESDAY: absunit = AbsoluteUnit.TUESDAY; break;
case WEDNESDAY: absunit = AbsoluteUnit.WEDNESDAY; break;
case THURSDAY: absunit = AbsoluteUnit.THURSDAY; break;
case FRIDAY: absunit = AbsoluteUnit.FRIDAY; break;
case SATURDAY: absunit = AbsoluteUnit.SATURDAY; break;
case HOUR: absunit = AbsoluteUnit.HOUR; break;
case MINUTE: absunit = AbsoluteUnit.MINUTE; break;
case SECOND:
if (direction == Direction.THIS) {
// absunit = AbsoluteUnit.NOW was set above
direction = Direction.PLAIN;
break;
}
// could just fall through here but that produces warnings
useNumeric = true;
break;
default:
useNumeric = true;
break;
}
if (!useNumeric) {
String result = formatAbsoluteImpl(direction, absunit);
if (result != null && result.length() > 0) {
return result;
}
}
// otherwise fallback to formatNumeric
return formatNumericImpl(offset, unit);
}
/**
* Gets the string value from qualitativeUnitMap with fallback based on style.
*/
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
* default calendar in this locale.
* @param relativeDateString the relative date e.g 'yesterday'
* @param timeString the time e.g '3:45'
* @return the date and time concatenated according to the default
* calendar in this locale e.g 'yesterday, 3:45'
*/
public String combineDateAndTime(String relativeDateString, String timeString) {
// BEGIN Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717).
MessageFormat msgFmt = new MessageFormat("");
msgFmt.applyPattern(combinedDateAndTime, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
StringBuffer combinedDateTimeBuffer = new StringBuffer(128);
return msgFmt.format(new Object[] { timeString, relativeDateString},
combinedDateTimeBuffer, new FieldPosition(0)).toString();
// END Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717).
}
/**
* Returns a copy of the NumberFormat this object is using.
* @return A copy of the NumberFormat.
*/
public NumberFormat getNumberFormat() {
// 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) {
return (NumberFormat) numberFormat.clone();
}
}
/**
* Return capitalization context.
* @return The capitalization context.
*/
public DisplayContext getCapitalizationContext() {
return capitalizationContext;
}
/**
* Return style
* @return The formatting style.
*/
public Style getFormatStyle() {
return style;
}
private String adjustForContext(String originalFormattedString) {
if (breakIterator == null || originalFormattedString.length() == 0
|| !UCharacter.isLowerCase(UCharacter.codePointAt(originalFormattedString, 0))) {
return originalFormattedString;
}
synchronized (breakIterator) {
return UCharacter.toTitleCase(
locale,
originalFormattedString,
breakIterator,
UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
}
}
private void checkNoAdjustForContext() {
if (breakIterator != null) {
throw new UnsupportedOperationException("Capitalization context is not supported in formatV");
}
}
private RelativeDateTimeFormatter(
EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap,
EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap,
String combinedDateAndTime,
PluralRules pluralRules,
NumberFormat numberFormat,
Style style,
DisplayContext capitalizationContext,
BreakIterator breakIterator,
ULocale locale) {
this.qualitativeUnitMap = qualitativeUnitMap;
this.patternMap = patternMap;
this.combinedDateAndTime = combinedDateAndTime;
this.pluralRules = pluralRules;
this.numberFormat = numberFormat;
this.style = style;
if (capitalizationContext.type() != DisplayContext.Type.CAPITALIZATION) {
throw new IllegalArgumentException(capitalizationContext.toString());
}
this.capitalizationContext = capitalizationContext;
this.breakIterator = breakIterator;
this.locale = locale;
this.dateFormatSymbols = new DateFormatSymbols(locale);
}
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, String[][]>> patternMap;
// Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717).
private final String combinedDateAndTime; // MessageFormat pattern for combining date and time.
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, String[][]>> relUnitPatternMap,
String dateTimePattern) {
this.qualitativeUnitMap = qualitativeUnitMap;
this.relUnitPatternMap = relUnitPatternMap;
this.dateTimePattern = dateTimePattern;
}
public final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap;
EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap;
public final String dateTimePattern; // Example: "{1}, {0}"
}
private static class Cache {
private final CacheBase<String, RelativeDateTimeFormatterData, ULocale> cache =
new SoftCache<String, RelativeDateTimeFormatterData, ULocale>() {
@Override
protected RelativeDateTimeFormatterData createInstance(String key, ULocale locale) {
return new Loader(locale).load();
}
};
public RelativeDateTimeFormatterData get(ULocale locale) {
String key = locale.toString();
return cache.getInstance(key, locale);
}
}
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;
}
/**
* Sink for enumerating all of the relative data time formatter names.
*
* 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.
*/
private static final class RelDateTimeDataSink extends UResource.Sink {
// For white list of units to handle in RelativeDateTimeFormatter.
private enum DateTimeUnit {
SECOND(RelativeUnit.SECONDS, null),
MINUTE(RelativeUnit.MINUTES, AbsoluteUnit.MINUTE),
HOUR(RelativeUnit.HOURS, AbsoluteUnit.HOUR),
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; // RelativeUnit.QUARTERS is deprecated
}
break;
default:
break;
}
return null;
}
}
EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap =
new EnumMap<>(Style.class);
EnumMap<Style, EnumMap<RelativeUnit, String[][]>> styleRelUnitPatterns =
new EnumMap<>(Style.class);
StringBuilder sb = new StringBuilder();
// 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;
}
}
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 int styleSuffixLength(Style style) {
switch (style) {
case SHORT: return 6;
case NARROW: return 7;
default: return 0;
}
}
public void consumeTableRelative(UResource.Key key, UResource.Value value) {
UResource.Table unitTypesTable = value.getTable();
for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
if (value.getType() == ICUResourceBundle.STRING) {
String valueString = value.getString();
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.class);
absMap.put(AbsoluteUnit.NOW, unitStrings);
}
if (unitStrings.get(Direction.PLAIN) == null) {
unitStrings.put(Direction.PLAIN, valueString);
}
continue;
}
}
Direction keyDirection = keyToDirection(key);
if (keyDirection == null) {
continue;
}
AbsoluteUnit absUnit = unit.absUnit;
if (absUnit == null) {
continue;
}
if (absMap == null) {
absMap = new EnumMap<>(AbsoluteUnit.class);
qualitativeUnitMap.put(style, absMap);
}
EnumMap<Direction, String> dirMap = absMap.get(absUnit);
if (dirMap == null) {
dirMap = new EnumMap<>(Direction.class);
absMap.put(absUnit, dirMap);
}
if (dirMap.get(keyDirection) == null) {
// Do not override values already entered.
dirMap.put(keyDirection, value.getString());
}
}
}
}
// Record past or future and
public void consumeTableRelativeTime(UResource.Key key, UResource.Value value) {
if (unit.relUnit == null) {
return;
}
UResource.Table unitTypesTable = value.getTable();
for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
if (key.contentEquals("past")) {
pastFutureIndex = 0;
} else if (key.contentEquals("future")) {
pastFutureIndex = 1;
} else {
continue;
}
// Get the details of the relative time.
consumeTimeDetail(key, value);
}
}
public void consumeTimeDetail(UResource.Key key, UResource.Value value) {
UResource.Table unitTypesTable = value.getTable();
EnumMap<RelativeUnit, String[][]> unitPatterns = styleRelUnitPatterns.get(style);
if (unitPatterns == null) {
unitPatterns = new EnumMap<>(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);
}
// Stuff the pattern for the correct plural index with a simple formatter.
for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
if (value.getType() == ICUResourceBundle.STRING) {
int pluralIndex = StandardPlural.indexFromString(key.toString());
if (patterns[pastFutureIndex][pluralIndex] == null) {
patterns[pastFutureIndex][pluralIndex] =
SimpleFormatterImpl.compileToStringMinMaxArguments(
value.getString(), sb, 0, 1);
}
}
}
}
private void handlePlainDirection(UResource.Key key, UResource.Value value) {
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.class);
qualitativeUnitMap.put(style, unitMap);
}
EnumMap<Direction,String> dirMap = unitMap.get(absUnit);
if (dirMap == null) {
dirMap = new EnumMap<>(Direction.class);
unitMap.put(absUnit, dirMap);
}
if (dirMap.get(Direction.PLAIN) == null) {
dirMap.put(Direction.PLAIN, value.toString());
}
}
// Handle at the Unit level,
public void consumeTimeUnit(UResource.Key key, UResource.Value value) {
UResource.Table unitTypesTable = value.getTable();
for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) {
if (key.contentEquals("dn") && value.getType() == ICUResourceBundle.STRING) {
handlePlainDirection(key, value);
}
if (value.getType() == ICUResourceBundle.TABLE) {
if (key.contentEquals("relative")) {
consumeTableRelative(key, value);
} else if (key.contentEquals("relativeTime")) {
consumeTableRelativeTime(key, value);
}
}
}
}
private void handleAlias(UResource.Key key, UResource.Value value, boolean noFallback) {
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);
}
return;
}
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
// Main entry point to sink
if (value.getType() == ICUResourceBundle.ALIAS) {
return;
}
UResource.Table table = value.getTable();
// Process each key / value in this table.
for (int i = 0; table.getKeyAndValue(i, key, value); i++) {
if (value.getType() == ICUResourceBundle.ALIAS) {
handleAlias(key, value, noFallback);
} else {
// Remember style and unit for deeper levels.
style = styleFromKey(key);
int limit = key.length() - styleSuffixLength(style);
unit = DateTimeUnit.orNullFromString(key.substring(0, limit));
if (unit != null) {
// Process only if unitString is in the white list.
consumeTimeUnit(key, value);
}
}
}
}
RelDateTimeDataSink() {
}
}
private static class Loader {
private final ULocale ulocale;
public Loader(ULocale ulocale) {
this.ulocale = ulocale;
}
private String getDateTimePattern() {
Calendar cal = Calendar.getInstance(ulocale);
return Calendar.getDateAtTimePattern(cal, ulocale, DateFormat.MEDIUM);
}
public RelativeDateTimeFormatterData load() {
// Sink for traversing data.
RelDateTimeDataSink sink = new RelDateTimeDataSink();
ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
getBundleInstance(ICUData.ICU_BASE_NAME, ulocale);
r.getAllItemsWithFallback("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");
}
}
}
}
return new RelativeDateTimeFormatterData(
sink.qualitativeUnitMap, sink.styleRelUnitPatterns,
getDateTimePattern());
}
}
private static final Cache cache = new Cache();
}