mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-07 22:44:49 +00:00
ICU-21266 Support toSkeleton() for all functional Unit Formatters
See #1347
This commit is contained in:
parent
a08ac00c67
commit
a84fdd0e90
14 changed files with 586 additions and 302 deletions
|
@ -612,21 +612,7 @@ static const char * const gSubTypes[] = {
|
|||
"teaspoon"
|
||||
};
|
||||
|
||||
// Must be sorted by first value and then second value.
|
||||
static int32_t unitPerUnitToSingleUnit[][4] = {
|
||||
{374, 383, 12, 1},
|
||||
{374, 389, 12, 2},
|
||||
{379, 383, 12, 6},
|
||||
{379, 389, 12, 7},
|
||||
{390, 343, 19, 0},
|
||||
{392, 350, 19, 2},
|
||||
{394, 343, 19, 3},
|
||||
{394, 472, 4, 2},
|
||||
{394, 473, 4, 3},
|
||||
{416, 465, 3, 1},
|
||||
{419, 12, 18, 9},
|
||||
{476, 390, 4, 1}
|
||||
};
|
||||
// unitPerUnitToSingleUnit no longer in use! TODO: remove from code-generation code.
|
||||
|
||||
// Shortcuts to the base unit in order to make the default constructor fast
|
||||
static const int32_t kBaseTypeIdx = 16;
|
||||
|
@ -2301,41 +2287,6 @@ bool MeasureUnit::findBySubType(StringPiece subType, MeasureUnit* output) {
|
|||
return false;
|
||||
}
|
||||
|
||||
MeasureUnit MeasureUnit::resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit, bool* isResolved) {
|
||||
int32_t unitOffset = unit.getOffset();
|
||||
int32_t perUnitOffset = perUnit.getOffset();
|
||||
if (unitOffset == -1 || perUnitOffset == -1) {
|
||||
*isResolved = false;
|
||||
return MeasureUnit();
|
||||
}
|
||||
|
||||
// binary search for (unitOffset, perUnitOffset)
|
||||
int32_t start = 0;
|
||||
int32_t end = UPRV_LENGTHOF(unitPerUnitToSingleUnit);
|
||||
while (start < end) {
|
||||
int32_t mid = (start + end) / 2;
|
||||
int32_t *midRow = unitPerUnitToSingleUnit[mid];
|
||||
if (unitOffset < midRow[0]) {
|
||||
end = mid;
|
||||
} else if (unitOffset > midRow[0]) {
|
||||
start = mid + 1;
|
||||
} else if (perUnitOffset < midRow[1]) {
|
||||
end = mid;
|
||||
} else if (perUnitOffset > midRow[1]) {
|
||||
start = mid + 1;
|
||||
} else {
|
||||
// We found a resolution for our unit / per-unit combo
|
||||
// return it.
|
||||
*isResolved = true;
|
||||
return MeasureUnit(midRow[2], midRow[3]);
|
||||
}
|
||||
}
|
||||
|
||||
*isResolved = false;
|
||||
return MeasureUnit();
|
||||
}
|
||||
|
||||
MeasureUnit *MeasureUnit::create(int typeId, int subTypeId, UErrorCode &status) {
|
||||
if (U_FAILURE(status)) {
|
||||
return NULL;
|
||||
|
|
|
@ -223,24 +223,16 @@ void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitR
|
|||
LongNameHandler *fillIn, UErrorCode &status) {
|
||||
// Not valid for mixed units that aren't built-in units, and there should
|
||||
// not be any built-in mixed units!
|
||||
U_ASSERT(uprv_strlen(unitRef.getType()) > 0 || unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
|
||||
U_ASSERT(uprv_strcmp(unitRef.getType(), "") != 0 ||
|
||||
unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
|
||||
U_ASSERT(fillIn != nullptr);
|
||||
if (uprv_strlen(unitRef.getType()) == 0 || uprv_strlen(perUnit.getType()) == 0) {
|
||||
// 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".
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
MeasureUnit unit = unitRef;
|
||||
if (uprv_strcmp(perUnit.getType(), "none") != 0) {
|
||||
// Compound unit: first try to simplify (e.g. "meter per second" is a
|
||||
// built-in unit).
|
||||
bool isResolved = false;
|
||||
MeasureUnit resolved = MeasureUnit::resolveUnitPerUnit(unit, perUnit, &isResolved);
|
||||
if (isResolved) {
|
||||
unit = resolved;
|
||||
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
|
||||
MeasureUnit simplified = unit.product(perUnit.reciprocal(status), status);
|
||||
if (uprv_strcmp(simplified.getType(), "") != 0) {
|
||||
unit = simplified;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
|
||||
|
@ -248,6 +240,14 @@ void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitR
|
|||
}
|
||||
}
|
||||
|
||||
if (uprv_strcmp(unit.getType(), "") == 0) {
|
||||
// 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".
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, simpleFormats, status);
|
||||
if (U_FAILURE(status)) {
|
||||
|
@ -263,6 +263,13 @@ void LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit
|
|||
const MeasureUnit &perUnit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent,
|
||||
LongNameHandler *fillIn, UErrorCode &status) {
|
||||
if (uprv_strcmp(unit.getType(), "") == 0 || uprv_strcmp(perUnit.getType(), "") == 0) {
|
||||
// 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".
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return;
|
||||
}
|
||||
if (fillIn == nullptr) {
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return;
|
||||
|
|
|
@ -843,10 +843,6 @@ void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString&
|
|||
sb.append(u' ');
|
||||
}
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (GeneratorHelpers::perUnit(macros, sb, status)) {
|
||||
sb.append(u' ');
|
||||
}
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (GeneratorHelpers::usage(macros, sb, status)) {
|
||||
sb.append(u' ');
|
||||
}
|
||||
|
@ -1025,14 +1021,6 @@ void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, Mac
|
|||
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
|
||||
}
|
||||
|
||||
void blueprint_helpers::generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb,
|
||||
UErrorCode&) {
|
||||
// Need to do char <-> UChar conversion...
|
||||
sb.append(UnicodeString(measureUnit.getType(), -1, US_INV));
|
||||
sb.append(u'-');
|
||||
sb.append(UnicodeString(measureUnit.getSubtype(), -1, US_INV));
|
||||
}
|
||||
|
||||
void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros,
|
||||
UErrorCode& status) {
|
||||
// A little bit of a hack: save the current unit (numerator), call the main measure unit
|
||||
|
@ -1059,15 +1047,21 @@ void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment,
|
|||
return;
|
||||
}
|
||||
|
||||
// Mixed units can only be represented by a full MeasureUnit instances, so
|
||||
// we ignore macros.perUnit.
|
||||
// Mixed units can only be represented by full MeasureUnit instances, so we
|
||||
// don't split the denominator into macros.perUnit.
|
||||
if (fullUnit.complexity == UMEASURE_UNIT_MIXED) {
|
||||
macros.unit = std::move(fullUnit).build(status);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(ICU-20941): Clean this up (see also
|
||||
// https://github.com/icu-units/icu/issues/35).
|
||||
// When we have a built-in unit (e.g. meter-per-second), we don't split it up
|
||||
MeasureUnit testBuiltin = fullUnit.copy(status).build(status);
|
||||
if (uprv_strcmp(testBuiltin.getType(), "") != 0) {
|
||||
macros.unit = std::move(testBuiltin);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(ICU-20941): Clean this up.
|
||||
for (int32_t i = 0; i < fullUnit.units.length(); i++) {
|
||||
SingleUnitImpl* subUnit = fullUnit.units[i];
|
||||
if (subUnit->dimensionality > 0) {
|
||||
|
@ -1523,28 +1517,17 @@ bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorC
|
|||
} else if (utils::unitIsPermille(macros.unit)) {
|
||||
sb.append(u"permille", -1);
|
||||
return true;
|
||||
} else if (uprv_strcmp(macros.unit.getType(), "") != 0) {
|
||||
sb.append(u"measure-unit/", -1);
|
||||
blueprint_helpers::generateMeasureUnitOption(macros.unit, sb, status);
|
||||
return true;
|
||||
} else {
|
||||
// TODO(icu-units#35): add support for not-built-in units.
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
|
||||
// Per-units are currently expected to be only MeasureUnits.
|
||||
if (utils::unitIsBaseUnit(macros.perUnit)) {
|
||||
// Default value: ok to ignore
|
||||
return false;
|
||||
} else if (utils::unitIsCurrency(macros.perUnit)) {
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return false;
|
||||
} else {
|
||||
sb.append(u"per-measure-unit/", -1);
|
||||
blueprint_helpers::generateMeasureUnitOption(macros.perUnit, sb, status);
|
||||
MeasureUnit unit = macros.unit;
|
||||
if (utils::unitIsCurrency(macros.perUnit)) {
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
return false;
|
||||
}
|
||||
if (!utils::unitIsBaseUnit(macros.perUnit)) {
|
||||
unit = unit.product(macros.perUnit.reciprocal(status), status);
|
||||
}
|
||||
sb.append(u"unit/", -1);
|
||||
sb.append(unit.getIdentifier());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,10 +240,10 @@ void parseCurrencyOption(const StringSegment& segment, MacroProps& macros, UErro
|
|||
|
||||
void generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode& status);
|
||||
|
||||
// "measure-unit/" is deprecated in favour of "unit/".
|
||||
void parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
|
||||
|
||||
void generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb, UErrorCode& status);
|
||||
|
||||
// "per-measure-unit/" is deprecated in favour of "unit/".
|
||||
void parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
|
||||
|
||||
/**
|
||||
|
@ -314,8 +314,6 @@ class GeneratorHelpers {
|
|||
|
||||
static bool unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
|
||||
|
||||
static bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
|
||||
|
||||
static bool usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
|
||||
|
||||
static bool precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
|
||||
|
|
|
@ -541,13 +541,6 @@ class U_I18N_API MeasureUnit: public UObject {
|
|||
* @internal
|
||||
*/
|
||||
int32_t getOffset() const;
|
||||
|
||||
/**
|
||||
* ICU use only.
|
||||
* @internal
|
||||
*/
|
||||
static MeasureUnit resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit, bool* isResolved);
|
||||
#endif /* U_HIDE_INTERNAL_API */
|
||||
|
||||
// All code between the "Start generated createXXX methods" comment and
|
||||
|
|
|
@ -3494,7 +3494,7 @@ void MeasureFormatTest::TestUnitPerUnitResolution() {
|
|||
UErrorCode status = U_ZERO_ERROR;
|
||||
Locale en("en");
|
||||
MeasureFormat fmt("en", UMEASFMT_WIDTH_SHORT, status);
|
||||
Measure measure(50.0, MeasureUnit::createPound(status), status);
|
||||
Measure measure(50.0, MeasureUnit::createPoundForce(status), status);
|
||||
LocalPointer<MeasureUnit> sqInch(MeasureUnit::createSquareInch(status));
|
||||
if (!assertSuccess("Create of format unit and per unit", status)) {
|
||||
return;
|
||||
|
|
|
@ -54,6 +54,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
|
|||
void notationCompact();
|
||||
void unitMeasure();
|
||||
void unitCompoundMeasure();
|
||||
void unitSkeletons();
|
||||
void unitUsage();
|
||||
void unitUsageErrorCodes();
|
||||
void unitUsageSkeletons();
|
||||
|
@ -104,12 +105,15 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
|
|||
CurrencyUnit CNY;
|
||||
|
||||
MeasureUnit METER;
|
||||
MeasureUnit METER_PER_SECOND;
|
||||
MeasureUnit DAY;
|
||||
MeasureUnit SQUARE_METER;
|
||||
MeasureUnit FAHRENHEIT;
|
||||
MeasureUnit SECOND;
|
||||
MeasureUnit POUND;
|
||||
MeasureUnit POUND_FORCE;
|
||||
MeasureUnit SQUARE_MILE;
|
||||
MeasureUnit SQUARE_INCH;
|
||||
MeasureUnit JOULE;
|
||||
MeasureUnit FURLONG;
|
||||
MeasureUnit KELVIN;
|
||||
|
|
|
@ -53,12 +53,15 @@ NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode& status)
|
|||
}
|
||||
METER = *unit;
|
||||
|
||||
METER_PER_SECOND = *LocalPointer<MeasureUnit>(MeasureUnit::createMeterPerSecond(status));
|
||||
DAY = *LocalPointer<MeasureUnit>(MeasureUnit::createDay(status));
|
||||
SQUARE_METER = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMeter(status));
|
||||
FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status));
|
||||
SECOND = *LocalPointer<MeasureUnit>(MeasureUnit::createSecond(status));
|
||||
POUND = *LocalPointer<MeasureUnit>(MeasureUnit::createPound(status));
|
||||
POUND_FORCE = *LocalPointer<MeasureUnit>(MeasureUnit::createPoundForce(status));
|
||||
SQUARE_MILE = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMile(status));
|
||||
SQUARE_INCH = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareInch(status));
|
||||
JOULE = *LocalPointer<MeasureUnit>(MeasureUnit::createJoule(status));
|
||||
FURLONG = *LocalPointer<MeasureUnit>(MeasureUnit::createFurlong(status));
|
||||
KELVIN = *LocalPointer<MeasureUnit>(MeasureUnit::createKelvin(status));
|
||||
|
@ -77,6 +80,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
|
|||
TESTCASE_AUTO(notationCompact);
|
||||
TESTCASE_AUTO(unitMeasure);
|
||||
TESTCASE_AUTO(unitCompoundMeasure);
|
||||
TESTCASE_AUTO(unitSkeletons);
|
||||
TESTCASE_AUTO(unitUsage);
|
||||
TESTCASE_AUTO(unitUsageErrorCodes);
|
||||
TESTCASE_AUTO(unitUsageSkeletons);
|
||||
|
@ -578,6 +582,23 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
u"0.0088 meters",
|
||||
u"0 meters");
|
||||
|
||||
// // TODO(ICU-20941): Support formatting for not-built-in units
|
||||
// assertFormatDescending(
|
||||
// u"Hectometers",
|
||||
// u"measure-unit/length-hectometer",
|
||||
// u"unit/hectometer",
|
||||
// NumberFormatter::with().unit(MeasureUnit::forIdentifier("hectometer", status)),
|
||||
// Locale::getEnglish(),
|
||||
// u"87,650 hm",
|
||||
// u"8,765 hm",
|
||||
// u"876.5 hm",
|
||||
// u"87.65 hm",
|
||||
// u"8.765 hm",
|
||||
// u"0.8765 hm",
|
||||
// u"0.08765 hm",
|
||||
// u"0.008765 hm",
|
||||
// u"0 hm");
|
||||
|
||||
// TODO: Implement Measure in C++
|
||||
// assertFormatSingleMeasure(
|
||||
// u"Meters with Measure Input",
|
||||
|
@ -694,10 +715,9 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
5,
|
||||
u"5 a\u00F1os");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
u"Mixed unit",
|
||||
nullptr,
|
||||
u"unit/yard-and-foot-and-inch",
|
||||
u"unit/yard-and-foot-and-inch",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("yard-and-foot-and-inch", status)),
|
||||
|
@ -705,10 +725,9 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
3.65,
|
||||
"3 yd, 1 ft, 11.4 in");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
u"Mixed unit, Scientific",
|
||||
nullptr,
|
||||
u"unit/yard-and-foot-and-inch E0",
|
||||
u"unit/yard-and-foot-and-inch E0",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("yard-and-foot-and-inch", status))
|
||||
|
@ -717,10 +736,9 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
3.65,
|
||||
"3 yd, 1 ft, 1.14E1 in");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
u"Mixed Unit (Narrow Version)",
|
||||
nullptr,
|
||||
u"unit/metric-ton-and-kilogram-and-gram unit-width-narrow",
|
||||
u"unit/metric-ton-and-kilogram-and-gram unit-width-narrow",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status))
|
||||
|
@ -729,10 +747,9 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
4.28571,
|
||||
u"4t 285kg 710g");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
u"Mixed Unit (Short Version)",
|
||||
nullptr,
|
||||
u"unit/metric-ton-and-kilogram-and-gram unit-width-short",
|
||||
u"unit/metric-ton-and-kilogram-and-gram unit-width-short",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status))
|
||||
|
@ -741,10 +758,9 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
4.28571,
|
||||
u"4 t, 285 kg, 710 g");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
u"Mixed Unit (Full Name Version)",
|
||||
nullptr,
|
||||
u"unit/metric-ton-and-kilogram-and-gram unit-width-full-name",
|
||||
u"unit/metric-ton-and-kilogram-and-gram unit-width-full-name",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status))
|
||||
|
@ -755,8 +771,8 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
|
||||
assertFormatSingle(
|
||||
u"Testing \"1 foot 12 inches\"",
|
||||
nullptr,
|
||||
u"unit/foot-and-inch",
|
||||
u"unit/foot-and-inch @### unit-width-full-name",
|
||||
u"unit/foot-and-inch @### unit-width-full-name",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("foot-and-inch", status))
|
||||
.precision(Precision::maxSignificantDigits(4))
|
||||
|
@ -776,7 +792,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
|||
|
||||
assertFormatSingle(
|
||||
u"Negative numbers: time",
|
||||
nullptr, // submitting after TODO(icu-units#35) is fixed: fill in skeleton!
|
||||
u"unit/hour-and-minute-and-second",
|
||||
u"unit/hour-and-minute-and-second",
|
||||
NumberFormatter::with().unit(MeasureUnit::forIdentifier("hour-and-minute-and-second", status)),
|
||||
Locale("de-DE"),
|
||||
|
@ -803,16 +819,21 @@ void NumberFormatterApiTest::unitCompoundMeasure() {
|
|||
u"0.008765 m/s",
|
||||
u"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(
|
||||
u"Built-in unit, meter-per-second",
|
||||
u"measure-unit/speed-meter-per-second",
|
||||
u"~unit/meter-per-second",
|
||||
NumberFormatter::with().unit(MeasureUnit::getMeterPerSecond()),
|
||||
Locale("en-GB"),
|
||||
2.4,
|
||||
u"2.4 m/s");
|
||||
assertFormatDescending(
|
||||
u"Meters Per Second Short, built-in m/s",
|
||||
u"measure-unit/speed-meter-per-second",
|
||||
u"unit/meter-per-second",
|
||||
NumberFormatter::with().unit(METER_PER_SECOND),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 m/s",
|
||||
u"8,765 m/s",
|
||||
u"876.5 m/s",
|
||||
u"87.65 m/s",
|
||||
u"8.765 m/s",
|
||||
u"0.8765 m/s",
|
||||
u"0.08765 m/s",
|
||||
u"0.008765 m/s",
|
||||
u"0 m/s");
|
||||
|
||||
assertFormatDescending(
|
||||
u"Pounds Per Square Mile Short (secondary unit has per-format) and adoptPerUnit method",
|
||||
|
@ -863,29 +884,51 @@ void NumberFormatterApiTest::unitCompoundMeasure() {
|
|||
// u"0.008765 J/fur",
|
||||
// u"0 J/fur");
|
||||
|
||||
// TODO(icu-units#59): THIS UNIT TEST DEMONSTRATES UNDESIREABLE BEHAVIOUR!
|
||||
// When specifying built-in types, one can give both a unit and a perUnit.
|
||||
// Resolving to a built-in unit does not always work.
|
||||
//
|
||||
// (Unit-testing philosophy: do we leave this enabled to demonstrate current
|
||||
// behaviour, and changing behaviour in the future? Or comment it out to
|
||||
// avoid asserting this is "correct"?)
|
||||
assertFormatDescending(
|
||||
u"Pounds per Square Inch: composed",
|
||||
u"measure-unit/force-pound-force per-measure-unit/area-square-inch",
|
||||
u"unit/pound-force-per-square-inch",
|
||||
NumberFormatter::with().unit(POUND_FORCE).perUnit(SQUARE_INCH),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 psi",
|
||||
u"8,765 psi",
|
||||
u"876.5 psi",
|
||||
u"87.65 psi",
|
||||
u"8.765 psi",
|
||||
u"0.8765 psi",
|
||||
u"0.08765 psi",
|
||||
u"0.008765 psi",
|
||||
u"0 psi");
|
||||
|
||||
assertFormatDescending(
|
||||
u"Pounds per Square Inch: built-in",
|
||||
u"measure-unit/force-pound-force per-measure-unit/area-square-inch",
|
||||
u"unit/pound-force-per-square-inch",
|
||||
NumberFormatter::with().unit(MeasureUnit::getPoundPerSquareInch()),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 psi",
|
||||
u"8,765 psi",
|
||||
u"876.5 psi",
|
||||
u"87.65 psi",
|
||||
u"8.765 psi",
|
||||
u"0.8765 psi",
|
||||
u"0.08765 psi",
|
||||
u"0.008765 psi",
|
||||
u"0 psi");
|
||||
|
||||
assertFormatSingle(
|
||||
u"DEMONSTRATING BAD BEHAVIOUR, TODO(icu-units#59)",
|
||||
u"m/s/s simplifies to m/s^2",
|
||||
u"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
|
||||
u"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::getMeterPerSecond())
|
||||
.perUnit(MeasureUnit::getSecond()),
|
||||
u"unit/meter-per-square-second",
|
||||
NumberFormatter::with().unit(METER_PER_SECOND).perUnit(SECOND),
|
||||
Locale("en-GB"),
|
||||
2.4,
|
||||
"2.4 m/s/s");
|
||||
u"2.4 m/s\u00B2");
|
||||
|
||||
assertFormatSingle(
|
||||
u"Negative numbers: acceleration",
|
||||
u"measure-unit/acceleration-meter-per-square-second",
|
||||
// TODO: when other PRs are merged, try: u"unit/meter-per-second-second" instead:
|
||||
u"measure-unit/acceleration-meter-per-square-second",
|
||||
u"unit/meter-per-second-second",
|
||||
NumberFormatter::with().unit(MeasureUnit::forIdentifier("meter-per-pow2-second", status)),
|
||||
Locale("af-ZA"),
|
||||
-9.81,
|
||||
|
@ -908,12 +951,10 @@ void NumberFormatterApiTest::unitCompoundMeasure() {
|
|||
status.assertSuccess();
|
||||
}
|
||||
|
||||
// .perUnit() may only be passed a built-in type, "square-second" is not a
|
||||
// built-in type.
|
||||
nf = NumberFormatter::with()
|
||||
.unit(MeasureUnit::getMeter())
|
||||
.perUnit(MeasureUnit::forIdentifier("square-second", status))
|
||||
.locale("en-GB");
|
||||
// .perUnit() may only be passed a built-in type, or something that combines
|
||||
// to a built-in type together with .unit().
|
||||
MeasureUnit SQUARE_SECOND = MeasureUnit::forIdentifier("square-second", status);
|
||||
nf = NumberFormatter::with().unit(FURLONG).perUnit(SQUARE_SECOND).locale("en-GB");
|
||||
status.assertSuccess(); // Error is only returned once we try to format.
|
||||
num = nf.formatDouble(2.4, status);
|
||||
if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) {
|
||||
|
@ -921,6 +962,128 @@ void NumberFormatterApiTest::unitCompoundMeasure() {
|
|||
nf.formatDouble(2.4, status).toString(status) + "\".");
|
||||
status.assertSuccess();
|
||||
}
|
||||
// As above, "square-second" is not a built-in type, however this time,
|
||||
// meter-per-square-second is a built-in type.
|
||||
assertFormatSingle(
|
||||
u"meter per square-second works as a composed unit",
|
||||
u"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
|
||||
u"unit/meter-per-square-second",
|
||||
NumberFormatter::with().unit(METER).perUnit(SQUARE_SECOND),
|
||||
Locale("en-GB"),
|
||||
2.4,
|
||||
u"2.4 m/s\u00B2");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitSkeletons() {
|
||||
const struct TestCase {
|
||||
const char *msg;
|
||||
const char16_t *inputSkeleton;
|
||||
const char16_t *normalizedSkeleton;
|
||||
} cases[] = {
|
||||
{"old-form built-in compound unit", //
|
||||
u"measure-unit/speed-meter-per-second", //
|
||||
u"unit/meter-per-second"},
|
||||
|
||||
{"old-form compound construction, converts to built-in", //
|
||||
u"measure-unit/length-meter per-measure-unit/duration-second", //
|
||||
u"unit/meter-per-second"},
|
||||
|
||||
{"old-form compound construction which does not simplify to a built-in", //
|
||||
u"measure-unit/energy-joule per-measure-unit/length-meter", //
|
||||
u"unit/joule-per-meter"},
|
||||
|
||||
{"old-form compound-compound ugliness resolves neatly", //
|
||||
u"measure-unit/speed-meter-per-second per-measure-unit/duration-second", //
|
||||
u"unit/meter-per-square-second"},
|
||||
|
||||
{"short-form built-in units stick with the built-in", //
|
||||
u"unit/meter-per-second", //
|
||||
u"unit/meter-per-second"},
|
||||
|
||||
{"short-form compound units stay as is", //
|
||||
u"unit/square-meter-per-square-meter", //
|
||||
u"unit/square-meter-per-square-meter"},
|
||||
|
||||
{"short-form compound units stay as is", //
|
||||
u"unit/joule-per-furlong", //
|
||||
u"unit/joule-per-furlong"},
|
||||
|
||||
{"short-form that doesn't consist of built-in units", //
|
||||
u"unit/hectometer-per-second", //
|
||||
u"unit/hectometer-per-second"},
|
||||
|
||||
{"short-form that doesn't consist of built-in units", //
|
||||
u"unit/meter-per-hectosecond", //
|
||||
u"unit/meter-per-hectosecond"},
|
||||
|
||||
// // TODO: binary prefixes not supported yet!
|
||||
// {"Round-trip example from icu-units#35", //
|
||||
// u"unit/kibijoule-per-furlong", //
|
||||
// u"unit/kibijoule-per-furlong"},
|
||||
};
|
||||
for (auto &cas : cases) {
|
||||
IcuTestErrorCode status(*this, cas.msg);
|
||||
auto nf = NumberFormatter::forSkeleton(cas.inputSkeleton, status);
|
||||
if (status.errIfFailureAndReset("NumberFormatter::forSkeleton failed")) {
|
||||
continue;
|
||||
}
|
||||
assertEquals( //
|
||||
UnicodeString(TRUE, cas.inputSkeleton, -1) + u" normalization", //
|
||||
cas.normalizedSkeleton, //
|
||||
nf.toSkeleton(status));
|
||||
status.errIfFailureAndReset("NumberFormatter::toSkeleton failed");
|
||||
}
|
||||
|
||||
const struct FailCase {
|
||||
const char *msg;
|
||||
const char16_t *inputSkeleton;
|
||||
UErrorCode expectedForSkelStatus;
|
||||
UErrorCode expectedToSkelStatus;
|
||||
} failCases[] = {
|
||||
{"Parsing measure-unit/* results in failure if not built-in unit",
|
||||
u"measure-unit/hectometer", //
|
||||
U_NUMBER_SKELETON_SYNTAX_ERROR, //
|
||||
U_ZERO_ERROR},
|
||||
|
||||
{"Parsing per-measure-unit/* results in failure if not built-in unit",
|
||||
u"measure-unit/meter per-measure-unit/hectosecond", //
|
||||
U_NUMBER_SKELETON_SYNTAX_ERROR, //
|
||||
U_ZERO_ERROR},
|
||||
};
|
||||
for (auto &cas : failCases) {
|
||||
IcuTestErrorCode status(*this, cas.msg);
|
||||
auto nf = NumberFormatter::forSkeleton(cas.inputSkeleton, status);
|
||||
if (status.expectErrorAndReset(cas.expectedForSkelStatus, cas.msg)) {
|
||||
continue;
|
||||
}
|
||||
nf.toSkeleton(status);
|
||||
status.expectErrorAndReset(cas.expectedToSkelStatus, cas.msg);
|
||||
}
|
||||
|
||||
IcuTestErrorCode status(*this, "unitSkeletons");
|
||||
MeasureUnit METER_PER_SECOND = MeasureUnit::forIdentifier("meter-per-second", status);
|
||||
|
||||
assertEquals( //
|
||||
".unit(METER_PER_SECOND) normalization", //
|
||||
u"unit/meter-per-second", //
|
||||
NumberFormatter::with().unit(METER_PER_SECOND).toSkeleton(status));
|
||||
assertEquals( //
|
||||
".unit(METER).perUnit(SECOND) normalization", //
|
||||
u"unit/meter-per-second",
|
||||
NumberFormatter::with().unit(METER).perUnit(SECOND).toSkeleton(status));
|
||||
assertEquals( //
|
||||
".unit(MeasureUnit::forIdentifier(\"hectometer\", status)) normalization", //
|
||||
u"unit/hectometer",
|
||||
NumberFormatter::with()
|
||||
.unit(MeasureUnit::forIdentifier("hectometer", status))
|
||||
.toSkeleton(status));
|
||||
assertEquals( //
|
||||
".unit(MeasureUnit::forIdentifier(\"hectometer\", status)) normalization", //
|
||||
u"unit/meter-per-hectosecond",
|
||||
NumberFormatter::with()
|
||||
.unit(METER)
|
||||
.perUnit(MeasureUnit::forIdentifier("hectosecond", status))
|
||||
.toSkeleton(status));
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitUsage() {
|
||||
|
@ -3532,11 +3695,11 @@ void NumberFormatterApiTest::fieldPositionCoverage() {
|
|||
}
|
||||
|
||||
{
|
||||
const char16_t* message = u"Measure unit field position with prefix and suffix";
|
||||
const char16_t* message = u"Measure unit field position with prefix and suffix, composed m/s";
|
||||
FormattedNumber result = assertFormatSingle(
|
||||
message,
|
||||
u"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
|
||||
u"unit/meter-per-second unit-width-full-name",
|
||||
u"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
|
||||
NumberFormatter::with().unit(METER).perUnit(SECOND).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
"ky", // locale with the interesting data
|
||||
68,
|
||||
|
@ -3553,6 +3716,28 @@ void NumberFormatterApiTest::fieldPositionCoverage() {
|
|||
UPRV_LENGTHOF(expectedFieldPositions));
|
||||
}
|
||||
|
||||
{
|
||||
const char16_t* message = u"Measure unit field position with prefix and suffix, built-in m/s";
|
||||
FormattedNumber result = assertFormatSingle(
|
||||
message,
|
||||
u"measure-unit/speed-meter-per-second unit-width-full-name",
|
||||
u"unit/meter-per-second unit-width-full-name",
|
||||
NumberFormatter::with().unit(METER_PER_SECOND).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
"ky", // locale with the interesting data
|
||||
68,
|
||||
u"секундасына 68 метр");
|
||||
static const UFieldPosition expectedFieldPositions[] = {
|
||||
// field, begin index, end index
|
||||
{UNUM_MEASURE_UNIT_FIELD, 0, 11},
|
||||
{UNUM_INTEGER_FIELD, 12, 14},
|
||||
{UNUM_MEASURE_UNIT_FIELD, 15, 19}};
|
||||
assertNumberFieldPositions(
|
||||
message,
|
||||
result,
|
||||
expectedFieldPositions,
|
||||
UPRV_LENGTHOF(expectedFieldPositions));
|
||||
}
|
||||
|
||||
{
|
||||
const char16_t* message = u"Measure unit field position with inner spaces";
|
||||
FormattedNumber result = assertFormatSingle(
|
||||
|
@ -4141,6 +4326,15 @@ void NumberFormatterApiTest::microPropsInternals() {
|
|||
assertEquals("Copy Assigned capacity", 4, copyAssigned.mixedMeasures.getCapacity());
|
||||
}
|
||||
|
||||
/* For skeleton comparisons: this checks the toSkeleton output for `f` and for
|
||||
* `conciseSkeleton` against the normalized version of `uskeleton` - this does
|
||||
* not round-trip uskeleton itself.
|
||||
*
|
||||
* If `conciseSkeleton` starts with a "~", its round-trip check is skipped.
|
||||
*
|
||||
* If `uskeleton` is nullptr, toSkeleton is expected to return an
|
||||
* U_UNSUPPORTED_ERROR.
|
||||
*/
|
||||
void NumberFormatterApiTest::assertFormatDescending(
|
||||
const char16_t* umessage,
|
||||
const char16_t* uskeleton,
|
||||
|
@ -4202,6 +4396,15 @@ void NumberFormatterApiTest::assertFormatDescending(
|
|||
}
|
||||
}
|
||||
|
||||
/* For skeleton comparisons: this checks the toSkeleton output for `f` and for
|
||||
* `conciseSkeleton` against the normalized version of `uskeleton` - this does
|
||||
* not round-trip uskeleton itself.
|
||||
*
|
||||
* If `conciseSkeleton` starts with a "~", its round-trip check is skipped.
|
||||
*
|
||||
* If `uskeleton` is nullptr, toSkeleton is expected to return an
|
||||
* U_UNSUPPORTED_ERROR.
|
||||
*/
|
||||
void NumberFormatterApiTest::assertFormatDescendingBig(
|
||||
const char16_t* umessage,
|
||||
const char16_t* uskeleton,
|
||||
|
@ -4263,6 +4466,15 @@ void NumberFormatterApiTest::assertFormatDescendingBig(
|
|||
}
|
||||
}
|
||||
|
||||
/* For skeleton comparisons: this checks the toSkeleton output for `f` and for
|
||||
* `conciseSkeleton` against the normalized version of `uskeleton` - this does
|
||||
* not round-trip uskeleton itself.
|
||||
*
|
||||
* If `conciseSkeleton` starts with a "~", its round-trip check is skipped.
|
||||
*
|
||||
* If `uskeleton` is nullptr, toSkeleton is expected to return an
|
||||
* U_UNSUPPORTED_ERROR.
|
||||
*/
|
||||
FormattedNumber
|
||||
NumberFormatterApiTest::assertFormatSingle(
|
||||
const char16_t* umessage,
|
||||
|
|
|
@ -215,16 +215,10 @@ public class LongNameHandler
|
|||
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);
|
||||
if (simplified != null) {
|
||||
MeasureUnit simplified = unit.product(perUnit.reciprocal());
|
||||
if (simplified.getType() != null) {
|
||||
unit = simplified;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
|
@ -232,6 +226,12 @@ public class LongNameHandler
|
|||
}
|
||||
}
|
||||
|
||||
if (unit.getType() == null) {
|
||||
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported.
|
||||
throw new UnsupportedOperationException("Unsanctioned unit, not yet supported: " +
|
||||
unit.getIdentifier());
|
||||
}
|
||||
|
||||
String[] simpleFormats = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, simpleFormats);
|
||||
// TODO(ICU4J): Reduce the number of object creations here?
|
||||
|
@ -249,6 +249,13 @@ public class LongNameHandler
|
|||
UnitWidth width,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
if (unit.getType() == null || perUnit.getType() == null) {
|
||||
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
|
||||
// error code.
|
||||
throw new UnsupportedOperationException(
|
||||
"Unsanctioned units, not yet supported: " + unit.getIdentifier() + "/" +
|
||||
perUnit.getIdentifier());
|
||||
}
|
||||
String[] primaryData = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, primaryData);
|
||||
String[] secondaryData = new String[ARRAY_LENGTH];
|
||||
|
|
|
@ -70,7 +70,9 @@ public class MeasureUnitImpl {
|
|||
MeasureUnitImpl result = new MeasureUnitImpl();
|
||||
result.complexity = this.complexity;
|
||||
result.identifier = this.identifier;
|
||||
result.singleUnits = new ArrayList<>(this.singleUnits);
|
||||
for (SingleUnitImpl single : this.singleUnits) {
|
||||
result.singleUnits.add(single.copy());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ import com.ibm.icu.impl.SoftCache;
|
|||
import com.ibm.icu.impl.StringSegment;
|
||||
import com.ibm.icu.impl.number.MacroProps;
|
||||
import com.ibm.icu.impl.number.RoundingUtils;
|
||||
import com.ibm.icu.impl.units.MeasureUnitImpl;
|
||||
import com.ibm.icu.impl.units.SingleUnitImpl;
|
||||
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
|
||||
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
|
||||
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
||||
|
@ -904,9 +906,6 @@ class NumberSkeletonImpl {
|
|||
if (macros.unit != null && GeneratorHelpers.unit(macros, sb)) {
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.perUnit != null && GeneratorHelpers.perUnit(macros, sb)) {
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.usage != null && GeneratorHelpers.usage(macros, sb)) {
|
||||
sb.append(' ');
|
||||
}
|
||||
|
@ -1026,6 +1025,7 @@ class NumberSkeletonImpl {
|
|||
sb.append(currency.getCurrencyCode());
|
||||
}
|
||||
|
||||
// "measure-unit/" is deprecated in favour of "unit/".
|
||||
private static void parseMeasureUnitOption(StringSegment segment, MacroProps macros) {
|
||||
// NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric)
|
||||
// http://unicode.org/reports/tr35/#Validity_Data
|
||||
|
@ -1048,12 +1048,7 @@ class NumberSkeletonImpl {
|
|||
throw new SkeletonSyntaxException("Unknown measure unit", segment);
|
||||
}
|
||||
|
||||
private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) {
|
||||
sb.append(unit.getType());
|
||||
sb.append("-");
|
||||
sb.append(unit.getSubtype());
|
||||
}
|
||||
|
||||
// "per-measure-unit/" is deprecated in favour of "unit/".
|
||||
private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) {
|
||||
// A little bit of a hack: save the current unit (numerator), call the main measure unit
|
||||
// parsing code, put back the numerator unit, and put the new unit into per-unit.
|
||||
|
@ -1068,13 +1063,44 @@ class NumberSkeletonImpl {
|
|||
* specified via a "unit/" concise skeleton.
|
||||
*/
|
||||
private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
|
||||
MeasureUnit[] units = MeasureUnit.parseCoreUnitIdentifier(segment.asString());
|
||||
if (units == null) {
|
||||
throw new SkeletonSyntaxException("Invalid core unit identifier", segment);
|
||||
MeasureUnitImpl fullUnit;
|
||||
try {
|
||||
fullUnit = MeasureUnitImpl.forIdentifier(segment.asString());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SkeletonSyntaxException("Invalid unit stem", segment);
|
||||
}
|
||||
macros.unit = units[0];
|
||||
if (units.length == 2) {
|
||||
macros.perUnit = units[1];
|
||||
|
||||
// Mixed units can only be represented by full MeasureUnit instances, so we
|
||||
// don't split the denominator into macros.perUnit.
|
||||
if (fullUnit.getComplexity() == MeasureUnit.Complexity.MIXED) {
|
||||
macros.unit = fullUnit.build();
|
||||
return;
|
||||
}
|
||||
|
||||
// When we have a built-in unit (e.g. meter-per-second), we don't split it up
|
||||
MeasureUnit testBuiltin = fullUnit.build();
|
||||
if (testBuiltin.getType() != null) {
|
||||
macros.unit = testBuiltin;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(ICU-20941): Clean this up.
|
||||
for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
|
||||
if (subUnit.getDimensionality() > 0) {
|
||||
if (macros.unit == null) {
|
||||
macros.unit = subUnit.build();
|
||||
} else {
|
||||
macros.unit = macros.unit.product(subUnit.build());
|
||||
}
|
||||
} else {
|
||||
// It's okay to mutate fullUnit, we're throwing it away after this:
|
||||
subUnit.setDimensionality(subUnit.getDimensionality() * -1);
|
||||
if (macros.perUnit == null) {
|
||||
macros.perUnit = subUnit.build();
|
||||
} else {
|
||||
macros.perUnit = macros.perUnit.product(subUnit.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1468,24 +1494,17 @@ class NumberSkeletonImpl {
|
|||
} else if (macros.unit == MeasureUnit.PERMILLE) {
|
||||
sb.append("permille");
|
||||
return true;
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean perUnit(MacroProps macros, StringBuilder sb) {
|
||||
// Per-units are currently expected to be only MeasureUnits.
|
||||
if (macros.perUnit instanceof Currency) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot generate number skeleton with per-unit that is not a standard measure unit");
|
||||
} else {
|
||||
sb.append("per-measure-unit/");
|
||||
BlueprintHelpers.generateMeasureUnitOption(macros.perUnit, sb);
|
||||
MeasureUnit unit = macros.unit;
|
||||
if (macros.perUnit != null) {
|
||||
if (macros.perUnit instanceof Currency) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot generate number skeleton with per-unit that is not a standard measure unit");
|
||||
}
|
||||
unit = unit.product(macros.perUnit.reciprocal());
|
||||
}
|
||||
sb.append("unit/");
|
||||
sb.append(unit.getIdentifier());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -381,7 +381,7 @@ public class MeasureUnit implements Serializable {
|
|||
|
||||
|
||||
/**
|
||||
* Get the type, such as "length"
|
||||
* Get the type, such as "length". May return null.
|
||||
*
|
||||
* @stable ICU 53
|
||||
*/
|
||||
|
@ -391,7 +391,7 @@ public class MeasureUnit implements Serializable {
|
|||
|
||||
|
||||
/**
|
||||
* Get the subType, such as “foot”.
|
||||
* Get the subType, such as “foot”. May return null.
|
||||
*
|
||||
* @stable ICU 53
|
||||
*/
|
||||
|
@ -702,47 +702,6 @@ public class MeasureUnit implements Serializable {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* For ICU use only.
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static MeasureUnit[] parseCoreUnitIdentifier(String coreUnitIdentifier) {
|
||||
// First search for the whole code unit identifier as a subType
|
||||
MeasureUnit whole = findBySubType(coreUnitIdentifier);
|
||||
if (whole != null) {
|
||||
return new MeasureUnit[] { whole }; // found a numerator but not denominator
|
||||
}
|
||||
|
||||
// If not found, try breaking apart numerator and denominator
|
||||
int perIdx = coreUnitIdentifier.indexOf("-per-");
|
||||
if (perIdx == -1) {
|
||||
// String does not contain "-per-"
|
||||
return null;
|
||||
}
|
||||
String numeratorStr = coreUnitIdentifier.substring(0, perIdx);
|
||||
String denominatorStr = coreUnitIdentifier.substring(perIdx + 5);
|
||||
MeasureUnit numerator = findBySubType(numeratorStr);
|
||||
MeasureUnit denominator = findBySubType(denominatorStr);
|
||||
if (numerator != null && denominator != null) {
|
||||
return new MeasureUnit[] { numerator, denominator }; // found both a numerator and denominator
|
||||
}
|
||||
|
||||
// The numerator or denominator were invalid
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* For ICU use only.
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static MeasureUnit resolveUnitPerUnit(MeasureUnit unit, MeasureUnit perUnit) {
|
||||
return unitPerUnitToSingleUnit.get(Pair.of(unit, perUnit));
|
||||
}
|
||||
|
||||
static final UnicodeSet ASCII = new UnicodeSet('a', 'z').freeze();
|
||||
static final UnicodeSet ASCII_HYPHEN_DIGITS = new UnicodeSet('-', '-', '0', '9', 'a', 'z').freeze();
|
||||
|
||||
|
@ -2010,23 +1969,7 @@ public class MeasureUnit implements Serializable {
|
|||
*/
|
||||
public static final MeasureUnit TEASPOON = MeasureUnit.internalGetInstance("volume", "teaspoon");
|
||||
|
||||
private static HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>unitPerUnitToSingleUnit =
|
||||
new HashMap<>();
|
||||
|
||||
static {
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.LITER, MeasureUnit.KILOMETER), MeasureUnit.LITER_PER_KILOMETER);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.POUND, MeasureUnit.SQUARE_INCH), MeasureUnit.POUND_PER_SQUARE_INCH);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.PIXEL, MeasureUnit.CENTIMETER), MeasureUnit.PIXEL_PER_CENTIMETER);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.DOT, MeasureUnit.CENTIMETER), MeasureUnit.DOT_PER_CENTIMETER);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.MILE, MeasureUnit.HOUR), MeasureUnit.MILE_PER_HOUR);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.MILLIGRAM, MeasureUnit.DECILITER), MeasureUnit.MILLIGRAM_PER_DECILITER);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.MILE, MeasureUnit.GALLON_IMPERIAL), MeasureUnit.MILE_PER_GALLON_IMPERIAL);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.KILOMETER, MeasureUnit.HOUR), MeasureUnit.KILOMETER_PER_HOUR);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.MILE, MeasureUnit.GALLON), MeasureUnit.MILE_PER_GALLON);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.PIXEL, MeasureUnit.INCH), MeasureUnit.PIXEL_PER_INCH);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.DOT, MeasureUnit.INCH), MeasureUnit.DOT_PER_INCH);
|
||||
unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit.METER, MeasureUnit.SECOND), MeasureUnit.METER_PER_SECOND);
|
||||
}
|
||||
// unitPerUnitToSingleUnit no longer in use! TODO: remove from code-generation code.
|
||||
|
||||
// End generated MeasureUnit constants
|
||||
/* Private */
|
||||
|
|
|
@ -2652,7 +2652,7 @@ public class MeasureUnitTest extends TestFmwk {
|
|||
// This fails unless we resolve to MeasureUnit.POUND_PER_SQUARE_INCH
|
||||
assertEquals("", "50 psi",
|
||||
fmt.formatMeasurePerUnit(
|
||||
new Measure(50, MeasureUnit.POUND),
|
||||
new Measure(50, MeasureUnit.POUND_FORCE),
|
||||
MeasureUnit.SQUARE_INCH,
|
||||
new StringBuilder(),
|
||||
new FieldPosition(0)).toString());
|
||||
|
|
|
@ -536,6 +536,23 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
"0.0088 meters",
|
||||
"0 meters");
|
||||
|
||||
// // TODO(ICU-20941): Support formatting for not-built-in units
|
||||
// assertFormatDescending(
|
||||
// "Hectometers",
|
||||
// "measure-unit/length-hectometer",
|
||||
// "unit/hectometer",
|
||||
// NumberFormatter.with().unit(MeasureUnit.forIdentifier("hectometer")),
|
||||
// ULocale.ENGLISH,
|
||||
// "87,650 hm",
|
||||
// "8,765 ham",
|
||||
// "876.5 hm",
|
||||
// "87.65 hm",
|
||||
// "8.765 hm",
|
||||
// "0.8765 hm",
|
||||
// "0.08765 hm",
|
||||
// "0.008765 hm",
|
||||
// "0 hm");
|
||||
|
||||
assertFormatSingleMeasure(
|
||||
"Meters with Measure Input",
|
||||
"unit-width-full-name",
|
||||
|
@ -656,10 +673,9 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
5,
|
||||
"5 a\u00F1os");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
"Mixed unit",
|
||||
null,
|
||||
"unit/yard-and-foot-and-inch",
|
||||
"unit/yard-and-foot-and-inch",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("yard-and-foot-and-inch")),
|
||||
|
@ -667,10 +683,9 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
3.65,
|
||||
"3 yd, 1 ft, 11.4 in");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
"Mixed unit, Scientific",
|
||||
null,
|
||||
"unit/yard-and-foot-and-inch E0",
|
||||
"unit/yard-and-foot-and-inch E0",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("yard-and-foot-and-inch"))
|
||||
|
@ -679,10 +694,9 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
3.65,
|
||||
"3 yd, 1 ft, 1.14E1 in");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
"Mixed Unit (Narrow Version)",
|
||||
null,
|
||||
"unit/metric-ton-and-kilogram-and-gram unit-width-narrow",
|
||||
"unit/metric-ton-and-kilogram-and-gram unit-width-narrow",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("metric-ton-and-kilogram-and-gram"))
|
||||
|
@ -691,10 +705,9 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
4.28571,
|
||||
"4t 285kg 710g");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
"Mixed Unit (Short Version)",
|
||||
null,
|
||||
"unit/metric-ton-and-kilogram-and-gram unit-width-short",
|
||||
"unit/metric-ton-and-kilogram-and-gram unit-width-short",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("metric-ton-and-kilogram-and-gram"))
|
||||
|
@ -703,10 +716,9 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
4.28571,
|
||||
"4 t, 285 kg, 710 g");
|
||||
|
||||
// TODO(icu-units#35): skeleton generation.
|
||||
assertFormatSingle(
|
||||
"Mixed Unit (Full Name Version)",
|
||||
null,
|
||||
"unit/metric-ton-and-kilogram-and-gram unit-width-full-name",
|
||||
"unit/metric-ton-and-kilogram-and-gram unit-width-full-name",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("metric-ton-and-kilogram-and-gram"))
|
||||
|
@ -717,8 +729,8 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
|
||||
assertFormatSingle(
|
||||
"Testing \"1 foot 12 inches\"",
|
||||
null,
|
||||
"unit/foot-and-inch",
|
||||
"unit/foot-and-inch @### unit-width-full-name",
|
||||
"unit/foot-and-inch @### unit-width-full-name",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.forIdentifier("foot-and-inch"))
|
||||
.precision(Precision.maxSignificantDigits(4))
|
||||
|
@ -738,7 +750,7 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
|
||||
assertFormatSingle(
|
||||
"Negative numbers: time",
|
||||
null, // submitting after TODO(icu-units#35) is fixed: fill in skeleton!
|
||||
"unit/hour-and-minute-and-second",
|
||||
"unit/hour-and-minute-and-second",
|
||||
NumberFormatter.with().unit(MeasureUnit.forIdentifier("hour-and-minute-and-second")),
|
||||
new ULocale("de-DE"),
|
||||
|
@ -751,7 +763,7 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
assertFormatDescending(
|
||||
"Meters Per Second Short (unit that simplifies) and perUnit method",
|
||||
"measure-unit/length-meter per-measure-unit/duration-second",
|
||||
"~unit/meter-per-second", // does not round-trip to the full skeleton above
|
||||
"unit/meter-per-second",
|
||||
NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND),
|
||||
ULocale.ENGLISH,
|
||||
"87,650 m/s",
|
||||
|
@ -764,16 +776,21 @@ 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",
|
||||
assertFormatDescending(
|
||||
"Meters Per Second Short, built-in m/s",
|
||||
"measure-unit/speed-meter-per-second",
|
||||
"~unit/meter-per-second",
|
||||
"unit/meter-per-second",
|
||||
NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND),
|
||||
new ULocale("en-GB"),
|
||||
2.4,
|
||||
"2.4 m/s");
|
||||
ULocale.ENGLISH,
|
||||
"87,650 m/s",
|
||||
"8,765 m/s",
|
||||
"876.5 m/s",
|
||||
"87.65 m/s",
|
||||
"8.765 m/s",
|
||||
"0.8765 m/s",
|
||||
"0.08765 m/s",
|
||||
"0.008765 m/s",
|
||||
"0 m/s");
|
||||
|
||||
assertFormatDescending(
|
||||
"Pounds Per Square Mile Short (secondary unit has per-format)",
|
||||
|
@ -824,29 +841,53 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
// "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.
|
||||
// Resolving to a built-in unit does not always work.
|
||||
//
|
||||
// (Unit-testing philosophy: do we leave this enabled to demonstrate current
|
||||
// behaviour, and changing behaviour in the future? Or comment it out to
|
||||
// avoid asserting this is "correct"?)
|
||||
assertFormatDescending(
|
||||
"Pounds per Square Inch: composed",
|
||||
"measure-unit/force-pound-force per-measure-unit/area-square-inch",
|
||||
"unit/pound-force-per-square-inch",
|
||||
NumberFormatter.with().unit(MeasureUnit.POUND_FORCE).perUnit(MeasureUnit.SQUARE_INCH),
|
||||
ULocale.ENGLISH,
|
||||
"87,650 psi",
|
||||
"8,765 psi",
|
||||
"876.5 psi",
|
||||
"87.65 psi",
|
||||
"8.765 psi",
|
||||
"0.8765 psi",
|
||||
"0.08765 psi",
|
||||
"0.008765 psi",
|
||||
"0 psi");
|
||||
|
||||
assertFormatDescending(
|
||||
"Pounds per Square Inch: built-in",
|
||||
"measure-unit/force-pound-force per-measure-unit/area-square-inch",
|
||||
"unit/pound-force-per-square-inch",
|
||||
NumberFormatter.with().unit(MeasureUnit.POUND_PER_SQUARE_INCH),
|
||||
ULocale.ENGLISH,
|
||||
"87,650 psi",
|
||||
"8,765 psi",
|
||||
"876.5 psi",
|
||||
"87.65 psi",
|
||||
"8.765 psi",
|
||||
"0.8765 psi",
|
||||
"0.08765 psi",
|
||||
"0.008765 psi",
|
||||
"0 psi");
|
||||
|
||||
assertFormatSingle(
|
||||
"DEMONSTRATING BAD BEHAVIOUR, TODO(icu-units#59)",
|
||||
"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
|
||||
"m/s/s simplifies to m/s^2",
|
||||
"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
|
||||
"unit/meter-per-square-second",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER_PER_SECOND)
|
||||
.perUnit(MeasureUnit.SECOND),
|
||||
new ULocale("en-GB"),
|
||||
2.4,
|
||||
"2.4 m/s/s");
|
||||
"2.4 m/s\u00B2");
|
||||
|
||||
assertFormatSingle(
|
||||
"Negative numbers: acceleration",
|
||||
"measure-unit/acceleration-meter-per-square-second",
|
||||
// TODO: when other PRs are merged, try: u"unit/meter-per-second-second" instead:
|
||||
"measure-unit/acceleration-meter-per-square-second",
|
||||
"unit/meter-per-second-second",
|
||||
NumberFormatter.with().unit(MeasureUnit.forIdentifier("meter-per-pow2-second")),
|
||||
new ULocale("af-ZA"),
|
||||
-9.81,
|
||||
|
@ -869,21 +910,125 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
// Pass
|
||||
}
|
||||
|
||||
// .perUnit() may only be passed a built-in type, "square-second" is not a
|
||||
// built-in type.
|
||||
// .perUnit() may only be passed a built-in type, or something that
|
||||
// combines to a built-in type together with .unit().
|
||||
nf = NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER)
|
||||
.unit(MeasureUnit.FURLONG)
|
||||
.perUnit(MeasureUnit.forIdentifier("square-second"))
|
||||
.locale(new ULocale("en-GB"));
|
||||
|
||||
try {
|
||||
nf.format(2.4d);
|
||||
fail("Expected failure, got: " + nf.format(2.4d) + ".");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// pass
|
||||
}
|
||||
// As above, "square-second" is not a built-in type, however this time,
|
||||
// meter-per-square-second is a built-in type.
|
||||
assertFormatSingle(
|
||||
"meter per square-second works as a composed unit",
|
||||
"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
|
||||
"unit/meter-per-square-second",
|
||||
NumberFormatter.with()
|
||||
.unit(MeasureUnit.METER)
|
||||
.perUnit(MeasureUnit.forIdentifier("square-second")),
|
||||
new ULocale("en-GB"),
|
||||
2.4,
|
||||
"2.4 m/s\u00B2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitSkeletons() {
|
||||
Object[][] cases = {
|
||||
{"old-form built-in compound unit", //
|
||||
"measure-unit/speed-meter-per-second", //
|
||||
"unit/meter-per-second"},
|
||||
|
||||
{"old-form compound construction, converts to built-in", //
|
||||
"measure-unit/length-meter per-measure-unit/duration-second", //
|
||||
"unit/meter-per-second"},
|
||||
|
||||
{"old-form compound construction which does not simplify to a built-in", //
|
||||
"measure-unit/energy-joule per-measure-unit/length-meter", //
|
||||
"unit/joule-per-meter"},
|
||||
|
||||
{"old-form compound-compound ugliness resolves neatly", //
|
||||
"measure-unit/speed-meter-per-second per-measure-unit/duration-second", //
|
||||
"unit/meter-per-square-second"},
|
||||
|
||||
{"short-form built-in units stick with the built-in", //
|
||||
"unit/meter-per-second", //
|
||||
"unit/meter-per-second"},
|
||||
|
||||
{"short-form compound units stay as is", //
|
||||
"unit/square-meter-per-square-meter", //
|
||||
"unit/square-meter-per-square-meter"},
|
||||
|
||||
{"short-form compound units stay as is", //
|
||||
"unit/joule-per-furlong", //
|
||||
"unit/joule-per-furlong"},
|
||||
|
||||
{"short-form that doesn't consist of built-in units", //
|
||||
"unit/hectometer-per-second", //
|
||||
"unit/hectometer-per-second"},
|
||||
|
||||
{"short-form that doesn't consist of built-in units", //
|
||||
"unit/meter-per-hectosecond", //
|
||||
"unit/meter-per-hectosecond"},
|
||||
|
||||
// // TODO: binary prefixes not supported yet!
|
||||
// {"Round-trip example from icu-units#35", //
|
||||
// "unit/kibijoule-per-furlong", //
|
||||
// "unit/kibijoule-per-furlong"},
|
||||
};
|
||||
for (Object[] cas : cases) {
|
||||
String msg = (String)cas[0];
|
||||
String inputSkeleton = (String)cas[1];
|
||||
String normalizedSkeleton = (String)cas[2];
|
||||
UnlocalizedNumberFormatter nf = NumberFormatter.forSkeleton(inputSkeleton);
|
||||
assertEquals(msg, normalizedSkeleton, nf.toSkeleton());
|
||||
}
|
||||
|
||||
Object NoException = new Object();
|
||||
Object[][] failCases = {
|
||||
{"Parsing measure-unit/* results in failure if not built-in unit",
|
||||
"measure-unit/hectometer", //
|
||||
true, //
|
||||
false},
|
||||
|
||||
{"Parsing per-measure-unit/* results in failure if not built-in unit",
|
||||
"measure-unit/meter per-measure-unit/hectosecond", //
|
||||
true, //
|
||||
false},
|
||||
};
|
||||
for (Object[] cas : failCases) {
|
||||
String msg = (String)cas[0];
|
||||
String inputSkeleton = (String)cas[1];
|
||||
boolean forSkeletonExpectFailure = (boolean)cas[2];
|
||||
boolean toSkeletonExpectFailure = (boolean)cas[3];
|
||||
UnlocalizedNumberFormatter nf = null;
|
||||
try {
|
||||
nf = NumberFormatter.forSkeleton(inputSkeleton);
|
||||
if (forSkeletonExpectFailure) {
|
||||
fail("forSkeleton() should have failed: " + msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (!forSkeletonExpectFailure) {
|
||||
fail("forSkeleton() should not have failed: " + msg);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
nf.toSkeleton();
|
||||
if (toSkeletonExpectFailure) {
|
||||
fail("toSkeleton() should have failed: " + msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (!toSkeletonExpectFailure) {
|
||||
fail("toSkeleton() should not have failed: " + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitUsage() {
|
||||
|
@ -1157,7 +1302,6 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
// to see divide-by-zero behaviour.
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void unitUsageErrorCodes() {
|
||||
UnlocalizedNumberFormatter unloc_formatter;
|
||||
|
@ -3409,11 +3553,11 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
}
|
||||
|
||||
{
|
||||
String message = "Measure unit field position with prefix and suffix";
|
||||
String message = "Measure unit field position with prefix and suffix, composed m/s";
|
||||
FormattedNumber result = assertFormatSingle(
|
||||
message,
|
||||
"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
|
||||
"~unit/meter-per-second unit-width-full-name", // does not round-trip to the full skeleton above
|
||||
"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
|
||||
NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND).unitWidth(UnitWidth.FULL_NAME),
|
||||
new ULocale("ky"), // locale with the interesting data
|
||||
68,
|
||||
|
@ -3429,6 +3573,27 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
expectedFieldPositions);
|
||||
}
|
||||
|
||||
{
|
||||
String message = "Measure unit field position with prefix and suffix, built-in m/s";
|
||||
FormattedNumber result = assertFormatSingle(
|
||||
message,
|
||||
"measure-unit/speed-meter-per-second unit-width-full-name",
|
||||
"unit/meter-per-second unit-width-full-name",
|
||||
NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND).unitWidth(UnitWidth.FULL_NAME),
|
||||
new ULocale("ky"), // locale with the interesting data
|
||||
68,
|
||||
"секундасына 68 метр");
|
||||
Object[][] expectedFieldPositions = new Object[][] {
|
||||
// field, begin index, end index
|
||||
{NumberFormat.Field.MEASURE_UNIT, 0, 11},
|
||||
{NumberFormat.Field.INTEGER, 12, 14},
|
||||
{NumberFormat.Field.MEASURE_UNIT, 15, 19}};
|
||||
assertNumberFieldPositions(
|
||||
message,
|
||||
result,
|
||||
expectedFieldPositions);
|
||||
}
|
||||
|
||||
{
|
||||
String message = "Measure unit field position with inner spaces";
|
||||
FormattedNumber result = assertFormatSingle(
|
||||
|
|
Loading…
Add table
Reference in a new issue