com.ibm.icu.impl.duration.impl.PeriodFormatterData Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
/*
******************************************************************************
* Copyright (C) 2009-2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
******************************************************************************
*/
package com.ibm.icu.impl.duration.impl;
import java.util.Arrays;
import com.ibm.icu.impl.duration.TimeUnit;
import com.ibm.icu.impl.duration.impl.DataRecord.ECountVariant;
import com.ibm.icu.impl.duration.impl.DataRecord.EDecimalHandling;
import com.ibm.icu.impl.duration.impl.DataRecord.EFractionHandling;
import com.ibm.icu.impl.duration.impl.DataRecord.EGender;
import com.ibm.icu.impl.duration.impl.DataRecord.EHalfPlacement;
import com.ibm.icu.impl.duration.impl.DataRecord.EHalfSupport;
import com.ibm.icu.impl.duration.impl.DataRecord.ENumberSystem;
import com.ibm.icu.impl.duration.impl.DataRecord.EPluralization;
import com.ibm.icu.impl.duration.impl.DataRecord.EUnitVariant;
import com.ibm.icu.impl.duration.impl.DataRecord.EZeroHandling;
import com.ibm.icu.impl.duration.impl.DataRecord.ScopeData;
/**
* PeriodFormatterData provides locale-specific data used to format
* relative dates and times, and convenience api to access it.
*
* An instance of PeriodFormatterData is usually created by requesting
* data for a given locale from an PeriodFormatterDataService.
*/
public class PeriodFormatterData {
final DataRecord dr;
String localeName;
// debug
public static boolean trace = false;
public PeriodFormatterData(String localeName, DataRecord dr) {
this.dr = dr;
this.localeName = localeName;
if(localeName == null) {
throw new NullPointerException("localename is null");
}
// System.err.println("** localeName is " + localeName);
if (dr == null) {
// Thread.dumpStack();
throw new NullPointerException("data record is null");
}
}
// none - chinese (all forms the same)
// plural - english, special form for 1
// dual - special form for 1 and 2
// paucal - russian, special form for 1, for 2-4 and n > 20 && n % 10 == 2-4
// rpt_dual_few - slovenian, special form for 1, 2, 3-4 and n as above
// hebrew, dual plus singular form for years > 11
// arabic, dual, plus singular form for all terms > 10
/**
* Return the pluralization format used by this locale.
* @return the pluralization format
*/
public int pluralization() {
return dr.pl;
}
/**
* Return true if zeros are allowed in the display.
* @return true if zeros should be allowed
*/
public boolean allowZero() {
return dr.allowZero;
}
public boolean weeksAloneOnly() {
return dr.weeksAloneOnly;
}
public int useMilliseconds() {
return dr.useMilliseconds;
}
/**
* Append the appropriate prefix to the string builder, depending on whether and
* how a limit and direction are to be displayed.
*
* @param tl how and whether to display the time limit
* @param td how and whether to display the time direction
* @param sb the string builder to which to append the text
* @return true if a following digit will require a digit prefix
*/
public boolean appendPrefix(int tl, int td, StringBuffer sb) {
if (dr.scopeData != null) {
int ix = tl * 3 + td;
ScopeData sd = dr.scopeData[ix];
if (sd != null) {
String prefix = sd.prefix;
if (prefix != null) {
sb.append(prefix);
return sd.requiresDigitPrefix;
}
}
}
return false;
}
/**
* Append the appropriate suffix to the string builder, depending on whether and
* how a limit and direction are to be displayed.
*
* @param tl how and whether to display the time limit
* @param td how and whether to display the time direction
* @param sb the string builder to which to append the text
*/
public void appendSuffix(int tl, int td, StringBuffer sb) {
if (dr.scopeData != null) {
int ix = tl * 3 + td;
ScopeData sd = dr.scopeData[ix];
if (sd != null) {
String suffix = sd.suffix;
if (suffix != null) {
if (trace) {
System.out.println("appendSuffix '" + suffix + "'");
}
sb.append(suffix);
}
}
}
}
/**
* Append the count and unit to the string builder.
*
* @param unit the unit to append
* @param count the count of units, * 1000
* @param cv the format to use for displaying the count
* @param uv the format to use for displaying the unit
* @param useCountSep if false, force no separator between count and unit
* @param useDigitPrefix if true, use the digit prefix
* @param multiple true if there are multiple units in this string
* @param last true if this is the last unit
* @param wasSkipped true if the unit(s) before this were skipped
* @param sb the string builder to which to append the text
* @return true if will require skip marker
*/
@SuppressWarnings("fallthrough")
public boolean appendUnit(TimeUnit unit, int count, int cv,
int uv, boolean useCountSep,
boolean useDigitPrefix, boolean multiple,
boolean last, boolean wasSkipped,
StringBuffer sb) {
int px = unit.ordinal();
boolean willRequireSkipMarker = false;
if (dr.requiresSkipMarker != null && dr.requiresSkipMarker[px] &&
dr.skippedUnitMarker != null) {
if (!wasSkipped && last) {
sb.append(dr.skippedUnitMarker);
}
willRequireSkipMarker = true;
}
if (uv != EUnitVariant.PLURALIZED) {
boolean useMedium = uv == EUnitVariant.MEDIUM;
String[] names = useMedium ? dr.mediumNames : dr.shortNames;
if (names == null || names[px] == null) {
names = useMedium ? dr.shortNames : dr.mediumNames;
}
if (names != null && names[px] != null) {
appendCount(unit, false, false, count, cv, useCountSep,
names[px], last, sb); // omit suffix, ok?
return false; // omit skip marker
}
}
// check cv
if (cv == ECountVariant.HALF_FRACTION && dr.halfSupport != null) {
switch (dr.halfSupport[px]) {
case EHalfSupport.YES: break;
case EHalfSupport.ONE_PLUS:
if (count > 1000) {
break;
}
// else fall through to decimal
case EHalfSupport.NO: {
count = (count / 500) * 500; // round to 1/2
cv = ECountVariant.DECIMAL1;
} break;
}
}
String name = null;
int form = computeForm(unit, count, cv, multiple && last);
if (form == FORM_SINGULAR_SPELLED) {
if (dr.singularNames == null) {
form = FORM_SINGULAR;
name = dr.pluralNames[px][form];
} else {
name = dr.singularNames[px];
}
} else if (form == FORM_SINGULAR_NO_OMIT) {
name = dr.pluralNames[px][FORM_SINGULAR];
} else if (form == FORM_HALF_SPELLED) {
name = dr.halfNames[px];
} else {
try {
name = dr.pluralNames[px][form];
} catch (NullPointerException e) {
System.out.println("Null Pointer in PeriodFormatterData["+localeName+"].au px: " + px + " form: " + form + " pn: " + Arrays.toString(dr.pluralNames));
throw e;
}
}
if (name == null) {
form = FORM_PLURAL;
name = dr.pluralNames[px][form];
}
boolean omitCount =
(form == FORM_SINGULAR_SPELLED || form == FORM_HALF_SPELLED) ||
(dr.omitSingularCount && form == FORM_SINGULAR) ||
(dr.omitDualCount && form == FORM_DUAL);
int suffixIndex = appendCount(unit, omitCount, useDigitPrefix, count, cv,
useCountSep, name, last, sb);
if (last && suffixIndex >= 0) {
String suffix = null;
if (dr.rqdSuffixes != null && suffixIndex < dr.rqdSuffixes.length) {
suffix = dr.rqdSuffixes[suffixIndex];
}
if (suffix == null && dr.optSuffixes != null &&
suffixIndex < dr.optSuffixes.length) {
suffix = dr.optSuffixes[suffixIndex];
}
if (suffix != null) {
sb.append(suffix);
}
}
return willRequireSkipMarker;
}
/**
* Append a count to the string builder.
*
* @param unit the unit
* @param count the count
* @param cv the format to use for displaying the count
* @param useSep whether to use the count separator, if available
* @param name the term name
* @param last true if this is the last unit to be formatted
* @param sb the string builder to which to append the text
* @return index to use if might have required or optional suffix, or -1 if none required
*/
public int appendCount(TimeUnit unit, boolean omitCount,
boolean useDigitPrefix,
int count, int cv, boolean useSep,
String name, boolean last, StringBuffer sb) {
if (cv == ECountVariant.HALF_FRACTION && dr.halves == null) {
cv = ECountVariant.INTEGER;
}
if (!omitCount && useDigitPrefix && dr.digitPrefix != null) {
sb.append(dr.digitPrefix);
}
int index = unit.ordinal();
switch (cv) {
case ECountVariant.INTEGER: {
if (!omitCount) {
appendInteger(count/1000, 1, 10, sb);
}
} break;
case ECountVariant.INTEGER_CUSTOM: {
int val = count / 1000;
// only custom names we have for now
if (unit == TimeUnit.MINUTE &&
(dr.fiveMinutes != null || dr.fifteenMinutes != null)) {
if (val != 0 && val % 5 == 0) {
if (dr.fifteenMinutes != null && (val == 15 || val == 45)) {
val = val == 15 ? 1 : 3;
if (!omitCount) appendInteger(val, 1, 10, sb);
name = dr.fifteenMinutes;
index = 8; // hack
break;
}
if (dr.fiveMinutes != null) {
val = val / 5;
if (!omitCount) appendInteger(val, 1, 10, sb);
name = dr.fiveMinutes;
index = 9; // hack
break;
}
}
}
if (!omitCount) appendInteger(val, 1, 10, sb);
} break;
case ECountVariant.HALF_FRACTION: {
// 0, 1/2, 1, 1-1/2...
int v = count / 500;
if (v != 1) {
if (!omitCount) appendCountValue(count, 1, 0, sb);
}
if ((v & 0x1) == 1) {
// hack, using half name
if (v == 1 && dr.halfNames != null && dr.halfNames[index] != null) {
sb.append(name);
return last ? index : -1;
}
int solox = v == 1 ? 0 : 1;
if (dr.genders != null && dr.halves.length > 2) {
if (dr.genders[index] == EGender.F) {
solox += 2;
}
}
int hp = dr.halfPlacements == null
? EHalfPlacement.PREFIX
: dr.halfPlacements[solox & 0x1];
String half = dr.halves[solox];
String measure = dr.measures == null ? null : dr.measures[index];
switch (hp) {
case EHalfPlacement.PREFIX:
sb.append(half);
break;
case EHalfPlacement.AFTER_FIRST: {
if (measure != null) {
sb.append(measure);
sb.append(half);
if (useSep && !omitCount) {
sb.append(dr.countSep);
}
sb.append(name);
} else { // ignore sep completely
sb.append(name);
sb.append(half);
return last ? index : -1; // might use suffix
}
} return -1; // exit early
case EHalfPlacement.LAST: {
if (measure != null) {
sb.append(measure);
}
if (useSep && !omitCount) {
sb.append(dr.countSep);
}
sb.append(name);
sb.append(half);
} return last ? index : -1; // might use suffix
}
}
} break;
default: {
int decimals = 1;
switch (cv) {
case ECountVariant.DECIMAL2: decimals = 2; break;
case ECountVariant.DECIMAL3: decimals = 3; break;
default: break;
}
if (!omitCount) appendCountValue(count, 1, decimals, sb);
} break;
}
if (!omitCount && useSep) {
sb.append(dr.countSep);
}
if (!omitCount && dr.measures != null && index < dr.measures.length) {
String measure = dr.measures[index];
if (measure != null) {
sb.append(measure);
}
}
sb.append(name);
return last ? index : -1;
}
/**
* Append a count value to the builder.
*
* @param count the count
* @param integralDigits the number of integer digits to display
* @param decimalDigits the number of decimal digits to display, <= 3
* @param sb the string builder to which to append the text
*/
public void appendCountValue(int count, int integralDigits,
int decimalDigits, StringBuffer sb) {
int ival = count / 1000;
if (decimalDigits == 0) {
appendInteger(ival, integralDigits, 10, sb);
return;
}
if (dr.requiresDigitSeparator && sb.length() > 0) {
sb.append(' ');
}
appendDigits(ival, integralDigits, 10, sb);
int dval = count % 1000;
if (decimalDigits == 1) {
dval /= 100;
} else if (decimalDigits == 2) {
dval /= 10;
}
sb.append(dr.decimalSep);
appendDigits(dval, decimalDigits, decimalDigits, sb);
if (dr.requiresDigitSeparator) {
sb.append(' ');
}
}
public void appendInteger(int num, int mindigits, int maxdigits,
StringBuffer sb) {
if (dr.numberNames != null && num < dr.numberNames.length) {
String name = dr.numberNames[num];
if (name != null) {
sb.append(name);
return;
}
}
if (dr.requiresDigitSeparator && sb.length() > 0) {
sb.append(' ');
}
switch (dr.numberSystem) {
case ENumberSystem.DEFAULT: appendDigits(num, mindigits, maxdigits, sb); break;
case ENumberSystem.CHINESE_TRADITIONAL: sb.append(
Utils.chineseNumber(num, Utils.ChineseDigits.TRADITIONAL)); break;
case ENumberSystem.CHINESE_SIMPLIFIED: sb.append(
Utils.chineseNumber(num, Utils.ChineseDigits.SIMPLIFIED)); break;
case ENumberSystem.KOREAN: sb.append(
Utils.chineseNumber(num, Utils.ChineseDigits.KOREAN)); break;
}
if (dr.requiresDigitSeparator) {
sb.append(' ');
}
}
/**
* Append digits to the string builder, using this.zero for '0' etc.
*
* @param num the integer to append
* @param mindigits the minimum number of digits to append
* @param maxdigits the maximum number of digits to append
* @param sb the string builder to which to append the text
*/
public void appendDigits(long num, int mindigits, int maxdigits,
StringBuffer sb) {
char[] buf = new char[maxdigits];
int ix = maxdigits;
while (ix > 0 && num > 0) {
buf[--ix] = (char)(dr.zero + (num % 10));
num /= 10;
}
for (int e = maxdigits - mindigits; ix > e;) {
buf[--ix] = dr.zero;
}
sb.append(buf, ix, maxdigits - ix);
}
/**
* Append a marker for skipped units internal to a string.
* @param sb the string builder to which to append the text
*/
public void appendSkippedUnit(StringBuffer sb) {
if (dr.skippedUnitMarker != null) {
sb.append(dr.skippedUnitMarker);
}
}
/**
* Append the appropriate separator between units
*
* @param unit the unit to which to append the separator
* @param afterFirst true if this is the first unit formatted
* @param beforeLast true if this is the next-to-last unit to be formatted
* @param sb the string builder to which to append the text
* @return true if a prefix will be required before a following unit
*/
public boolean appendUnitSeparator(TimeUnit unit, boolean longSep,
boolean afterFirst, boolean beforeLast,
StringBuffer sb) {
// long seps
// false, false "...b', '...d"
// false, true "...', and 'c"
// true, false - "a', '...c"
// true, true - "a' and 'b"
if ((longSep && dr.unitSep != null) || dr.shortUnitSep != null) {
if (longSep && dr.unitSep != null) {
int ix = (afterFirst ? 2 : 0) + (beforeLast ? 1 : 0);
sb.append(dr.unitSep[ix]);
return dr.unitSepRequiresDP != null && dr.unitSepRequiresDP[ix];
}
sb.append(dr.shortUnitSep); // todo: investigate whether DP is required
}
return false;
}
private static final int
FORM_PLURAL = 0,
FORM_SINGULAR = 1,
FORM_DUAL = 2,
FORM_PAUCAL = 3,
FORM_SINGULAR_SPELLED = 4, // following are not in the pluralization list
FORM_SINGULAR_NO_OMIT = 5, // a hack
FORM_HALF_SPELLED = 6;
private int computeForm(TimeUnit unit, int count, int cv,
boolean lastOfMultiple) {
// first check if a particular form is forced by the countvariant. if
// SO, just return that. otherwise convert the count to an integer
// and use pluralization rules to determine which form to use.
// careful, can't assume any forms but plural exist.
if (trace) {
System.err.println("pfd.cf unit: " + unit + " count: " + count + " cv: " + cv + " dr.pl: " + dr.pl);
Thread.dumpStack();
}
if (dr.pl == EPluralization.NONE) {
return FORM_PLURAL;
}
// otherwise, assume we have at least a singular and plural form
int val = count/1000;
switch (cv) {
case ECountVariant.INTEGER:
case ECountVariant.INTEGER_CUSTOM: {
// do more analysis based on floor of count
} break;
case ECountVariant.HALF_FRACTION: {
switch (dr.fractionHandling) {
case EFractionHandling.FPLURAL:
return FORM_PLURAL;
case EFractionHandling.FSINGULAR_PLURAL_ANDAHALF:
case EFractionHandling.FSINGULAR_PLURAL: {
// if half-floor is 1/2, use singular
// else if half-floor is not integral, use plural
// else do more analysis
int v = count / 500;
if (v == 1) {
if (dr.halfNames != null && dr.halfNames[unit.ordinal()] != null) {
return FORM_HALF_SPELLED;
}
return FORM_SINGULAR_NO_OMIT;
}
if ((v & 0x1) == 1) {
if (dr.pl == EPluralization.ARABIC && v > 21) { // hack
return FORM_SINGULAR_NO_OMIT;
}
if (v == 3 && dr.pl == EPluralization.PLURAL &&
dr.fractionHandling != EFractionHandling.FSINGULAR_PLURAL_ANDAHALF) {
return FORM_PLURAL;
}
}
// it will display like an integer, so do more analysis
} break;
case EFractionHandling.FPAUCAL: {
int v = count / 500;
if (v == 1 || v == 3) {
return FORM_PAUCAL;
}
// else use integral form
} break;
default:
throw new IllegalStateException();
}
} break;
default: { // for all decimals
switch (dr.decimalHandling) {
case EDecimalHandling.DPLURAL: break;
case EDecimalHandling.DSINGULAR: return FORM_SINGULAR_NO_OMIT;
case EDecimalHandling.DSINGULAR_SUBONE:
if (count < 1000) {
return FORM_SINGULAR_NO_OMIT;
}
break;
case EDecimalHandling.DPAUCAL:
if (dr.pl == EPluralization.PAUCAL) {
return FORM_PAUCAL;
}
break;
default:
break;
}
return FORM_PLURAL;
}
}
// select among pluralization forms
if (trace && count == 0) {
System.err.println("EZeroHandling = " + dr.zeroHandling);
}
if (count == 0 && dr.zeroHandling == EZeroHandling.ZSINGULAR) {
return FORM_SINGULAR_SPELLED;
}
int form = FORM_PLURAL;
switch(dr.pl) {
case EPluralization.NONE: break; // never get here
case EPluralization.PLURAL: {
if (val == 1) {
form = FORM_SINGULAR_SPELLED; // defaults to form_singular if no spelled forms
}
} break;
case EPluralization.DUAL: {
if (val == 2) {
form = FORM_DUAL;
} else if (val == 1) {
form = FORM_SINGULAR;
}
} break;
case EPluralization.PAUCAL: {
int v = val;
v = v % 100;
if (v > 20) {
v = v % 10;
}
if (v == 1) {
form = FORM_SINGULAR;
} else if (v > 1 && v < 5) {
form = FORM_PAUCAL;
}
} break;
/*
case EPluralization.RPT_DUAL_FEW: {
int v = val;
if (v > 20) {
v = v % 10;
}
if (v == 1) {
form = FORM_SINGULAR;
} else if (v == 2) {
form = FORM_DUAL;
} else if (v > 2 && v < 5) {
form = FORM_PAUCAL;
}
} break;
*/
case EPluralization.HEBREW: {
if (val == 2) {
form = FORM_DUAL;
} else if (val == 1) {
if (lastOfMultiple) {
form = FORM_SINGULAR_SPELLED;
} else {
form = FORM_SINGULAR;
}
} else if (unit == TimeUnit.YEAR && val > 11) {
form = FORM_SINGULAR_NO_OMIT;
}
} break;
case EPluralization.ARABIC: {
if (val == 2) {
form = FORM_DUAL;
} else if (val == 1) {
form = FORM_SINGULAR;
} else if (val > 10) {
form = FORM_SINGULAR_NO_OMIT;
}
} break;
default:
System.err.println("dr.pl is " + dr.pl);
throw new IllegalStateException();
}
return form;
}
}