diff --git a/common/supplemental/units.xml b/common/supplemental/units.xml index ebaa8e9d08f..33d6d68d466 100644 --- a/common/supplemental/units.xml +++ b/common/supplemental/units.xml @@ -255,7 +255,7 @@ For terms of use, see http://www.unicode.org/copyright.html - + diff --git a/tools/cldr-code/src/main/java/org/unicode/cldr/util/Rational.java b/tools/cldr-code/src/main/java/org/unicode/cldr/util/Rational.java index 98c3521689b..30a0ebdb456 100644 --- a/tools/cldr-code/src/main/java/org/unicode/cldr/util/Rational.java +++ b/tools/cldr-code/src/main/java/org/unicode/cldr/util/Rational.java @@ -330,6 +330,10 @@ public static Rational of(BigDecimal bigDecimal) { } } + public static Rational of(double doubleValue) { + return of(new BigDecimal(doubleValue)); + } + public enum FormatStyle { /** * Simple numerator / denominator, plain BigInteger.toString(), dropping " / 1".
@@ -728,4 +732,8 @@ public boolean approximatelyEquals(Rational b, Rational epsilon) { public boolean approximatelyEquals(Rational b) { return approximatelyEquals(b, EPSILON); } + + public boolean approximatelyEquals(Number b) { + return approximatelyEquals(Rational.of(b.doubleValue()), EPSILON); + } } diff --git a/tools/cldr-code/src/main/java/org/unicode/cldr/util/UnitConverter.java b/tools/cldr-code/src/main/java/org/unicode/cldr/util/UnitConverter.java index 694dee2e677..8327307b7a0 100644 --- a/tools/cldr-code/src/main/java/org/unicode/cldr/util/UnitConverter.java +++ b/tools/cldr-code/src/main/java/org/unicode/cldr/util/UnitConverter.java @@ -1825,19 +1825,27 @@ public Set getSystemsEnum(String unit) { UnitId id = createUnitId(unit); // we walk through all the units in the numerator and denominator, and keep the - // *intersection* of - // the units. So {ussystem} and {ussystem, uksystem} => ussystem - // Special case: {dmetric} intersect {metric} => {dmetric}. We do that by adding dmetric to - // any set with metric, then removing dmetric if there is a metric + // *intersection* of the units. + // So {ussystem} and {ussystem, uksystem} => ussystem + // Special case: {metric_adjacent} intersect {metric} => {metric_adjacent}. + // We do that by adding metric_adjacent to any set with metric, + // then removing metric_adjacent if there is a metric. + // Same for si_acceptable. main: for (Map unitsToPowers : Arrays.asList(id.denUnitsToPowers, id.numUnitsToPowers)) { for (String subunit : unitsToPowers.keySet()) { subunit = UnitConverter.stripPrefix(subunit, null); Set systems = new TreeSet<>(sourceToSystems.get(subunit)); + if (systems.contains(UnitSystem.metric)) { + systems.add(UnitSystem.metric_adjacent); + } + if (systems.contains(UnitSystem.si)) { + systems.add(UnitSystem.si_acceptable); + } if (result == null) { - result = systems; + result = systems; // first setting } else { result.retainAll(systems); } @@ -1846,9 +1854,17 @@ public Set getSystemsEnum(String unit) { } } } - return result == null || result.isEmpty() - ? ImmutableSet.of(UnitSystem.other) - : ImmutableSet.copyOf(EnumSet.copyOf(result)); + if (result == null || result.isEmpty()) { + return ImmutableSet.of(UnitSystem.other); + } + if (result.contains(UnitSystem.metric)) { + result.remove(UnitSystem.metric_adjacent); + } + if (result.contains(UnitSystem.si)) { + result.remove(UnitSystem.si_acceptable); + } + + return ImmutableSet.copyOf(EnumSet.copyOf(result)); // the enum is to sort } // private void addSystems(Set result, String subunit) { diff --git a/tools/cldr-code/src/main/java/org/unicode/cldr/util/UnitPreferences.java b/tools/cldr-code/src/main/java/org/unicode/cldr/util/UnitPreferences.java index 79ccb40a39c..299af7824ea 100644 --- a/tools/cldr-code/src/main/java/org/unicode/cldr/util/UnitPreferences.java +++ b/tools/cldr-code/src/main/java/org/unicode/cldr/util/UnitPreferences.java @@ -265,9 +265,15 @@ public UnitPreference getUnitPreference( Rational conversion = converter.convert(sourceAmount, sourceUnit, mu, false); return new UnitPreference(conversion, mu, null); } + String region = resolveRegion(locale); + + return getUnitPreference(sourceAmount, sourceUnit, usage, region); + } + public UnitPreference getUnitPreference( + Rational sourceAmount, String sourceUnit, String usage, String region) { + UnitConverter converter = SupplementalDataInfo.getInstance().getUnitConverter(); String quantity = converter.getQuantityFromUnit(sourceUnit, false); - String baseUnit = converter.getBaseUnitFromQuantity(quantity); Map> usageToRegionsToInfo = getFastMap().get(quantity); @@ -296,7 +302,6 @@ public UnitPreference getUnitPreference( sourceAmount = Rational.NEGATIVE_ONE; } - String region = resolveRegion(locale); Collection infoList = regionToInfo.get(region); if (infoList == null || infoList.isEmpty()) { infoList = regionToInfo.get("001"); diff --git a/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestUnits.java b/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestUnits.java index 4a20addbb1b..a2c94c4cd6e 100644 --- a/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestUnits.java +++ b/tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestUnits.java @@ -39,10 +39,12 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.io.UncheckedIOException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -4519,17 +4521,104 @@ public void testQuantitiesMissingFromPreferences() { "There are no explicit preferences for %s, but %s is not metric", quantity, unit)); } - Set systems = converter.getSystemsEnum(pref.unit); + Set prefSystems = converter.getSystemsEnum(pref.unit); String errorOrWarningString = String.format( "Test default preference is metric: input unit=%s, quantity=%s, pref-unit=%s, systems: %s", - unit, quantity, pref.unit, systems); - if (Collections.disjoint(systems, UnitSystem.SiOrMetric)) { + unit, quantity, pref.unit, prefSystems); + if (Collections.disjoint(prefSystems, UnitSystem.SiOrMetric)) { + Set prefSystems2 = converter.getSystemsEnum(pref.unit); errln(errorOrWarningString); } else { logln("OK " + errorOrWarningString); } } } + + public void testUnitPreferencesTest() { + try { + UnitPreferences prefs = SDI.getUnitPreferences(); + + // # Quantity; Usage; Region; Input (r); Input (d); Input Unit; Output (r); + // Output (d); Output Unit + // Example: + // area; default; 001; 1100000; 1100000.0; square-meter; + // 11/10; 1.1; square-kilometer + // duration; media; 001; 66; 66.0; second; 1; minute; 6; + // 6.0; second + Files.lines(Path.of(CLDRPaths.TEST_DATA + "units/unitPreferencesTest.txt")) + .forEach( + line -> { + if (line.startsWith("#") || line.isBlank()) { + return; + } + try { + List parts = SPLIT_SEMI.splitToList(line); + Map highMixed_unit_identifiers = + new LinkedHashMap<>(); + String quantity = parts.get(0); + String usage = parts.get(1); + String region = parts.get(2); + Rational inputRational = Rational.of(parts.get(3)); + double inputDouble = Double.parseDouble(parts.get(4)); + String inputUnit = parts.get(5); + // account for multi-part output + int size = parts.size(); + // This section has larger elements with integer values + for (int i = 6; i < size - 3; i += 2) { + highMixed_unit_identifiers.put( + parts.get(i + 1), Long.parseLong(parts.get(i))); + } + Rational expectedValue = Rational.of(parts.get(size - 3)); + Double expectedValueDouble = + Double.parseDouble(parts.get(size - 2)); + String expectedOutputUnit = parts.get(size - 1); + + // Check that the double values are approximately the same as + // the Rational ones + assertTrue( + String.format( + "input rational ~ input double, %s %s", + inputRational, inputDouble), + inputRational.approximatelyEquals(inputDouble)); + assertTrue( + String.format( + "output rational ~ output double, %s %s", + expectedValue, expectedValueDouble), + expectedValue.approximatelyEquals(expectedValueDouble)); + + // check that the quantity is consistent + String expectedQuantity = + converter.getQuantityFromUnit(inputUnit, false); + assertEquals( + "Input: Quantity consistency check", + expectedQuantity, + quantity); + + // TODO handle mixed_unit_identifiers + if (!highMixed_unit_identifiers.isEmpty()) { + warnln("mixed_unit_identifiers not yet checked: " + line); + return; + } + // check output unit, then value + UnitPreference unitPreference = + prefs.getUnitPreference( + inputRational, inputUnit, usage, region); + String actualUnit = unitPreference.unit; + assertEquals("Output unit", expectedOutputUnit, actualUnit); + + Rational actualValue = + converter.convert( + inputRational, inputUnit, actualUnit, false); + assertEquals( + "Output numeric value", expectedValue, actualValue); + } catch (Exception e) { + errln(e.getMessage() + "\n\t" + line); + } + }); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } }