mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-08 06:53:45 +00:00
ICU-20568 Add .unit().usage() support to ICU4J NumberFormatter (2/2)
* Throw UnsupportedOperationException for "unsanctioned units"
* Deal with the parent issue for LongNameMultiplexer
* Fix NullPointerException: UnitConversionHandler.processQuantity must
call fParent.processQuantity
* toSkeleton not supported for not-built-in units
* Add and use interface
LongNameMultiplexer.ParentlessMicroPropsGenerator
* Match up C++ and Java unit tests in NumberFormatterApiTest.java
* Permit user-override of precision() for usage(), closes icu-units#95
* Use BogusRounder to propagate mathContext.
* Port C++ change from PR #1322, commit c84ded050a
, to Java.
* Test the usage-without-unit error. Document it in
NumberFormatterSettings.java
* General review and corrections.
This commit is contained in:
parent
7ba2b48f7b
commit
e3bb5e5f07
17 changed files with 461 additions and 277 deletions
|
@ -450,10 +450,8 @@ void MixedUnitLongNameHandler::processQuantity(DecimalQuantity &quantity, MicroP
|
|||
const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &quantity,
|
||||
MicroProps µs,
|
||||
UErrorCode &status) const {
|
||||
// TODO(icu-units#21): mixed units without usage() is not yet supported.
|
||||
// That should be the only reason why this happens, so delete this whole if
|
||||
// once fixed:
|
||||
if (micros.mixedMeasuresCount == 0) {
|
||||
U_ASSERT(micros.mixedMeasuresCount > 0); // Mixed unit: we must have more than one unit value
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return µs.helpers.emptyWeakModifier;
|
||||
}
|
||||
|
@ -498,6 +496,7 @@ const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &
|
|||
auto appendable = UnicodeStringAppendable(num);
|
||||
fIntegerFormatter.formatDecimalQuantity(fdec, status).appendTo(appendable, status);
|
||||
compiledFormatter.format(num, outputMeasuresList[i], status);
|
||||
// TODO(icu-units#67): fix field positions
|
||||
}
|
||||
|
||||
UnicodeString *finalSimpleFormats = &fMixedUnitData[(fMixedUnitCount - 1) * ARRAY_LENGTH];
|
||||
|
@ -515,6 +514,7 @@ const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &
|
|||
return µs.helpers.emptyWeakModifier;
|
||||
}
|
||||
|
||||
// TODO(icu-units#67): fix field positions
|
||||
// Return a SimpleModifier for the "premixed" pattern
|
||||
micros.helpers.mixedUnitModifier =
|
||||
SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural});
|
||||
|
|
|
@ -159,14 +159,13 @@ class MixedUnitLongNameHandler : public MicroPropsGenerator, public ModifierStor
|
|||
// Not owned
|
||||
const MicroPropsGenerator *parent;
|
||||
|
||||
// Total number of units in the MeasureUnit this LongNameHandler was
|
||||
// configured for: for "foot-and-inch", this will be 2. (If not a mixed unit,
|
||||
// this will be 1.)
|
||||
// Total number of units in the MeasureUnit this handler was configured for:
|
||||
// for "foot-and-inch", this will be 2.
|
||||
int32_t fMixedUnitCount = 1;
|
||||
// If this LongNameHandler is for a mixed unit, this stores unit data for
|
||||
// each of the individual units. For each unit, it stores ARRAY_LENGTH
|
||||
// strings, as returned by getMeasureData. (Each unit with index `i` has
|
||||
// ARRAY_LENGTH strings starting at index `i*ARRAY_LENGTH` in this array.)
|
||||
// Stores unit data for each of the individual units. For each unit, it
|
||||
// stores ARRAY_LENGTH strings, as returned by getMeasureData. (Each unit
|
||||
// with index `i` has ARRAY_LENGTH strings starting at index
|
||||
// `i*ARRAY_LENGTH` in this array.)
|
||||
LocalArray<UnicodeString> fMixedUnitData;
|
||||
// A localized NumberFormatter used to format the integer-valued bigger
|
||||
// units of Mixed Unit measurements.
|
||||
|
|
|
@ -67,11 +67,11 @@ class IntMeasures : public MaybeStackArray<int64_t, 2> {
|
|||
UErrorCode status = U_ZERO_ERROR;
|
||||
};
|
||||
|
||||
// TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do
|
||||
// we want to better document why? There's an explanation for processQuantity:
|
||||
// * As MicroProps is the "base instance", this implementation of
|
||||
// * MicroPropsGenerator::processQuantity() just ensures that the output
|
||||
// * `micros` is correctly initialized.
|
||||
/**
|
||||
* MicroProps is the first MicroPropsGenerator that should be should be called,
|
||||
* producing an initialized MicroProps instance that will be passed on and
|
||||
* modified throughout the rest of the chain of MicroPropsGenerator instances.
|
||||
*/
|
||||
struct MicroProps : public MicroPropsGenerator {
|
||||
|
||||
// NOTE: All of these fields are properly initialized in NumberFormatterImpl.
|
||||
|
|
|
@ -103,6 +103,8 @@ void Usage::set(StringPiece value) {
|
|||
fUsage[fLength] = 0;
|
||||
}
|
||||
|
||||
// Populates micros.mixedMeasures and modifies quantity, based on the values in
|
||||
// measures.
|
||||
void mixedMeasuresToMicros(const MaybeStackVector<Measure> &measures, DecimalQuantity *quantity,
|
||||
MicroProps *micros, UErrorCode status) {
|
||||
micros->mixedMeasuresCount = measures.length() - 1;
|
||||
|
@ -159,13 +161,13 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m
|
|||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
const MaybeStackVector<Measure>& routedUnits = routed.measures;
|
||||
const MaybeStackVector<Measure>& routedMeasures = routed.measures;
|
||||
micros.outputUnit = routed.outputUnit.copy(status).build(status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mixedMeasuresToMicros(routedUnits, &quantity, µs, status);
|
||||
mixedMeasuresToMicros(routedMeasures, &quantity, µs, status);
|
||||
|
||||
UnicodeString precisionSkeleton = routed.precision;
|
||||
if (micros.rounder.fPrecision.isBogus()) {
|
||||
|
|
|
@ -906,6 +906,11 @@ void NumberFormatterApiTest::unitUsage() {
|
|||
FormattedNumber formattedNum;
|
||||
UnicodeString uTestCase;
|
||||
|
||||
status.assertSuccess();
|
||||
formattedNum =
|
||||
NumberFormatter::with().usage("road").locale(Locale::getEnglish()).formatInt(1, status);
|
||||
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
|
||||
|
||||
unloc_formatter = NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter());
|
||||
|
||||
uTestCase = u"unitUsage() en-ZA road";
|
||||
|
@ -1113,25 +1118,49 @@ void NumberFormatterApiTest::unitUsage() {
|
|||
u"0 pounds, 0 ounces");
|
||||
|
||||
assertFormatDescendingBig(
|
||||
u"Scientific notation with Usage: possible when using a reasonable Precision",
|
||||
u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
|
||||
u"scientific @### usage/default unit/square-meter unit-width-full-name",
|
||||
NumberFormatter::with()
|
||||
.unit(SQUARE_METER)
|
||||
.usage("default")
|
||||
.notation(Notation::scientific())
|
||||
.precision(Precision::minMaxSignificantDigits(1, 4))
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
Locale("en-ZA"),
|
||||
u"8,765E1 square kilometres",
|
||||
u"8,765E0 square kilometres",
|
||||
u"8,765E1 hectares",
|
||||
u"8,765E0 hectares",
|
||||
u"8,765E3 square metres",
|
||||
u"8,765E2 square metres",
|
||||
u"8,765E1 square metres",
|
||||
u"8,765E0 square metres",
|
||||
u"0E0 square centimetres");
|
||||
u"Scientific notation with Usage: possible when using a reasonable Precision",
|
||||
u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
|
||||
u"scientific @### usage/default unit/square-meter unit-width-full-name",
|
||||
NumberFormatter::with()
|
||||
.unit(SQUARE_METER)
|
||||
.usage("default")
|
||||
.notation(Notation::scientific())
|
||||
.precision(Precision::minMaxSignificantDigits(1, 4))
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
Locale("en-ZA"),
|
||||
u"8,765E1 square kilometres",
|
||||
u"8,765E0 square kilometres",
|
||||
u"8,765E1 hectares",
|
||||
u"8,765E0 hectares",
|
||||
u"8,765E3 square metres",
|
||||
u"8,765E2 square metres",
|
||||
u"8,765E1 square metres",
|
||||
u"8,765E0 square metres",
|
||||
u"0E0 square centimetres");
|
||||
|
||||
assertFormatSingle(
|
||||
u"Rounding Mode propagates: rounding down",
|
||||
u"usage/road measure-unit/length-centimeter rounding-mode-floor",
|
||||
u"usage/road unit/centimeter rounding-mode-floor",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("centimeter", status))
|
||||
.usage("road")
|
||||
.roundingMode(UNUM_ROUND_FLOOR),
|
||||
Locale("en-ZA"),
|
||||
34500,
|
||||
u"300 m");
|
||||
|
||||
assertFormatSingle(
|
||||
u"Rounding Mode propagates: rounding up",
|
||||
u"usage/road measure-unit/length-centimeter rounding-mode-ceiling",
|
||||
u"usage/road unit/centimeter rounding-mode-ceiling",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("centimeter", status))
|
||||
.usage("road")
|
||||
.roundingMode(UNUM_ROUND_CEILING),
|
||||
Locale("en-ZA"),
|
||||
30500,
|
||||
u"350 m");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitUsageErrorCodes() {
|
||||
|
|
|
@ -22,11 +22,12 @@ import com.ibm.icu.util.MeasureUnit;
|
|||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
|
||||
public class LongNameHandler
|
||||
implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator {
|
||||
|
||||
private static final int DNAM_INDEX = StandardPlural.COUNT;
|
||||
private static final int PER_INDEX = StandardPlural.COUNT + 1;
|
||||
protected static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
|
||||
static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
|
||||
|
||||
private static int getIndex(String pluralKeyword) {
|
||||
// pluralKeyword can also be "dnam" or "per"
|
||||
|
@ -39,7 +40,7 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
|
|||
}
|
||||
}
|
||||
|
||||
protected static String getWithPlural(String[] strings, StandardPlural plural) {
|
||||
static String getWithPlural(String[] strings, StandardPlural plural) {
|
||||
String result = strings[plural.ordinal()];
|
||||
if (result == null) {
|
||||
result = strings[StandardPlural.OTHER.ordinal()];
|
||||
|
@ -79,7 +80,7 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
|
|||
|
||||
// NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
|
||||
|
||||
protected static void getMeasureData(
|
||||
static void getMeasureData(
|
||||
ULocale locale,
|
||||
MeasureUnit unit,
|
||||
UnitWidth width,
|
||||
|
@ -199,13 +200,13 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
|
|||
* <p>
|
||||
* Mixed units are not supported, use MixedUnitLongNameHandler.forMeasureUnit.
|
||||
*
|
||||
* @param locale The desired locale.
|
||||
* @param unit The measure unit to construct a LongNameHandler for. If
|
||||
* `perUnit` is also defined, `unit` must not be a mixed unit.
|
||||
* @param perUnit If `unit` is a mixed unit, `perUnit` must be "none".
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param rules Does not take ownership.
|
||||
* @param parent Does not take ownership.
|
||||
* @param locale The desired locale.
|
||||
* @param unit The measure unit to construct a LongNameHandler for. If
|
||||
* `perUnit` is also defined, `unit` must not be a mixed unit.
|
||||
* @param perUnit If `unit` is a mixed unit, `perUnit` must be null.
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param rules Plural rules.
|
||||
* @param parent Plural rules.
|
||||
*/
|
||||
public static LongNameHandler forMeasureUnit(
|
||||
ULocale locale,
|
||||
|
@ -214,6 +215,12 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
|
|||
UnitWidth width,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
if (unit.getType() == null || (perUnit != null && perUnit.getType() == null)) {
|
||||
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
|
||||
// error code. Once we support not-built-in units here, unitRef may be
|
||||
// anything, but if not built-in, perUnit has to be "none".
|
||||
throw new UnsupportedOperationException("Unsanctioned units, not yet supported");
|
||||
}
|
||||
if (perUnit != null) {
|
||||
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
|
||||
MeasureUnit simplified = MeasureUnit.resolveUnitPerUnit(unit, perUnit);
|
||||
|
@ -314,6 +321,20 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
|
|||
return micros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a plural-appropriate Modifier for a unit: `quantity` is taken as
|
||||
* the final smallest unit, while the larger unit values must be provided
|
||||
* via `micros.mixedMeasures`.
|
||||
*
|
||||
* Does not call parent.processQuantity, so cannot get a MicroProps instance
|
||||
* that way. Instead, the instance is passed in as a parameter.
|
||||
*/
|
||||
public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
|
||||
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
|
||||
micros.modOuter = modifiers.get(pluralForm);
|
||||
return micros;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getModifier(Signum signum, StandardPlural plural) {
|
||||
// Signum ignored
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
|
@ -13,10 +11,27 @@ import com.ibm.icu.util.ULocale;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A MicroPropsGenerator that multiplexes between different LongNameHandlers,
|
||||
* depending on the outputUnit.
|
||||
*
|
||||
* See processQuantity() for the input requirements.
|
||||
*/
|
||||
public class LongNameMultiplexer implements MicroPropsGenerator {
|
||||
/**
|
||||
* LongNameMultiplexer calls the parent MicroPropsGenerator itself,
|
||||
* receiving the MicroProps instance in use for this formatting pipeline.
|
||||
* Next it multiplexes between name handlers (fHandlers) which are not given
|
||||
* access to a parent. Consequently LongNameMultiplexer must give these
|
||||
* handlers the MicroProps instance.
|
||||
*/
|
||||
public static interface ParentlessMicroPropsGenerator {
|
||||
public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros);
|
||||
}
|
||||
|
||||
private final MicroPropsGenerator fParent;
|
||||
|
||||
private List<MicroPropsGenerator> fHandlers;
|
||||
private List<ParentlessMicroPropsGenerator> fHandlers;
|
||||
|
||||
// Each MeasureUnit corresponds to the same-index MicroPropsGenerator
|
||||
// pointed to in fHandlers.
|
||||
|
@ -50,7 +65,7 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
|
|||
result.fHandlers.add(mlnh);
|
||||
} else {
|
||||
LongNameHandler lnh = LongNameHandler
|
||||
.forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null );
|
||||
.forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null);
|
||||
result.fHandlers.add(lnh);
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +77,6 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
|
|||
// one of the units provided to the factory function.
|
||||
@Override
|
||||
public MicroProps processQuantity(DecimalQuantity quantity) {
|
||||
|
||||
// We call parent->processQuantity() from the Multiplexer, instead of
|
||||
// letting LongNameHandler handle it: we don't know which LongNameHandler to
|
||||
// call until we've called the parent!
|
||||
|
@ -70,12 +84,11 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
|
|||
|
||||
// Call the correct LongNameHandler based on outputUnit
|
||||
for (int i = 0; i < this.fHandlers.size(); i++) {
|
||||
if (fMeasureUnits.get(i).equals( micros.outputUnit)) {
|
||||
return fHandlers.get(i).processQuantity(quantity);
|
||||
if (fMeasureUnits.get(i).equals(micros.outputUnit)) {
|
||||
ParentlessMicroPropsGenerator handler = fHandlers.get(i);
|
||||
return handler.processQuantityWithMicros(quantity, micros);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new AssertionError
|
||||
(" We shouldn't receive any outputUnit for which we haven't already got a LongNameHandler");
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@ import com.ibm.icu.util.MeasureUnit;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
// TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do we want to better document why?
|
||||
// There's an explanation for processQuantity:
|
||||
// - As MicroProps is the "base instance", this implementation of
|
||||
// - MicoPropsGenerator::processQuantity() just ensures that the output
|
||||
// - `micros` is correctly initialized.
|
||||
/**
|
||||
* MicroProps is the first MicroPropsGenerator that should be should be called,
|
||||
* producing an initialized MicroProps instance that will be passed on and
|
||||
* modified throughout the rest of the chain of MicroPropsGenerator instances.
|
||||
*/
|
||||
public class MicroProps implements Cloneable, MicroPropsGenerator {
|
||||
// Populated globally:
|
||||
public SignDisplay sign;
|
||||
|
@ -55,7 +55,7 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
|
|||
|
||||
// In the case of mixed units, this is the set of integer-only units
|
||||
// *preceding* the final unit.
|
||||
public List<Measure> mixedMeasures ;
|
||||
public List<Measure> mixedMeasures;
|
||||
|
||||
private volatile boolean exhausted;
|
||||
|
||||
|
@ -79,17 +79,19 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
|
|||
* will be modified and thus not be available for re-use.
|
||||
*
|
||||
* @param quantity The quantity for consideration and optional mutation.
|
||||
* @return a MicroProps instance to populate.
|
||||
* @return an initialized MicroProps instance.
|
||||
*/
|
||||
@Override
|
||||
public MicroProps processQuantity(DecimalQuantity quantity) {
|
||||
if (immutable) {
|
||||
return (MicroProps) this.clone();
|
||||
} else if (exhausted) {
|
||||
// Safety check
|
||||
throw new AssertionError("Cannot re-use a mutable MicroProps in the quantity chain");
|
||||
} else {
|
||||
exhausted = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
assert !exhausted : "Cannot re-use a mutable MicroProps in the quantity chain";
|
||||
exhausted = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,6 +19,7 @@ package com.ibm.icu.impl.number;
|
|||
* {@link MicroProps} with properties that are not quantity-dependent. Each element in the linked list
|
||||
* calls {@link #processQuantity} on its "parent", then does its work, and then returns the result.
|
||||
*
|
||||
* <p>
|
||||
* This chain of MicroPropsGenerators is typically constructed by NumberFormatterImpl::macrosToMicroGenerator() when
|
||||
* constructing a NumberFormatter.
|
||||
*
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.impl.FormattedStringBuilder;
|
||||
import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.number.LocalizedNumberFormatter;
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
|
@ -13,26 +12,28 @@ import com.ibm.icu.text.PluralRules;
|
|||
import com.ibm.icu.text.SimpleFormatter;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierStore {
|
||||
// Not owned
|
||||
/** Similar to LongNameHandler, but only for MIXED units. */
|
||||
public class MixedUnitLongNameHandler
|
||||
implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator {
|
||||
private final PluralRules rules;
|
||||
// Not owned
|
||||
private final MicroPropsGenerator parent;
|
||||
|
||||
// If this LongNameHandler is for a mixed unit, this stores unit data for
|
||||
// each of the individual units. For each unit, it stores ARRAY_LENGTH
|
||||
// strings, as returned by getMeasureData.
|
||||
/**
|
||||
* Stores unit data for each of the individual units. For each unit, it
|
||||
* stores ARRAY_LENGTH strings, as returned by getMeasureData.
|
||||
*/
|
||||
private List<String[]> fMixedUnitData;
|
||||
|
||||
// A localized NumberFormatter used to format the integer-valued bigger
|
||||
// units of Mixed Unit measurements.
|
||||
/**
|
||||
* A localized NumberFormatter used to format the integer-valued bigger
|
||||
* units of Mixed Unit measurements.
|
||||
*/
|
||||
private LocalizedNumberFormatter fIntegerFormatter;
|
||||
|
||||
// A localised list formatter for joining mixed units together.
|
||||
/** A localised list formatter for joining mixed units together. */
|
||||
private ListFormatter fListFormatter;
|
||||
|
||||
private MixedUnitLongNameHandler(PluralRules rules, MicroPropsGenerator parent) {
|
||||
|
@ -49,8 +50,8 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
|
|||
* @param mixedUnit The mixed measure unit to construct a
|
||||
* MixedUnitLongNameHandler for.
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param rules Does not take ownership.
|
||||
* @param parent Does not take ownership.
|
||||
* @param rules PluralRules instance.
|
||||
* @param parent MicroPropsGenerator instance.
|
||||
*/
|
||||
public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale, MeasureUnit mixedUnit,
|
||||
NumberFormatter.UnitWidth width, PluralRules rules,
|
||||
|
@ -89,43 +90,61 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
|
|||
/**
|
||||
* Produces a plural-appropriate Modifier for a mixed unit: `quantity` is
|
||||
* taken as the final smallest unit, while the larger unit values must be
|
||||
* provided via `micros.mixedMeasures`.
|
||||
* provided by `micros.mixedMeasures`, micros being the MicroProps instance
|
||||
* returned by the parent.
|
||||
*
|
||||
* This function must not be called if this instance has no parent: call
|
||||
* processQuantityWithMicros() instead.
|
||||
*/
|
||||
@Override
|
||||
public MicroProps processQuantity(DecimalQuantity quantity) {
|
||||
assert (fMixedUnitData.size() > 1);
|
||||
MicroProps micros;
|
||||
// if (parent != null)
|
||||
micros = parent.processQuantity(quantity);
|
||||
micros.modOuter = getMixedUnitModifier(quantity, micros);
|
||||
return micros;
|
||||
}
|
||||
|
||||
// Required for ModifierStore. And ModifierStore is required by
|
||||
// SimpleModifier constructor's last parameter. We assert his will never get
|
||||
// called though.
|
||||
/**
|
||||
* Produces a plural-appropriate Modifier for a mixed unit: `quantity` is
|
||||
* taken as the final smallest unit, while the larger unit values must be
|
||||
* provided via `micros.mixedMeasures`.
|
||||
*
|
||||
* Does not call parent.processQuantity, so cannot get a MicroProps instance
|
||||
* that way. Instead, the instance is passed in as a parameter.
|
||||
*/
|
||||
public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
|
||||
assert (fMixedUnitData.size() > 1);
|
||||
micros.modOuter = getMixedUnitModifier(quantity, micros);
|
||||
return micros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Required for ModifierStore. And ModifierStore is required by
|
||||
* SimpleModifier constructor's last parameter. We assert his will never get
|
||||
* called though.
|
||||
*/
|
||||
@Override
|
||||
public Modifier getModifier(Modifier.Signum signum, StandardPlural plural) {
|
||||
// TODO(units): investigate this method while investigating where
|
||||
// LongNameHandler.getModifier() gets used. To be sure it remains
|
||||
// unreachable:
|
||||
|
||||
assert false : "should be unreachable";
|
||||
return null;
|
||||
}
|
||||
|
||||
// For a mixed unit, returns a Modifier that takes only one parameter: the
|
||||
// smallest and final unit of the set. The bigger units' values and labels
|
||||
// get baked into this Modifier, together with the unit label of the final
|
||||
// unit.
|
||||
/**
|
||||
* For a mixed unit, returns a Modifier that takes only one parameter: the
|
||||
* smallest and final unit of the set. The bigger units' values and labels
|
||||
* get baked into this Modifier, together with the unit label of the final
|
||||
* unit.
|
||||
*/
|
||||
private Modifier getMixedUnitModifier(DecimalQuantity quantity, MicroProps micros) {
|
||||
// TODO(icu-units#21): mixed units without usage() is not yet supported.
|
||||
// That should be the only reason why this happens, so delete this whole if
|
||||
// once fixed:
|
||||
if (micros.mixedMeasures.size() == 0) {
|
||||
assert false : "Mixed unit: we must have more than one unit value";
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
// Algorithm:
|
||||
//
|
||||
// For the mixed-units measurement of: "3 yard, 1 foot, 2.6 inch", we should
|
||||
|
@ -153,30 +172,30 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
|
|||
String simpleFormat = LongNameHandler.getWithPlural(this.fMixedUnitData.get(i), pluralForm);
|
||||
SimpleFormatter compiledFormatter = SimpleFormatter.compileMinMaxArguments(simpleFormat, 0, 1);
|
||||
|
||||
|
||||
FormattedStringBuilder appendable = new FormattedStringBuilder();
|
||||
this.fIntegerFormatter.formatImpl(fdec, appendable);
|
||||
outputMeasuresList.add(compiledFormatter.format(appendable.toString()));
|
||||
// TODO: fix this issue https://github.com/icu-units/icu/issues/67
|
||||
// TODO(icu-units#67): fix field positions
|
||||
}
|
||||
|
||||
String[] finalSimpleFormats = this.fMixedUnitData.get(this.fMixedUnitData.size() - 1);
|
||||
StandardPlural finalPlural = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
|
||||
String finalSimpleFormat = LongNameHandler.getWithPlural(finalSimpleFormats, finalPlural);
|
||||
SimpleFormatter finalFormatter = SimpleFormatter.compileMinMaxArguments(finalSimpleFormat, 0, 1);
|
||||
finalFormatter.format("{0}", outputMeasuresList.get(outputMeasuresList.size() -1));
|
||||
outputMeasuresList.add(finalFormatter.format("{0}"));
|
||||
|
||||
// Combine list into a "premixed" pattern
|
||||
String premixedFormatPattern = this.fListFormatter.format(outputMeasuresList);
|
||||
SimpleFormatter premixedCompiled = SimpleFormatter.compileMinMaxArguments(premixedFormatPattern, 0, 1);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String premixedCompiled =
|
||||
SimpleFormatterImpl.compileToStringMinMaxArguments(premixedFormatPattern, sb, 0, 1);
|
||||
|
||||
// Return a SimpleModifier for the "premixed" pattern
|
||||
// TODO(icu-units#67): fix field positions
|
||||
Modifier.Parameters params = new Modifier.Parameters();
|
||||
params.obj = this;
|
||||
params.signum = Modifier.Signum.POS_ZERO;
|
||||
params.plural = finalPlural;
|
||||
|
||||
return new SimpleModifier(premixedCompiled.getTextWithNoArguments(), null, false, params);
|
||||
/*TODO: it was SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural});*/
|
||||
// Return a SimpleModifier for the "premixed" pattern
|
||||
return new SimpleModifier(premixedCompiled, null, false, params);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.impl.units.ComplexUnitsConverter;
|
||||
|
@ -13,8 +12,9 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A MicroPropsGenerator which converts a measurement from a simple MeasureUnit
|
||||
* to a Mixed MeasureUnit.
|
||||
* A MicroPropsGenerator which converts a measurement from one MeasureUnit to
|
||||
* another. In particular, the output MeasureUnit may be a mixed unit. (The
|
||||
* input unit may not be a mixed unit.)
|
||||
*/
|
||||
public class UnitConversionHandler implements MicroPropsGenerator {
|
||||
|
||||
|
@ -22,22 +22,23 @@ public class UnitConversionHandler implements MicroPropsGenerator {
|
|||
private MeasureUnit fOutputUnit;
|
||||
private ComplexUnitsConverter fComplexUnitConverter;
|
||||
|
||||
public UnitConversionHandler(MeasureUnit outputUnit, MicroPropsGenerator parent) {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param inputUnit Specifies the input MeasureUnit. Mixed units are not
|
||||
* supported as input (because input is just a single decimal quantity).
|
||||
* @param outputUnit Specifies the output MeasureUnit.
|
||||
* @param parent The parent MicroPropsGenerator.
|
||||
*/
|
||||
public UnitConversionHandler(MeasureUnit inputUnit,
|
||||
MeasureUnit outputUnit,
|
||||
MicroPropsGenerator parent) {
|
||||
this.fOutputUnit = outputUnit;
|
||||
this.fParent = parent;
|
||||
|
||||
List<MeasureUnit> singleUnits = outputUnit.splitToSingleUnits();
|
||||
|
||||
assert outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED;
|
||||
assert singleUnits.size() > 1;
|
||||
|
||||
MeasureUnitImpl inputUnitImpl = MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier());
|
||||
MeasureUnitImpl outputUnitImpl = MeasureUnitImpl.forIdentifier(outputUnit.getIdentifier());
|
||||
// TODO(icu-units#97): The input unit should be the largest unit, not the first unit, in the identifier.
|
||||
this.fComplexUnitConverter =
|
||||
new ComplexUnitsConverter(
|
||||
new MeasureUnitImpl(outputUnitImpl.getSingleUnits().get(0)),
|
||||
outputUnitImpl,
|
||||
new UnitsData().getConversionRates());
|
||||
this.fComplexUnitConverter = new ComplexUnitsConverter(inputUnitImpl, outputUnitImpl,
|
||||
new UnitsData().getConversionRates());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,16 +46,12 @@ public class UnitConversionHandler implements MicroPropsGenerator {
|
|||
*/
|
||||
@Override
|
||||
public MicroProps processQuantity(DecimalQuantity quantity) {
|
||||
/*TODO: Questions : shall we check the parent if it is equals null */
|
||||
MicroProps result = this.fParent == null?
|
||||
this.fParent.processQuantity(quantity):
|
||||
new MicroProps(false);
|
||||
MicroProps result = this.fParent.processQuantity(quantity);
|
||||
|
||||
quantity.roundToInfinity(); // Enables toDouble
|
||||
List<Measure> measures = this.fComplexUnitConverter.convert(quantity.toBigDecimal());
|
||||
|
||||
result.outputUnit = this.fOutputUnit;
|
||||
result.mixedMeasures = new ArrayList<>();
|
||||
UsagePrefsHandler.mixedMeasuresToMicros(measures, quantity, result);
|
||||
|
||||
return result;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// © 2020 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html
|
||||
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.impl.IllegalIcuArgumentException;
|
||||
|
@ -47,15 +46,18 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
|
|||
return Precision.increment(num.divide(den, MathContext.DECIMAL128));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates micros.mixedMeasures and modifies quantity, based on the values
|
||||
* in measures.
|
||||
*/
|
||||
protected static void mixedMeasuresToMicros(List<Measure> measures, DecimalQuantity quantity, MicroProps micros) {
|
||||
micros.mixedMeasures = new ArrayList<>();
|
||||
if (measures.size() > 1) {
|
||||
// For debugging
|
||||
assert (micros.outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED);
|
||||
|
||||
// Check that we received measurements with the expected MeasureUnits:
|
||||
List<MeasureUnit> singleUnits = micros.outputUnit.splitToSingleUnits();
|
||||
|
||||
assert measures.size() == singleUnits.size();
|
||||
// Check that we received the expected number of measurements:
|
||||
assert measures.size() == micros.outputUnit.splitToSingleUnits().size();
|
||||
|
||||
// Mixed units: except for the last value, we pass all values to the
|
||||
// LongNameHandler via micros->mixedMeasures.
|
||||
|
@ -95,7 +97,6 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
|
|||
|
||||
final List<Measure> routedMeasures = routed.measures;
|
||||
micros.outputUnit = routed.outputUnit.build();
|
||||
micros.mixedMeasures = new ArrayList<>();
|
||||
|
||||
UsagePrefsHandler.mixedMeasuresToMicros(routedMeasures, quantity, micros);
|
||||
|
||||
|
@ -103,14 +104,16 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
|
|||
|
||||
assert micros.rounder != null;
|
||||
|
||||
// TODO: use the user precision if the user already set precision.
|
||||
if (precisionSkeleton != null && precisionSkeleton.length() > 0) {
|
||||
micros.rounder = parseSkeletonToPrecision(precisionSkeleton);
|
||||
} else {
|
||||
// We use the same rounding mode as COMPACT notation: known to be a
|
||||
// human-friendly rounding mode: integers, but add a decimal digit
|
||||
// as needed to ensure we have at least 2 significant digits.
|
||||
micros.rounder = Precision.integer().withMinDigits(2);
|
||||
if (micros.rounder instanceof Precision.BogusRounder) {
|
||||
Precision.BogusRounder rounder = (Precision.BogusRounder)micros.rounder;
|
||||
if (precisionSkeleton != null && precisionSkeleton.length() > 0) {
|
||||
micros.rounder = rounder.into(parseSkeletonToPrecision(precisionSkeleton));
|
||||
} else {
|
||||
// We use the same rounding mode as COMPACT notation: known to be a
|
||||
// human-friendly rounding mode: integers, but add a decimal digit
|
||||
// as needed to ensure we have at least 2 significant digits.
|
||||
micros.rounder = rounder.into(Precision.integer().withMinDigits(2));
|
||||
}
|
||||
}
|
||||
|
||||
return micros;
|
||||
|
|
|
@ -210,10 +210,9 @@ class NumberFormatterImpl {
|
|||
|| !(isPercent || isPermille)
|
||||
|| isCompactNotation
|
||||
);
|
||||
|
||||
// TODO(icu-units#95): Add the logic in this file that sets the rounder to bogus/pass-through if isMixedUnit is true.
|
||||
boolean isMixedUnit = isCldrUnit && macros.unit.getType() == null &&
|
||||
macros.unit.getComplexity() == MeasureUnit.Complexity.MIXED;
|
||||
macros.unit.getComplexity() == MeasureUnit.Complexity.MIXED;
|
||||
|
||||
PluralRules rules = macros.rules;
|
||||
|
||||
// Select the numbering system.
|
||||
|
@ -275,7 +274,9 @@ class NumberFormatterImpl {
|
|||
}
|
||||
chain = usagePrefsHandler = new UsagePrefsHandler(macros.loc, macros.unit, macros.usage, chain);
|
||||
} else if (isMixedUnit) {
|
||||
chain = new UnitConversionHandler(macros.unit, chain);
|
||||
// TODO(icu-units#97): The input unit should be the largest unit, not the first unit, in the identifier.
|
||||
MeasureUnit inputUnit = macros.unit.splitToSingleUnits().get(0);
|
||||
chain = new UnitConversionHandler(inputUnit, macros.unit, chain);
|
||||
}
|
||||
|
||||
// Multiplier
|
||||
|
@ -290,6 +291,9 @@ class NumberFormatterImpl {
|
|||
micros.rounder = Precision.COMPACT_STRATEGY;
|
||||
} else if (isCurrency) {
|
||||
micros.rounder = Precision.MONETARY_STANDARD;
|
||||
} else if (macros.usage != null) {
|
||||
// Bogus Precision - it will get set in the UsagePrefsHandler instead
|
||||
micros.rounder = Precision.BOGUS_PRECISION;
|
||||
} else {
|
||||
micros.rounder = Precision.DEFAULT_MAX_FRAC_6;
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
|||
static final int KEY_SCALE = 13;
|
||||
static final int KEY_THRESHOLD = 14;
|
||||
static final int KEY_PER_UNIT = 15;
|
||||
static final int KEY_MAX = 16;
|
||||
static final int KEY_USAGE = 17;
|
||||
static final int KEY_USAGE = 16;
|
||||
static final int KEY_MAX = 17;
|
||||
|
||||
private final NumberFormatterSettings<?> parent;
|
||||
private final int key;
|
||||
|
@ -134,7 +134,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
|||
* <p>
|
||||
* The default is to render without units (equivalent to {@link NoUnit#BASE}).
|
||||
*
|
||||
* <P>
|
||||
* <p>
|
||||
* If the input usage is correctly set the output unit <b>will change</b>
|
||||
* according to `usage`, `locale` and `unit` value.
|
||||
* </p>
|
||||
|
@ -492,44 +492,57 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
|||
return create(KEY_SCALE, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Specifies the usage for which numbers will be formatted ("person-height",
|
||||
* "road", "rainfall", etc.)
|
||||
*
|
||||
* <p>
|
||||
* When a `usage` is specified, the output unit will change depending on the
|
||||
* `Locale` and the unit quantity. For example, formatting length
|
||||
* measurements specified in meters:
|
||||
*
|
||||
* `NumberFormatter.with().usage("person").unit(MeasureUnit.METER).locale(new ULocale("en-US"))`
|
||||
* * When formatting 0.25, the output will be "10 inches".
|
||||
* * When formatting 1.50, the output will be "4 feet and 11 inches".
|
||||
* <p>
|
||||
* <pre>
|
||||
* NumberFormatter.with().usage("person").unit(MeasureUnit.METER).locale(new ULocale("en-US"))
|
||||
* </pre>
|
||||
* <ul>
|
||||
* <li> When formatting 0.25, the output will be "10 inches".
|
||||
* <li> When formatting 1.50, the output will be "4 feet and 11 inches".
|
||||
* </li>
|
||||
*
|
||||
* <p>
|
||||
* The input unit specified via unit() determines the type of measurement
|
||||
* being formatted (e.g. "length" when the unit is "foot"). The usage
|
||||
* requested will be looked for only within this category of measurement
|
||||
* units.
|
||||
*
|
||||
* <p>
|
||||
* The output unit can be found via FormattedNumber.getOutputUnit().
|
||||
*
|
||||
* <p>
|
||||
* If the usage has multiple parts (e.g. "land-agriculture-grain") and does
|
||||
* not match a known usage preference, the last part will be dropped
|
||||
* repeatedly until a match is found (e.g. trying "land-agriculture", then
|
||||
* "land"). If a match is still not found, usage will fall back to
|
||||
* "default".
|
||||
*
|
||||
* <p>
|
||||
* Setting usage to an empty string clears the usage (disables usage-based
|
||||
* localized formatting).
|
||||
*
|
||||
* <p>
|
||||
* Setting a usage string but not a correct input unit will result in an
|
||||
* U_ILLEGAL_ARGUMENT_ERROR.
|
||||
*
|
||||
* <p>
|
||||
* When using usage, specifying rounding or precision is unnecessary.
|
||||
* Specifying a precision in some manner will override the default
|
||||
* formatting.
|
||||
*
|
||||
*
|
||||
* @param usage A usage parameter from the units resource.
|
||||
* @return The fluent chain
|
||||
* @throws IllegalArgumentException in case of Setting a usage string but not a correct input unit.
|
||||
* @draft ICU 67
|
||||
* @draft ICU 68
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public T usage(String usage) {
|
||||
|
|
|
@ -1468,10 +1468,13 @@ class NumberSkeletonImpl {
|
|||
} else if (macros.unit == MeasureUnit.PERMILLE) {
|
||||
sb.append("permille");
|
||||
return true;
|
||||
} else {
|
||||
} else if (macros.unit.getType() != null) {
|
||||
sb.append("measure-unit/");
|
||||
BlueprintHelpers.generateMeasureUnitOption(macros.unit, sb);
|
||||
return true;
|
||||
} else {
|
||||
// TODO(icu-units#35): add support for not-built-in units.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import com.ibm.icu.util.Currency.CurrencyUsage;
|
|||
*
|
||||
* @stable ICU 62
|
||||
* @see NumberFormatter
|
||||
* @internal
|
||||
*/
|
||||
public abstract class Precision {
|
||||
|
||||
|
@ -366,6 +365,13 @@ public abstract class Precision {
|
|||
// PACKAGE-PRIVATE APIS //
|
||||
//////////////////////////
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final BogusRounder BOGUS_PRECISION = new BogusRounder();
|
||||
|
||||
static final InfiniteRounderImpl NONE = new InfiniteRounderImpl();
|
||||
|
||||
static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0);
|
||||
|
@ -545,6 +551,40 @@ public abstract class Precision {
|
|||
// INTERNALS //
|
||||
///////////////
|
||||
|
||||
/**
|
||||
* An BogusRounder's MathContext into precision.
|
||||
*
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class BogusRounder extends Precision {
|
||||
@Override
|
||||
public void apply(DecimalQuantity value) {
|
||||
throw new AssertionError("BogusRounder must not be applied");
|
||||
}
|
||||
|
||||
@Override
|
||||
BogusRounder createCopy() {
|
||||
BogusRounder copy = new BogusRounder();
|
||||
copy.mathContext = mathContext;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the BogusRounder's MathContext into precision.
|
||||
*
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public Precision into(Precision precision) {
|
||||
Precision copy = precision.createCopy();
|
||||
copy.mathContext = mathContext;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
static class InfiniteRounderImpl extends Precision {
|
||||
|
||||
public InfiniteRounderImpl() {
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.junit.Test;
|
|||
import com.ibm.icu.dev.test.TestFmwk;
|
||||
import com.ibm.icu.dev.test.format.FormattedValueTest;
|
||||
import com.ibm.icu.dev.test.serializable.SerializableTestUtility;
|
||||
import com.ibm.icu.impl.IllegalIcuArgumentException;
|
||||
import com.ibm.icu.impl.number.Grouper;
|
||||
import com.ibm.icu.impl.number.LocalizedNumberFormatterAsFormat;
|
||||
import com.ibm.icu.impl.number.MacroProps;
|
||||
|
@ -644,6 +645,17 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
1,
|
||||
"kelvin");
|
||||
|
||||
assertFormatSingle(
|
||||
"Person unit not in short form",
|
||||
"measure-unit/duration-year-person unit-width-full-name",
|
||||
"unit/year-person unit-width-full-name",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.YEAR_PERSON)
|
||||
.unitWidth(UnitWidth.FULL_NAME),
|
||||
ULocale.forLanguageTag("es-MX"),
|
||||
5,
|
||||
"5 a\u00F1os");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
"Mixed unit",
|
||||
|
@ -738,6 +750,17 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
"0.008765 m/s",
|
||||
"0 m/s");
|
||||
|
||||
// TODO(icu-units#35): does not normalize as desired: while "unit/*" does
|
||||
// get split into unit/perUnit, ".unit(*)" and "measure-unit/*" don't:
|
||||
assertFormatSingle(
|
||||
"Built-in unit, meter-per-second",
|
||||
"measure-unit/speed-meter-per-second",
|
||||
"~unit/meter-per-second",
|
||||
NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND),
|
||||
new ULocale("en-GB"),
|
||||
2.4,
|
||||
"2.4 m/s");
|
||||
|
||||
assertFormatDescending(
|
||||
"Pounds Per Square Mile Short (secondary unit has per-format)",
|
||||
"measure-unit/mass-pound per-measure-unit/area-square-mile",
|
||||
|
@ -770,16 +793,22 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
"0.008765 J/fur",
|
||||
"0 J/fur");
|
||||
|
||||
// TODO(icu-units#35): does not normalize as desired: while "unit/*" does
|
||||
// get split into unit/perUnit, ".unit(*)" and "measure-unit/*" don't:
|
||||
assertFormatSingle(
|
||||
"Built-in unit, meter-per-second",
|
||||
"measure-unit/speed-meter-per-second",
|
||||
"~unit/meter-per-second",
|
||||
NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND),
|
||||
new ULocale("en-GB"),
|
||||
2.4,
|
||||
"2.4 m/s");
|
||||
// // TODO(ICU-20941): Support constructions such as this one.
|
||||
// assertFormatDescending(
|
||||
// "Joules Per Furlong Short with unit identifier via API",
|
||||
// "measure-unit/energy-joule per-measure-unit/length-furlong",
|
||||
// "unit/joule-per-furlong",
|
||||
// NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
|
||||
// ULocale.ENGLISH,
|
||||
// "87,650 J/fur",
|
||||
// "8,765 J/fur",
|
||||
// "876.5 J/fur",
|
||||
// "87.65 J/fur",
|
||||
// "8.765 J/fur",
|
||||
// "0.8765 J/fur",
|
||||
// "0.08765 J/fur",
|
||||
// "0.008765 J/fur",
|
||||
// "0 J/fur");
|
||||
|
||||
// TODO(icu-units#59): THIS UNIT TEST DEMONSTRATES UNDESIRABLE BEHAVIOUR!
|
||||
// When specifying built-in types, one can give both a unit and a perUnit.
|
||||
|
@ -839,6 +868,13 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
FormattedNumber formattedNum;
|
||||
String uTestCase;
|
||||
|
||||
try {
|
||||
NumberFormatter.with().usage("road").locale(ULocale.ENGLISH).format(1);
|
||||
fail("should give an error, usage() without unit() is invalid");
|
||||
} catch (IllegalIcuArgumentException e) {
|
||||
// Pass
|
||||
}
|
||||
|
||||
unloc_formatter = NumberFormatter.with().usage("road").unit(MeasureUnit.METER);
|
||||
|
||||
uTestCase = "unitUsage() en-ZA road";
|
||||
|
@ -881,15 +917,10 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
uTestCase = "unitUsage() en-GB road";
|
||||
formatter = unloc_formatter.locale(new ULocale("en-GB"));
|
||||
formattedNum = formatter.format(321d);
|
||||
|
||||
// status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)");
|
||||
|
||||
assertTrue(
|
||||
uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
|
||||
MeasureUnit.YARD.equals(formattedNum.getOutputUnit()));
|
||||
// status.errIfFailureAndReset("unitUsage() en-GB road, getOutputUnit(...)");
|
||||
assertEquals(uTestCase, "350 yd", formattedNum.toString());
|
||||
//status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)");
|
||||
{
|
||||
final Object[][] expectedFieldPositions = {
|
||||
{NumberFormat.Field.INTEGER, 0, 3},
|
||||
|
@ -919,15 +950,10 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
uTestCase = "unitUsage() en-US road";
|
||||
formatter = unloc_formatter.locale(new ULocale("en-US"));
|
||||
formattedNum = formatter.format(321d);
|
||||
// status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)");
|
||||
|
||||
assertTrue(
|
||||
uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
|
||||
MeasureUnit.FOOT == formattedNum.getOutputUnit());
|
||||
// status.errIfFailureAndReset("unitUsage() en-US road, getOutputUnit(...)");
|
||||
|
||||
assertEquals(uTestCase, "1,050 ft", formattedNum.toString());
|
||||
// status.errIfFailureAndReset("unitUsage() en-US road, toString(...)");
|
||||
{
|
||||
final Object[][] expectedFieldPositions = {
|
||||
{NumberFormat.Field.GROUPING_SEPARATOR, 1, 2},
|
||||
|
@ -958,15 +984,10 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
uTestCase = "unitUsage() en-GB person";
|
||||
formatter = unloc_formatter.locale(new ULocale("en-GB"));
|
||||
formattedNum = formatter.format(80d);
|
||||
// status.errIfFailureAndReset("unitUsage() en-GB person formatDouble");
|
||||
|
||||
assertTrue(
|
||||
uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
|
||||
MeasureUnit.forIdentifier("stone-and-pound").equals(formattedNum.getOutputUnit()));
|
||||
// status.errIfFailureAndReset("unitUsage() en-GB person - formattedNum.getOutputUnit(status)");
|
||||
|
||||
assertEquals(uTestCase, "12 st, 8.4 lb", formattedNum.toString());
|
||||
//status.errIfFailureAndReset("unitUsage() en-GB person, toString(...)");
|
||||
{
|
||||
final Object[][] expectedFieldPositions = {
|
||||
// // Desired output: TODO(icu-units#67)
|
||||
|
@ -1053,29 +1074,51 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
"0 pounds, 0.31 ounces",
|
||||
"0 pounds, 0 ounces");
|
||||
|
||||
// TODO: this is about the user overriding the usage precision.
|
||||
// TODO: should be done!
|
||||
// assertFormatDescendingBig(
|
||||
// "Scientific notation with Usage: possible when using a reasonable Precision",
|
||||
// "scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
|
||||
// "scientific @### usage/default unit/square-meter unit-width-full-name",
|
||||
// NumberFormatter.with()
|
||||
// .unit(MeasureUnit.SQUARE_METER)
|
||||
// .usage("default")
|
||||
// .notation(Notation.scientific())
|
||||
// .precision(Precision.minMaxSignificantDigits(1, 4))
|
||||
// .unitWidth(UnitWidth.FULL_NAME),
|
||||
// new ULocale("en-ZA"),
|
||||
// "8,765E1 square kilometres",
|
||||
// "8,765E0 square kilometres",
|
||||
// "8,765E1 hectares",
|
||||
// "8,765E0 hectares",
|
||||
// "8,765E3 square metres",
|
||||
// "8,765E2 square metres",
|
||||
// "8,765E1 square metres",
|
||||
// "8,765E0 square metres",
|
||||
// "0E0 square centimetres");
|
||||
}
|
||||
assertFormatDescendingBig(
|
||||
"Scientific notation with Usage: possible when using a reasonable Precision",
|
||||
"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
|
||||
"scientific @### usage/default unit/square-meter unit-width-full-name",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.SQUARE_METER)
|
||||
.usage("default")
|
||||
.notation(Notation.scientific())
|
||||
.precision(Precision.minMaxSignificantDigits(1, 4))
|
||||
.unitWidth(UnitWidth.FULL_NAME),
|
||||
new ULocale("en-ZA"),
|
||||
"8,765E1 square kilometres",
|
||||
"8,765E0 square kilometres",
|
||||
"8,765E1 hectares",
|
||||
"8,765E0 hectares",
|
||||
"8,765E3 square metres",
|
||||
"8,765E2 square metres",
|
||||
"8,765E1 square metres",
|
||||
"8,765E0 square metres",
|
||||
"0E0 square centimetres");
|
||||
|
||||
assertFormatSingle(
|
||||
"Rounding Mode propagates: rounding down",
|
||||
"usage/road measure-unit/length-centimeter rounding-mode-floor",
|
||||
"usage/road unit/centimeter rounding-mode-floor",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("centimeter"))
|
||||
.usage("road")
|
||||
.roundingMode(RoundingMode.FLOOR),
|
||||
new ULocale("en-ZA"),
|
||||
34500,
|
||||
"300 m");
|
||||
|
||||
assertFormatSingle(
|
||||
"Rounding Mode propagates: rounding up",
|
||||
"usage/road measure-unit/length-centimeter rounding-mode-ceiling",
|
||||
"usage/road unit/centimeter rounding-mode-ceiling",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("centimeter"))
|
||||
.usage("road")
|
||||
.roundingMode(RoundingMode.CEILING),
|
||||
new ULocale("en-ZA"),
|
||||
30500,
|
||||
"350 m");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
|
@ -1122,18 +1165,17 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
321,
|
||||
"300 m");
|
||||
|
||||
// TODO(younies): enable this test case
|
||||
// assertFormatSingle(
|
||||
// "Precision can be overridden: override takes precedence",
|
||||
// "usage/road measure-unit/length-meter @#",
|
||||
// "usage/road unit/meter @#",
|
||||
// NumberFormatter.with()
|
||||
// .unit(MeasureUnit.METER)
|
||||
// .usage("road")
|
||||
// .precision(Precision.maxSignificantDigits(2)),
|
||||
// new ULocale("en-ZA"),
|
||||
// 321,
|
||||
// "320 m");
|
||||
assertFormatSingle(
|
||||
"Precision can be overridden: override takes precedence",
|
||||
"usage/road measure-unit/length-meter @#",
|
||||
"usage/road unit/meter @#",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER)
|
||||
.usage("road")
|
||||
.precision(Precision.maxSignificantDigits(2)),
|
||||
new ULocale("en-ZA"),
|
||||
321,
|
||||
"320 m");
|
||||
|
||||
assertFormatSingle(
|
||||
"Compact notation with Usage: bizarre, but possible (short)",
|
||||
|
@ -1147,74 +1189,70 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
987654321L,
|
||||
"988K km");
|
||||
|
||||
assertFormatSingle(
|
||||
"Compact notation with Usage: bizarre, but possible (short, precision override)",
|
||||
"compact-short usage/road measure-unit/length-meter @#",
|
||||
"compact-short usage/road unit/meter @#",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER)
|
||||
.usage("road")
|
||||
.notation(Notation.compactShort())
|
||||
.precision(Precision.maxSignificantDigits(2)),
|
||||
new ULocale("en-ZA"),
|
||||
987654321L,
|
||||
"990K km");
|
||||
|
||||
assertFormatSingle(
|
||||
"Compact notation with Usage: unusual but possible (long)",
|
||||
"compact-long usage/road measure-unit/length-meter @#",
|
||||
"compact-long usage/road unit/meter @#",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER)
|
||||
.usage("road")
|
||||
.notation(Notation.compactLong())
|
||||
.precision(Precision.maxSignificantDigits(2)),
|
||||
new ULocale("en-ZA"),
|
||||
987654321,
|
||||
"990 thousand km");
|
||||
|
||||
// TODO(younies): enable override precision test cases.
|
||||
// assertFormatSingle(
|
||||
// "Compact notation with Usage: bizarre, but possible (short, precision override)",
|
||||
// "compact-short usage/road measure-unit/length-meter @#",
|
||||
// "compact-short usage/road unit/meter @#",
|
||||
// NumberFormatter.with()
|
||||
// .unit(MeasureUnit.METER)
|
||||
// .usage("road")
|
||||
// .notation(Notation.compactShort())
|
||||
// .precision(Precision.maxSignificantDigits(2)),
|
||||
// new ULocale("en-ZA"),
|
||||
// 987654321L,
|
||||
// "990K km");
|
||||
assertFormatSingle(
|
||||
"Compact notation with Usage: unusual but possible (long, precision override)",
|
||||
"compact-long usage/road measure-unit/length-meter @#",
|
||||
"compact-long usage/road unit/meter @#",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER)
|
||||
.usage("road")
|
||||
.notation(Notation.compactLong())
|
||||
.precision(Precision.maxSignificantDigits(2)),
|
||||
new ULocale("en-ZA"),
|
||||
987654321,
|
||||
"990 thousand km");
|
||||
|
||||
// TODO(younies): enable override precision test cases.
|
||||
// assertFormatSingle(
|
||||
// "Compact notation with Usage: unusual but possible (long)",
|
||||
// "compact-long usage/road measure-unit/length-meter @#",
|
||||
// "compact-long usage/road unit/meter @#",
|
||||
// NumberFormatter.with()
|
||||
// .unit(MeasureUnit.METER)
|
||||
// .usage("road")
|
||||
// .notation(Notation.compactLong())
|
||||
// .precision(Precision.maxSignificantDigits(2)),
|
||||
// new ULocale("en-ZA"),
|
||||
// 987654321,
|
||||
// "990 thousand km");
|
||||
assertFormatSingle(
|
||||
"Scientific notation, not recommended, requires precision override for road",
|
||||
"scientific usage/road measure-unit/length-meter",
|
||||
"scientific usage/road unit/meter",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER)
|
||||
.usage("road")
|
||||
.notation(Notation.scientific()),
|
||||
new ULocale("en-ZA"),
|
||||
321.45,
|
||||
// Rounding to the nearest "50" is not exponent-adjusted in scientific notation:
|
||||
"0E2 m");
|
||||
|
||||
// TODO(younies): enable override precision test cases.
|
||||
// assertFormatSingle(
|
||||
// "Compact notation with Usage: unusual but possible (long, precision override)",
|
||||
// "compact-long usage/road measure-unit/length-meter @#",
|
||||
// "compact-long usage/road unit/meter @#",
|
||||
// NumberFormatter.with()
|
||||
// .unit(MeasureUnit.METER)
|
||||
// .usage("road")
|
||||
// .notation(Notation.compactLong())
|
||||
// .precision(Precision.maxSignificantDigits(2)),
|
||||
// new ULocale("en-ZA"),
|
||||
// 987654321,
|
||||
// "990 thousand km");
|
||||
|
||||
// TODO(younies): enable override precision test cases.
|
||||
// assertFormatSingle(
|
||||
// "Scientific notation, not recommended, requires precision override for road",
|
||||
// "scientific usage/road measure-unit/length-meter",
|
||||
// "scientific usage/road unit/meter",
|
||||
// NumberFormatter.with().unit(MeasureUnit.METER).usage("road").notation(Notation.scientific()),
|
||||
// new ULocale("en-ZA"),
|
||||
// 321.45,
|
||||
// // Rounding to the nearest "50" is not exponent-adjusted in scientific notation:
|
||||
// "0E2 m");
|
||||
|
||||
// TODO(younies): enable override precision test cases.
|
||||
// assertFormatSingle(
|
||||
// "Scientific notation with Usage: possible when using a reasonable Precision",
|
||||
// "scientific usage/road measure-unit/length-meter @###",
|
||||
// "scientific usage/road unit/meter @###",
|
||||
// NumberFormatter.with()
|
||||
// .unit(MeasureUnit.METER)
|
||||
// .usage("road")
|
||||
// .notation(Notation.scientific())
|
||||
// .precision(Precision.maxSignificantDigits(4)),
|
||||
// new ULocale("en-ZA"),
|
||||
// 321.45, // 0.45 rounds down, 0.55 rounds up.
|
||||
// "3,214E2 m");
|
||||
assertFormatSingle(
|
||||
"Scientific notation with Usage: possible when using a reasonable Precision",
|
||||
"scientific usage/road measure-unit/length-meter @###",
|
||||
"scientific usage/road unit/meter @###",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER)
|
||||
.usage("road")
|
||||
.notation(Notation.scientific())
|
||||
.precision(Precision.maxSignificantDigits(4)),
|
||||
new ULocale("en-ZA"),
|
||||
321.45, // 0.45 rounds down, 0.55 rounds up.
|
||||
"3,214E2 m");
|
||||
|
||||
assertFormatSingle(
|
||||
"Scientific notation with Usage: possible when using a reasonable Precision",
|
||||
|
|
Loading…
Add table
Reference in a new issue