mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-07 22:44:49 +00:00
ICU-10603 Add NUMERIC to MeasureFormat.FormatWidth.
X-SVN-Rev: 34797
This commit is contained in:
parent
ef9fbd093c
commit
652d2952a2
2 changed files with 232 additions and 13 deletions
|
@ -16,9 +16,11 @@ import java.io.InvalidObjectException;
|
|||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -31,6 +33,7 @@ import com.ibm.icu.impl.SimpleCache;
|
|||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.Measure;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.TimeZone;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.ULocale.Category;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
@ -104,9 +107,23 @@ public class MeasureFormat extends UFormat {
|
|||
|
||||
// Measure unit -> format width -> plural form -> pattern ("{0} meters")
|
||||
private final transient Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat;
|
||||
|
||||
private final transient NumericFormatters numericFormatters;
|
||||
|
||||
static final SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>> localeToUnitToStyleToCountToFormat
|
||||
private static final SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>> localeToUnitToStyleToCountToFormat
|
||||
= new SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>>();
|
||||
|
||||
private static final SimpleCache<ULocale, NumericFormatters> localeToNumericDurationFormatters
|
||||
= new SimpleCache<ULocale,NumericFormatters>();
|
||||
|
||||
private static final Map<MeasureUnit, Integer> hmsTo012 =
|
||||
new HashMap<MeasureUnit, Integer>();
|
||||
|
||||
static {
|
||||
hmsTo012.put(MeasureUnit.HOUR, 0);
|
||||
hmsTo012.put(MeasureUnit.MINUTE, 1);
|
||||
hmsTo012.put(MeasureUnit.SECOND, 2);
|
||||
}
|
||||
|
||||
// For serialization: sub-class types.
|
||||
private static final int MEASURE_FORMAT = 0;
|
||||
|
@ -145,7 +162,17 @@ public class MeasureFormat extends UFormat {
|
|||
* @draft ICU 53
|
||||
* @provisional
|
||||
*/
|
||||
NARROW("unitsNarrow");
|
||||
NARROW("unitsNarrow"),
|
||||
|
||||
/**
|
||||
* Identical to NARROW except when formatMeasures is called with
|
||||
* an hour and minute; minute and second; or hour, minute, and second Measures.
|
||||
* In these cases formatMeasures formats as 5:37:23 instead of 5h, 37m, 23s.
|
||||
*
|
||||
* @draft ICU 53
|
||||
* @provisional
|
||||
*/
|
||||
NUMERIC("unitsNarrow");
|
||||
|
||||
// Be sure to update the toFormatWidth and fromFormatWidth() functions
|
||||
// when adding an enum value.
|
||||
|
@ -182,14 +209,27 @@ public class MeasureFormat extends UFormat {
|
|||
*/
|
||||
public static MeasureFormat getInstance(ULocale locale, FormatWidth width, NumberFormat format) {
|
||||
PluralRules rules = PluralRules.forLocale(locale);
|
||||
Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat;
|
||||
Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat;
|
||||
NumericFormatters formatters = null;
|
||||
unitToStyleToCountToFormat = localeToUnitToStyleToCountToFormat.get(locale);
|
||||
if (unitToStyleToCountToFormat == null) {
|
||||
unitToStyleToCountToFormat = loadLocaleData(locale, rules);
|
||||
localeToUnitToStyleToCountToFormat.put(locale, unitToStyleToCountToFormat);
|
||||
}
|
||||
if (width == FormatWidth.NUMERIC) {
|
||||
formatters = localeToNumericDurationFormatters.get(locale);
|
||||
if (formatters == null) {
|
||||
formatters = loadNumericFormatters(locale);
|
||||
localeToNumericDurationFormatters.put(locale, formatters);
|
||||
}
|
||||
}
|
||||
return new MeasureFormat(
|
||||
locale, width, new ImmutableNumberFormat(format), rules, unitToStyleToCountToFormat);
|
||||
locale,
|
||||
width,
|
||||
new ImmutableNumberFormat(format),
|
||||
rules,
|
||||
unitToStyleToCountToFormat,
|
||||
formatters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -278,7 +318,6 @@ public class MeasureFormat extends UFormat {
|
|||
@SuppressWarnings("unchecked")
|
||||
public <T extends Appendable> T formatMeasures(
|
||||
T appendable, FieldPosition fieldPosition, Measure... measures) {
|
||||
|
||||
// fast track for trivial cases
|
||||
if (measures.length == 0) {
|
||||
return (T) appendable;
|
||||
|
@ -287,6 +326,15 @@ public class MeasureFormat extends UFormat {
|
|||
return formatMeasure(measures[0], appendable, fieldPosition);
|
||||
}
|
||||
|
||||
if (length == FormatWidth.NUMERIC) {
|
||||
// If we have just hour, minute, or second follow the numeric
|
||||
// track.
|
||||
Number[] hms = toHMS(measures);
|
||||
if (hms != null) {
|
||||
return formatNumeric(hms, appendable);
|
||||
}
|
||||
}
|
||||
|
||||
// Zero out our field position so that we can tell when we find our field.
|
||||
FieldPosition fpos = new FieldPosition(fieldPosition.getFieldAttribute(), fieldPosition.getField());
|
||||
FieldPosition dummyPos = new FieldPosition(0);
|
||||
|
@ -421,7 +469,8 @@ public class MeasureFormat extends UFormat {
|
|||
this.length,
|
||||
new ImmutableNumberFormat(format),
|
||||
this.rules,
|
||||
this.unitToStyleToCountToFormat);
|
||||
this.unitToStyleToCountToFormat,
|
||||
this.numericFormatters);
|
||||
}
|
||||
|
||||
private MeasureFormat(
|
||||
|
@ -429,12 +478,14 @@ public class MeasureFormat extends UFormat {
|
|||
FormatWidth width,
|
||||
ImmutableNumberFormat format,
|
||||
PluralRules rules,
|
||||
Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat) {
|
||||
Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat,
|
||||
NumericFormatters formatters) {
|
||||
setLocale(locale, locale);
|
||||
this.length = width;
|
||||
this.numberFormat = format;
|
||||
this.rules = rules;
|
||||
this.unitToStyleToCountToFormat = unitToStyleToCountToFormat;
|
||||
this.numericFormatters = formatters;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -448,7 +499,36 @@ public class MeasureFormat extends UFormat {
|
|||
this.numberFormat = null;
|
||||
this.rules = null;
|
||||
this.unitToStyleToCountToFormat = null;
|
||||
this.numericFormatters = null;
|
||||
}
|
||||
|
||||
static class NumericFormatters {
|
||||
private DateFormat hourMinute;
|
||||
private DateFormat minuteSecond;
|
||||
private DateFormat hourMinuteSecond;
|
||||
|
||||
public NumericFormatters(
|
||||
DateFormat hourMinute,
|
||||
DateFormat minuteSecond,
|
||||
DateFormat hourMinuteSecond) {
|
||||
this.hourMinute = hourMinute;
|
||||
this.minuteSecond = minuteSecond;
|
||||
this.hourMinuteSecond = hourMinuteSecond;
|
||||
}
|
||||
|
||||
public DateFormat getHourMinute() { return hourMinute; }
|
||||
public DateFormat getMinuteSecond() { return minuteSecond; }
|
||||
public DateFormat getHourMinuteSecond() { return hourMinuteSecond; }
|
||||
}
|
||||
|
||||
private static NumericFormatters loadNumericFormatters(
|
||||
ULocale locale) {
|
||||
ICUResourceBundle r = (ICUResourceBundle)UResourceBundle.
|
||||
getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
|
||||
return new NumericFormatters(
|
||||
loadNumericDurationFormat(r, "hm"),
|
||||
loadNumericDurationFormat(r, "ms"),
|
||||
loadNumericDurationFormat(r, "hms"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -576,6 +656,10 @@ public class MeasureFormat extends UFormat {
|
|||
Number n, StringBuffer buffer, FieldPosition pos) {
|
||||
return nf.format(n, buffer, pos);
|
||||
}
|
||||
|
||||
public synchronized String format(Number number) {
|
||||
return nf.format(number);
|
||||
}
|
||||
}
|
||||
|
||||
static final class PatternData {
|
||||
|
@ -605,6 +689,113 @@ public class MeasureFormat extends UFormat {
|
|||
return new MeasureProxy(getLocale(), length, numberFormat.get(), CURRENCY_FORMAT);
|
||||
}
|
||||
|
||||
// type is one of "hm", "ms" or "hms"
|
||||
private static DateFormat loadNumericDurationFormat(
|
||||
ICUResourceBundle r, String type) {
|
||||
r = r.getWithFallback(String.format("durationUnits/%s", type));
|
||||
// We replace 'h' with 'H' because 'h' does not make sense in the context of durations.
|
||||
DateFormat result = new SimpleDateFormat(r.getString().replace("h", "H"));
|
||||
result.setTimeZone(TimeZone.GMT_ZONE);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Number[] toHMS(Measure[] measures) {
|
||||
Number[] result = new Number[3];
|
||||
int count = 0;
|
||||
for (Measure m : measures) {
|
||||
Integer idx = hmsTo012.get(m.getUnit());
|
||||
if (idx == null) {
|
||||
return null;
|
||||
}
|
||||
if (result[idx.intValue()] != null) {
|
||||
return null;
|
||||
}
|
||||
result[idx.intValue()] = m.getNumber();
|
||||
count++;
|
||||
}
|
||||
if (count < 2) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private <T extends Appendable> T formatNumeric(Number[] hms, T appendable) {
|
||||
int startIndex = -1;
|
||||
int endIndex = -1;
|
||||
for (int i = 0; i < hms.length; i++) {
|
||||
if (hms[i] != null) {
|
||||
endIndex = i;
|
||||
if (startIndex == -1) {
|
||||
startIndex = endIndex;
|
||||
}
|
||||
} else {
|
||||
hms[i] = Integer.valueOf(0);
|
||||
}
|
||||
}
|
||||
long millis = (long) (((hms[0].doubleValue() * 60.0
|
||||
+ hms[1].doubleValue()) * 60.0
|
||||
+ hms[2].doubleValue()) * 1000.0);
|
||||
Date d = new Date(millis);
|
||||
if (startIndex == 0 && endIndex == 2) {
|
||||
return formatNumeric(
|
||||
d,
|
||||
numericFormatters.getHourMinuteSecond(),
|
||||
DateFormat.Field.SECOND,
|
||||
hms[endIndex],
|
||||
appendable);
|
||||
}
|
||||
if (startIndex == 1 && endIndex == 2) {
|
||||
return formatNumeric(
|
||||
d,
|
||||
numericFormatters.getMinuteSecond(),
|
||||
DateFormat.Field.SECOND,
|
||||
hms[endIndex],
|
||||
appendable);
|
||||
}
|
||||
if (startIndex == 0 && endIndex == 1) {
|
||||
return formatNumeric(
|
||||
d,
|
||||
numericFormatters.getHourMinute(),
|
||||
DateFormat.Field.MINUTE,
|
||||
hms[endIndex],
|
||||
appendable);
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
private <T extends Appendable> T formatNumeric(
|
||||
Date duration,
|
||||
DateFormat formatter,
|
||||
DateFormat.Field smallestField,
|
||||
Number smallestAmount,
|
||||
T appendable) {
|
||||
// Format the smallest amount ahead of time.
|
||||
String smallestAmountFormatted;
|
||||
smallestAmountFormatted = numberFormat.format(smallestAmount);
|
||||
|
||||
// Format the duration using the provided DateFormat object. The smallest
|
||||
// field in this result will be missing the fractional part.
|
||||
AttributedCharacterIterator iterator = formatter.formatToCharacterIterator(duration);
|
||||
|
||||
// iterate through formatted text copying to 'builder' one character at a time.
|
||||
// When we get to the smallest amount, skip over it and copy
|
||||
// 'smallestAmountFormatted' to the builder instead.
|
||||
for (iterator.first(); iterator.getIndex() < iterator.getEndIndex();) {
|
||||
try {
|
||||
if (iterator.getAttributes().containsKey(smallestField)) {
|
||||
appendable.append(smallestAmountFormatted);
|
||||
iterator.setIndex(iterator.getRunLimit(smallestField));
|
||||
} else {
|
||||
appendable.append(iterator.current());
|
||||
iterator.next();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return appendable;
|
||||
}
|
||||
|
||||
private Object writeReplace() throws ObjectStreamException {
|
||||
return new MeasureProxy(
|
||||
getLocale(), length, numberFormat.get(), MEASURE_FORMAT);
|
||||
|
@ -699,6 +890,8 @@ public class MeasureFormat extends UFormat {
|
|||
return FormatWidth.SHORT;
|
||||
case 2:
|
||||
return FormatWidth.NARROW;
|
||||
case 3:
|
||||
return FormatWidth.NUMERIC;
|
||||
default:
|
||||
return FormatWidth.WIDE;
|
||||
}
|
||||
|
@ -712,6 +905,8 @@ public class MeasureFormat extends UFormat {
|
|||
return 1;
|
||||
case NARROW:
|
||||
return 2;
|
||||
case NUMERIC:
|
||||
return 3;
|
||||
default:
|
||||
throw new IllegalStateException("Unable to serialize Format Width " + fw);
|
||||
}
|
||||
|
|
|
@ -70,6 +70,22 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
TimeUnitAmount[] _1m_59_9996s = {
|
||||
new TimeUnitAmount(1.0, TimeUnit.MINUTE),
|
||||
new TimeUnitAmount(59.9996, TimeUnit.SECOND)};
|
||||
TimeUnitAmount[] _5h_17m = {
|
||||
new TimeUnitAmount(5.0, TimeUnit.HOUR),
|
||||
new TimeUnitAmount(17.0, TimeUnit.MINUTE)};
|
||||
TimeUnitAmount[] _19m_28s = {
|
||||
new TimeUnitAmount(19.0, TimeUnit.MINUTE),
|
||||
new TimeUnitAmount(28.0, TimeUnit.SECOND)};
|
||||
TimeUnitAmount[] _0h_0m_17s = {
|
||||
new TimeUnitAmount(0.0, TimeUnit.HOUR),
|
||||
new TimeUnitAmount(0.0, TimeUnit.MINUTE),
|
||||
new TimeUnitAmount(17.0, TimeUnit.SECOND)};
|
||||
TimeUnitAmount[] _6h_56_92m = {
|
||||
new TimeUnitAmount(6.0, TimeUnit.HOUR),
|
||||
new TimeUnitAmount(56.92, TimeUnit.MINUTE)};
|
||||
TimeUnitAmount[] _3h_5h = {
|
||||
new TimeUnitAmount(3.0, TimeUnit.HOUR),
|
||||
new TimeUnitAmount(5.0, TimeUnit.HOUR)};
|
||||
|
||||
Object[][] fullData = {
|
||||
{_1m_59_9996s, "1 minute, 59.9996 seconds"},
|
||||
|
@ -85,21 +101,27 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
{_1h_23_5m, "1 hr, 23.5 mins"},
|
||||
{_1h_0m_23s, "1 hr, 0 mins, 23 secs"},
|
||||
{_2y_5M_3w_4d, "2 yrs, 5 mths, 3 wks, 4 days"}};
|
||||
Object[][] narrowData = {
|
||||
{_1m_59_9996s, "1 min, 59.9996 secs"},
|
||||
{_19m, "19 mins"},
|
||||
{_1h_23_5s, "1 hr, 23.5 secs"},
|
||||
{_1h_23_5m, "1 hr, 23.5 mins"},
|
||||
{_1h_0m_23s, "1 hr, 0 mins, 23 secs"},
|
||||
{_2y_5M_3w_4d, "2 yrs, 5 mths, 3 wks, 4 days"}};
|
||||
|
||||
|
||||
// TODO(Travis Keep): We need to support numeric formatting. Either here or in TimeUnitFormat.
|
||||
/*
|
||||
Object[][] numericData = {
|
||||
{_1m_59_9996s, "1:59.9996"},
|
||||
{_19m, "19 mins"},
|
||||
{_19m, "19m"},
|
||||
{_1h_23_5s, "1:00:23.5"},
|
||||
{_1h_0m_23s, "1:00:23"},
|
||||
{_1h_23_5m, "1:23.5"},
|
||||
{_5h_17m, "5:17"},
|
||||
{_19m_28s, "19:28"},
|
||||
{_2y_5M_3w_4d, "2 yrs, 5 mths, 3 wks, 4 days"},
|
||||
{_2y_5M_3w_4d, "2y, 5m, 3w, 4d"},
|
||||
{_0h_0m_17s, "0:00:17"},
|
||||
{_6h_56_92m, "6:56.92"}};
|
||||
*/
|
||||
{_6h_56_92m, "6:56.92"},
|
||||
{_3h_5h, "3h, 5h"}};
|
||||
|
||||
NumberFormat nf = NumberFormat.getNumberInstance(ULocale.ENGLISH);
|
||||
nf.setMaximumFractionDigits(4);
|
||||
|
@ -107,6 +129,8 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
verifyFormatPeriod("en FULL", mf, fullData);
|
||||
mf = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.SHORT, nf);
|
||||
verifyFormatPeriod("en SHORT", mf, abbrevData);
|
||||
mf = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.NUMERIC, nf);
|
||||
verifyFormatPeriod("en NUMERIC", mf, numericData);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue