ICU-22378 Fix temperature format ignoring -u-mu-fahrenhe

This commit is contained in:
Mihai Nita 2023-05-02 15:43:11 -07:00
parent 6c05042cbc
commit 0e4b10b112
5 changed files with 131 additions and 4 deletions

View file

@ -409,14 +409,19 @@ MaybeStackVector<UnitPreference>
MaybeStackVector<UnitPreference> result;
// TODO: remove this once all the categories are allowed.
// WARNING: when this is removed please make sure to keep the "fahrenhe" => "fahrenheit" mapping
UErrorCode internalMuStatus = U_ZERO_ERROR;
if (category.compare("temperature") == 0) {
CharString localeUnitCharString = getKeyWordValue(locale, "mu", internalMuStatus);
if (U_SUCCESS(internalMuStatus)) {
// The value for -u-mu- is `fahrenhe`, but CLDR and everything else uses `fahrenheit`
if (localeUnitCharString == "fahrenhe") {
localeUnitCharString = CharString("fahrenheit", status);
}
// TODO: use the unit category as Java especially when all the categories are allowed..
if (localeUnitCharString == "celsius" //
|| localeUnitCharString == "fahrenheit" //
|| localeUnitCharString == "kelvin" //
if (localeUnitCharString == "celsius"
|| localeUnitCharString == "fahrenheit"
|| localeUnitCharString == "kelvin"
) {
UnitPreference unitPref;
unitPref.unit.append(localeUnitCharString, status);

View file

@ -103,6 +103,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition {
void toDecimalNumber();
void microPropsInternals();
void formatUnitsAliases();
void testIssue22378();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0) override;

View file

@ -133,6 +133,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha
TESTCASE_AUTO(toDecimalNumber);
TESTCASE_AUTO(microPropsInternals);
TESTCASE_AUTO(formatUnitsAliases);
TESTCASE_AUTO(testIssue22378);
TESTCASE_AUTO_END;
}
@ -6051,6 +6052,63 @@ void NumberFormatterApiTest::formatUnitsAliases() {
}
}
void NumberFormatterApiTest::testIssue22378() {
IcuTestErrorCode status(*this, "testIssue22378");
// I checked the results before the fix and everything works the same except
// "fr-FR-u-mu-fahrenhe" and "fr_FR@mu=fahrenhe"
struct TestCase {
const std::string localeId;
const UnicodeString expectedFormat;
} testCases[]{
{"en-US", u"73\u00B0F"},
{"en-US-u-mu-fahrenhe", u"73\u00B0F"},
// Unlike ULocale, forLanguageTag fails wih U_ILLEGAL_ARGUMENT_ERROR
// because fahrenheit is not valid value for -u-mu-
// {"en-US-u-mu-fahrenheit", u"73\u00B0F"},
{"en-US-u-mu-celsius", u"23\u00B0C"},
{"en-US-u-mu-badvalue", u"73\u00B0F"},
{"en_US@mu=fahrenhe", u"73\u00B0F"},
{"en_US@mu=fahrenheit", u"73\u00B0F"},
{"en_US@mu=celsius", u"23\u00B0C"},
{"en_US@mu=badvalue", u"73\u00B0F"},
{"fr-FR", u"23\u202F\u00B0C"},
{"fr-FR-u-mu-fahrenhe", u"73\u202F\u00B0F"},
// Unlike ULocale, forLanguageTag fails wih U_ILLEGAL_ARGUMENT_ERROR
// because fahrenheit is not valid value for -u-mu-
// {"fr-FR-u-mu-fahrenheit", u"23\u202F\u00B0C"},
{"fr-FR-u-mu-celsius", u"23\u202F\u00B0C"},
{"fr-FR-u-mu-badvalue", u"23\u202F\u00B0C"},
{"fr_FR@mu=fahrenhe", u"73\u202F\u00B0F"},
{"fr_FR@mu=fahrenheit", u"73\u202F\u00B0F"},
{"fr_FR@mu=celsius", u"23\u202F\u00B0C"},
{"fr_FR@mu=badvalue", u"23\u202F\u00B0C"},
};
UnlocalizedNumberFormatter formatter = NumberFormatter::with()
.usage("weather")
.unit(MeasureUnit::getCelsius());
double value = 23.0;
for (const auto &testCase : testCases) {
std::string localeId = testCase.localeId;
const Locale locale = (localeId.find("@") != std::string::npos)
? Locale(localeId.c_str())
: Locale::forLanguageTag(localeId, status);
UnicodeString actualFormat = formatter.locale(locale)
.formatDouble(value, status)
.toString(status);
assertEquals(u"-u-mu- honored (" + UnicodeString(localeId.c_str()) + u")",
testCase.expectedFormat, actualFormat);
}
UnicodeString result = formatter.locale("en-US").formatDouble(value, status).getOutputUnit(status).getIdentifier();
assertEquals("Testing default -u-mu- for en-US", MeasureUnit::getFahrenheit().getIdentifier(), result);
result = formatter.locale("fr-FR").formatDouble(value, status).getOutputUnit(status).getIdentifier();
assertEquals("Testing default -u-mu- for fr-FR", MeasureUnit::getCelsius().getIdentifier(), result);
}
/* For skeleton comparisons: this checks the toSkeleton output for `f` and for
* `conciseSkeleton` against the normalized version of `uskeleton` - this does
* not round-trip uskeleton itself.

View file

@ -72,8 +72,13 @@ public class UnitPreferences {
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")) {
// WARNING: when this is removed please make sure to keep the "fahrenhe" => "fahrenheit" mapping
if ("temperature".equals(category)) {
String localeUnit = locale.getKeywordValue("mu");
// The value for -u-mu- is `fahrenhe`, but CLDR and everything else uses `fahrenheit`
if ("fahrenhe".equals(localeUnit)) {
localeUnit = "fahrenheit";
}
String localeUnitCategory;
try {
localeUnitCategory = localeUnit == null ? null : data.getCategory(MeasureUnitImpl.forIdentifier(localeUnit));

View file

@ -5995,6 +5995,64 @@ public class NumberFormatterApiTest extends TestFmwk {
}
}
@Test
public void testIssue22378() {
class TestCase {
final String localeId;
final String expectedFormat;
TestCase(String localeId, String expectedFormat) {
this.localeId = localeId;
this.expectedFormat = expectedFormat;
}
}
// I checked the results before the fix and everything works the same except
// "fr-FR-u-mu-fahrenhe" and "fr_FR@mu=fahrenhe"
final TestCase [] testCases = {
new TestCase("en-US", "73\u00B0F"),
new TestCase("en-US-u-mu-fahrenhe", "73\u00B0F"),
// WAI. "fahrenheit" is an invalid -u-mu- value, we get the default for en-US
new TestCase("en-US-u-mu-fahrenheit", "73\u00B0F"),
new TestCase("en-US-u-mu-celsius", "23\u00B0C"),
new TestCase("en-US-u-mu-badvalue", "73\u00B0F"),
new TestCase("en_US@mu=fahrenhe", "73\u00B0F"),
new TestCase("en_US@mu=fahrenheit", "73\u00B0F"),
new TestCase("en_US@mu=celsius", "23\u00B0C"),
new TestCase("en_US@mu=badvalue", "73\u00B0F"),
new TestCase("fr-FR", "23\u202F\u00B0C"),
new TestCase("fr-FR-u-mu-fahrenhe", "73\u202F\u00B0F"),
// WAI. Celsius because "fahrenheit" is an invalid -u-mu- value, we get the default for fr-FR
new TestCase("fr-FR-u-mu-fahrenheit", "23\u202F\u00B0C"),
new TestCase("fr-FR-u-mu-celsius", "23\u202F\u00B0C"),
new TestCase("fr-FR-u-mu-badvalue", "23\u202F\u00B0C"),
new TestCase("fr_FR@mu=fahrenhe", "73\u202F\u00B0F"),
new TestCase("fr_FR@mu=fahrenheit", "73\u202F\u00B0F"),
new TestCase("fr_FR@mu=celsius", "23\u202F\u00B0C"),
new TestCase("fr_FR@mu=badvalue", "23\u202F\u00B0C"),
};
final UnlocalizedNumberFormatter formatter = NumberFormatter.with()
.usage("weather")
.unit(MeasureUnit.CELSIUS);
final double value = 23.0;
for (TestCase testCase : testCases) {
String localeId = testCase.localeId;
ULocale locale = localeId.contains("@")
? new ULocale(localeId)
: ULocale.forLanguageTag(localeId);
String actualFormat = formatter.locale(locale).format(value).toString();
assertEquals("-u-mu- honored (" + localeId + ")", testCase.expectedFormat, actualFormat);
}
String result = formatter.locale(Locale.US).format(value).getOutputUnit().getIdentifier();
assertEquals("Testing default -u-mu- for en-US", MeasureUnit.FAHRENHEIT.getIdentifier(), result);
result = formatter.locale(Locale.FRANCE).format(value).getOutputUnit().getIdentifier();
assertEquals("Testing default -u-mu- for fr-FR", MeasureUnit.CELSIUS.getIdentifier(), result);
}
static void assertFormatDescending(
String message,
String skeleton,