From 00cedadc92427612ec76eca4e7388d43295a2d8e Mon Sep 17 00:00:00 2001 From: Younies Mahmoud Date: Fri, 19 Feb 2021 01:50:46 +0000 Subject: [PATCH] ICU-21349 Add extra ComplexUnitsConverter constructor that takes only CLDR units Identifier See #1586 --- icu4c/source/i18n/units_complexconverter.cpp | 15 +++++ icu4c/source/i18n/units_complexconverter.h | 27 +++++++-- icu4c/source/test/intltest/units_test.cpp | 42 +++++++------- .../icu/impl/units/ComplexUnitsConverter.java | 24 +++++++- .../com/ibm/icu/dev/test/impl/UnitsTest.java | 56 +++++++++++-------- 5 files changed, 110 insertions(+), 54 deletions(-) diff --git a/icu4c/source/i18n/units_complexconverter.cpp b/icu4c/source/i18n/units_complexconverter.cpp index 2aa331ac3f0..8ecb1b38531 100644 --- a/icu4c/source/i18n/units_complexconverter.cpp +++ b/icu4c/source/i18n/units_complexconverter.cpp @@ -42,9 +42,24 @@ ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &targetUnit, return; } } + this->init(*biggestUnit, ratesInfo, status); } +ComplexUnitsConverter::ComplexUnitsConverter(StringPiece inputUnitIdentifier, + StringPiece outputUnitsIdentifier, UErrorCode &status) { + if (U_FAILURE(status)) { + return; + } + MeasureUnitImpl inputUnit = MeasureUnitImpl::forIdentifier(inputUnitIdentifier, status); + MeasureUnitImpl outputUnits = MeasureUnitImpl::forIdentifier(outputUnitsIdentifier, status); + + this->units_ = outputUnits.extractIndividualUnitsWithIndices(status); + U_ASSERT(units_.length() != 0); + + this->init(inputUnit, ConversionRates(status), status); +} + ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, const MeasureUnitImpl &outputUnits, const ConversionRates &ratesInfo, UErrorCode &status) diff --git a/icu4c/source/i18n/units_complexconverter.h b/icu4c/source/i18n/units_complexconverter.h index 581e52f3cb3..21f3d9b49b9 100644 --- a/icu4c/source/i18n/units_complexconverter.h +++ b/icu4c/source/i18n/units_complexconverter.h @@ -52,14 +52,15 @@ class U_I18N_API ComplexUnitsConverter : public UMemory { * Constructs `ComplexUnitsConverter` for an `targetUnit` that could be Single, Compound or Mixed. * In case of: * 1- Single and Compound units, - * the conversion will not perform anything, the input will be equal to the output. + * the conversion will not perform anything, the input will be equal to the output. * 2- Mixed Unit - * the conversion will consider the input is the biggest unit. And will convert it to be spread - * through the target units. For example: if target unit is "inch-and-foot", and the input is 2.5. The - * converter will consider the input value in "foot", because foot is the biggest unit. Then, it - * will convert 2.5 feet to "inch-and-foot". + * the conversion will consider the input is the biggest unit. And will convert it to be spread + * through the target units. For example: if target unit is "inch-and-foot", and the input is 2.5. + * The converter will consider the input value in "foot", because foot is the biggest unit. + * Then, it will convert 2.5 feet to "inch-and-foot". * - * @param targetUnit could be any type. (single, compound or mixed). + * @param targetUnit could be any units type (single, compound or mixed). + * @param ratesInfo * @param status */ ComplexUnitsConverter(const MeasureUnitImpl &targetUnit, const ConversionRates &ratesInfo, @@ -74,6 +75,20 @@ class U_I18N_API ComplexUnitsConverter : public UMemory { * @param outputUnits represents the output unit. could be any type. (single, compound or mixed). * @param status */ + ComplexUnitsConverter(StringPiece inputUnitIdentifier, StringPiece outputUnitsIdentifier, + UErrorCode &status); + + /** + * Constructor of `ComplexUnitsConverter`. + * NOTE: + * - inputUnit and outputUnits must be under the same category + * - e.g. meter to feet and inches --> all of them are length units. + * + * @param inputUnit represents the source unit. (should be single or compound unit). + * @param outputUnits represents the output unit. could be any type. (single, compound or mixed). + * @param ratesInfo + * @param status + */ ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, const MeasureUnitImpl &outputUnits, const ConversionRates &ratesInfo, UErrorCode &status); diff --git a/icu4c/source/test/intltest/units_test.cpp b/icu4c/source/test/intltest/units_test.cpp index 93b865aeb18..3a8522bf46f 100644 --- a/icu4c/source/test/intltest/units_test.cpp +++ b/icu4c/source/test/intltest/units_test.cpp @@ -515,7 +515,7 @@ void UnitsTest::testConverterWithCLDRTests() { void UnitsTest::testComplexUnitsConverter() { IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverter"); - // DBL_EPSILON is aproximately 2.22E-16, and is the precision of double for + // DBL_EPSILON is approximately 2.22E-16, and is the precision of double for // values in the range [1.0, 2.0), but half the precision of double for // [2.0, 4.0). U_ASSERT(1.0 + DBL_EPSILON > 1.0); @@ -621,15 +621,10 @@ void UnitsTest::testComplexUnitsConverter() { MeasureUnit input, output; MeasureUnitImpl tempInput, tempOutput; MaybeStackVector measures; - for (const TestCase &testCase : testCases) { - input = MeasureUnit::forIdentifier(testCase.input, status); - output = MeasureUnit::forIdentifier(testCase.output, status); - const MeasureUnitImpl& inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status); - const MeasureUnitImpl& outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status); - auto converter = ComplexUnitsConverter(inputImpl, outputImpl, rates, status); + auto testATestCase = [&](const ComplexUnitsConverter& converter ,StringPiece initMsg , const TestCase &testCase) { measures = converter.convert(testCase.value, nullptr, status); - CharString msg; + CharString msg(initMsg, status); msg.append(testCase.msg, status); msg.append(" ", status); msg.append(testCase.input, status); @@ -650,7 +645,24 @@ void UnitsTest::testComplexUnitsConverter() { assertEquals(msg.data(), testCase.expected[i].getUnit().getIdentifier(), measures[i]->getUnit().getIdentifier()); } + }; + + for (const auto &testCase : testCases) + { + input = MeasureUnit::forIdentifier(testCase.input, status); + output = MeasureUnit::forIdentifier(testCase.output, status); + const MeasureUnitImpl& inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status); + const MeasureUnitImpl& outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status); + + ComplexUnitsConverter converter1(inputImpl, outputImpl, rates, status); + testATestCase(converter1, "ComplexUnitsConverter #1 " , testCase); + + // Test ComplexUnitsConverter created with CLDR units identifiers. + ComplexUnitsConverter converter2( testCase.input, testCase.output, status); + testATestCase(converter2, "ComplexUnitsConverter #1 " , testCase); } + + status.assertSuccess(); // TODO(icu-units#63): test negative numbers! @@ -714,20 +726,6 @@ void UnitsTest::testComplexUnitsConverterSorting() { } } } - - MeasureUnitImpl source = MeasureUnitImpl::forIdentifier("meter", status); - MeasureUnitImpl target = MeasureUnitImpl::forIdentifier("inch-and-foot", status); - - ComplexUnitsConverter complexConverter(source, target, conversionRates, status); - auto measures = complexConverter.convert(10.0, nullptr, status); - - if (2 == measures.length()) { - assertEquals("inch-and-foot unit 0", "inch", measures[0]->getUnit().getIdentifier()); - assertEquals("inch-and-foot unit 1", "foot", measures[1]->getUnit().getIdentifier()); - - assertEqualsNear("inch-and-foot value 0", 9.7008, measures[0]->getNumber().getDouble(), 0.0001); - assertEqualsNear("inch-and-foot value 1", 32, measures[1]->getNumber().getInt64(), 0.00001); - } } /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/ComplexUnitsConverter.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/ComplexUnitsConverter.java index 134faa33e26..8a2b2f27f75 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/ComplexUnitsConverter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/ComplexUnitsConverter.java @@ -64,14 +64,32 @@ public class ComplexUnitsConverter { * Constructs ComplexUnitsConverter NOTE: - inputUnit and outputUnits must be under the same category - * e.g. meter to feet and inches --> all of them are length units. * - * @param targetUnit + * @param inputUnitIdentifier + * represents the source unit identifier. (should be single or compound unit). + * @param outputUnitsIdentifier + * represents the output unit identifier. could be any type. (single, compound or mixed). + */ + public ComplexUnitsConverter(String inputUnitIdentifier, String outputUnitsIdentifier) { + this( + MeasureUnitImpl.forIdentifier(inputUnitIdentifier), + MeasureUnitImpl.forIdentifier(outputUnitsIdentifier), + new ConversionRates() + ); + } + + /** + * Constructs ComplexUnitsConverter NOTE: - inputUnit and outputUnits must be under the same category - + * e.g. meter to feet and inches --> all of them are length units. + * + * @param inputUnit * represents the source unit. (should be single or compound unit). * @param outputUnits * represents the output unit. could be any type. (single, compound or mixed). + * @param conversionRates */ - public ComplexUnitsConverter(MeasureUnitImpl targetUnit, MeasureUnitImpl outputUnits, + public ComplexUnitsConverter(MeasureUnitImpl inputUnit, MeasureUnitImpl outputUnits, ConversionRates conversionRates) { - this.inputUnit_ = targetUnit; + this.inputUnit_ = inputUnit; this.units_ = outputUnits.extractIndividualUnitsWithIndices(); assert (!this.units_.isEmpty()); 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 8155be3bb33..952ffcdc756 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 @@ -55,7 +55,31 @@ public class UnitsTest { this.expected = expected; this.accuracy = accuracy; } + + void testATestCase(ComplexUnitsConverter converter) { + List measures = converter.convert(value, null).measures; + + assertEquals("measures length", expected.length, measures.size()); + int i = 0; + for (Measure measure : measures) { + double accuracy = 0.0; + if (i == expected.length - 1) { + accuracy = accuracy; + } + assertTrue("input " + value + ", output measure " + i + ": expected " + + expected[i] + ", expected unit " + + expected[i].getUnit() + " got unit " + measure.getUnit(), + expected[i].getUnit().equals(measure.getUnit())); + assertEquals("input " + value + ", output measure " + i + ": expected " + + expected[i] + ", expected number " + + expected[i].getNumber() + " got number " + measure.getNumber(), + expected[i].getNumber().doubleValue(), + measure.getNumber().doubleValue(), accuracy); + i++; + } + } } + TestCase[] testCases = new TestCase[] { // Significantly less than 2.0. new TestCase( @@ -129,35 +153,21 @@ public class UnitsTest { 0), }; + ConversionRates rates = new ConversionRates(); MeasureUnit input, output; - List measures; for (TestCase testCase : testCases) { input = MeasureUnit.forIdentifier(testCase.input); output = MeasureUnit.forIdentifier(testCase.output); final MeasureUnitImpl inputImpl = MeasureUnitImpl.forIdentifier(input.getIdentifier()); final MeasureUnitImpl outputImpl = MeasureUnitImpl.forIdentifier(output.getIdentifier()); - ComplexUnitsConverter converter = new ComplexUnitsConverter(inputImpl, outputImpl, rates); - measures = converter.convert(testCase.value, null).measures; - - assertEquals("measures length", testCase.expected.length, measures.size()); - int i = 0; - for (Measure measure : measures) { - double accuracy = 0.0; - if (i == testCase.expected.length - 1) { - accuracy = testCase.accuracy; - } - assertTrue("input " + testCase.value + ", output measure " + i + ": expected " + - testCase.expected[i] + ", expected unit " + - testCase.expected[i].getUnit() + " got unit " + measure.getUnit(), - testCase.expected[i].getUnit().equals(measure.getUnit())); - assertEquals("input " + testCase.value + ", output measure " + i + ": expected " + - testCase.expected[i] + ", expected number " + - testCase.expected[i].getNumber() + " got number " + measure.getNumber(), - testCase.expected[i].getNumber().doubleValue(), - measure.getNumber().doubleValue(), accuracy); - i++; - } + ComplexUnitsConverter converter1 = new ComplexUnitsConverter(inputImpl, outputImpl, rates); + + testCase.testATestCase(converter1); + + // Test ComplexUnitsConverter created with CLDR units identifiers. + ComplexUnitsConverter converter2 = new ComplexUnitsConverter(testCase.input, testCase.output); + testCase.testATestCase(converter2); } // TODO(icu-units#63): test negative numbers! @@ -165,7 +175,7 @@ public class UnitsTest { @Test - public void testComplexUnitConverterSorting() { + public void testComplexUnitsConverterSorting() { class TestCase { String message; String inputUnit; -- 2.40.0