com.ibm.icu.text.RelativeDateTimeFormatter Maven / Gradle / Ivy
Show all versions of com.liferay.commerce.currency.service
/*
*******************************************************************************
* Copyright (C) 2013-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.text;
import java.util.EnumMap;
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.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
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.util.UResourceBundle;
/**
* Formats simple relative dates. There are two types of relative dates that
* it handles:
*
* - relative dates with a quantity e.g "in 5 days"
* - relative dates without a quantity e.g "next Tuesday"
*
*
* 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.
*
* Here are some examples of use:
*
*
* 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"
*
*
*
* In the future, we may add more forms, such as abbreviated/short forms
* (3 secs ago), and relative day periods ("yesterday afternoon"), etc.
*
* @stable ICU 53
*/
public final class RelativeDateTimeFormatter {
/**
* The formatting style
* @stable ICU 54
*
*/
public static enum Style {
/**
* Everything spelled out.
* @stable ICU 54
*/
LONG,
/**
* Abbreviations used when possible.
* @stable ICU 54
*/
SHORT,
/**
* Use single letters when possible.
* @stable ICU 54
*/
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
*/
public static enum RelativeUnit {
/**
* Seconds
* @stable ICU 53
*/
SECONDS,
/**
* Minutes
* @stable ICU 53
*/
MINUTES,
/**
* Hours
* @stable ICU 53
*/
HOURS,
/**
* Days
* @stable ICU 53
*/
DAYS,
/**
* Weeks
* @stable ICU 53
*/
WEEKS,
/**
* Months
* @stable ICU 53
*/
MONTHS,
/**
* Years
* @stable ICU 53
*/
YEARS,
/**
* Quarters
* @internal TODO: propose for addition in ICU 57
* @deprecated This API is ICU internal only.
*/
@Deprecated
QUARTERS,
}
/**
* Represents an absolute unit.
* @stable ICU 53
*/
public static enum AbsoluteUnit {
/**
* Sunday
* @stable ICU 53
*/
SUNDAY,
/**
* Monday
* @stable ICU 53
*/
MONDAY,
/**
* Tuesday
* @stable ICU 53
*/
TUESDAY,
/**
* Wednesday
* @stable ICU 53
*/
WEDNESDAY,
/**
* Thursday
* @stable ICU 53
*/
THURSDAY,
/**
* Friday
* @stable ICU 53
*/
FRIDAY,
/**
* Saturday
* @stable ICU 53
*/
SATURDAY,
/**
* Day
* @stable ICU 53
*/
DAY,
/**
* Week
* @stable ICU 53
*/
WEEK,
/**
* Month
* @stable ICU 53
*/
MONTH,
/**
* Year
* @stable ICU 53
*/
YEAR,
/**
* Now
* @stable ICU 53
*/
NOW,
/**
* Quarter
* @internal TODO: propose for addition in ICU 57
* @deprecated This API is ICU internal only.
*/
@Deprecated
QUARTER,
}
/**
* Represents a direction for an absolute unit e.g "Next Tuesday"
* or "Last Tuesday"
* @stable ICU 53
*/
public static enum Direction {
/**
* Two before. Not fully supported in every locale
* @stable ICU 53
*/
LAST_2,
/**
* Last
* @stable ICU 53
*/
LAST,
/**
* This
* @stable ICU 53
*/
THIS,
/**
* Next
* @stable ICU 53
*/
NEXT,
/**
* Two after. Not fully supported in every locale
* @stable ICU 53
*/
NEXT_2,
/**
* Plain, which means the absence of a qualifier
* @stable ICU 53
*/
PLAIN,
}
/**
* Represents the unit for formatting a relative date. e.g "in 5 days"
* or "next year"
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public static enum RelativeDateTimeUnit {
/**
* Specifies that relative unit is year, e.g. "last year",
* "in 5 years".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
YEAR,
/**
* Specifies that relative unit is quarter, e.g. "last quarter",
* "in 5 quarters".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
QUARTER,
/**
* Specifies that relative unit is month, e.g. "last month",
* "in 5 months".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
MONTH,
/**
* Specifies that relative unit is week, e.g. "last week",
* "in 5 weeks".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
WEEK,
/**
* Specifies that relative unit is day, e.g. "yesterday",
* "in 5 days".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
DAY,
/**
* Specifies that relative unit is hour, e.g. "1 hour ago",
* "in 5 hours".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
HOUR,
/**
* Specifies that relative unit is minute, e.g. "1 minute ago",
* "in 5 minutes".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
MINUTE,
/**
* Specifies that relative unit is second, e.g. "1 second ago",
* "in 5 seconds".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
SECOND,
/**
* Specifies that relative unit is Sunday, e.g. "last Sunday",
* "this Sunday", "next Sunday", "in 5 Sundays".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
SUNDAY,
/**
* Specifies that relative unit is Monday, e.g. "last Monday",
* "this Monday", "next Monday", "in 5 Mondays".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
MONDAY,
/**
* Specifies that relative unit is Tuesday, e.g. "last Tuesday",
* "this Tuesday", "next Tuesday", "in 5 Tuesdays".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
TUESDAY,
/**
* Specifies that relative unit is Wednesday, e.g. "last Wednesday",
* "this Wednesday", "next Wednesday", "in 5 Wednesdays".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
WEDNESDAY,
/**
* Specifies that relative unit is Thursday, e.g. "last Thursday",
* "this Thursday", "next Thursday", "in 5 Thursdays".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
THURSDAY,
/**
* Specifies that relative unit is Friday, e.g. "last Friday",
* "this Friday", "next Friday", "in 5 Fridays".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
FRIDAY,
/**
* Specifies that relative unit is Saturday, e.g. "last Saturday",
* "this Saturday", "next Saturday", "in 5 Saturdays".
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
SATURDAY,
}
/**
* Returns a RelativeDateTimeFormatter for the default locale.
* @stable ICU 53
*/
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.
* @stable ICU 53
*/
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.
* @stable ICU 54
*/
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.
* @stable ICU 53
*/
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.
* @stable ICU 54
*/
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,
SimpleFormatterImpl.compileToStringMinMaxArguments(
data.dateTimePattern, new StringBuilder(), 2, 2),
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.
* @stable ICU 54
*/
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"
* @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.
* @stable ICU 53
*/
public String format(double quantity, Direction direction, RelativeUnit unit) {
if (direction != Direction.LAST && direction != Direction.NEXT) {
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) {
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 = SimpleFormatterImpl.formatCompiledPattern(formatter, formatStr);
}
return adjustForContext(result);
}
/**
* 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".
*
* @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)
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public String formatNumeric(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 http://bugs.icu-project.org/trac/ticket/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 (offset < 0) {
direction = Direction.LAST;
offset = -offset;
}
String result = format(offset, direction, relunit);
return (result != null)? result: "";
}
private int[] styleToDateFormatSymbolsWidth = {
DateFormatSymbols.WIDE, DateFormatSymbols.SHORT, DateFormatSymbols.NARROW
};
/**
* Formats a relative date without a quantity.
* @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.
* @stable ICU 53
*/
public String format(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 != null ? adjustForContext(result) : null;
}
/**
* 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 object’s locale.
*
* @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)
* @draft ICU 57
* @provisional This API might change or be removed in a future release.
*/
public String format(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 http://bugs.icu-project.org/trac/ticket/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 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;
case HOUR:
default:
useNumeric = true;
break;
}
if (!useNumeric) {
String result = format(direction, absunit);
if (result != null && result.length() > 0) {
return result;
}
}
// otherwise fallback to formatNumeric
return formatNumeric(offset, unit);
}
/**
* Gets the string value from qualitativeUnitMap with fallback based on style.
*/
private String getAbsoluteUnitString(Style style, AbsoluteUnit unit, Direction direction) {
EnumMap> unitMap;
EnumMap 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'
* @stable ICU 53
*/
public String combineDateAndTime(String relativeDateString, String timeString) {
return SimpleFormatterImpl.formatCompiledPattern(
combinedDateAndTime, timeString, relativeDateString);
}
/**
* Returns a copy of the NumberFormat this object is using.
* @return A copy of the NumberFormat.
* @stable ICU 53
*/
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.
* @stable ICU 54
*/
public DisplayContext getCapitalizationContext() {
return capitalizationContext;
}
/**
* Return style
* @return The formatting style.
* @stable ICU 54
*/
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 RelativeDateTimeFormatter(
EnumMap