diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp index 74ee0ef3fd3..bb32d0381a5 100644 --- a/icu4c/source/i18n/number_longnames.cpp +++ b/icu4c/source/i18n/number_longnames.cpp @@ -246,7 +246,8 @@ LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, con 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); + // Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale. + SimpleFormatter secondaryCompiled(secondaryFormat, 0, 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? diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c index 2e296f033f9..8919c786ec8 100644 --- a/icu4c/source/test/cintltst/unumberformattertst.c +++ b/icu4c/source/test/cintltst/unumberformattertst.c @@ -9,9 +9,11 @@ // Helpful in toString methods and elsewhere. #define UNISTR_FROM_STRING_EXPLICIT +#include #include "unicode/unumberformatter.h" #include "unicode/umisc.h" #include "unicode/unum.h" +#include "unicode/ustring.h" #include "cformtst.h" #include "cintltst.h" #include "cmemory.h" @@ -26,6 +28,8 @@ static void TestFormattedValue(void); static void TestSkeletonParseError(void); +static void TestPerUnitInArabic(void); + void addUNumberFormatterTest(TestNode** root); #define TESTCASE(x) addTest(root, &x, "tsformat/unumberformatter/" #x) @@ -36,6 +40,7 @@ void addUNumberFormatterTest(TestNode** root) { TESTCASE(TestExampleCode); TESTCASE(TestFormattedValue); TESTCASE(TestSkeletonParseError); + TESTCASE(TestPerUnitInArabic); } @@ -254,5 +259,88 @@ static void TestSkeletonParseError() { unumf_close(uformatter); } - +static void TestPerUnitInArabic() { + const char* simpleMeasureUnits[] = { + "area-acre", + "digital-bit", + "digital-byte", + "temperature-celsius", + "length-centimeter", + "duration-day", + "angle-degree", + "temperature-fahrenheit", + "volume-fluid-ounce", + "length-foot", + "volume-gallon", + "digital-gigabit", + "digital-gigabyte", + "mass-gram", + "area-hectare", + "duration-hour", + "length-inch", + "digital-kilobit", + "digital-kilobyte", + "mass-kilogram", + "length-kilometer", + "volume-liter", + "digital-megabit", + "digital-megabyte", + "length-meter", + "length-mile", + "length-mile-scandinavian", + "volume-milliliter", + "length-millimeter", + "duration-millisecond", + "duration-minute", + "duration-month", + "mass-ounce", + "concentr-percent", + "digital-petabyte", + "mass-pound", + "duration-second", + "mass-stone", + "digital-terabit", + "digital-terabyte", + "duration-week", + "length-yard", + "duration-year" + }; +#define BUFFER_LEN 256 + char buffer[BUFFER_LEN]; + UChar ubuffer[BUFFER_LEN]; + const char* locale = "ar"; + UErrorCode status = U_ZERO_ERROR; + UFormattedNumber* formatted = unumf_openResult(&status); + if (U_FAILURE(status)) { + log_err("FAIL: unumf_openResult failed"); + return; + } + for(int32_t i=0; i < UPRV_LENGTHOF(simpleMeasureUnits); ++i) { + for(int32_t j=0; j < UPRV_LENGTHOF(simpleMeasureUnits); ++j) { + status = U_ZERO_ERROR; + sprintf(buffer, "measure-unit/%s per-measure-unit/%s", + simpleMeasureUnits[i], simpleMeasureUnits[j]); + int32_t outputlen = 0; + u_strFromUTF8(ubuffer, BUFFER_LEN, &outputlen, buffer, strlen(buffer), &status); + if (U_FAILURE(status)) { + log_err("FAIL u_strFromUTF8: %s = %s ( %s )\n", locale, buffer, + u_errorName(status)); + } + UNumberFormatter* nf = unumf_openForSkeletonAndLocale( + ubuffer, outputlen, locale, &status); + if (U_FAILURE(status)) { + log_err("FAIL unumf_openForSkeletonAndLocale: %s = %s ( %s )\n", + locale, buffer, u_errorName(status)); + } else { + unumf_formatDouble(nf, 1, formatted, &status); + if (U_FAILURE(status)) { + log_err("FAIL unumf_formatDouble: %s = %s ( %s )\n", + locale, buffer, u_errorName(status)); + } + } + unumf_close(nf); + } + } + unumf_closeResult(formatted); +} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 2597aa80cce..cb0be280a9a 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -258,6 +258,7 @@ class NumberSkeletonTest : public IntlTest { void defaultTokens(); void flexibleSeparators(); void wildcardCharacters(); + void perUnitInArabic(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 3aad601a457..d5e4bcfd512 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -30,6 +30,7 @@ void NumberSkeletonTest::runIndexedTest(int32_t index, UBool exec, const char*& TESTCASE_AUTO(defaultTokens); TESTCASE_AUTO(flexibleSeparators); TESTCASE_AUTO(wildcardCharacters); + TESTCASE_AUTO(perUnitInArabic); TESTCASE_AUTO_END; } @@ -362,5 +363,77 @@ void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t c } } +void NumberSkeletonTest::perUnitInArabic() { + IcuTestErrorCode status(*this, "perUnitInArabic"); + + struct TestCase { + const char16_t* type; + const char16_t* subtype; + } cases[] = { + {u"area", u"acre"}, + {u"digital", u"bit"}, + {u"digital", u"byte"}, + {u"temperature", u"celsius"}, + {u"length", u"centimeter"}, + {u"duration", u"day"}, + {u"angle", u"degree"}, + {u"temperature", u"fahrenheit"}, + {u"volume", u"fluid-ounce"}, + {u"length", u"foot"}, + {u"volume", u"gallon"}, + {u"digital", u"gigabit"}, + {u"digital", u"gigabyte"}, + {u"mass", u"gram"}, + {u"area", u"hectare"}, + {u"duration", u"hour"}, + {u"length", u"inch"}, + {u"digital", u"kilobit"}, + {u"digital", u"kilobyte"}, + {u"mass", u"kilogram"}, + {u"length", u"kilometer"}, + {u"volume", u"liter"}, + {u"digital", u"megabit"}, + {u"digital", u"megabyte"}, + {u"length", u"meter"}, + {u"length", u"mile"}, + {u"length", u"mile-scandinavian"}, + {u"volume", u"milliliter"}, + {u"length", u"millimeter"}, + {u"duration", u"millisecond"}, + {u"duration", u"minute"}, + {u"duration", u"month"}, + {u"mass", u"ounce"}, + {u"concentr", u"percent"}, + {u"digital", u"petabyte"}, + {u"mass", u"pound"}, + {u"duration", u"second"}, + {u"mass", u"stone"}, + {u"digital", u"terabit"}, + {u"digital", u"terabyte"}, + {u"duration", u"week"}, + {u"length", u"yard"}, + {u"duration", u"year"}, + }; + + for (const auto& cas1 : cases) { + for (const auto& cas2 : cases) { + UnicodeString skeleton(u"measure-unit/"); + skeleton += cas1.type; + skeleton += u"-"; + skeleton += cas1.subtype; + skeleton += u" "; + skeleton += u"per-measure-unit/"; + skeleton += cas2.type; + skeleton += u"-"; + skeleton += cas2.subtype; + + status.setScope(skeleton); + UnicodeString actual = NumberFormatter::forSkeleton(skeleton, status).locale("ar") + .formatDouble(5142.3, status) + .toString(status); + status.errIfFailureAndReset(); + } + } +} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java index 459e5140ba7..4f58c66ba1c 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java @@ -241,8 +241,10 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore { String compiled = SimpleFormatterImpl .compileToStringMinMaxArguments(rawPerUnitFormat, sb, 2, 2); String secondaryFormat = getWithPlural(secondaryData, StandardPlural.ONE); + + // Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale. String secondaryCompiled = SimpleFormatterImpl - .compileToStringMinMaxArguments(secondaryFormat, sb, 1, 1); + .compileToStringMinMaxArguments(secondaryFormat, sb, 0, 1); String secondaryString = SimpleFormatterImpl.getTextWithNoArguments(secondaryCompiled) .trim(); perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java index 03caedc29ef..db6ee5ed9d5 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java @@ -349,4 +349,65 @@ public class NumberSkeletonTest { assertEquals(mode.toString(), modeString, skeleton.substring(14)); } } + + @Test + public void perUnitInArabic() { + String[][] cases = { + {"area", "acre"}, + {"digital", "bit"}, + {"digital", "byte"}, + {"temperature", "celsius"}, + {"length", "centimeter"}, + {"duration", "day"}, + {"angle", "degree"}, + {"temperature", "fahrenheit"}, + {"volume", "fluid-ounce"}, + {"length", "foot"}, + {"volume", "gallon"}, + {"digital", "gigabit"}, + {"digital", "gigabyte"}, + {"mass", "gram"}, + {"area", "hectare"}, + {"duration", "hour"}, + {"length", "inch"}, + {"digital", "kilobit"}, + {"digital", "kilobyte"}, + {"mass", "kilogram"}, + {"length", "kilometer"}, + {"volume", "liter"}, + {"digital", "megabit"}, + {"digital", "megabyte"}, + {"length", "meter"}, + {"length", "mile"}, + {"length", "mile-scandinavian"}, + {"volume", "milliliter"}, + {"length", "millimeter"}, + {"duration", "millisecond"}, + {"duration", "minute"}, + {"duration", "month"}, + {"mass", "ounce"}, + {"concentr", "percent"}, + {"digital", "petabyte"}, + {"mass", "pound"}, + {"duration", "second"}, + {"mass", "stone"}, + {"digital", "terabit"}, + {"digital", "terabyte"}, + {"duration", "week"}, + {"length", "yard"}, + {"duration", "year"}, + }; + + ULocale arabic = new ULocale("ar"); + for (String[] cas1 : cases) { + for (String[] cas2 : cases) { + String skeleton = "measure-unit/"; + skeleton += cas1[0] + "-" + cas1[1] + " per-measure-unit/" + cas2[0] + "-" + cas2[1]; + + String actual = NumberFormatter.forSkeleton(skeleton).locale(arabic).format(5142.3) + .toString(); + // Just make sure it won't throw exception + } + } + } }