diff --git a/icu4c/source/i18n/measunit.cpp b/icu4c/source/i18n/measunit.cpp index b8ca9f3dc258..386c5e54c105 100644 --- a/icu4c/source/i18n/measunit.cpp +++ b/icu4c/source/i18n/measunit.cpp @@ -2767,16 +2767,21 @@ StringEnumeration* MeasureUnit::getAvailableTypes(UErrorCode &errorCode) { } bool MeasureUnit::validateAndGet(StringPiece type, StringPiece subtype, MeasureUnit &result) { - // Find the type index using binary search + // Find the type and subtype indices using binary search int32_t typeIdx = binarySearch(gTypes, 0, UPRV_LENGTHOF(gTypes), type); - if (typeIdx == -1) { - return false; // Type not found - } + int32_t subtypeIdx = typeIdx >= 0 ? binarySearch(typeIdx, subtype) : -1; - // Find the subtype within the type's range using binary search - int32_t subtypeIdx = binarySearch(typeIdx, subtype); - if (subtypeIdx == -1) { - return false; // Subtype not found + if (typeIdx >= 0 && subtypeIdx < 0) { + // if we did find the type, but didn't find the subtype, that might be because the sybtype name + // is an alias-- try using MeasureUnit::forIdentifier(), which will resolve aliases + UErrorCode localStatus = U_ZERO_ERROR; + MeasureUnit tempUnit = MeasureUnit::forIdentifier(subtype, localStatus); + if (U_SUCCESS(localStatus) && uprv_strcmp(type.data(), tempUnit.getType()) == 0) { + subtypeIdx = tempUnit.fSubTypeId + gOffsets[tempUnit.fTypeId]; + } + } + if (typeIdx < 0 || subtypeIdx < 0) { + return false; } // Create the MeasureUnit and return it diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 1d21d805a0e2..27d57341a38d 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -1076,7 +1076,7 @@ void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, Mac if (MeasureUnit::validateAndGet(type.toStringPiece(), subType.toStringPiece(), unit)) { macros.unit = unit; return; - } + } status = U_NUMBER_SKELETON_SYNTAX_ERROR; } diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt index d326891c4870..6db7fa78ec5e 100644 --- a/icu4c/source/test/depstest/dependencies.txt +++ b/icu4c/source/test/depstest/dependencies.txt @@ -890,7 +890,7 @@ library: i18n formatting formattable_cnv regex regex_cnv translit double_conversion number_representation number_output numberformatter number_skeletons number_usageprefs numberparser - units_extra unitsformatter + units unitsformatter universal_time_scale uclean_i18n display_options @@ -1055,7 +1055,7 @@ group: number_skeletons number_skeletons.o number_capi.o number_asformat.o numrange_capi.o deps numberformatter - units_extra + units group: number_symbolswrapper number_symbolswrapper.o @@ -1126,21 +1126,16 @@ group: sharedbreakiterator deps breakiterator -group: units_extra - measunit_extra.o - deps - units bytestriebuilder bytestrie resourcebundle uclean_i18n - double_conversion - group: units - measunit.o currunit.o + measunit.o currunit.o measunit_extra.o deps - stringenumeration errorcode + stringenumeration errorcode bytestriebuilder bytestrie resourcebundle uclean_i18n + double_conversion group: unitsformatter units_data.o units_converter.o units_complexconverter.o units_router.o deps - resourcebundle units_extra double_conversion number_representation formattable sort + resourcebundle units double_conversion number_representation formattable sort number_rounding group: decnumber diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 41127cf08792..c9ac9feef0da 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -306,6 +306,7 @@ class NumberSkeletonTest : public IntlTest { void perUnitToSkeleton(); void measurementSystemOverride(); void longSkeletonCrash(); + void unitAliases(); void runIndexedTest(int32_t index, UBool exec, const char*& name, char* par = nullptr) override; diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 3d6d15e4888e..a1c3b1238d0b 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -36,6 +36,7 @@ void NumberSkeletonTest::runIndexedTest(int32_t index, UBool exec, const char*& TESTCASE_AUTO(perUnitToSkeleton); TESTCASE_AUTO(measurementSystemOverride); TESTCASE_AUTO(longSkeletonCrash); + TESTCASE_AUTO(unitAliases); TESTCASE_AUTO_END; } @@ -581,4 +582,31 @@ void NumberSkeletonTest::longSkeletonCrash() { UnlocalizedNumberFormatter nf = NumberFormatter::forSkeleton(skeleton, err); } +void NumberSkeletonTest::unitAliases() { + IcuTestErrorCode status(*this, "unitAliases"); + + struct TestCase { + const char16_t* skeleton; + const char16_t* expectedResult; + } testCases[] = { + { u"measure-unit/concentr-part-per-1e6", u"3.14 ppm" }, + { u"measure-unit/concentr-part-per-million", u"3.14 ppm" }, + { u"measure-unit/concentr-permillion", u"3.14 ppm" }, + { u"measure-unit/concentr-milligram-ofglucose-per-deciliter", u"3.14 mg/dL" }, + { u"measure-unit/concentr-milligram-per-deciliter", u"3.14 mg/dL" }, + { u"measure-unit/mass-tonne", u"3.14 t" }, + { u"measure-unit/mass-metric-ton", u"3.14 t" }, + }; + + for (const auto& testCase : testCases) { + LocalizedNumberFormatter nf = NumberFormatter::forSkeleton(testCase.skeleton, status).locale(Locale::getUS()); + UnicodeString actualResult = nf.formatDouble(3.14, status).toString(status); + + status.setScope(testCase.skeleton); + if (!status.errIfFailureAndReset()) { + assertEquals(testCase.skeleton, testCase.expectedResult, actualResult); + } + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/core/src/main/java/com/ibm/icu/number/NumberSkeletonImpl.java index 43115c01d2d7..9f88beb74969 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/number/NumberSkeletonImpl.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/number/NumberSkeletonImpl.java @@ -1094,11 +1094,12 @@ private static void parseMeasureUnitOption(StringSegment segment, MacroProps mac } String type = segment.subSequence(0, firstHyphen).toString(); String subType = segment.subSequence(firstHyphen + 1, segment.length()).toString(); - MeasureUnit unit = MeasureUnit.getUnit(type, subType); + MeasureUnit unit = MeasureUnit.validateAndGet(type, subType); if (unit != null) { macros.unit = unit; return; } + throw new SkeletonSyntaxException("Unknown measure unit", segment); } diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java b/icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java index f1d011c30a67..01db52b9a2f8 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java @@ -890,6 +890,23 @@ public static MeasureUnit getUnit(String type, String subtype) { return units != null ? units.get(subtype) : null; } + @Deprecated + public static MeasureUnit validateAndGet(String type, String subtype) { + MeasureUnit result = MeasureUnit.getUnit(type, subtype); + + if (result == null) { + try { + result = MeasureUnit.forIdentifier(subtype); + if (!result.getType().equals(type)) { + result = null; + } + } catch (IllegalArgumentException e) { + // leave result as null + } + } + return result; + } + static final UnicodeSet ASCII = new UnicodeSet('a', 'z').freeze(); static final UnicodeSet ASCII_HYPHEN_DIGITS = new UnicodeSet('-', '-', '0', '9', 'a', 'z').freeze(); diff --git a/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/number/NumberSkeletonTest.java index 685d164a19b4..1486f380c01e 100644 --- a/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/number/NumberSkeletonTest.java +++ b/icu4j/main/core/src/test/java/com/ibm/icu/dev/test/number/NumberSkeletonTest.java @@ -549,4 +549,45 @@ public void measurementSystemOverride() { "Wrong result: " + languageTag + ":" + skeleton, expectedResult, actualResult); } } + + @Test + public void unitAliases() { + class TestCase { + String skeleton; + String expectedResult; + + TestCase(String skeleton, String expectedResult) { + this.skeleton = skeleton; + this.expectedResult = expectedResult; + } + } + + TestCase[] testCases = + new TestCase[] { + new TestCase("measure-unit/concentr-part-per-1e6", "3.14 ppm"), + new TestCase("measure-unit/concentr-part-per-million", "3.14 ppm"), + new TestCase("measure-unit/concentr-permillion", "3.14 ppm"), + new TestCase( + "measure-unit/concentr-milligram-ofglucose-per-deciliter", + "3.14 mg/dL"), + new TestCase("measure-unit/concentr-milligram-per-deciliter", "3.14 mg/dL"), + new TestCase("measure-unit/mass-tonne", "3.14 t"), + new TestCase("measure-unit/mass-metric-ton", "3.14 t"), + }; + + for (TestCase testCase : testCases) { + try { + LocalizedNumberFormatter nf = + NumberFormatter.forSkeleton(testCase.skeleton).locale(Locale.US); + String actualResult = nf.format(3.14).toString(); + + assertEquals( + "Wrong result for " + testCase.skeleton + ":", + testCase.expectedResult, + actualResult); + } catch (SkeletonSyntaxException e) { + fail(testCase.skeleton); + } + } + } }