mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-07 06:25:30 +00:00
parent
f5367befba
commit
dbfe830108
15 changed files with 362 additions and 68 deletions
|
@ -153,7 +153,7 @@ UsagePrefsHandler::UsagePrefsHandler(const Locale &locale,
|
|||
const StringPiece usage,
|
||||
const MicroPropsGenerator *parent,
|
||||
UErrorCode &status)
|
||||
: fUnitsRouter(inputUnit, StringPiece(locale.getCountry()), usage, status),
|
||||
: fUnitsRouter(inputUnit, locale, usage, status),
|
||||
fParent(parent) {
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "bytesinkutil.h"
|
||||
#include "cstring.h"
|
||||
#include "number_decimalquantity.h"
|
||||
#include "resource.h"
|
||||
#include "uassert.h"
|
||||
#include "unicode/locid.h"
|
||||
#include "unicode/unistr.h"
|
||||
#include "unicode/ures.h"
|
||||
#include "units_data.h"
|
||||
|
@ -387,24 +389,97 @@ U_I18N_API UnitPreferences::UnitPreferences(UErrorCode &status) {
|
|||
ures_getAllItemsWithFallback(unitsBundle.getAlias(), "unitPreferenceData", sink, status);
|
||||
}
|
||||
|
||||
// TODO: make outPreferences const?
|
||||
//
|
||||
// TODO: consider replacing `UnitPreference **&outPreferences` with slice class
|
||||
// of some kind.
|
||||
void U_I18N_API UnitPreferences::getPreferencesFor(StringPiece category, StringPiece usage,
|
||||
StringPiece region,
|
||||
const UnitPreference *const *&outPreferences,
|
||||
int32_t &preferenceCount, UErrorCode &status) const {
|
||||
int32_t idx = getPreferenceMetadataIndex(&metadata_, category, usage, region, status);
|
||||
if (U_FAILURE(status)) {
|
||||
outPreferences = nullptr;
|
||||
preferenceCount = 0;
|
||||
return;
|
||||
CharString getKeyWordValue(const Locale &locale, StringPiece kw, UErrorCode &status) {
|
||||
CharString result;
|
||||
if (U_FAILURE(status)) { return result; }
|
||||
{
|
||||
CharStringByteSink sink(&result);
|
||||
locale.getKeywordValue(kw, sink, status);
|
||||
}
|
||||
if (U_SUCCESS(status) && result.isEmpty()) {
|
||||
status = U_MISSING_RESOURCE_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MaybeStackVector<UnitPreference>
|
||||
U_I18N_API UnitPreferences::getPreferencesFor(StringPiece category, StringPiece usage,
|
||||
const Locale &locale, UErrorCode &status) const {
|
||||
|
||||
MaybeStackVector<UnitPreference> result;
|
||||
|
||||
// TODO: remove this once all the categories are allowed.
|
||||
UErrorCode internalMuStatus = U_ZERO_ERROR;
|
||||
if (category.compare("temperature") == 0) {
|
||||
CharString localeUnitCharString = getKeyWordValue(locale, "mu", internalMuStatus);
|
||||
if (U_SUCCESS(internalMuStatus)) {
|
||||
// TODO: use the unit category as Java especially when all the categories are allowed..
|
||||
if (localeUnitCharString == "celsius" //
|
||||
|| localeUnitCharString == "fahrenheit" //
|
||||
|| localeUnitCharString == "kelvin" //
|
||||
) {
|
||||
UnitPreference unitPref;
|
||||
unitPref.unit.append(localeUnitCharString, status);
|
||||
result.emplaceBackAndCheckErrorCode(status, unitPref);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CharString region(locale.getCountry(), status);
|
||||
|
||||
// Check the locale system tag, e.g `ms=metric`.
|
||||
UErrorCode internalMeasureTagStatus = U_ZERO_ERROR;
|
||||
CharString localeSystem = getKeyWordValue(locale, "measure", internalMeasureTagStatus);
|
||||
bool isLocaleSystem = false;
|
||||
if (U_SUCCESS(internalMeasureTagStatus)) {
|
||||
if (localeSystem == "metric") {
|
||||
region.clear();
|
||||
region.append("001", status);
|
||||
isLocaleSystem = true;
|
||||
} else if (localeSystem == "ussystem") {
|
||||
region.clear();
|
||||
region.append("US", status);
|
||||
isLocaleSystem = true;
|
||||
} else if (localeSystem == "uksystem") {
|
||||
region.clear();
|
||||
region.append("GB", status);
|
||||
isLocaleSystem = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the region tag, e.g. `rg=uszzz`.
|
||||
if (!isLocaleSystem) {
|
||||
UErrorCode internalRgTagStatus = U_ZERO_ERROR;
|
||||
CharString localeRegion = getKeyWordValue(locale, "rg", internalRgTagStatus);
|
||||
if (U_SUCCESS(internalRgTagStatus) && localeRegion.length() >= 3) {
|
||||
if (localeRegion == "default") {
|
||||
region.clear();
|
||||
region.append(localeRegion, status);
|
||||
} else if (localeRegion[0] >= '0' && localeRegion[0] <= '9') {
|
||||
region.clear();
|
||||
region.append(localeRegion.data(), 3, status);
|
||||
} else {
|
||||
// Take the first two character and capitalize them.
|
||||
region.clear();
|
||||
region.append(uprv_toupper(localeRegion[0]), status);
|
||||
region.append(uprv_toupper(localeRegion[1]), status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t idx =
|
||||
getPreferenceMetadataIndex(&metadata_, category, usage, region.toStringPiece(), status);
|
||||
if (U_FAILURE(status)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
U_ASSERT(idx >= 0); // Failures should have been taken care of by `status`.
|
||||
const UnitPreferenceMetadata *m = metadata_[idx];
|
||||
outPreferences = unitPrefs_.getAlias() + m->prefsOffset;
|
||||
preferenceCount = m->prefsCount;
|
||||
for (int32_t i = 0; i < m->prefsCount; i++) {
|
||||
result.emplaceBackAndCheckErrorCode(status, *(unitPrefs_[i + m->prefsOffset]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace units
|
||||
|
|
|
@ -99,6 +99,13 @@ struct U_I18N_API UnitPreference : public UMemory {
|
|||
CharString unit;
|
||||
double geq;
|
||||
UnicodeString skeleton;
|
||||
|
||||
UnitPreference(const UnitPreference &other) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
this->unit.append(other.unit, status);
|
||||
this->geq = other.geq;
|
||||
this->skeleton = other.skeleton;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -189,12 +196,11 @@ class U_I18N_API UnitPreferences {
|
|||
* @param preferenceCount The number of unit preferences that belong to the
|
||||
* result set.
|
||||
* @param status Receives status.
|
||||
*
|
||||
* TODO(hugovdm): maybe replace `UnitPreference **&outPreferences` with a slice class?
|
||||
*/
|
||||
void getPreferencesFor(StringPiece category, StringPiece usage, StringPiece region,
|
||||
const UnitPreference *const *&outPreferences, int32_t &preferenceCount,
|
||||
UErrorCode &status) const;
|
||||
MaybeStackVector<UnitPreference> getPreferencesFor(StringPiece category, StringPiece usage,
|
||||
const Locale &locale,
|
||||
|
||||
UErrorCode &status) const;
|
||||
|
||||
protected:
|
||||
// Metadata about the sets of preferences, this is the index for looking up
|
||||
|
|
|
@ -43,17 +43,17 @@ Precision UnitsRouter::parseSkeletonToPrecision(icu::UnicodeString precisionSkel
|
|||
return result;
|
||||
}
|
||||
|
||||
UnitsRouter::UnitsRouter(StringPiece inputUnitIdentifier, StringPiece region, StringPiece usage,
|
||||
UnitsRouter::UnitsRouter(StringPiece inputUnitIdentifier, const Locale &locale, StringPiece usage,
|
||||
UErrorCode &status) {
|
||||
this->init(MeasureUnit::forIdentifier(inputUnitIdentifier, status), region, usage, status);
|
||||
this->init(MeasureUnit::forIdentifier(inputUnitIdentifier, status), locale, usage, status);
|
||||
}
|
||||
|
||||
UnitsRouter::UnitsRouter(const MeasureUnit &inputUnit, StringPiece region, StringPiece usage,
|
||||
UnitsRouter::UnitsRouter(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage,
|
||||
UErrorCode &status) {
|
||||
this->init(std::move(inputUnit), region, usage, status);
|
||||
this->init(std::move(inputUnit), locale, usage, status);
|
||||
}
|
||||
|
||||
void UnitsRouter::init(const MeasureUnit &inputUnit, StringPiece region, StringPiece usage,
|
||||
void UnitsRouter::init(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage,
|
||||
UErrorCode &status) {
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
|
@ -73,22 +73,19 @@ void UnitsRouter::init(const MeasureUnit &inputUnit, StringPiece region, StringP
|
|||
return;
|
||||
}
|
||||
|
||||
const UnitPreference *const *unitPreferences;
|
||||
int32_t preferencesCount = 0;
|
||||
prefs.getPreferencesFor(category.toStringPiece(), usage, region, unitPreferences, preferencesCount,
|
||||
status);
|
||||
|
||||
for (int i = 0; i < preferencesCount; ++i) {
|
||||
U_ASSERT(unitPreferences[i] != nullptr);
|
||||
const auto &preference = *unitPreferences[i];
|
||||
const MaybeStackVector<UnitPreference> unitPrefs =
|
||||
prefs.getPreferencesFor(category.toStringPiece(), usage, locale, status);
|
||||
for (int32_t i = 0, n = unitPrefs.length(); i < n; ++i) {
|
||||
U_ASSERT(unitPrefs[i] != nullptr);
|
||||
const auto preference = unitPrefs[i];
|
||||
|
||||
MeasureUnitImpl complexTargetUnitImpl =
|
||||
MeasureUnitImpl::forIdentifier(preference.unit.data(), status);
|
||||
MeasureUnitImpl::forIdentifier(preference->unit.data(), status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UnicodeString precision = preference.skeleton;
|
||||
UnicodeString precision = preference->skeleton;
|
||||
|
||||
// For now, we only have "precision-increment" in Units Preferences skeleton.
|
||||
// Therefore, we check if the skeleton starts with "precision-increment" and force the program to
|
||||
|
@ -103,7 +100,7 @@ void UnitsRouter::init(const MeasureUnit &inputUnit, StringPiece region, StringP
|
|||
outputUnits_.emplaceBackAndCheckErrorCode(status,
|
||||
complexTargetUnitImpl.copy(status).build(status));
|
||||
converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnitImpl, complexTargetUnitImpl,
|
||||
preference.geq, std::move(precision),
|
||||
preference->geq, std::move(precision),
|
||||
conversionRates, status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "cmemory.h"
|
||||
#include "measunit_impl.h"
|
||||
#include "unicode/locid.h"
|
||||
#include "unicode/measunit.h"
|
||||
#include "unicode/stringpiece.h"
|
||||
#include "unicode/uobject.h"
|
||||
|
@ -118,9 +119,10 @@ namespace units {
|
|||
*/
|
||||
class U_I18N_API UnitsRouter {
|
||||
public:
|
||||
UnitsRouter(StringPiece inputUnitIdentifier, StringPiece locale, StringPiece usage,
|
||||
UnitsRouter(StringPiece inputUnitIdentifier, const Locale &locale, StringPiece usage,
|
||||
UErrorCode &status);
|
||||
UnitsRouter(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage,
|
||||
UErrorCode &status);
|
||||
UnitsRouter(const MeasureUnit &inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
|
||||
|
||||
/**
|
||||
* Performs locale and usage sensitive unit conversion.
|
||||
|
@ -153,7 +155,7 @@ class U_I18N_API UnitsRouter {
|
|||
static number::Precision parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
|
||||
UErrorCode &status);
|
||||
|
||||
void init(const MeasureUnit &inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
|
||||
void init(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage, UErrorCode &status);
|
||||
};
|
||||
|
||||
} // namespace units
|
||||
|
|
|
@ -70,6 +70,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
|
|||
void unitGender();
|
||||
void unitNotConvertible();
|
||||
void unitPercent();
|
||||
void unitLocaleTags();
|
||||
void percentParity();
|
||||
void roundingFraction();
|
||||
void roundingFigures();
|
||||
|
|
|
@ -93,6 +93,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
|
|||
TESTCASE_AUTO(unitNounClass);
|
||||
TESTCASE_AUTO(unitNotConvertible);
|
||||
TESTCASE_AUTO(unitPercent);
|
||||
TESTCASE_AUTO(unitLocaleTags);
|
||||
if (!quick) {
|
||||
// Slow test: run in exhaustive mode only
|
||||
TESTCASE_AUTO(percentParity);
|
||||
|
@ -2948,6 +2949,90 @@ void NumberFormatterApiTest::unitPercent() {
|
|||
u"50 meters per percent");
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::unitLocaleTags() {
|
||||
IcuTestErrorCode status(*this, "unitLocaleTags");
|
||||
|
||||
const struct TestCase {
|
||||
const UnicodeString message;
|
||||
const char *locale;
|
||||
const char *inputUnit;
|
||||
const double inputValue;
|
||||
const char *usage;
|
||||
const char *expectedOutputUnit;
|
||||
const double expectedOutputValue;
|
||||
const UnicodeString expectedFormattedNumber;
|
||||
} cases[] = {
|
||||
// Test without any tag behaviour
|
||||
{u"Test the locale without any addition and without usage", "en-US", "celsius", 0, nullptr,
|
||||
"celsius", 0.0, u"0 degrees Celsius"},
|
||||
{u"Test the locale without any addition and usage", "en-US", "celsius", 0, "default",
|
||||
"fahrenheit", 32.0, u"32 degrees Fahrenheit"},
|
||||
|
||||
// Test the behaviour of the `mu` tag.
|
||||
{u"Test the locale with mu = celsius and without usage", "en-US-u-mu-celsius", "fahrenheit", 0,
|
||||
nullptr, "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
|
||||
{u"Test the locale with mu = celsius and with usage", "en-US-u-mu-celsius", "fahrenheit", 0,
|
||||
"default", "celsius", -18.0, u"-18 degrees Celsius"},
|
||||
{u"Test the locale with mu = calsius (wrong spelling) and with usage", "en-US-u-mu-calsius",
|
||||
"fahrenheit", 0, "default", "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
|
||||
{u"Test the locale with mu = meter (only temprature units are supported) and with usage",
|
||||
"en-US-u-mu-meter", "foot", 0, "default", "inch", 0.0, u"0 inches"},
|
||||
|
||||
// Test the behaviour of the `ms` tag
|
||||
{u"Test the locale with ms = metric and without usage", "en-US-u-ms-metric", "fahrenheit", 0,
|
||||
nullptr, "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
|
||||
{u"Test the locale with ms = metric and with usage", "en-US-u-ms-metric", "fahrenheit", 0,
|
||||
"default", "celsius", -18, u"-18 degrees Celsius"},
|
||||
{u"Test the locale with ms = Matric (wrong spelling) and with usage", "en-US-u-ms-Matric",
|
||||
"fahrenheit", 0, "default", "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
|
||||
|
||||
// Test the behaviour of the `rg` tag
|
||||
{u"Test the locale with rg = UK and without usage", "en-US-u-rg-ukzzzz", "fahrenheit", 0,
|
||||
nullptr, "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
|
||||
{u"Test the locale with rg = UK and with usage", "en-US-u-rg-ukzzzz", "fahrenheit", 0, "default",
|
||||
"celsius", -18, u"-18 degrees Celsius"},
|
||||
{"Test the locale with mu = fahrenheit and without usage", "en-US-u-mu-fahrenheit", "celsius", 0,
|
||||
nullptr, "celsius", 0.0, "0 degrees Celsius"},
|
||||
{"Test the locale with mu = fahrenheit and with usage", "en-US-u-mu-fahrenheit", "celsius", 0,
|
||||
"default", "fahrenheit", 32.0, "32 degrees Fahrenheit"},
|
||||
{u"Test the locale with rg = UKOI and with usage", "en-US-u-rg-ukoizzzz", "fahrenheit", 0,
|
||||
"default", "celsius", -18.0, u"-18 degrees Celsius"},
|
||||
|
||||
// Test the priorities
|
||||
{u"Test the locale with mu,ms,rg --> mu tag wins", "en-US-u-mu-celsius-ms-ussystem-rg-uszzzz",
|
||||
"celsius", 0, "default", "celsius", 0.0, u"0 degrees Celsius"},
|
||||
{u"Test the locale with ms,rg --> ms tag wins", "en-US-u-ms-metric-rg-uszzzz", "foot", 1,
|
||||
"default", "centimeter", 30.0, u"30 centimeters"},
|
||||
};
|
||||
|
||||
for (const auto &testCase : cases) {
|
||||
UnicodeString message = testCase.message;
|
||||
Locale locale(testCase.locale);
|
||||
auto inputUnit = MeasureUnit::forIdentifier(testCase.inputUnit, status);
|
||||
auto inputValue = testCase.inputValue;
|
||||
auto usage = testCase.usage;
|
||||
auto expectedOutputUnit = MeasureUnit::forIdentifier(testCase.expectedOutputUnit, status);
|
||||
UnicodeString expectedFormattedNumber = testCase.expectedFormattedNumber;
|
||||
|
||||
auto nf = NumberFormatter::with()
|
||||
.locale(locale) //
|
||||
.unit(inputUnit) //
|
||||
.unitWidth(UNUM_UNIT_WIDTH_FULL_NAME); //
|
||||
if (usage != nullptr) {
|
||||
nf = nf.usage(usage);
|
||||
}
|
||||
auto fn = nf.formatDouble(inputValue, status);
|
||||
if (status.errIfFailureAndReset()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assertEquals(message, fn.toString(status), expectedFormattedNumber);
|
||||
// TODO: ICU-22154
|
||||
// assertEquals(message, fn.getOutputUnit(status).getIdentifier(),
|
||||
// expectedOutputUnit.getIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
void NumberFormatterApiTest::percentParity() {
|
||||
IcuTestErrorCode status(*this, "percentParity");
|
||||
UnlocalizedNumberFormatter uNoUnitPercent = NumberFormatter::with().unit(NoUnit::percent());
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
#if !UCONFIG_NO_FORMATTING
|
||||
|
||||
#include "cstring.h"
|
||||
#include "measunit_impl.h"
|
||||
#include "unicode/locid.h"
|
||||
#include "units_data.h"
|
||||
|
||||
#include "intltest.h"
|
||||
|
@ -118,8 +120,7 @@ void UnitsDataTest::testGetPreferencesFor() {
|
|||
{"Unknown usage US", "length", "foobar", "US", USLenMax, USLenMin},
|
||||
{"Unknown usage 001", "length", "foobar", "XX", WorldLenMax, WorldLenMin},
|
||||
{"Fallback", "length", "person-height-xyzzy", "DE", "centimeter", "centimeter"},
|
||||
{"Fallback twice", "length", "person-height-xyzzy-foo", "DE", "centimeter",
|
||||
"centimeter"},
|
||||
{"Fallback twice", "length", "person-height-xyzzy-foo", "DE", "centimeter", "centimeter"},
|
||||
// Confirming results for some unitPreferencesTest.txt test cases
|
||||
{"001 area", "area", "default", "001", "square-kilometer", "square-centimeter"},
|
||||
{"GB area", "area", "default", "GB", "square-mile", "square-inch"},
|
||||
|
@ -140,18 +141,20 @@ void UnitsDataTest::testGetPreferencesFor() {
|
|||
|
||||
for (const auto &t : testCases) {
|
||||
logln(t.name);
|
||||
const UnitPreference *const *prefs;
|
||||
int32_t prefsCount;
|
||||
preferences.getPreferencesFor(t.category, t.usage, t.region, prefs, prefsCount, status);
|
||||
CharString localeID;
|
||||
localeID.append("und-", status); // append undefined language.
|
||||
localeID.append(t.region, status);
|
||||
Locale locale(localeID.data());
|
||||
auto unitPrefs = preferences.getPreferencesFor(t.category, t.usage, locale, status);
|
||||
if (status.errIfFailureAndReset("getPreferencesFor(\"%s\", \"%s\", \"%s\", ...", t.category,
|
||||
t.usage, t.region)) {
|
||||
continue;
|
||||
}
|
||||
if (prefsCount > 0) {
|
||||
if (unitPrefs.length() > 0) {
|
||||
assertEquals(UnicodeString(t.name) + " - max unit", t.expectedBiggest,
|
||||
prefs[0]->unit.data());
|
||||
unitPrefs[0]->unit.data());
|
||||
assertEquals(UnicodeString(t.name) + " - min unit", t.expectedSmallest,
|
||||
prefs[prefsCount - 1]->unit.data());
|
||||
unitPrefs[unitPrefs.length() - 1]->unit.data());
|
||||
} else {
|
||||
errln(UnicodeString(t.name) + ": failed to find preferences");
|
||||
}
|
||||
|
|
|
@ -948,7 +948,11 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie
|
|||
return;
|
||||
}
|
||||
|
||||
UnitsRouter router(inputMeasureUnit, region, usage, status);
|
||||
CharString localeID;
|
||||
localeID.append("und-", status); // append undefined language.
|
||||
localeID.append(region, status);
|
||||
Locale locale(localeID.data());
|
||||
UnitsRouter router(inputMeasureUnit, locale, usage, status);
|
||||
if (status.errIfFailureAndReset("UnitsRouter(<%s>, \"%.*s\", \"%.*s\", status)",
|
||||
inputMeasureUnit.getIdentifier(), region.length(), region.data(),
|
||||
usage.length(), usage.data())) {
|
||||
|
@ -976,7 +980,7 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie
|
|||
|
||||
// Test UnitsRouter created with CLDR units identifiers.
|
||||
CharString inputUnitIdentifier(inputUnit, status);
|
||||
UnitsRouter router2(inputUnitIdentifier.data(), region, usage, status);
|
||||
UnitsRouter router2(inputUnitIdentifier.data(), locale, usage, status);
|
||||
if (status.errIfFailureAndReset("UnitsRouter2(<%s>, \"%.*s\", \"%.*s\", status)",
|
||||
inputUnitIdentifier.data(), region.length(), region.data(),
|
||||
usage.length(), usage.data())) {
|
||||
|
|
|
@ -20,8 +20,7 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
|
|||
assert parent != null;
|
||||
|
||||
this.fParent = parent;
|
||||
this.fUnitsRouter =
|
||||
new UnitsRouter(MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier()), locale.getCountry(), usage);
|
||||
this.fUnitsRouter = new UnitsRouter(MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier()), locale, usage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,14 +4,28 @@ package com.ibm.icu.impl.units;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.UResource;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
public class UnitPreferences {
|
||||
private static final Map<String, String> measurementSystem;
|
||||
|
||||
static {
|
||||
Map tempMS = new HashMap<String, String>();
|
||||
tempMS.put("metric", "001");
|
||||
tempMS.put("ussystem", "US");
|
||||
tempMS.put("uksystem", "GB");
|
||||
measurementSystem = Collections.unmodifiableMap(tempMS);
|
||||
}
|
||||
|
||||
|
||||
private HashMap<String, HashMap<String, UnitPreference[]>> mapToUnitPreferences = new HashMap<>();
|
||||
|
||||
|
@ -56,7 +70,48 @@ public class UnitPreferences {
|
|||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public UnitPreference[] getPreferencesFor(String category, String usage, String region) {
|
||||
public UnitPreference[] getPreferencesFor(String category, String usage, ULocale locale, UnitsData data) {
|
||||
// TODO: remove this condition when all the categories are allowed.
|
||||
if (category.equals("temperature")) {
|
||||
String localeUnit = locale.getKeywordValue("mu");
|
||||
String localeUnitCategory;
|
||||
try {
|
||||
localeUnitCategory = localeUnit == null ? null : data.getCategory(MeasureUnitImpl.forIdentifier(localeUnit));
|
||||
} catch (Exception e) {
|
||||
localeUnitCategory = null;
|
||||
}
|
||||
|
||||
if (localeUnitCategory != null && category.equals(localeUnitCategory)) {
|
||||
UnitPreference[] preferences = {new UnitPreference(localeUnit, null, null)};
|
||||
return preferences;
|
||||
}
|
||||
}
|
||||
|
||||
String region = locale.getCountry();
|
||||
|
||||
// Check the locale system tag, e.g `ms=metric`.
|
||||
String localeSystem = locale.getKeywordValue("measure");
|
||||
boolean isLocaleSystem = false;
|
||||
if (measurementSystem.containsKey(localeSystem)) {
|
||||
isLocaleSystem = true;
|
||||
region = measurementSystem.get(localeSystem);
|
||||
}
|
||||
|
||||
// Check the region tag, e.g. `rg=uszzz`.
|
||||
if (!isLocaleSystem) {
|
||||
String localeRegion = locale.getKeywordValue("rg");
|
||||
if (localeRegion != null && localeRegion.length() >= 3) {
|
||||
if (localeRegion.equals("default")) {
|
||||
region = localeRegion;
|
||||
} else if (Character.isDigit(localeRegion.charAt(0))) {
|
||||
region = localeRegion.substring(0, 3); // e.g. 001
|
||||
} else {
|
||||
// Capitalize the first two character of the region, e.g. ukzzzz or usca
|
||||
region = localeRegion.substring(0, 2).toUpperCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String[] subUsages = getAllUsages(usage);
|
||||
UnitPreference[] result = null;
|
||||
for (String subUsage :
|
||||
|
@ -64,6 +119,7 @@ public class UnitPreferences {
|
|||
result = getUnitPreferences(category, subUsage, region);
|
||||
if (result != null) break;
|
||||
}
|
||||
|
||||
// TODO: if a category is missing, we get an assertion failure, or we
|
||||
// return null, causing a NullPointerException. In C++, we return an
|
||||
// U_MISSING_RESOURCE_ERROR error.
|
||||
|
@ -101,8 +157,8 @@ public class UnitPreferences {
|
|||
|
||||
public UnitPreference(String unit, String geq, String skeleton) {
|
||||
this.unit = unit;
|
||||
this.geq = new BigDecimal(geq);
|
||||
this.skeleton = skeleton;
|
||||
this.geq = geq == null ? BigDecimal.valueOf( Double.MIN_VALUE) /* -inf */ : new BigDecimal(geq);
|
||||
this.skeleton = skeleton == null? "" : skeleton;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.ibm.icu.impl.ICUData;
|
|||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.IllegalIcuArgumentException;
|
||||
import com.ibm.icu.impl.UResource;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
/**
|
||||
|
@ -109,8 +110,8 @@ public class UnitsData {
|
|||
return Categories.indexToCategory[index];
|
||||
}
|
||||
|
||||
public UnitPreferences.UnitPreference[] getPreferencesFor(String category, String usage, String region) {
|
||||
return this.unitPreferences.getPreferencesFor(category, usage, region);
|
||||
public UnitPreferences.UnitPreference[] getPreferencesFor(String category, String usage, ULocale locale) {
|
||||
return this.unitPreferences.getPreferencesFor(category, usage, locale, this);
|
||||
}
|
||||
|
||||
public static class SimpleUnitIdentifiersSink extends UResource.Sink {
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.ibm.icu.impl.IllegalIcuArgumentException;
|
|||
import com.ibm.icu.impl.number.MicroProps;
|
||||
import com.ibm.icu.number.Precision;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* `UnitsRouter` responsible for converting from a single unit (such as `meter` or `meter-per-second`) to
|
||||
|
@ -22,7 +23,7 @@ import com.ibm.icu.util.MeasureUnit;
|
|||
* `foot+inch`, otherwise, the output will be in `inch`.
|
||||
* <p>
|
||||
* NOTE:
|
||||
* the output units and the their limits MUST BE in order, for example, if the output units, from the
|
||||
* the output units and their limits MUST BE in order, for example, if the output units, from the
|
||||
* previous example, are the following:
|
||||
* {`inch` , limit: no value (-inf)}
|
||||
* {`foot+inch`, limit: 3.0}
|
||||
|
@ -46,17 +47,17 @@ public class UnitsRouter {
|
|||
private ArrayList<MeasureUnit> outputUnits_ = new ArrayList<>();
|
||||
private ArrayList<ConverterPreference> converterPreferences_ = new ArrayList<>();
|
||||
|
||||
public UnitsRouter(String inputUnitIdentifier, String region, String usage) {
|
||||
this(MeasureUnitImpl.forIdentifier(inputUnitIdentifier), region, usage);
|
||||
public UnitsRouter(String inputUnitIdentifier, ULocale locale, String usage) {
|
||||
this(MeasureUnitImpl.forIdentifier(inputUnitIdentifier), locale, usage);
|
||||
}
|
||||
|
||||
public UnitsRouter(MeasureUnitImpl inputUnit, String region, String usage) {
|
||||
public UnitsRouter(MeasureUnitImpl inputUnit, ULocale locale, String usage) {
|
||||
// TODO: do we want to pass in ConversionRates and UnitPreferences instead?
|
||||
// of loading in each UnitsRouter instance? (Or make global?)
|
||||
UnitsData data = new UnitsData();
|
||||
|
||||
String category = data.getCategory(inputUnit);
|
||||
UnitPreferences.UnitPreference[] unitPreferences = data.getPreferencesFor(category, usage, region);
|
||||
UnitPreferences.UnitPreference[] unitPreferences = data.getPreferencesFor(category, usage, locale);
|
||||
|
||||
for (int i = 0; i < unitPreferences.length; ++i) {
|
||||
UnitPreferences.UnitPreference preference = unitPreferences[i];
|
||||
|
|
|
@ -26,7 +26,7 @@ import com.ibm.icu.impl.units.UnitsData;
|
|||
import com.ibm.icu.impl.units.UnitsRouter;
|
||||
import com.ibm.icu.util.Measure;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
public class UnitsTest {
|
||||
|
||||
|
@ -613,6 +613,7 @@ public class UnitsTest {
|
|||
*/
|
||||
String category;
|
||||
String usage;
|
||||
ULocale locale;
|
||||
String region;
|
||||
Pair<String, MeasureUnitImpl> inputUnit;
|
||||
BigDecimal input;
|
||||
|
@ -651,6 +652,7 @@ public class UnitsTest {
|
|||
this.category = category;
|
||||
this.usage = usage;
|
||||
this.region = region;
|
||||
this.locale = new ULocale("und-" + this.region);
|
||||
this.inputUnit = Pair.of(inputUnitString, MeasureUnitImpl.UnitsParser.parseForIdentifier(inputUnitString));
|
||||
this.input = new BigDecimal(inputValue);
|
||||
for (Pair<String, String> output :
|
||||
|
@ -667,8 +669,8 @@ public class UnitsTest {
|
|||
outputUnits.add(unit.second);
|
||||
}
|
||||
return "TestCase: " + category + ", " + usage + ", " + region + "; Input: " + input +
|
||||
" " + inputUnit.first + "; Expected Values: " + expectedInOrder +
|
||||
", Expected Units: " + outputUnits;
|
||||
" " + inputUnit.first + "; Expected Values: " + expectedInOrder +
|
||||
", Expected Units: " + outputUnits;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -686,7 +688,8 @@ public class UnitsTest {
|
|||
}
|
||||
|
||||
for (TestCase testCase : tests) {
|
||||
UnitsRouter router = new UnitsRouter(testCase.inputUnit.second, testCase.region, testCase.usage);
|
||||
UnitsRouter router = new UnitsRouter(testCase.inputUnit.second, testCase.locale,
|
||||
testCase.usage);
|
||||
List<Measure> measures = router.route(testCase.input, null).complexConverterResult.measures;
|
||||
|
||||
assertEquals("For " + testCase.toString() + ", Measures size must be the same as expected units",
|
||||
|
@ -707,7 +710,7 @@ public class UnitsTest {
|
|||
|
||||
// Test UnitsRouter created with CLDR units identifiers.
|
||||
for (TestCase testCase : tests) {
|
||||
UnitsRouter router = new UnitsRouter(testCase.inputUnit.first, testCase.region, testCase.usage);
|
||||
UnitsRouter router = new UnitsRouter(testCase.inputUnit.first, testCase.locale, testCase.usage);
|
||||
List<Measure> measures = router.route(testCase.input, null).complexConverterResult.measures;
|
||||
|
||||
assertEquals("Measures size must be the same as expected units",
|
||||
|
@ -785,7 +788,9 @@ public class UnitsTest {
|
|||
|
||||
UnitsData data = new UnitsData();
|
||||
for (TestCase t : testCases) {
|
||||
UnitPreferences.UnitPreference prefs[] = data.getPreferencesFor(t.category, t.usage, t.region);
|
||||
ULocale locale = new ULocale("und-" + t.region);
|
||||
UnitPreferences.UnitPreference prefs[] = data.getPreferencesFor(t.category, t.usage,
|
||||
locale);
|
||||
if (prefs.length > 0) {
|
||||
assertEquals(t.name + " - max unit", t.expectedBiggest, prefs[0].getUnit());
|
||||
assertEquals(t.name + " - min unit", t.expectedSmallest, prefs[prefs.length - 1].getUnit());
|
||||
|
|
|
@ -489,6 +489,65 @@ public class NumberFormatterApiTest extends TestFmwk {
|
|||
"Kun");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitWithLocaleTags() {
|
||||
String[][] tests = {
|
||||
// 0-message, 1- locale, 2- input unit, 3- input value, 4- usage, 5- output unit, 6- output value, 7- formatted number.
|
||||
// Test without any tag behaviour
|
||||
{"Test the locale without any addition and without usage", "en-US", "celsius", "0", null, "celsius", "0.0", "0 degrees Celsius"},
|
||||
{"Test the locale without any addition and usage", "en-US", "celsius", "0", "default", "fahrenheit", "32.0", "32 degrees Fahrenheit"},
|
||||
|
||||
// Test the behaviour of the `mu` tag.
|
||||
{"Test the locale with mu = celsius and without usage", "en-US-u-mu-celsius", "fahrenheit", "0", null, "fahrenheit", "0.0", "0 degrees Fahrenheit"},
|
||||
{"Test the locale with mu = celsius and with usage", "en-US-u-mu-celsius", "fahrenheit", "0", "default", "celsius", "-18.0", "-18 degrees Celsius"},
|
||||
{"Test the locale with mu = calsius (wrong spelling) and with usage", "en-US-u-mu-calsius", "fahrenheit", "0", "default", "fahrenheit", "0.0", "0 degrees Fahrenheit"},
|
||||
{"Test the locale with mu = fahrenheit and without usage", "en-US-u-mu-fahrenheit", "celsius", "0", null, "celsius", "0.0", "0 degrees Celsius"},
|
||||
{"Test the locale with mu = fahrenheit and with usage", "en-US-u-mu-fahrenheit", "celsius", "0", "default", "fahrenheit", "32.0", "32 degrees Fahrenheit"},
|
||||
{"Test the locale with mu = meter (only temprature units are supported) and with usage", "en-US-u-mu-meter", "foot", "0", "default", "foot", "0.0", "0 inches"},
|
||||
|
||||
// Test the behaviour of the `ms` tag
|
||||
{"Test the locale with ms = metric and without usage", "en-US-u-ms-metric", "fahrenheit", "0", null, "fahrenheit", "0.0", "0 degrees Fahrenheit"},
|
||||
{"Test the locale with ms = metric and with usage", "en-US-u-ms-metric", "fahrenheit", "0", "default", "celsius", "-18", "-18 degrees Celsius"},
|
||||
{"Test the locale with ms = Matric (wrong spelling) and with usage", "en-US-u-ms-Matric", "fahrenheit", "0", "default", "fahrenheit", "0.0", "0 degrees Fahrenheit"},
|
||||
|
||||
// Test the behaviour of the `rg` tag
|
||||
{"Test the locale with rg = UK and without usage", "en-US-u-rg-ukzzzz", "fahrenheit", "0", null, "fahrenheit", "0.0", "0 degrees Fahrenheit"},
|
||||
{"Test the locale with rg = UK and with usage", "en-US-u-rg-ukzzzz", "fahrenheit", "0", "default", "celsius", "-18", "-18 degrees Celsius"},
|
||||
{"Test the locale with rg = UKOI and with usage", "en-US-u-rg-ukoizzzz", "fahrenheit", "0", "default", "celsius", "-18" , "-18 degrees Celsius"},
|
||||
|
||||
// Test the priorities
|
||||
{"Test the locale with mu,ms,rg --> mu tag wins", "en-US-u-mu-celsius-ms-ussystem-rg-uszzzz", "celsius", "0", "default", "celsius", "0.0", "0 degrees Celsius"},
|
||||
{"Test the locale with ms,rg --> ms tag wins", "en-US-u-ms-metric-rg-uszzzz", "foot", "1", "default", "foot", "30.0", "30 centimeters"},
|
||||
};
|
||||
|
||||
int testIndex = 0;
|
||||
for (String[] test : tests) {
|
||||
String message = test[0] + ", index = " + testIndex++;
|
||||
ULocale locale = ULocale.forLanguageTag(test[1]);
|
||||
MeasureUnit inputUnit = MeasureUnit.forIdentifier(test[2]);
|
||||
double inputValue = Double.parseDouble(test[3]);
|
||||
String usage = test[4];
|
||||
MeasureUnit expectedOutputUnit = MeasureUnit.forIdentifier(test[5]);
|
||||
BigDecimal expectedOutputValue = new BigDecimal(test[6]);
|
||||
String expectedFormattedMessage = test[7];
|
||||
|
||||
LocalizedNumberFormatter nf = NumberFormatter.with().locale(locale).unit(inputUnit).unitWidth(UnitWidth.FULL_NAME);
|
||||
if (usage != null) {
|
||||
nf = nf.usage(usage);
|
||||
}
|
||||
|
||||
FormattedNumber fn = nf.format(inputValue);
|
||||
MeasureUnit actualOutputUnit = fn.getOutputUnit();
|
||||
BigDecimal actualOutputValue = fn.toBigDecimal();
|
||||
String actualFormattedMessage = fn.toString();
|
||||
|
||||
assertEquals(message, expectedFormattedMessage, actualFormattedMessage);
|
||||
// TODO: ICU-22154
|
||||
// assertEquals(message, expectedOutputUnit, actualOutputUnit);
|
||||
assertTrue(message, expectedOutputValue.subtract(actualOutputValue).abs().compareTo(BigDecimal.valueOf(0.0001)) <= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unitMeasure() {
|
||||
assertFormatDescending(
|
||||
|
|
Loading…
Add table
Reference in a new issue