diff --git a/icu4c/source/i18n/units_converter.cpp b/icu4c/source/i18n/units_converter.cpp index 710363efb65..4858cbd233c 100644 --- a/icu4c/source/i18n/units_converter.cpp +++ b/icu4c/source/i18n/units_converter.cpp @@ -490,15 +490,36 @@ Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source, } UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target, - const ConversionRates &ratesInfo, UErrorCode &status) + const ConversionRates &ratesInfo, UErrorCode &status) : conversionRate_(source.copy(status), target.copy(status)) { - if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || - target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { + this->init(ratesInfo, status); +} + +UnitsConverter::UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier, + UErrorCode &status) + : conversionRate_(MeasureUnitImpl::forIdentifier(sourceIdentifier, status), + MeasureUnitImpl::forIdentifier(targetIdentifier, status)) { + if (U_FAILURE(status)) { + return; + } + + ConversionRates ratesInfo(status); + this->init(ratesInfo, status); +} + +void UnitsConverter::init(const ConversionRates &ratesInfo, UErrorCode &status) { + if (U_FAILURE(status)) { + return; + } + + if (this->conversionRate_.source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || + this->conversionRate_.target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { status = U_INTERNAL_PROGRAM_ERROR; return; } - Convertibility unitsState = extractConvertibility(source, target, ratesInfo, status); + Convertibility unitsState = extractConvertibility(this->conversionRate_.source, + this->conversionRate_.target, ratesInfo, status); if (U_FAILURE(status)) return; if (unitsState == Convertibility::UNCONVERTIBLE) { status = U_INTERNAL_PROGRAM_ERROR; @@ -507,11 +528,12 @@ UnitsConverter::UnitsConverter(const MeasureUnitImpl &source, const MeasureUnitI loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState, ratesInfo, status); + } int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit, - const MeasureUnitImpl &secondUnit, - const ConversionRates &ratesInfo, UErrorCode &status) { + const MeasureUnitImpl &secondUnit, + const ConversionRates &ratesInfo, UErrorCode &status) { if (U_FAILURE(status)) { return 0; } @@ -532,8 +554,9 @@ int32_t UnitsConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit, return 0; } - // Represents the conversion factor from the firstUnit to the base unit that specified in the - // conversion data which is considered as the root of the firstUnit and the secondUnit. + // Represents the conversion factor from the firstUnit to the base + // unit that specified in the conversion data which is considered as + // the root of the firstUnit and the secondUnit. Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status); Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status); diff --git a/icu4c/source/i18n/units_converter.h b/icu4c/source/i18n/units_converter.h index e8a0caefbea..1d5a3f86c98 100644 --- a/icu4c/source/i18n/units_converter.h +++ b/icu4c/source/i18n/units_converter.h @@ -145,6 +145,20 @@ Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source, */ class U_I18N_API UnitsConverter : public UMemory { public: + /** + * Constructor of `UnitConverter`. + * NOTE: + * - source and target must be under the same category + * - e.g. meter to mile --> both of them are length units. + * NOTE: + * This constructor creates an instance of `ConversionRates` internally. + * + * @param sourceIdentifier represents the source unit identifier. + * @param targetIdentifier represents the target unit identifier. + * @param status + */ + UnitsConverter(StringPiece sourceIdentifier, StringPiece targetIdentifier, UErrorCode &status); + /** * Constructor of `UnitConverter`. * NOTE: @@ -191,6 +205,11 @@ class U_I18N_API UnitsConverter : public UMemory { private: ConversionRate conversionRate_; + + /** + * Initialises the object. + */ + void init(const ConversionRates &ratesInfo, UErrorCode &status); }; } // namespace units diff --git a/icu4c/source/test/intltest/units_test.cpp b/icu4c/source/test/intltest/units_test.cpp index a18e751e470..8e9bfbc75ed 100644 --- a/icu4c/source/test/intltest/units_test.cpp +++ b/icu4c/source/test/intltest/units_test.cpp @@ -363,6 +363,30 @@ void UnitsTest::testConverter() { assertEqualsNear( UnicodeString("testConverter inverse: ") + testCase.target + " back to " + testCase.source, testCase.inputValue, converter.convertInverse(testCase.expectedValue), maxDelta); + + + // TODO: Test UnitsConverter created using CLDR separately. + // Test UnitsConverter created by CLDR unit identifiers + UnitsConverter converter2(testCase.source, testCase.target, status); + if (status.errIfFailureAndReset("UnitsConverter(<%s>, <%s>, ...)", testCase.source, + testCase.target)) { + continue; + } + + maxDelta = 1e-6 * uprv_fabs(testCase.expectedValue); + if (testCase.expectedValue == 0) { + maxDelta = 1e-12; + } + assertEqualsNear(UnicodeString("testConverter2: ") + testCase.source + " to " + testCase.target, + testCase.expectedValue, converter2.convert(testCase.inputValue), maxDelta); + + maxDelta = 1e-6 * uprv_fabs(testCase.inputValue); + if (testCase.inputValue == 0) { + maxDelta = 1e-12; + } + assertEqualsNear( + UnicodeString("testConverter2 inverse: ") + testCase.target + " back to " + testCase.source, + testCase.inputValue, converter2.convertInverse(testCase.expectedValue), maxDelta); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsConverter.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsConverter.java index f732c2f5c34..9f80f439246 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsConverter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsConverter.java @@ -16,6 +16,26 @@ public class UnitsConverter { private boolean reciprocal; private BigDecimal offset; + /** + * Constructor of UnitsConverter. + * NOTE: + * - source and target must be under the same category + * - e.g. meter to mile --> both of them are length units. + *

+ * NOTE: + * This constructor creates an instance of UnitsConverter internally. + * + * @param sourceIdentifier represents the source unit identifier. + * @param targetIdentifier represents the target unit identifier. + */ + public UnitsConverter(String sourceIdentifier, String targetIdentifier) { + this( + MeasureUnitImpl.forIdentifier(sourceIdentifier), + MeasureUnitImpl.forIdentifier(targetIdentifier), + new ConversionRates() + ); + } + /** * Constructor of UnitsConverter. * NOTE: 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 952ffcdc756..107503a247e 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 @@ -371,14 +371,14 @@ public class UnitsTest { @Test public void testConverter() { class TestData { - MeasureUnitImpl source; - MeasureUnitImpl target; - BigDecimal input; - BigDecimal expected; + final String sourceIdentifier; + final String targetIdentifier; + final BigDecimal input; + final BigDecimal expected; - TestData(String source, String target, double input, double expected) { - this.source = MeasureUnitImpl.UnitsParser.parseForIdentifier(source); - this.target = MeasureUnitImpl.UnitsParser.parseForIdentifier(target); + TestData(String sourceIdentifier, String targetIdentifier, double input, double expected) { + this.sourceIdentifier = sourceIdentifier; + this.targetIdentifier = targetIdentifier; this.input = BigDecimal.valueOf(input); this.expected = BigDecimal.valueOf(expected); } @@ -440,23 +440,48 @@ public class UnitsTest { ConversionRates conversionRates = new ConversionRates(); for (TestData test : tests) { - UnitsConverter converter = new UnitsConverter(test.source, test.target, conversionRates); + MeasureUnitImpl source = MeasureUnitImpl.forIdentifier(test.sourceIdentifier); + MeasureUnitImpl target = MeasureUnitImpl.forIdentifier(test.targetIdentifier); + + UnitsConverter converter = new UnitsConverter(source, target, conversionRates); double maxDelta = 1e-6 * Math.abs(test.expected.doubleValue()); if (test.expected.doubleValue() == 0) { maxDelta = 1e-12; } - assertEquals("testConverter: " + test.source + " to " + test.target, - test.expected.doubleValue(), converter.convert(test.input).doubleValue(), - maxDelta); + assertEquals("testConverter: " + test.sourceIdentifier + " to " + test.targetIdentifier, + test.expected.doubleValue(), converter.convert(test.input).doubleValue(), + maxDelta); maxDelta = 1e-6 * Math.abs(test.input.doubleValue()); if (test.input.doubleValue() == 0) { maxDelta = 1e-12; } - assertEquals("testConverter inverse: " + test.target + " back to " + test.source, - test.input.doubleValue(), converter.convertInverse(test.expected).doubleValue(), - maxDelta); + assertEquals( + "testConverter inverse: " + test.targetIdentifier + " back to " + test.sourceIdentifier, + test.input.doubleValue(), converter.convertInverse(test.expected).doubleValue(), + maxDelta); + + + // TODO: Test UnitsConverter created using CLDR separately. + // Test UnitsConverter created by CLDR unit identifiers + UnitsConverter converter2 = new UnitsConverter(test.sourceIdentifier, test.targetIdentifier); + + maxDelta = 1e-6 * Math.abs(test.expected.doubleValue()); + if (test.expected.doubleValue() == 0) { + maxDelta = 1e-12; + } + assertEquals("testConverter2: " + test.sourceIdentifier + " to " + test.targetIdentifier, + test.expected.doubleValue(), converter2.convert(test.input).doubleValue(), + maxDelta); + + maxDelta = 1e-6 * Math.abs(test.input.doubleValue()); + if (test.input.doubleValue() == 0) { + maxDelta = 1e-12; + } + assertEquals("testConverter2 inverse: " + test.targetIdentifier + " back to " + test.sourceIdentifier, + test.input.doubleValue(), converter2.convertInverse(test.expected).doubleValue(), + maxDelta); } }