humanize.ICUHumanize Maven / Gradle / Ivy
Show all versions of humanize-icu Show documentation
package humanize;
import static humanize.util.Constants.EMPTY;
import humanize.icu.spi.context.DefaultICUContext;
import humanize.icu.spi.context.ICUContextFactory;
import humanize.icu.spi.MessageFormat;
import humanize.spi.context.ContextFactory;
import humanize.text.TextUtils;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import humanize.text.Replacer;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.text.SimpleDateFormat;
/**
*
* Facility for adding a "human touch" to data. It is thread-safe and supports
* per-thread internationalization. Additionally provides a concise facade for
* access to the International Components for
* Unicode (ICU) Java APIs.
*
*
* @author mfornos
*
*/
public final class ICUHumanize {
private static final ContextFactory contextFactory = loadContextFactory();
private static final ThreadLocal context = new ThreadLocal() {
protected DefaultICUContext initialValue() {
return (DefaultICUContext) contextFactory.createContext();
};
};
/**
*
* Returns an ICU based DateFormat instance for the current thread.
*
*
* Date/Time format syntax:
*
*
* The date/time format is specified by means of a string time pattern. In
* this pattern, all ASCII letters are reserved as pattern letters, which
* are defined as the following:
*
*
*
*
* Symbol Meaning Presentation Example
* ------ ------- ------------ -------
* G era designator (Text) AD
* y year (Number) 1996
* Y year (week of year) (Number) 1997
* u extended year (Number) 4601
* U cyclic year name (Text,NumFallback) ren-chen (29)
* Q Quarter (Text & Number) Q2 & 02
* M month in year (Text & Number) July & 07
* d day in month (Number) 10
* h hour in am/pm (1~12) (Number) 12
* H hour in day (0~23) (Number) 0
* m minute in hour (Number) 30
* s second in minute (Number) 55
* S fractional second (Number) 978
* E day of week (Text) Tuesday
* e day of week (local 1~7) (Text & Number) Tues & 2
* D day in year (Number) 189
* F day of week in month (Number) 2 (2nd Wed in July)
* w week in year (Number) 27
* W week in month (Number) 2
* a am/pm marker (Text) PM
* k hour in day (1~24) (Number) 24
* K hour in am/pm (0~11) (Number) 0
* z time zone (Text) PST
* zzzz time zone (Text) Pacific Standard Time
* Z time zone (RFC 822) (Number) -0800
* ZZZZ time zone (RFC 822) (Text & Number) GMT-08:00
* ZZZZZ time zone (ISO 8601) (Text & Number) -08:00 & Z
* v time zone (generic) (Text) PT
* vvvv time zone (generic) (Text) Pacific Time
* V time zone (abreviation) (Text) PST
* VVVV time zone (location) (Text) United States Time (Los Angeles)
* g Julian day (Number) 2451334
* A milliseconds in day (Number) 69540000
* q stand alone quarter (Text & Number) Q2 & 02
* L stand alone month (Text & Number) July & 07
* c stand alone day of week (Text & Number) Tuesday & 2
* ' escape for text (Delimiter) 'Date='
* '' single quote (Literal) 'o''clock'
*
*
* The count of pattern letters determine the format.
*
*
* (Text): 4 or more, use full form, <4, use short or abbreviated form if
* it exists. (e.g., "EEEE" produces "Monday", "EEE" produces "Mon")
*
*
* (Number): the minimum number of digits. Shorter numbers are zero-padded
* to this amount (e.g. if "m" produces "6", "mm" produces "06"). Year is
* handled specially; that is, if the count of 'y' is 2, the Year will be
* truncated to 2 digits. (e.g., if "yyyy" produces "1997", "yy" produces
* "97".) Unlike other fields, fractional seconds are padded on the right
* with zero.
*
*
* (Text & Number): 3 or over, use text, otherwise use number. (e.g.,
* "M" produces "1", "MM" produces "01", "MMM" produces "Jan", and "MMMM"
* produces "January".)
*
*
* (Text,NumFallback): Behaves like Text if there is supporting data, like
* Number otherwise.
*
*
* Any characters in the pattern that are not in the ranges of ['a'..'z']
* and ['A'..'Z'] will be treated as quoted text. For instance, characters
* like ':', '.', ' ', '#' and '@' will appear in the resulting time text
* even they are not embraced within single quotes.
*
*
* A pattern containing any invalid pattern letter will result in a failing
* UErrorCode result during formatting or parsing.
*
*
* Examples using the US locale:
*
*
*
* Format Pattern Result
* -------------- -------
* "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time
* "EEE, MMM d, ''yy" ->> Wed, July 10, '96
* "h:mm a" ->> 12:08 PM
* "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
* "K:mm a, vvv" ->> 0:00 PM, PT
* "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 1996.July.10 AD 12:08 PM
*
*
* @param pattern
* Format pattern that follows the conventions of
* {@link com.ibm.icu.text.DateFormat DateFormat}
* @return a DateFormat instance for the current thread
*/
public static DateFormat dateFormatInstance(final String pattern) {
return DateFormat.getPatternInstance(pattern, context.get().getLocale());
}
/**
*
* Same as {@link #dateFormatInstance(String) dateFormatInstance} for the
* specified locale.
*
*
* @param pattern
* Format pattern that follows the conventions of
* {@link com.ibm.icu.text.DateFormat DateFormat}
* @param locale
* Target locale
* @return a DateFormat instance for the current thread
*/
public static DateFormat dateFormatInstance(final String pattern, final Locale locale) {
return withinLocale(new Callable() {
public DateFormat call() throws Exception {
return dateFormatInstance(pattern);
}
}, locale);
}
/**
*
* Returns an ICU based DecimalFormat instance for the current thread. It
* has a variety of features designed to make it possible to parse and
* format numbers in any locale, including support for Western, Arabic, or
* Indic digits. It also supports different flavors of numbers, including
* integers ("123"), fixed-point numbers ("123.4"), scientific notation
* ("1.23E4"), percentages ("12%"), and currency amounts ("$123.00",
* "USD123.00", "123.00 US dollars"). All of these flavors can be easily
* localized.
*
*
* Patterns
*
*
* A DecimalFormat
consists of a pattern and a set of
* symbols. The pattern may be set directly using #applyPattern ,
* or indirectly using other API methods which manipulate aspects of the
* pattern, such as the minimum number of integer digits. The symbols are
* stored in a DecimalFormatSymbols object. When using the NumberFormat
* factory methods, the pattern and symbols are read from ICU's locale data.
*
*
Special Pattern Characters
*
*
* Many characters in a pattern are taken literally; they are matched during
* parsing and output unchanged during formatting. Special characters, on
* the other hand, stand for other characters, strings, or classes of
* characters. For example, the '#' character is replaced by a localized
* digit. Often the replacement character is the same as the pattern
* character; in the U.S. locale, the ',' grouping character is replaced by
* ','. However, the replacement is still happening, and if the symbols are
* modified, the grouping character changes. Some special characters affect
* the behavior of the formatter by their presence; for example, if the
* percent character is seen, then the value is multiplied by 100 before
* being displayed.
*
*
* To insert a special character in a pattern as a literal, that is, without
* any special meaning, the character must be quoted. There are some
* exceptions to this which are noted below.
*
*
* The characters listed here are used in non-localized patterns. Localized
* patterns use the corresponding characters taken from this formatter's
* DecimalFormatSymbols object instead, and these characters lose their
* special status. Two exceptions are the currency sign and quote, which are
* not localized.
*
*
*
* Symbol
* Location
* Localized?
* Meaning
*
* 0
* Number
* Yes
* Digit
*
* 1-9
* Number
* Yes
* '1' through '9' indicate rounding.
*
*
* @
* Number
* No
* Significant digit
*
* #
* Number
* Yes
* Digit, zero shows as absent
*
* .
* Number
* Yes
* Decimal separator or monetary decimal separator
*
* -
* Number
* Yes
* Minus sign
*
* ,
* Number
* Yes
* Grouping separator
*
* E
* Number
* Yes
* Separates mantissa and exponent in scientific notation.
* Need not be quoted in prefix or suffix.
*
* +
* Exponent
* Yes
* Prefix positive exponents with localized plus sign.
* Need not be quoted in prefix or suffix.
*
* ;
* Subpattern boundary
* Yes
* Separates positive and negative subpatterns
*
* %
* Prefix or suffix
* Yes
* Multiply by 100 and show as percentage
*
* \u2030
* Prefix or suffix
* Yes
* Multiply by 1000 and show as per mille
*
* ¤
(\u00A4
)
* Prefix or suffix
* No
* Currency sign, replaced by currency symbol. If doubled, replaced by
* international currency symbol. If tripled, replaced by currency plural
* names, for example, "US dollar" or "US dollars" for America. If present
* in a pattern, the monetary decimal separator is used instead of the
* decimal separator.
*
* '
* Prefix or suffix
* No
* Used to quote special characters in a prefix or suffix, for example,
* "'#'#"
formats 123 to "#123"
. To create a
* single quote itself, use two in a row: "# o''clock"
.
*
* *
* Prefix or suffix boundary
* Yes
* Pad escape, precedes pad character
*
*
* A DecimalFormat
pattern contains a postive and negative
* subpattern, for example, "#,##0.00;(#,##0.00)". Each subpattern has a
* prefix, a numeric part, and a suffix. If there is no explicit negative
* subpattern, the negative subpattern is the localized minus sign prefixed
* to the positive subpattern. That is, "0.00" alone is equivalent to
* "0.00;-0.00". If there is an explicit negative subpattern, it serves only
* to specify the negative prefix and suffix; the number of digits, minimal
* digits, and other characteristics are ignored in the negative subpattern.
* That means that "#,##0.0#;(#)" has precisely the same result as
* "#,##0.0#;(#,##0.0#)".
*
*
* The prefixes, suffixes, and various symbols used for infinity, digits,
* thousands separators, decimal separators, etc. may be set to arbitrary
* values, and they will appear properly during formatting. However, care
* must be taken that the symbols and strings do not conflict, or parsing
* will be unreliable. For example, either the positive and negative
* prefixes or the suffixes must be distinct for #parse to be able to
* distinguish positive from negative values. Another example is that the
* decimal separator and thousands separator should be distinct characters,
* or parsing will be impossible.
*
*
* The grouping separator is a character that separates clusters of
* integer digits to make large numbers more legible. It commonly used for
* thousands, but in some locales it separates ten-thousands. The
* grouping size is the number of digits between the grouping
* separators, such as 3 for "100,000,000" or 4 for "1 0000 0000". There are
* actually two different grouping sizes: One used for the least significant
* integer digits, the primary grouping size, and one used for all
* others, the secondary grouping size. In most locales these are
* the same, but sometimes they are different. For example, if the primary
* grouping interval is 3, and the secondary is 2, then this corresponds to
* the pattern "#,##,##0", and the number 123456789 is formatted as
* "12,34,56,789". If a pattern contains multiple grouping separators, the
* interval between the last one and the end of the integer defines the
* primary grouping size, and the interval between the last two defines the
* secondary grouping size. All others are ignored, so "#,##,###,####" ==
* "###,###,####" == "##,#,###,####".
*
*
* Illegal patterns, such as "#.#.#" or "#.###,###", will cause
* DecimalFormat
to throw an IllegalArgumentException with a
* message that describes the problem.
*
*
Pattern BNF
*
*
* pattern := subpattern (';' subpattern)?
* subpattern := prefix? number exponent? suffix?
* number := (integer ('.' fraction)?) | sigDigits
* prefix := '\u0000'..'\uFFFD' - specialCharacters
* suffix := '\u0000'..'\uFFFD' - specialCharacters
* integer := '#'* '0'* '0'
* fraction := '0'* '#'*
* sigDigits := '#'* '@' '@'* '#'*
* exponent := 'E' '+'? '0'* '0'
* padSpec := '*' padChar
* padChar := '\u0000'..'\uFFFD' - quote
*
* Notation:
* X* 0 or more instances of X
* X? 0 or 1 instances of X
* X|Y either X or Y
* C..D any character from C up to D, inclusive
* S-T characters in S, except those in T
*
*
* The first subpattern is for positive numbers. The second (optional)
* subpattern is for negative numbers.
*
*
* Not indicated in the BNF syntax above:
*
*
* - The grouping separator ',' can occur inside the integer and sigDigits
* elements, between any two pattern characters of that element, as long as
* the integer or sigDigits element is not followed by the exponent element.
*
*
- Two grouping intervals are recognized: That between the decimal point
* and the first grouping symbol, and that between the first and second
* grouping symbols. These intervals are identical in most locales, but in
* some locales they differ. For example, the pattern "#,##,###"
* formats the number 123456789 as "12,34,56,789".
*
* -
* The pad specifier
padSpec
may appear before the prefix,
* after the prefix, before the suffix, after the suffix, or not at all.
*
* -
* In place of '0', the digits '1' through '9' may be used to indicate a
* rounding increment.
*
*
* @param pattern
* Format pattern that follows the conventions of
* {@link com.ibm.icu.text.DecimalFormat DecimalFormat}
* @return a DecimalFormat instance for the current thread
*/
public static DecimalFormat decimalFormatInstance(final String pattern) {
DecimalFormat decFmt = context.get().getDecimalFormat();
decFmt.applyPattern(pattern);
return decFmt;
}
/**
*
* Same as {@link #decimalFormatInstance(String) decimalFormatInstance} for
* the specified locale.
*
*
* @param pattern
* Format pattern that follows the conventions of
* {@link com.ibm.icu.text.DecimalFormat DecimalFormat}
* @param locale
* Target locale
* @return a DecimalFormat instance for the current thread
*/
public static DecimalFormat decimalFormatInstance(final String pattern, final Locale locale) {
return withinLocale(new Callable() {
public DecimalFormat call() throws Exception {
return decimalFormatInstance(pattern);
}
}, locale);
}
/**
*
* Formats a number of seconds as hours, minutes and seconds.
*
*
* @param value
* Number of seconds
* @return Number of seconds as hours, minutes and seconds
*/
public static String duration(final Number value) {
// NOTE: does not support any other locale
return withinLocale(new Callable() {
public String call() throws Exception {
return context.get().getRuleBasedNumberFormat(RuleBasedNumberFormat.DURATION).format(value);
}
}, Locale.ENGLISH);
}
/**
*
* Gets the ICU based DecimalFormat instance for the current thread with the
* given pattern and uses it to format the given arguments.
*
*
* @param pattern
* Format pattern that follows the conventions of
* {@link com.ibm.icu.text.MessageFormat MessageFormat}
* @param args
* Arguments
* @return The formatted String
*/
public static String format(final String pattern, final Object... args) {
return messageFormatInstance(pattern).render(args);
}
/**
*
* Smartly formats the given number as a monetary amount.
*
*
*
* For en_GB:
*
*
* Input
* Output
*
*
* 34
* "£34"
*
*
* 1000
* "£1,000"
*
*
* 12.5
* "£12.50"
*
*
*
*
* @param value
* Number to be formatted
* @return String representing the monetary amount
*/
public static String formatCurrency(final Number value) {
DecimalFormat decf = context.get().getCurrencyFormat();
return stripZeros(decf, decf.format(value));
}
/**
*
* Same as {@link #formatCurrency(Number) formatCurrency} for the specified
* locale.
*
*
* @param value
* Number to be formatted
* @param locale
* Target locale
* @return String representing the monetary amount
*/
public static String formatCurrency(final Number value, final Locale locale) {
return withinLocale(new Callable() {
public String call() {
return formatCurrency(value);
}
}, locale);
}
/**
*
* Same as {@link #formatDate(int, Date) formatDate} with SHORT style.
*
*
* @param value
* Date to be formatted
* @return String representation of the date
*/
public static String formatDate(final Date value) {
return formatDate(DateFormat.SHORT, value);
}
/**
*
* Same as {@link #formatDate(Date) formatDate} for the specified locale.
*
*
* @param value
* Date to be formatted
* @param locale
* Target locale
* @return String representation of the date
*/
public static String formatDate(final Date value, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return formatDate(value);
}
}, locale);
}
/**
*
* Formats a date according to the given pattern.
*
*
* @param value
* Date to be formatted
* @param pattern
* The pattern. See {@link dateFormatInstance(String)}
* @return a formatted date/time string
*/
public static String formatDate(final Date value, final String pattern) {
return new SimpleDateFormat(pattern, context.get().getLocale()).format(value);
}
/**
*
* Same as {@link #formatDate(Date, String) formatDate} for the specified
* locale.
*
*
* @param value
* Date to be formatted
* @param pattern
* The pattern. See {@link dateFormatInstance(String)}
* @param locale
* Target locale
* @return a formatted date/time string
*/
public static String formatDate(final Date value, final String pattern, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return formatDate(value, pattern);
}
}, locale);
}
/**
*
* Formats the given date with the specified style.
*
*
* @param style
* DateFormat style
* @param value
* Date to be formatted
* @return String representation of the date
*/
public static String formatDate(final int style, final Date value) {
return context.get().formatDate(style, value);
}
/**
*
* Same as {@link #formatDate(int, Date) formatDate} for the specified
* locale.
*
*
* @param style
* DateFormat style
* @param value
* Date to be formatted
* @param locale
* Target locale
* @return String representation of the date
*/
public static String formatDate(final int style, final Date value, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return formatDate(style, value);
}
}, locale);
}
/**
*
* Formats the given date/time with SHORT style.
*
*
* @param value
* Date to be formatted
* @return String representation of the date
*/
public static String formatDateTime(final Date value) {
return context.get().formatDateTime(value);
}
/**
*
* Same as {@link #formatDateTime(Date) formatDateTime} for the specified
* locale.
*
*
* @param value
* Date to be formatted
* @param locale
* Target locale
* @return String representation of the date
*/
public static String formatDateTime(final Date value, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return formatDateTime(value);
}
}, locale);
}
/**
*
* Formats the given date/time with the specified styles.
*
*
* @param dateStyle
* Date style
* @param timeStyle
* Time style
* @param value
* Date to be formatted
* @return String representation of the date
*/
public static String formatDateTime(final int dateStyle, final int timeStyle, final Date value) {
return context.get().formatDateTime(dateStyle, timeStyle, value);
}
/**
*
* Same as {@link #formatDateTime(int, int, Date) formatDateTime} for the
* specified locale.
*
*
* @param dateStyle
* Date style
* @param timeStyle
* Time style
* @param value
* Date to be formatted
* @param locale
* Target locale
* @return String representation of the date
*/
public static String formatDateTime(final int dateStyle, final int timeStyle, final Date value, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return formatDateTime(dateStyle, timeStyle, value);
}
}, locale);
}
/**
*
* Formats the given number to the standard decimal format for the default
* locale.
*
*
* @param value
* Number to be formatted
* @return Standard localized format representation
*/
public static String formatDecimal(final Number value) {
return context.get().formatDecimal(value);
}
/**
*
* Same as {@link #formatDecimal(Number) formatDecimal} for the specified
* locale.
*
*
* @param value
* Number to be formatted
* @param locale
* Target locale
* @return Standard localized format representation
*/
public static String formatDecimal(final Number value, final Locale locale) {
return withinLocale(new Callable() {
public String call() {
return formatDecimal(value);
}
}, locale);
}
/**
*
* Formats the given ratio as a percentage.
*
*
*
*
* Input
* Output
*
*
* 0.5
* "50%"
*
*
* 1
* "100%"
*
*
* 0.564
* "56%"
*
*
*
* @param value
* Ratio to be converted
* @return String representing the percentage
*/
public static String formatPercent(final Number value) {
return context.get().getPercentFormat().format(value);
}
/**
*
* Same as {@link #formatPercent(Number) formatPercent} for the specified
* locale.
*
*
* @param value
* Ratio to be converted
* @param locale
* Target locale
* @return String representing the percentage
*/
public static String formatPercent(final Number value, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return formatPercent(value);
}
}, locale);
}
/**
*
* Formats a monetary amount with currency plural names, for example,
* "US dollar" or "US dollars" for America.
*
*
* @param value
* Number to be formatted
* @return String representing the monetary amount
*/
public static String formatPluralCurrency(final Number value) {
DecimalFormat decf = context.get().getPluralCurrencyFormat();
return stripZeros(decf, decf.format(value));
}
/**
*
* Same as {@link #formatPluralCurrency(Number) formatPluralCurrency} for
* the specified locale.
*
*
* @param value
* Number to be formatted
* @param locale
* Target locale
* @return String representing the monetary amount
*/
public static String formatPluralCurrency(final Number value, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return formatPluralCurrency(value);
}
}, locale);
}
/**
*
* Returns an ICU based MessageFormat instance for the current thread. This
* formatter supports a rich pattern model. For plural rules see CLDR Language Plural Rules
*
*
* Patterns and Their Interpretation
*
* MessageFormat
uses patterns of the following form:
*
*
*
* MessageFormatPattern:
* String
* MessageFormatPattern FormatElement String
*
* FormatElement:
* { ArgumentIndexOrName }
* { ArgumentIndexOrName , FormatType }
* { ArgumentIndexOrName , FormatType , FormatStyle }
*
* ArgumentIndexOrName: one of
* ['0'-'9']+
* [:ID_START:][:ID_CONTINUE:]*
*
* FormatType: one of
* number date time choice
*
* FormatStyle:
* short
* medium
* long
* full
* integer
* currency
* percent
* SubformatPattern
*
* String:
* StringPartopt
* String StringPart
*
* StringPart:
* ''
* ' QuotedString '
* UnquotedString
*
* SubformatPattern:
* SubformatPatternPartopt
* SubformatPattern SubformatPatternPart
*
* SubFormatPatternPart:
* ' QuotedPattern '
* UnquotedPattern
*
*
*
*
*
* Within a String, "''"
represents a single quote. A
* QuotedString can contain arbitrary characters except single
* quotes; the surrounding single quotes are removed. An
* UnquotedString can contain arbitrary characters except single
* quotes and left curly brackets. Thus, a string that should result in the
* formatted message "'{0}'" can be written as "'''{'0}''"
or
* "'''{0}'''"
.
*
* Within a SubformatPattern, different rules apply. A
* QuotedPattern can contain arbitrary characters except single
* quotes; but the surrounding single quotes are not
* removed, so they may be interpreted by the subformat. For example,
* "{1,number,$'#',##}"
will produce a number format with the
* pound-sign quoted, with a result such as: "$#31,45". An
* UnquotedPattern can contain arbitrary characters except single
* quotes, but curly braces within it must be balanced. For example,
* "ab {0} de"
and "ab '}' de"
are valid subformat
* patterns, but "ab {0'}' de"
and "ab } de"
are
* not.
*
*
* - Warning:
*
- The rules for using quotes within message format patterns
* unfortunately have shown to be somewhat confusing. In particular, it
* isn't always obvious to localizers whether single quotes need to be
* doubled or not. Make sure to inform localizers about the rules, and tell
* them (for example, by using comments in resource bundle source files)
* which strings will be processed by MessageFormat. Note that localizers
* may need to use single quotes in translated strings where the original
* version doesn't have them.
* Note also that the simplest way to avoid the problem is to use the real
* apostrophe (single quote) character \u2019 (') for human-readable text,
* and to use the ASCII apostrophe (\u0027 ' ) only in program syntax, like
* quoting in MessageFormat. See the annotations for U+0027 Apostrophe in
* The Unicode Standard.
*
*
*
* The ArgumentIndex value is a non-negative integer written using
* the digits '0' through '9', and represents an index into the
* arguments
array passed to the format
methods or
* the result array returned by the parse
methods.
*
* The FormatType and FormatStyle values are used to create a
* Format
instance for the format element. The following table
* shows how the values map to Format instances. Combinations not shown in
* the table are illegal. A SubformatPattern must be a valid pattern
* string for the Format subclass used.
*
*
*
* Format Type
* Format Style
* Subformat Created
*
* (none)
* null
*
* number
* (none)
* NumberFormat.getInstance(getLocale())
*
* integer
* NumberFormat.getIntegerInstance(getLocale())
*
* currency
* NumberFormat.getCurrencyInstance(getLocale())
*
* percent
* NumberFormat.getPercentInstance(getLocale())
*
* SubformatPattern
*
* new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))
*
* date
* (none)
*
* DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())
*
* short
*
* DateFormat.getDateInstance(DateFormat.SHORT, getLocale())
*
* medium
*
* DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())
*
* long
* DateFormat.getDateInstance(DateFormat.LONG, getLocale())
*
* full
* DateFormat.getDateInstance(DateFormat.FULL, getLocale())
*
* SubformatPattern
* new SimpleDateFormat(subformatPattern, getLocale())
*
* time
* (none)
*
* DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())
*
* short
*
* DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())
*
* medium
*
* DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())
*
* long
* DateFormat.getTimeInstance(DateFormat.LONG, getLocale())
*
* full
* DateFormat.getTimeInstance(DateFormat.FULL, getLocale())
*
* SubformatPattern
* new SimpleDateFormat(subformatPattern, getLocale())
*
* choice
* SubformatPattern
* new ChoiceFormat(subformatPattern)
*
* spellout
* Ruleset name (optional)
*
* new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.SPELLOUT)
.setDefaultRuleset(ruleset);
*
* ordinal
* Ruleset name (optional)
*
* new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.ORDINAL)
.setDefaultRuleset(ruleset);
*
* duration
* Ruleset name (optional)
*
* new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.DURATION)
.setDefaultRuleset(ruleset);
*
* plural
* SubformatPattern
* new PluralFormat(subformatPattern)
*
*
* Examples:
*
*
* MessageFormat msg = messageFormatInstance("There {0, plural, one{is one file}other{are {0} files}} on {1}.")
*
* msg.render(1000, "disk"); // == "There are 1,000 files on disk."
*
*
* @param pattern
* Format pattern that follows the conventions of
* {@link com.ibm.icu.text.MessageFormat MessageFormat}
* @return a MessageFormat instance for the current thread
*/
public static MessageFormat messageFormatInstance(final String pattern) {
MessageFormat msg = context.get().getMessageFormat();
msg.applyPattern(pattern);
return msg;
}
/**
*
* Same as {@link #messageFormatInstance(String) messageFormatInstance} for
* the specified locale.
*
*
* @param locale
* Target locale
* @param pattern
* Format pattern that follows the conventions of
* {@link com.ibm.icu.text.MessageFormat MessageFormat}
* @return a MessageFormat instance for the current thread
*/
public static MessageFormat messageFormatInstance(final String pattern, final Locale locale) {
return withinLocale(new Callable() {
public MessageFormat call() throws Exception {
return messageFormatInstance(pattern);
}
}, locale);
}
/**
*
* Same as {@link #naturalDay(int, Date) naturalDay} with DateFormat.SHORT
* style.
*
*
* @param value
* Date to be converted
* @return String with "today", "tomorrow" or "yesterday" compared to
* current day. Otherwise, returns a string formatted according to a
* locale sensitive DateFormat.
*/
public static String naturalDay(final Date value) {
return naturalDay(DateFormat.RELATIVE_SHORT, value);
}
/**
*
* Same as {@link #naturalDay(Date) naturalDay} for the specified locale.
*
*
* @param value
* Date to be converted
* @param locale
* Target locale
* @return String with "today", "tomorrow" or "yesterday" compared to
* current day. Otherwise, returns a string formatted according to a
* locale sensitive DateFormat.
*/
public static String naturalDay(final Date value, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return naturalDay(value);
}
}, locale);
}
/**
*
* For dates that are the current day or within one day, return "today",
* "tomorrow" or "yesterday", as appropriate. Otherwise, returns a string
* formatted according to a locale sensitive DateFormat.
*
*
* @param style
* DateFormat style. RELATIVE_SHORT, RELATIVE_MEDIUM or
* RELATIVE_LONG
* @param value
* Date to be converted
* @return String with "today", "tomorrow" or "yesterday" compared to
* current day. Otherwise, returns a string formatted according to a
* locale sensitive DateFormat.
*/
public static String naturalDay(final int style, final Date value) {
return formatDate(style, value).toLowerCase();
}
/**
*
* Same as {@link #naturalTime(Date, Date) naturalTime} with current date as
* reference.
*
*
* @param duration
* Date to be used as duration from current date
* @return String representing the relative date
*/
public static String naturalTime(final Date duration) {
return context.get().getDurationFormat().formatDurationFromNowTo(duration);
}
/**
*
* Computes both past and future relative dates.
*
*
*
* E.g. "1 day ago", "1 day from now", "10 years ago", "3 minutes from now"
* and so on.
*
*
* @param reference
* Date to be used as reference
* @param duration
* Date to be used as duration from reference
* @return String representing the relative date
*/
public static String naturalTime(final Date reference, final Date duration) {
long diff = duration.getTime() - reference.getTime();
return context.get().getDurationFormat().formatDurationFrom(diff, reference.getTime());
}
/**
*
* Same as {@link #naturalTime(Date, Date) naturalTime} for the specified
* locale.
*
*
* @param reference
* Date to be used as reference
* @param duration
* Date to be used as duration from reference
* @param locale
* Target locale
* @return String representing the relative date
*/
public static String naturalTime(final Date reference, final Date duration, final Locale locale) {
return withinLocale(new Callable() {
public String call() {
return naturalTime(reference, duration);
}
}, locale);
}
/**
* Same as {@link #naturalTime(Date) naturalTime} for the specified locale.
*
* @param duration
* Date to be used as duration from current date
* @param locale
* Target locale
* @return String representing the relative date
*/
public static String naturalTime(final Date duration, final Locale locale) {
return naturalTime(new Date(), duration, locale);
}
/**
*
* Converts a number to its ordinal as a string.
*
*
*
*
* Input
* Output
*
*
* 1
* "1st"
*
*
* 2
* "2nd"
*
*
* 3
* "3rd"
*
*
* 4
* "4th"
*
*
* 1002
* "1002nd"
*
*
* 2012
* "2012th"
*
*
*
* @param value
* Number to be converted
* @return String representing the number as ordinal
*/
public static String ordinalize(final Number value) {
return context.get().getRuleBasedNumberFormat(RuleBasedNumberFormat.ORDINAL).format(value);
}
/**
*
* Same as {@link #ordinalize(Number) ordinalize} for the specified locale.
*
*
* @param value
* Number to be converted
* @param locale
* Target locale
* @return String representing the number as ordinal
*/
public static String ordinalize(final Number value, final Locale locale) {
return withinLocale(new Callable() {
public String call() {
return ordinalize(value);
}
}, locale);
}
/**
*
* Converts the given text to number.
*
*
* @param text
* String containing a spelled out number.
* @return Text converted to Number
* @throws ParseException
*/
public static Number parseNumber(final String text) throws ParseException {
return context.get().getRuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT).parse(text);
}
/**
*
* Same as {@link #parseNumber(String) parseNumber} for the specified
* locale.
*
*
* @param text
* String containing a spelled out number.
* @param locale
* Target locale
* @return Text converted to Number
* @throws ParseException
*/
public static Number parseNumber(final String text, final Locale locale) throws ParseException {
return withinLocale(new Callable() {
public Number call() throws Exception {
return parseNumber(text);
}
}, locale);
}
/**
*
* Replaces characters outside the Basic Multilingual Plane with their name.
*
*
* @param value
* The text to be matched
* @return text with characters outside BMP replaced by their name or the
* given text unaltered
*/
public static String replaceSupplementary(final String value) {
return TextUtils.replaceSupplementary(value, new Replacer() {
public String replace(String in) {
return UCharacter.getName(in, ", ");
}
});
}
/**
*
* Guesses the best locale-dependent pattern to format the date/time fields
* that the skeleton specifies.
*
*
* @param value
* The date to be formatted
* @param skeleton
* A pattern containing only the variable fields. For example,
* "MMMdd" and "mmhh" are skeletons.
* @return A string with a text representation of the date
*/
public static String smartDateFormat(final Date value, final String skeleton) {
return formatDate(value, context.get().getBestPattern(skeleton));
}
/**
*
* Same as {@link #smartDateFormat(Date) smartDateFormat} for the specified
* locale.
*
*
* @param value
* The date to be formatted
* @param skeleton
* A pattern containing only the variable fields. For example,
* "MMMdd" and "mmhh" are skeletons.
* @param locale
* Target locale
* @return A string with a text representation of the date
*/
public static String smartDateFormat(final Date value, final String skeleton, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return smartDateFormat(value, skeleton);
}
}, locale);
}
/**
*
* Converts the given number to words.
*
*
*
*
* Input
* Output
*
*
* 2840
* "two thousand eight hundred and forty"
*
*
* 1412605
* "one million four hundred and twelve thousand six hundred and five"
*
*
* 23380000000L
* "twenty-three billion three hundred and eighty million"
*
*
* 90489348043803948043 BigInt
*
* "ninety quintillion four hundred and eighty-nine quadrillion three
* hundred and forty-eight trillion and forty-three billion eight hundred
* and three million nine hundred and forty-eight thousand and forty-three"
*
*
*
* @param value
* Number to be converted
* @return the number converted to words
*/
public static String spellNumber(final Number value) {
return context.get().getRuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT).format(value);
}
/**
*
* Same as {@link #spellNumber(Number) spellNumber} for the specified
* locale.
*
*
* @param value
* Number to be converted
* @param locale
* Target locale
* @return the number converted to words
*/
public static String spellNumber(final Number value, final Locale locale) {
return withinLocale(new Callable() {
public String call() throws Exception {
return spellNumber(value);
}
}, locale);
}
// ( private methods )------------------------------------------------------
private static ContextFactory loadContextFactory() {
ServiceLoader ldr = ServiceLoader.load(ContextFactory.class);
for (ContextFactory factory : ldr) {
if (ICUContextFactory.class.isAssignableFrom(factory.getClass()))
return factory;
}
throw new RuntimeException("No ContextFactory was found");
}
private static String stripZeros(final DecimalFormat decf, final String fmtd) {
char decsep = decf.getDecimalFormatSymbols().getDecimalSeparator();
return fmtd.replaceAll("\\" + decsep + "00", EMPTY);
}
/**
*
* Wraps the given operation on a context with the specified locale.
*
*
* @param operation
* Operation to be performed
* @param locale
* Target locale
* @return Result of the operation
*/
private static T withinLocale(final Callable operation, final Locale locale) {
DefaultICUContext ctx = context.get();
Locale oldLocale = ctx.getLocale();
try {
ctx.setLocale(locale);
return operation.call();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
ctx.setLocale(oldLocale);
context.set(ctx);
}
}
private ICUHumanize() {
}
}