ICU-21451 Implement inverse unit handling for new consumption unit preferences in CLDR
See #1550
diff --git a/icu4c/source/data/misc/units.txt b/icu4c/source/data/misc/units.txt
index 9941a20..f8c8645 100644
--- a/icu4c/source/data/misc/units.txt
+++ b/icu4c/source/data/misc/units.txt
@@ -925,6 +925,16 @@
unit{"liter-per-kilometer"}
}
}
+ CA{
+ {
+ unit{"mile-per-gallon-imperial"}
+ }
+ }
+ GB{
+ {
+ unit{"mile-per-gallon-imperial"}
+ }
+ }
IT{
{
unit{"liter-per-kilometer"}
@@ -965,32 +975,6 @@
unit{"liter-per-kilometer"}
}
}
- }
- }
- "consumption-inverse"{
- "default"{
- 001{
- {
- unit{"kilometer-per-centiliter"}
- }
- }
- }
- "vehicle-fuel"{
- 001{
- {
- unit{"kilometer-per-centiliter"}
- }
- }
- CA{
- {
- unit{"mile-per-gallon-imperial"}
- }
- }
- GB{
- {
- unit{"mile-per-gallon-imperial"}
- }
- }
US{
{
unit{"mile-per-gallon"}
@@ -1328,10 +1312,12 @@
unit{"meter"}
}
{
+ geq{"10"}
skeleton{"precision-increment/10"}
unit{"meter"}
}
{
+ skeleton{"precision-increment/1"}
unit{"meter"}
}
}
@@ -1346,6 +1332,12 @@
unit{"yard"}
}
{
+ geq{"10"}
+ skeleton{"precision-increment/10"}
+ unit{"yard"}
+ }
+ {
+ skeleton{"precision-increment/1"}
unit{"yard"}
}
}
@@ -1362,9 +1354,14 @@
unit{"meter"}
}
{
+ geq{"10"}
skeleton{"precision-increment/10"}
unit{"meter"}
}
+ {
+ skeleton{"precision-increment/1"}
+ unit{"meter"}
+ }
}
US{
{
@@ -1377,9 +1374,14 @@
unit{"foot"}
}
{
+ geq{"10"}
skeleton{"precision-increment/10"}
unit{"foot"}
}
+ {
+ skeleton{"precision-increment/1"}
+ unit{"foot"}
+ }
}
}
"snowfall"{
diff --git a/icu4c/source/i18n/units_data.cpp b/icu4c/source/i18n/units_data.cpp
index 42bd624..2d94a85 100644
--- a/icu4c/source/i18n/units_data.cpp
+++ b/icu4c/source/i18n/units_data.cpp
@@ -282,6 +282,10 @@
if (U_FAILURE(status)) { return -1; }
if (idx >= 0) { return idx; }
if (!foundCategory) {
+ // TODO: failures can happen if units::getUnitCategory returns a category
+ // that does not appear in unitPreferenceData. Do we want a unit test that
+ // checks unitPreferenceData has full coverage of categories? Or just trust
+ // CLDR?
status = U_ILLEGAL_ARGUMENT_ERROR;
return -1;
}
@@ -370,12 +374,12 @@
const UChar *uCategory =
ures_getStringByKey(unitQuantities.getAlias(), baseUnitIdentifier, &categoryLength, &status);
if (U_FAILURE(status)) {
- // TODO(CLDR-13787,hugovdm): special-casing the consumption-inverse
- // case. Once CLDR-13787 is clarified, this should be generalised (or
- // possibly removed):
+ // TODO(icu-units#130): support inverting any unit, with correct
+ // fallback logic: inversion and fallback may depend on presence or
+ // absence of a usage for that category.
if (uprv_strcmp(baseUnitIdentifier, "meter-per-cubic-meter") == 0) {
status = U_ZERO_ERROR;
- result.append("consumption-inverse", status);
+ result.append("consumption", status);
return result;
}
}
@@ -415,7 +419,11 @@
const UnitPreference *const *&outPreferences,
int32_t &preferenceCount, UErrorCode &status) const {
int32_t idx = getPreferenceMetadataIndex(&metadata_, category, usage, region, status);
- if (U_FAILURE(status)) { return; }
+ if (U_FAILURE(status)) {
+ outPreferences = nullptr;
+ preferenceCount = 0;
+ return;
+ }
U_ASSERT(idx >= 0); // Failures should have been taken care of by `status`.
const UnitPreferenceMetadata *m = metadata_[idx];
outPreferences = unitPrefs_.getAlias() + m->prefsOffset;
diff --git a/icu4c/source/i18n/units_router.cpp b/icu4c/source/i18n/units_router.cpp
index 3158718..882077b 100644
--- a/icu4c/source/i18n/units_router.cpp
+++ b/icu4c/source/i18n/units_router.cpp
@@ -56,7 +56,7 @@
CharString category = getUnitCategory(baseUnit.getIdentifier(), status);
const UnitPreference *const *unitPreferences;
- int32_t preferencesCount;
+ int32_t preferencesCount = 0;
prefs.getPreferencesFor(category.data(), usage, region, unitPreferences, preferencesCount, status);
for (int i = 0; i < preferencesCount; ++i) {
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index b3bfb87..e06ab57 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -1239,7 +1239,7 @@
u"8,8 km",
u"900 m",
u"90 m",
- u"10 m",
+ u"9 m",
u"0 m");
uTestCase = u"unitUsage() en-GB road";
@@ -1274,8 +1274,8 @@
u"54 mi",
u"5.4 mi",
u"0.54 mi",
- u"96 yd",
- u"9.6 yd",
+ u"100 yd",
+ u"10 yd",
u"0 yd");
uTestCase = u"unitUsage() en-US road";
diff --git a/icu4c/source/test/intltest/units_data_test.cpp b/icu4c/source/test/intltest/units_data_test.cpp
index 1846451..405c4e4 100644
--- a/icu4c/source/test/intltest/units_data_test.cpp
+++ b/icu4c/source/test/intltest/units_data_test.cpp
@@ -10,6 +10,7 @@
using namespace ::icu::units;
+// These test are no in ICU4J. TODO: consider porting them to Java?
class UnitsDataTest : public IntlTest {
public:
UnitsDataTest() {}
@@ -38,12 +39,14 @@
const char *expectedCategory;
} testCases[]{
{"kilogram-per-cubic-meter", "mass-density"},
+ {"cubic-meter-per-kilogram", "specific-volume"},
+ {"meter-per-second", "speed"},
+ // TODO(icu-units#130): inverse-speed
+ // {"second-per-meter", "speed"},
+ // Consumption specifically supports inverse units (mile-per-galon,
+ // liter-per-100-kilometer):
{"cubic-meter-per-meter", "consumption"},
- // TODO(CLDR-13787,hugovdm): currently we're treating
- // consumption-inverse as a separate category. Once consumption
- // preference handling has been clarified by CLDR-13787, this function
- // should be fixed.
- {"meter-per-cubic-meter", "consumption-inverse"},
+ {"meter-per-cubic-meter", "consumption"},
};
IcuTestErrorCode status(*this, "testGetUnitCategory");
diff --git a/icu4c/source/test/testdata/cldr/units/unitPreferencesTest.txt b/icu4c/source/test/testdata/cldr/units/unitPreferencesTest.txt
index 3d2f933..91c62cb 100644
--- a/icu4c/source/test/testdata/cldr/units/unitPreferencesTest.txt
+++ b/icu4c/source/test/testdata/cldr/units/unitPreferencesTest.txt
@@ -83,14 +83,22 @@
consumption; vehicle-fuel; BR; 1 / 1000000; 1.0E-6; cubic-meter-per-meter; 1; 1.0; liter-per-kilometer
consumption; vehicle-fuel; BR; 9 / 10000000; 9.0E-7; cubic-meter-per-meter; 9 / 10; 0.9; liter-per-kilometer
-consumption-inverse; default; 001; 110000000; 1.1E8; meter-per-cubic-meter; 11 / 10; 1.1; kilometer-per-centiliter
-consumption-inverse; default; 001; 100000000; 1.0E8; meter-per-cubic-meter; 1; 1.0; kilometer-per-centiliter
-consumption-inverse; default; 001; 90000000; 9.0E7; meter-per-cubic-meter; 9 / 10; 0.9; kilometer-per-centiliter
+# TODO: these are local ICU-specific edits until the CLDR test file is updated:
+# consumption-inverse; default; 001; 110000000; 1.1E8; meter-per-cubic-meter; 11 / 10; 1.1; kilometer-per-centiliter
+consumption; default; 001; 110000000; 1.1E8; meter-per-cubic-meter; 10 / 11; 0.90909090909090906; liter-per-100-kilometer
+# consumption-inverse; default; 001; 100000000; 1.0E8; meter-per-cubic-meter; 1; 1.0; kilometer-per-centiliter
+consumption; default; 001; 100000000; 1.0E8; meter-per-cubic-meter; 1; 1.0; liter-per-100-kilometer
+# consumption-inverse; default; 001; 90000000; 9.0E7; meter-per-cubic-meter; 9 / 10; 0.9; kilometer-per-centiliter
+consumption; default; 001; 90000000; 9.0E7; meter-per-cubic-meter; 10 / 9; 1.1111111111111112; liter-per-100-kilometer
-consumption-inverse; vehicle-fuel; 001; 110000000; 1.1E8; meter-per-cubic-meter; 11 / 10; 1.1; kilometer-per-centiliter
-consumption-inverse; vehicle-fuel; 001; 100000000; 1.0E8; meter-per-cubic-meter; 1; 1.0; kilometer-per-centiliter
-consumption-inverse; vehicle-fuel; 001; 90000000; 9.0E7; meter-per-cubic-meter; 9 / 10; 0.9; kilometer-per-centiliter
+# consumption-inverse; vehicle-fuel; 001; 110000000; 1.1E8; meter-per-cubic-meter; 11 / 10; 1.1; kilometer-per-centiliter
+consumption; vehicle-fuel; 001; 110000000; 1.1E8; meter-per-cubic-meter; 10 / 11; 0.90909090909090906; liter-per-100-kilometer
+# consumption-inverse; vehicle-fuel; 001; 100000000; 1.0E8; meter-per-cubic-meter; 1; 1.0; kilometer-per-centiliter
+consumption; vehicle-fuel; 001; 100000000; 1.0E8; meter-per-cubic-meter; 1; 1.0; liter-per-100-kilometer
+# consumption-inverse; vehicle-fuel; 001; 90000000; 9.0E7; meter-per-cubic-meter; 9 / 10; 0.9; kilometer-per-centiliter
+consumption; vehicle-fuel; 001; 90000000; 9.0E7; meter-per-cubic-meter; 10 / 9; 1.1111111111111112; liter-per-100-kilometer
+# TODO: these still pass, as expected/desired. Leaving them categorised as consumption-inverse, this is ignored by tests anyway
consumption-inverse; vehicle-fuel; US; 52800000000 / 112903; 467658.0781732992; meter-per-cubic-meter; 11 / 10; 1.1; mile-per-gallon
consumption-inverse; vehicle-fuel; US; 48000000000 / 112903; 425143.707430272; meter-per-cubic-meter; 1; 1.0; mile-per-gallon
consumption-inverse; vehicle-fuel; US; 43200000000 / 112903; 382629.3366872448; meter-per-cubic-meter; 9 / 10; 0.9; mile-per-gallon
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitPreferences.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitPreferences.java
index 92496e4..4ef5479 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitPreferences.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitPreferences.java
@@ -64,7 +64,9 @@
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.
assert (result != null) : "At least the category must be exist";
return result;
}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsData.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsData.java
index 7391f56..19ef8f7 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsData.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsData.java
@@ -64,11 +64,10 @@
String baseUnitIdentifier = MeasureUnit.fromMeasureUnitImpl(baseMeasureUnit).getIdentifier();
if (baseUnitIdentifier.equals("meter-per-cubic-meter")) {
- // TODO(CLDR-13787,hugovdm): special-casing the consumption-inverse
- // case. Once CLDR-13787 is clarified, this should be generalised (or
- // possibly removed):
-
- return "consumption-inverse";
+ // TODO(icu-units#130): support inverting any unit, with correct
+ // fallback logic: inversion and fallback may depend on presence or
+ // absence of a usage for that category.
+ return "consumption";
}
return this.categories.mapFromUnitToCategory.get(baseUnitIdentifier);
diff --git a/icu4j/main/shared/data/icudata.jar b/icu4j/main/shared/data/icudata.jar
index 74ccb00..4d9cc23 100644
--- a/icu4j/main/shared/data/icudata.jar
+++ b/icu4j/main/shared/data/icudata.jar
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a4adc39da0522d36906b035fe47aa7a9becfefaa4a5ccef2cdf6f5c23d194777
-size 13306768
+oid sha256:e738e530bcd2dcafff1de1d603c79d5a1edc04c095ca52366259c354f19e56ed
+size 13306751
diff --git a/icu4j/main/shared/data/icutzdata.jar b/icu4j/main/shared/data/icutzdata.jar
index 30b714f..08986d6 100644
--- a/icu4j/main/shared/data/icutzdata.jar
+++ b/icu4j/main/shared/data/icutzdata.jar
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4dce4d778b403e5b91fbe11eba9e0b342c9fe28f2c8d39c7ae8641b311c9a71f
-size 95083
+oid sha256:19f02ee2a2dc722a729fa9258175a738fc6021d252769b85c023a927135c7c26
+size 95080
diff --git a/icu4j/main/shared/data/testdata.jar b/icu4j/main/shared/data/testdata.jar
index e703923..1315090 100644
--- a/icu4j/main/shared/data/testdata.jar
+++ b/icu4j/main/shared/data/testdata.jar
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:c80f58afc2714a7e1af4706c075a23ae37de228f0d383bf0cbaec340ade76030
-size 726478
+oid sha256:1970fbcc18ec8a8b86702fe73ffbba842e9379bd973edbfb4e189ac6ac6d2a83
+size 723496
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
index e9abbc2..f0899a1 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
@@ -432,6 +432,7 @@
public void testUnitPreferencesWithCLDRTests() throws IOException {
class TestCase {
+ // TODO: content of outputUnitInOrder isn't checked? Only size?
final ArrayList<Pair<String, MeasureUnitImpl>> outputUnitInOrder = new ArrayList<>();
final ArrayList<BigDecimal> expectedInOrder = new ArrayList<>();
/**
@@ -486,6 +487,11 @@
expectedInOrder.add(new BigDecimal(output.second));
}
}
+
+ public String toString() {
+ return "TestCase: " + category + ", " + usage + ", " + region +
+ "; Input: " + input + " " + inputUnit.first + "; Expected Values: " + expectedInOrder;
+ }
}
// Read Test data from the unitPreferencesTest
@@ -517,7 +523,7 @@
.compareTwoBigDecimal(testCase.expectedInOrder.get(i),
BigDecimal.valueOf(measures.get(i).getNumber().doubleValue()),
BigDecimal.valueOf(0.00001))) {
- fail(testCase.toString() + measures.toString());
+ fail("Test failed: " + testCase + "; Got unexpected result: " + measures);
}
}
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index 0912f15..35b62e9 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -1218,7 +1218,7 @@
"8,8 km",
"900 m",
"90 m",
- "10 m",
+ "9 m",
"0 m");
uTestCase = "unitUsage() en-GB road";
@@ -1250,8 +1250,8 @@
"54 mi",
"5.4 mi",
"0.54 mi",
- "96 yd",
- "9.6 yd",
+ "100 yd",
+ "10 yd",
"0 yd");
uTestCase = "unitUsage() en-US road";