ICU-13520 Adds compound unit support to NumberFormatter.
X-SVN-Rev: 40747
This commit is contained in:
parent
73569e99bd
commit
4d10bf03f5
@ -764,10 +764,11 @@ UnicodeString &MeasureFormat::formatMeasurePerUnit(
|
||||
if (U_FAILURE(status)) {
|
||||
return appendTo;
|
||||
}
|
||||
MeasureUnit *resolvedUnit =
|
||||
MeasureUnit::resolveUnitPerUnit(measure.getUnit(), perUnit);
|
||||
if (resolvedUnit != NULL) {
|
||||
Measure newMeasure(measure.getNumber(), resolvedUnit, status);
|
||||
bool isResolved = false;
|
||||
MeasureUnit resolvedUnit =
|
||||
MeasureUnit::resolveUnitPerUnit(measure.getUnit(), perUnit, &isResolved);
|
||||
if (isResolved) {
|
||||
Measure newMeasure(measure.getNumber(), new MeasureUnit(resolvedUnit), status);
|
||||
return formatMeasure(
|
||||
newMeasure, **numberFormat, appendTo, pos, status);
|
||||
}
|
||||
|
@ -1211,8 +1211,8 @@ int32_t MeasureUnit::internalGetIndexForTypeAndSubtype(const char *type, const c
|
||||
return gIndexes[t] + st - gOffsets[t];
|
||||
}
|
||||
|
||||
MeasureUnit *MeasureUnit::resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit) {
|
||||
MeasureUnit MeasureUnit::resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit, bool* isResolved) {
|
||||
int32_t unitOffset = unit.getOffset();
|
||||
int32_t perUnitOffset = perUnit.getOffset();
|
||||
|
||||
@ -1233,10 +1233,13 @@ MeasureUnit *MeasureUnit::resolveUnitPerUnit(
|
||||
} else {
|
||||
// We found a resolution for our unit / per-unit combo
|
||||
// return it.
|
||||
return new MeasureUnit(midRow[2], midRow[3]);
|
||||
*isResolved = true;
|
||||
return MeasureUnit(midRow[2], midRow[3]);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
*isResolved = false;
|
||||
return MeasureUnit();
|
||||
}
|
||||
|
||||
MeasureUnit *MeasureUnit::create(int typeId, int subTypeId, UErrorCode &status) {
|
||||
|
@ -45,6 +45,25 @@ Derived NumberFormatterSettings<Derived>::adoptUnit(const icu::MeasureUnit *unit
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::perUnit(const icu::MeasureUnit &perUnit) const {
|
||||
Derived copy(*this);
|
||||
// See comments above about slicing.
|
||||
copy.fMacros.perUnit = perUnit;
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::adoptPerUnit(const icu::MeasureUnit *perUnit) const {
|
||||
Derived copy(*this);
|
||||
// See comments above about slicing and ownership.
|
||||
if (perUnit != nullptr) {
|
||||
copy.fMacros.perUnit = *perUnit;
|
||||
delete perUnit;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberFormatterSettings<Derived>::rounding(const Rounder &rounder) const {
|
||||
Derived copy(*this);
|
||||
|
@ -308,6 +308,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe,
|
||||
LongNameHandler::forMeasureUnit(
|
||||
macros.locale,
|
||||
macros.unit,
|
||||
macros.perUnit,
|
||||
unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain,
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
|
||||
|
||||
#include "unicode/simpleformatter.h"
|
||||
#include "unicode/ures.h"
|
||||
#include "ureslocs.h"
|
||||
#include "charstr.h"
|
||||
@ -19,6 +20,37 @@ using namespace icu::number::impl;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int32_t DNAM_INDEX = StandardPlural::Form::COUNT;
|
||||
constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1;
|
||||
constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 2;
|
||||
|
||||
static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) {
|
||||
// pluralKeyword can also be "dnam" or "per"
|
||||
if (uprv_strcmp(pluralKeyword, "dnam") == 0) {
|
||||
return DNAM_INDEX;
|
||||
} else if (uprv_strcmp(pluralKeyword, "per") == 0) {
|
||||
return PER_INDEX;
|
||||
} else {
|
||||
StandardPlural::Form plural = StandardPlural::fromString(pluralKeyword, status);
|
||||
return plural;
|
||||
}
|
||||
}
|
||||
|
||||
static UnicodeString getWithPlural(
|
||||
const UnicodeString* strings,
|
||||
int32_t plural,
|
||||
UErrorCode& status) {
|
||||
UnicodeString result = strings[plural];
|
||||
if (result.isBogus()) {
|
||||
result = strings[StandardPlural::Form::OTHER];
|
||||
}
|
||||
if (result.isBogus()) {
|
||||
// There should always be data in the "other" plural variant.
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////
|
||||
/// BEGIN DATA LOADING ///
|
||||
@ -28,7 +60,7 @@ class PluralTableSink : public ResourceSink {
|
||||
public:
|
||||
explicit PluralTableSink(UnicodeString *outArray) : outArray(outArray) {
|
||||
// Initialize the array to bogus strings.
|
||||
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
|
||||
for (int32_t i = 0; i < ARRAY_LENGTH; i++) {
|
||||
outArray[i].setToBogus();
|
||||
}
|
||||
}
|
||||
@ -36,17 +68,13 @@ class PluralTableSink : public ResourceSink {
|
||||
void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) U_OVERRIDE {
|
||||
ResourceTable pluralsTable = value.getTable(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
for (int i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
|
||||
// In MeasureUnit data, ignore dnam and per units for now.
|
||||
if (uprv_strcmp(key, "dnam") == 0 || uprv_strcmp(key, "per") == 0) {
|
||||
continue;
|
||||
}
|
||||
StandardPlural::Form plural = StandardPlural::fromString(key, status);
|
||||
for (int32_t i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
|
||||
int32_t index = getIndex(key, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (!outArray[plural].isBogus()) {
|
||||
if (!outArray[index].isBogus()) {
|
||||
continue;
|
||||
}
|
||||
outArray[plural] = value.getUnicodeString(status);
|
||||
outArray[index] = value.getUnicodeString(status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
}
|
||||
}
|
||||
@ -105,6 +133,22 @@ void getCurrencyLongNameData(const Locale &locale, const CurrencyUnit ¤cy,
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &width, UErrorCode& status) {
|
||||
LocalUResourceBundlePointer unitsBundle(ures_open(U_ICUDATA_UNIT, locale.getName(), &status));
|
||||
if (U_FAILURE(status)) { return {}; }
|
||||
CharString key;
|
||||
key.append("units", status);
|
||||
if (width == UNUM_UNIT_WIDTH_NARROW) {
|
||||
key.append("Narrow", status);
|
||||
} else if (width == UNUM_UNIT_WIDTH_SHORT) {
|
||||
key.append("Short", status);
|
||||
}
|
||||
key.append("/compound/per", status);
|
||||
int32_t len = 0;
|
||||
const UChar* ptr = ures_getStringByKeyWithFallback(unitsBundle.getAlias(), key.data(), &len, &status);
|
||||
return UnicodeString(ptr, len);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
/// END DATA LOADING ///
|
||||
////////////////////////
|
||||
@ -112,11 +156,24 @@ void getCurrencyLongNameData(const Locale &locale, const CurrencyUnit ¤cy,
|
||||
} // namespace
|
||||
|
||||
LongNameHandler
|
||||
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent,
|
||||
UErrorCode &status) {
|
||||
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
MeasureUnit unit = unitRef;
|
||||
if (uprv_strcmp(perUnit.getType(), "none") != 0) {
|
||||
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
|
||||
bool isResolved = false;
|
||||
MeasureUnit resolved = MeasureUnit::resolveUnitPerUnit(unit, perUnit, &isResolved);
|
||||
if (isResolved) {
|
||||
unit = resolved;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
return forCompoundUnit(loc, unit, perUnit, width, rules, parent, status);
|
||||
}
|
||||
}
|
||||
|
||||
LongNameHandler result(rules, parent);
|
||||
UnicodeString simpleFormats[StandardPlural::Form::COUNT];
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, simpleFormats, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
// TODO: What field to use for units?
|
||||
@ -124,12 +181,47 @@ LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unit, cons
|
||||
return result;
|
||||
}
|
||||
|
||||
LongNameHandler
|
||||
LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status) {
|
||||
LongNameHandler result(rules, parent);
|
||||
UnicodeString primaryData[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, primaryData, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
UnicodeString secondaryData[ARRAY_LENGTH];
|
||||
getMeasureData(loc, perUnit, width, secondaryData, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
|
||||
UnicodeString perUnitFormat;
|
||||
if (!secondaryData[PER_INDEX].isBogus()) {
|
||||
perUnitFormat = secondaryData[PER_INDEX];
|
||||
} else {
|
||||
UnicodeString rawPerUnitFormat = getPerUnitFormat(loc, width, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
// rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
|
||||
SimpleFormatter compiled(rawPerUnitFormat, 2, 2, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
UnicodeString secondaryFormat = getWithPlural(secondaryData, StandardPlural::Form::ONE, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
SimpleFormatter secondaryCompiled(secondaryFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
UnicodeString secondaryString = secondaryCompiled.getTextWithNoArguments().trim();
|
||||
// TODO: Why does UnicodeString need to be explicit in the following line?
|
||||
compiled.format(UnicodeString(u"{0}"), secondaryString, perUnitFormat, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
}
|
||||
// TODO: What field to use for units?
|
||||
multiSimpleFormatsToModifiers(primaryData, perUnitFormat, UNUM_FIELD_COUNT, result.fModifiers, status);
|
||||
return result;
|
||||
}
|
||||
|
||||
LongNameHandler LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy,
|
||||
const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent,
|
||||
UErrorCode &status) {
|
||||
LongNameHandler result(rules, parent);
|
||||
UnicodeString simpleFormats[StandardPlural::Form::COUNT];
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getCurrencyLongNameData(loc, currency, simpleFormats, status);
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
simpleFormatsToModifiers(simpleFormats, UNUM_CURRENCY_FIELD, result.fModifiers, status);
|
||||
@ -139,20 +231,30 @@ LongNameHandler LongNameHandler::forCurrencyLongNames(const Locale &loc, const C
|
||||
void LongNameHandler::simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
|
||||
SimpleModifier *output, UErrorCode &status) {
|
||||
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
|
||||
UnicodeString simpleFormat = simpleFormats[i];
|
||||
if (simpleFormat.isBogus()) {
|
||||
simpleFormat = simpleFormats[StandardPlural::Form::OTHER];
|
||||
}
|
||||
if (simpleFormat.isBogus()) {
|
||||
// There should always be data in the "other" plural variant.
|
||||
status = U_INTERNAL_PROGRAM_ERROR;
|
||||
return;
|
||||
}
|
||||
UnicodeString simpleFormat = getWithPlural(simpleFormats, i, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
SimpleFormatter compiledFormatter(simpleFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
output[i] = SimpleModifier(compiledFormatter, field, false);
|
||||
}
|
||||
}
|
||||
|
||||
void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, SimpleModifier *output, UErrorCode &status) {
|
||||
SimpleFormatter trailCompiled(trailFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
|
||||
UnicodeString leadFormat = getWithPlural(leadFormats, i, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
UnicodeString compoundFormat;
|
||||
trailCompiled.format(leadFormat, compoundFormat, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
SimpleFormatter compoundCompiled(compoundFormat, 1, 1, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
output[i] = SimpleModifier(compoundCompiled, field, false);
|
||||
}
|
||||
}
|
||||
|
||||
void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs,
|
||||
UErrorCode &status) const {
|
||||
parent->processQuantity(quantity, micros, status);
|
||||
|
@ -21,8 +21,9 @@ class LongNameHandler : public MicroPropsGenerator, public UMemory {
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
static LongNameHandler
|
||||
forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
void
|
||||
processQuantity(DecimalQuantity &quantity, MicroProps µs, UErrorCode &status) const U_OVERRIDE;
|
||||
@ -35,8 +36,15 @@ class LongNameHandler : public MicroPropsGenerator, public UMemory {
|
||||
LongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent)
|
||||
: rules(rules), parent(parent) {}
|
||||
|
||||
static LongNameHandler
|
||||
forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, UErrorCode &status);
|
||||
|
||||
static void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
|
||||
SimpleModifier *output, UErrorCode &status);
|
||||
static void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
|
||||
Field field, SimpleModifier *output, UErrorCode &status);
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
@ -196,8 +196,8 @@ class U_I18N_API MeasureUnit: public UObject {
|
||||
* ICU use only.
|
||||
* @internal
|
||||
*/
|
||||
static MeasureUnit *resolveUnitPerUnit(
|
||||
const MeasureUnit &unit, const MeasureUnit &perUnit);
|
||||
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
|
||||
|
@ -10,17 +10,17 @@
|
||||
#ifndef __NOUNIT_H__
|
||||
#define __NOUNIT_H__
|
||||
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "unicode/measunit.h"
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief C++ API: units for percent and permille
|
||||
*/
|
||||
|
||||
|
||||
#include "unicode/measunit.h"
|
||||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
||||
#ifndef U_HIDE_DRAFT_API
|
||||
|
@ -1297,6 +1297,9 @@ struct U_I18N_API MacroProps : public UMemory {
|
||||
/** @internal */
|
||||
MeasureUnit unit; // = NoUnit::base();
|
||||
|
||||
/** @internal */
|
||||
MeasureUnit perUnit; // = NoUnit::base();
|
||||
|
||||
/** @internal */
|
||||
Rounder rounder; // = Rounder(); (bogus)
|
||||
|
||||
@ -1389,29 +1392,29 @@ class U_I18N_API NumberFormatterSettings {
|
||||
* <li>Percent: "12.3%"
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* All units will be properly localized with locale data, and all units are compatible with notation styles,
|
||||
* rounding strategies, and other number formatter settings.
|
||||
*
|
||||
* <p>
|
||||
* Pass this method any instance of {@link MeasureUnit}. For units of measure:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with().adoptUnit(MeasureUnit::createMeter(status))
|
||||
* NumberFormatter::with().adoptUnit(MeasureUnit::createMeter(status))
|
||||
* </pre>
|
||||
*
|
||||
* Currency:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with()::unit(CurrencyUnit(u"USD", status))
|
||||
* NumberFormatter::with().unit(CurrencyUnit(u"USD", status))
|
||||
* </pre>
|
||||
*
|
||||
* Percent:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with()::unit(NoUnit.percent())
|
||||
* NumberFormatter::with().unit(NoUnit.percent())
|
||||
* </pre>
|
||||
*
|
||||
* See {@link #perUnit} for information on how to format strings like "5 meters per second".
|
||||
*
|
||||
* The default is to render without units (equivalent to NoUnit.base()).
|
||||
*
|
||||
* @param unit
|
||||
@ -1420,6 +1423,7 @@ class U_I18N_API NumberFormatterSettings {
|
||||
* @see MeasureUnit
|
||||
* @see Currency
|
||||
* @see NoUnit
|
||||
* @see #perUnit
|
||||
* @draft ICU 60
|
||||
*/
|
||||
Derived unit(const icu::MeasureUnit &unit) const;
|
||||
@ -1437,6 +1441,43 @@ class U_I18N_API NumberFormatterSettings {
|
||||
*/
|
||||
Derived adoptUnit(const icu::MeasureUnit *unit) const;
|
||||
|
||||
/**
|
||||
* Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit and SECOND to
|
||||
* the perUnit.
|
||||
*
|
||||
* Pass this method any instance of {@link MeasureUnit}. For example:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter::with()
|
||||
* .adoptUnit(MeasureUnit::createMeter(status))
|
||||
* .adoptPerUnit(MeasureUnit::createSecond(status))
|
||||
* </pre>
|
||||
*
|
||||
* The default is not to display any unit in the denominator.
|
||||
*
|
||||
* If a per-unit is specified without a primary unit via {@link #unit}, the behavior is undefined.
|
||||
*
|
||||
* @param perUnit
|
||||
* The unit to render in the denominator.
|
||||
* @return The fluent chain
|
||||
* @see #unit
|
||||
* @draft ICU 61
|
||||
*/
|
||||
Derived perUnit(const icu::MeasureUnit &perUnit) const;
|
||||
|
||||
/**
|
||||
* Like perUnit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory
|
||||
* methods, which return pointers that need ownership.
|
||||
*
|
||||
* @param perUnit
|
||||
* The unit to render in the denominator.
|
||||
* @return The fluent chain.
|
||||
* @see #perUnit
|
||||
* @see MeasureUnit
|
||||
* @draft ICU 61
|
||||
*/
|
||||
Derived adoptPerUnit(const icu::MeasureUnit *perUnit) const;
|
||||
|
||||
/**
|
||||
* Specifies the rounding strategy to use when formatting numbers.
|
||||
*
|
||||
|
@ -45,6 +45,7 @@ class NumberFormatterApiTest : public IntlTest {
|
||||
void notationScientific();
|
||||
void notationCompact();
|
||||
void unitMeasure();
|
||||
void unitCompoundMeasure();
|
||||
void unitCurrency();
|
||||
void unitPercent();
|
||||
void roundingFraction();
|
||||
@ -75,6 +76,11 @@ class NumberFormatterApiTest : public IntlTest {
|
||||
MeasureUnit DAY;
|
||||
MeasureUnit SQUARE_METER;
|
||||
MeasureUnit FAHRENHEIT;
|
||||
MeasureUnit SECOND;
|
||||
MeasureUnit POUND;
|
||||
MeasureUnit SQUARE_MILE;
|
||||
MeasureUnit JOULE;
|
||||
MeasureUnit FURLONG;
|
||||
|
||||
NumberingSystem MATHSANB;
|
||||
NumberingSystem LATN;
|
||||
|
@ -26,29 +26,25 @@ NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode &status)
|
||||
SWISS_SYMBOLS(Locale("de-CH"), status),
|
||||
MYANMAR_SYMBOLS(Locale("my"), status) {
|
||||
|
||||
MeasureUnit *unit = MeasureUnit::createMeter(status);
|
||||
// Check for error on the first MeasureUnit in case there is no data
|
||||
LocalPointer<MeasureUnit> unit(MeasureUnit::createMeter(status));
|
||||
if (U_FAILURE(status)) {
|
||||
dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status));
|
||||
return;
|
||||
}
|
||||
METER = *unit;
|
||||
delete unit;
|
||||
unit = MeasureUnit::createDay(status);
|
||||
DAY = *unit;
|
||||
delete unit;
|
||||
unit = MeasureUnit::createSquareMeter(status);
|
||||
SQUARE_METER = *unit;
|
||||
delete unit;
|
||||
unit = MeasureUnit::createFahrenheit(status);
|
||||
FAHRENHEIT = *unit;
|
||||
delete unit;
|
||||
|
||||
NumberingSystem *ns = NumberingSystem::createInstanceByName("mathsanb", status);
|
||||
MATHSANB = *ns;
|
||||
delete ns;
|
||||
ns = NumberingSystem::createInstanceByName("latn", status);
|
||||
LATN = *ns;
|
||||
delete ns;
|
||||
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));
|
||||
SQUARE_MILE = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMile(status));
|
||||
JOULE = *LocalPointer<MeasureUnit>(MeasureUnit::createJoule(status));
|
||||
FURLONG = *LocalPointer<MeasureUnit>(MeasureUnit::createFurlong(status));
|
||||
|
||||
MATHSANB = *LocalPointer<NumberingSystem>(NumberingSystem::createInstanceByName("mathsanb", status));
|
||||
LATN = *LocalPointer<NumberingSystem>(NumberingSystem::createInstanceByName("latn", status));
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
|
||||
@ -60,6 +56,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
|
||||
TESTCASE_AUTO(notationScientific);
|
||||
TESTCASE_AUTO(notationCompact);
|
||||
TESTCASE_AUTO(unitMeasure);
|
||||
TESTCASE_AUTO(unitCompoundMeasure);
|
||||
TESTCASE_AUTO(unitCurrency);
|
||||
TESTCASE_AUTO(unitPercent);
|
||||
TESTCASE_AUTO(roundingFraction);
|
||||
@ -355,8 +352,8 @@ void NumberFormatterApiTest::notationCompact() {
|
||||
|
||||
void NumberFormatterApiTest::unitMeasure() {
|
||||
assertFormatDescending(
|
||||
u"Meters Short",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(METER)),
|
||||
u"Meters Short and unit() method",
|
||||
NumberFormatter::with().unit(METER),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 m",
|
||||
u"8,765 m",
|
||||
@ -369,7 +366,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
u"0 m");
|
||||
|
||||
assertFormatDescending(
|
||||
u"Meters Long",
|
||||
u"Meters Long and adoptUnit() method",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(METER))
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
Locale::getEnglish(),
|
||||
@ -386,7 +383,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
assertFormatDescending(
|
||||
u"Compact Meters Long",
|
||||
NumberFormatter::with().notation(Notation::compactLong())
|
||||
.adoptUnit(new MeasureUnit(METER))
|
||||
.unit(METER)
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
Locale::getEnglish(),
|
||||
u"88 thousand meters",
|
||||
@ -410,14 +407,14 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// TODO: Implement Measure in C++
|
||||
// assertFormatSingleMeasure(
|
||||
// u"Measure format method takes precedence over fluent chain",
|
||||
// NumberFormatter::with().adoptUnit(new MeasureUnit(METER)),
|
||||
// NumberFormatter::with().unit(METER),
|
||||
// Locale::getEnglish(),
|
||||
// new Measure(5.43, USD),
|
||||
// u"$5.43");
|
||||
|
||||
assertFormatSingle(
|
||||
u"Meters with Negative Sign",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(METER)),
|
||||
NumberFormatter::with().unit(METER),
|
||||
Locale::getEnglish(),
|
||||
-9876543.21,
|
||||
u"-9,876,543.21 m");
|
||||
@ -425,7 +422,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// The locale string "सान" appears only in brx.txt:
|
||||
assertFormatSingle(
|
||||
u"Interesting Data Fallback 1",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(DAY))
|
||||
NumberFormatter::with().unit(DAY)
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
|
||||
Locale::createFromName("brx"),
|
||||
5.43,
|
||||
@ -434,7 +431,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// Requires following the alias from unitsNarrow to unitsShort:
|
||||
assertFormatSingle(
|
||||
u"Interesting Data Fallback 2",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(DAY))
|
||||
NumberFormatter::with().unit(DAY)
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
|
||||
Locale::createFromName("brx"),
|
||||
5.43,
|
||||
@ -444,7 +441,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// requiring fallback to the root.
|
||||
assertFormatSingle(
|
||||
u"Interesting Data Fallback 3",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(SQUARE_METER))
|
||||
NumberFormatter::with().unit(SQUARE_METER)
|
||||
.unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
|
||||
Locale::createFromName("en-GB"),
|
||||
5.43,
|
||||
@ -454,7 +451,7 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
// NOTE: This example is in the documentation.
|
||||
assertFormatSingle(
|
||||
u"Difference between Narrow and Short (Narrow Version)",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(FAHRENHEIT))
|
||||
NumberFormatter::with().unit(FAHRENHEIT)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_NARROW),
|
||||
Locale("es-US"),
|
||||
5.43,
|
||||
@ -462,13 +459,57 @@ void NumberFormatterApiTest::unitMeasure() {
|
||||
|
||||
assertFormatSingle(
|
||||
u"Difference between Narrow and Short (Short Version)",
|
||||
NumberFormatter::with().adoptUnit(new MeasureUnit(FAHRENHEIT))
|
||||
NumberFormatter::with().unit(FAHRENHEIT)
|
||||
.unitWidth(UNUM_UNIT_WIDTH_SHORT),
|
||||
Locale("es-US"),
|
||||
5.43,
|
||||
u"5.43 °F");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitCompoundMeasure() {
|
||||
assertFormatDescending(
|
||||
u"Meters Per Second Short (unit that simplifies) and perUnit method",
|
||||
NumberFormatter::with().unit(METER).perUnit(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",
|
||||
NumberFormatter::with().unit(POUND).adoptPerUnit(new MeasureUnit(SQUARE_MILE)),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 lb/mi²",
|
||||
u"8,765 lb/mi²",
|
||||
u"876.5 lb/mi²",
|
||||
u"87.65 lb/mi²",
|
||||
u"8.765 lb/mi²",
|
||||
u"0.8765 lb/mi²",
|
||||
u"0.08765 lb/mi²",
|
||||
u"0.008765 lb/mi²",
|
||||
u"0 lb/mi²");
|
||||
|
||||
assertFormatDescending(
|
||||
u"Joules Per Furlong Short (unit with no simplifications or special patterns)",
|
||||
NumberFormatter::with().unit(JOULE).perUnit(FURLONG),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 J/fur",
|
||||
u"8,765 J/fur",
|
||||
u"876.5 J/fur",
|
||||
u"87.65 J/fur",
|
||||
u"8.765 J/fur",
|
||||
u"0.8765 J/fur",
|
||||
u"0.08765 J/fur",
|
||||
u"0.008765 J/fur",
|
||||
u"0 J/fur");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitCurrency() {
|
||||
assertFormatDescending(
|
||||
u"Currency",
|
||||
|
@ -4,6 +4,7 @@ package com.ibm.icu.impl.number;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
import com.ibm.icu.impl.CurrencyData;
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
@ -22,38 +23,63 @@ import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
public class LongNameHandler implements MicroPropsGenerator {
|
||||
|
||||
private static final int DNAM_INDEX = StandardPlural.COUNT;
|
||||
private static final int PER_INDEX = StandardPlural.COUNT + 1;
|
||||
private static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
|
||||
|
||||
private static int getIndex(String pluralKeyword) {
|
||||
// pluralKeyword can also be "dnam" or "per"
|
||||
if (pluralKeyword.equals("dnam")) {
|
||||
return DNAM_INDEX;
|
||||
} else if (pluralKeyword.equals("per")) {
|
||||
return PER_INDEX;
|
||||
} else {
|
||||
return StandardPlural.fromString(pluralKeyword).ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getWithPlural(String[] strings, StandardPlural plural) {
|
||||
String result = strings[plural.ordinal()];
|
||||
if (result == null) {
|
||||
result = strings[StandardPlural.OTHER.ordinal()];
|
||||
}
|
||||
if (result == null) {
|
||||
// There should always be data in the "other" plural variant.
|
||||
throw new ICUException("Could not find data in 'other' plural variant");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
/// BEGIN DATA LOADING ///
|
||||
//////////////////////////
|
||||
|
||||
private static final class PluralTableSink extends UResource.Sink {
|
||||
|
||||
Map<StandardPlural, String> output;
|
||||
String[] outArray;
|
||||
|
||||
public PluralTableSink(Map<StandardPlural, String> output) {
|
||||
this.output = output;
|
||||
public PluralTableSink(String[] outArray) {
|
||||
this.outArray = outArray;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
|
||||
UResource.Table pluralsTable = value.getTable();
|
||||
for (int i = 0; pluralsTable.getKeyAndValue(i, key, value); ++i) {
|
||||
if (key.contentEquals("dnam") || key.contentEquals("per")) {
|
||||
continue;
|
||||
}
|
||||
StandardPlural plural = StandardPlural.fromString(key);
|
||||
if (output.containsKey(plural)) {
|
||||
int index = getIndex(key.toString());
|
||||
if (outArray[index] != null) {
|
||||
continue;
|
||||
}
|
||||
String formatString = value.getString();
|
||||
output.put(plural, formatString);
|
||||
outArray[index] = formatString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void getMeasureData(ULocale locale, MeasureUnit unit, UnitWidth width,
|
||||
Map<StandardPlural, String> output) {
|
||||
PluralTableSink sink = new PluralTableSink(output);
|
||||
// NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
|
||||
|
||||
private static void getMeasureData(ULocale locale, MeasureUnit unit, UnitWidth width, String[] outArray) {
|
||||
PluralTableSink sink = new PluralTableSink(outArray);
|
||||
ICUResourceBundle resource;
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
|
||||
StringBuilder key = new StringBuilder();
|
||||
@ -67,16 +93,20 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
key.append(unit.getType());
|
||||
key.append("/");
|
||||
key.append(unit.getSubtype());
|
||||
try {
|
||||
resource.getAllItemsWithFallback(key.toString(), sink);
|
||||
} catch (MissingResourceException e) {
|
||||
throw new IllegalArgumentException("No data for unit " + unit + ", width " + width, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void getCurrencyLongNameData(ULocale locale, Currency currency, Map<StandardPlural, String> output) {
|
||||
private static void getCurrencyLongNameData(ULocale locale, Currency currency, String[] outArray) {
|
||||
// In ICU4J, this method gets a CurrencyData from CurrencyData.provider.
|
||||
// TODO(ICU4J): Implement this without going through CurrencyData, like in ICU4C?
|
||||
Map<String, String> data = CurrencyData.provider.getInstance(locale, true).getUnitPatterns();
|
||||
for (Map.Entry<String, String> e : data.entrySet()) {
|
||||
String pluralKeyword = e.getKey();
|
||||
StandardPlural plural = StandardPlural.fromString(e.getKey());
|
||||
int index = getIndex(pluralKeyword);
|
||||
String longName = currency.getName(locale, Currency.PLURAL_LONG_NAME, pluralKeyword, null);
|
||||
String simpleFormat = e.getValue();
|
||||
// Example pattern from data: "{0} {1}"
|
||||
@ -84,7 +114,25 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
simpleFormat = simpleFormat.replace("{1}", longName);
|
||||
// String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
|
||||
// SimpleModifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
|
||||
output.put(plural, simpleFormat);
|
||||
outArray[index] = simpleFormat;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPerUnitFormat(ULocale locale, UnitWidth width) {
|
||||
ICUResourceBundle resource;
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
|
||||
StringBuilder key = new StringBuilder();
|
||||
key.append("units");
|
||||
if (width == UnitWidth.NARROW) {
|
||||
key.append("Narrow");
|
||||
} else if (width == UnitWidth.SHORT) {
|
||||
key.append("Short");
|
||||
}
|
||||
key.append("/compound/per");
|
||||
try {
|
||||
return resource.getStringWithFallback(key.toString());
|
||||
} catch (MissingResourceException e) {
|
||||
throw new IllegalArgumentException("Could not find x-per-y format for " + locale + ", width " + width);
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +153,7 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
|
||||
public static LongNameHandler forCurrencyLongNames(ULocale locale, Currency currency, PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
Map<StandardPlural, String> simpleFormats = new EnumMap<StandardPlural, String>(StandardPlural.class);
|
||||
String[] simpleFormats = new String[ARRAY_LENGTH];
|
||||
getCurrencyLongNameData(locale, currency, simpleFormats);
|
||||
// TODO(ICU4J): Reduce the number of object creations here?
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
|
||||
@ -114,9 +162,20 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
}
|
||||
|
||||
public static LongNameHandler forMeasureUnit(ULocale locale, MeasureUnit unit, UnitWidth width, PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
Map<StandardPlural, String> simpleFormats = new EnumMap<StandardPlural, String>(StandardPlural.class);
|
||||
public static LongNameHandler forMeasureUnit(ULocale locale, MeasureUnit unit, MeasureUnit perUnit, UnitWidth width,
|
||||
PluralRules rules, MicroPropsGenerator parent) {
|
||||
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) {
|
||||
unit = simplified;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
|
||||
}
|
||||
}
|
||||
|
||||
String[] simpleFormats = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, simpleFormats);
|
||||
// TODO: What field to use for units?
|
||||
// TODO(ICU4J): Reduce the number of object creations here?
|
||||
@ -126,20 +185,52 @@ public class LongNameHandler implements MicroPropsGenerator {
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
}
|
||||
|
||||
private static void simpleFormatsToModifiers(Map<StandardPlural, String> simpleFormats, NumberFormat.Field field,
|
||||
private static LongNameHandler forCompoundUnit(ULocale locale, MeasureUnit unit, MeasureUnit perUnit,
|
||||
UnitWidth width, PluralRules rules, MicroPropsGenerator parent) {
|
||||
String[] primaryData = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, unit, width, primaryData);
|
||||
String[] secondaryData = new String[ARRAY_LENGTH];
|
||||
getMeasureData(locale, perUnit, width, secondaryData);
|
||||
String perUnitFormat;
|
||||
if (secondaryData[PER_INDEX] != null) {
|
||||
perUnitFormat = secondaryData[PER_INDEX];
|
||||
} else {
|
||||
String rawPerUnitFormat = getPerUnitFormat(locale, width);
|
||||
// rawPerUnitFormat is something like "{0}/{1}"; we need to substitute in the secondary unit.
|
||||
// TODO: Lots of thrashing. Improve?
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(rawPerUnitFormat, sb, 2, 2);
|
||||
String secondaryFormat = getWithPlural(secondaryData, StandardPlural.ONE);
|
||||
String secondaryCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(secondaryFormat, sb, 1, 1);
|
||||
String secondaryString = SimpleFormatterImpl.getTextWithNoArguments(secondaryCompiled).trim();
|
||||
perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
|
||||
}
|
||||
// TODO: What field to use for units?
|
||||
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
|
||||
StandardPlural.class);
|
||||
multiSimpleFormatsToModifiers(primaryData, perUnitFormat, null, modifiers);
|
||||
return new LongNameHandler(modifiers, rules, parent);
|
||||
}
|
||||
|
||||
private static void simpleFormatsToModifiers(String[] simpleFormats, NumberFormat.Field field,
|
||||
Map<StandardPlural, SimpleModifier> output) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
String simpleFormat = simpleFormats.get(plural);
|
||||
if (simpleFormat == null) {
|
||||
simpleFormat = simpleFormats.get(StandardPlural.OTHER);
|
||||
}
|
||||
if (simpleFormat == null) {
|
||||
// There should always be data in the "other" plural variant.
|
||||
throw new ICUException("Could not find data in 'other' plural variant with field " + field);
|
||||
}
|
||||
String simpleFormat = getWithPlural(simpleFormats, plural);
|
||||
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
|
||||
output.put(plural, new SimpleModifier(compiled, null, false));
|
||||
output.put(plural, new SimpleModifier(compiled, field, false));
|
||||
}
|
||||
}
|
||||
|
||||
private static void multiSimpleFormatsToModifiers(String[] leadFormats, String trailFormat,
|
||||
NumberFormat.Field field, Map<StandardPlural, SimpleModifier> output) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String trailCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(trailFormat, sb, 1, 1);
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
String leadFormat = getWithPlural(leadFormats, plural);
|
||||
String compoundFormat = SimpleFormatterImpl.formatCompiledPattern(trailCompiled, leadFormat);
|
||||
String compoundCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(compoundFormat, sb, 1, 1);
|
||||
output.put(plural, new SimpleModifier(compoundCompiled, field, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import com.ibm.icu.util.ULocale;
|
||||
public class MacroProps implements Cloneable {
|
||||
public Notation notation;
|
||||
public MeasureUnit unit;
|
||||
public MeasureUnit perUnit;
|
||||
public Rounder rounder;
|
||||
public Grouper grouper;
|
||||
public Padder padder;
|
||||
@ -39,6 +40,7 @@ public class MacroProps implements Cloneable {
|
||||
public void fallback(MacroProps fallback) {
|
||||
if (notation == null) notation = fallback.notation;
|
||||
if (unit == null) unit = fallback.unit;
|
||||
if (perUnit == null) perUnit = fallback.perUnit;
|
||||
if (rounder == null) rounder = fallback.rounder;
|
||||
if (grouper == null) grouper = fallback.grouper;
|
||||
if (padder == null) padder = fallback.padder;
|
||||
@ -58,6 +60,7 @@ public class MacroProps implements Cloneable {
|
||||
return Utility.hash(
|
||||
notation,
|
||||
unit,
|
||||
perUnit,
|
||||
rounder,
|
||||
grouper,
|
||||
padder,
|
||||
@ -80,6 +83,7 @@ public class MacroProps implements Cloneable {
|
||||
MacroProps other = (MacroProps) _other;
|
||||
return Utility.equals(notation, other.notation)
|
||||
&& Utility.equals(unit, other.unit)
|
||||
&& Utility.equals(perUnit, other.perUnit)
|
||||
&& Utility.equals(rounder, other.rounder)
|
||||
&& Utility.equals(grouper, other.grouper)
|
||||
&& Utility.equals(padder, other.padder)
|
||||
|
@ -247,7 +247,7 @@ class NumberFormatterImpl {
|
||||
// Lazily create PluralRules
|
||||
rules = PluralRules.forLocale(macros.loc);
|
||||
}
|
||||
chain = LongNameHandler.forMeasureUnit(macros.loc, macros.unit, unitWidth, rules, chain);
|
||||
chain = LongNameHandler.forMeasureUnit(macros.loc, macros.unit, macros.perUnit, unitWidth, rules, chain);
|
||||
} else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
|
||||
if (rules == null) {
|
||||
// Lazily create PluralRules
|
||||
|
@ -39,7 +39,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
static final int KEY_SIGN = 10;
|
||||
static final int KEY_DECIMAL = 11;
|
||||
static final int KEY_THRESHOLD = 12;
|
||||
static final int KEY_MAX = 13;
|
||||
static final int KEY_PER_UNIT = 13;
|
||||
static final int KEY_MAX = 14;
|
||||
|
||||
final NumberFormatterSettings<?> parent;
|
||||
final int key;
|
||||
@ -123,6 +124,10 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
* NumberFormatter.with().unit(NoUnit.PERCENT)
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* See {@link #perUnit} for information on how to format strings like "5 meters per second".
|
||||
*
|
||||
* <p>
|
||||
* The default is to render without units (equivalent to {@link NoUnit#BASE}).
|
||||
*
|
||||
* @param unit
|
||||
@ -131,6 +136,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
* @see MeasureUnit
|
||||
* @see Currency
|
||||
* @see NoUnit
|
||||
* @see #perUnit
|
||||
* @draft ICU 60
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
@ -138,6 +144,34 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
return create(KEY_UNIT, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit and SECOND to
|
||||
* the perUnit.
|
||||
*
|
||||
* <p>
|
||||
* Pass this method any instance of {@link MeasureUnit}. For example:
|
||||
*
|
||||
* <pre>
|
||||
* NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND)
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The default is not to display any unit in the denominator.
|
||||
*
|
||||
* <p>
|
||||
* If a per-unit is specified without a primary unit via {@link #unit}, the behavior is undefined.
|
||||
*
|
||||
* @param perUnit
|
||||
* The unit to render in the denominator.
|
||||
* @return The fluent chain
|
||||
* @see #unit
|
||||
* @draft ICU 61
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public T perUnit(MeasureUnit perUnit) {
|
||||
return create(KEY_PER_UNIT, perUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the rounding strategy to use when formatting numbers.
|
||||
*
|
||||
@ -518,6 +552,11 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
||||
macros.threshold = (Long) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_PER_UNIT:
|
||||
if (macros.perUnit == null) {
|
||||
macros.perUnit = (MeasureUnit) current.value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown key: " + current.key);
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ public class MeasureUnit implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MeasureUnit instance (creates a singleton instance).
|
||||
* Creates a MeasureUnit instance (creates a singleton instance) or returns one from the cache.
|
||||
* <p>
|
||||
* Normally this method should not be used, since there will be no formatting data
|
||||
* available for it, and it may not be returned by getAvailable().
|
||||
|
@ -475,6 +475,54 @@ public class NumberFormatterApiTest {
|
||||
"5.43 °F");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitCompoundMeasure() {
|
||||
assertFormatDescending(
|
||||
"Meters Per Second Short (unit that simplifies)",
|
||||
"",
|
||||
NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND),
|
||||
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)",
|
||||
"",
|
||||
NumberFormatter.with().unit(MeasureUnit.POUND).perUnit(MeasureUnit.SQUARE_MILE),
|
||||
ULocale.ENGLISH,
|
||||
"87,650 lb/mi²",
|
||||
"8,765 lb/mi²",
|
||||
"876.5 lb/mi²",
|
||||
"87.65 lb/mi²",
|
||||
"8.765 lb/mi²",
|
||||
"0.8765 lb/mi²",
|
||||
"0.08765 lb/mi²",
|
||||
"0.008765 lb/mi²",
|
||||
"0 lb/mi²");
|
||||
|
||||
assertFormatDescending(
|
||||
"Joules Per Furlong Short (unit with no simplifications or special patterns)",
|
||||
"",
|
||||
NumberFormatter.with().unit(MeasureUnit.JOULE).perUnit(MeasureUnit.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");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitCurrency() {
|
||||
assertFormatDescending(
|
||||
|
Loading…
Reference in New Issue
Block a user