]> granicus.if.org Git - icu/commitdiff
Intermediate work on new unit identifiers
authorShane F. Carr <shane@unicode.org>
Mon, 13 Jan 2020 20:52:52 +0000 (21:52 +0100)
committerShane F. Carr <shane@unicode.org>
Fri, 17 Jan 2020 16:14:18 +0000 (17:14 +0100)
icu4c/source/i18n/measunit.cpp
icu4c/source/i18n/unicode/measunit.h
icu4c/source/test/intltest/measfmttest.cpp

index 66ddd0a1d54663c66079332569c60d9233be0106..0886b23caf75e85e0ca32dbf5e9bef532365233a 100644 (file)
@@ -2006,21 +2006,54 @@ static int32_t binarySearch(
     return -1;
 }
 
-MeasureUnit::MeasureUnit() {
+MeasureUnit::MeasureUnit() : MeasureUnit(kBaseTypeIdx, kBaseSubTypeIdx) {
+}
+
+MeasureUnit::MeasureUnit(int32_t typeId, int32_t subTypeId)
+        : fId(nullptr), fSubTypeId(subTypeId), fTypeId(typeId) {
     fCurrency[0] = 0;
-    fTypeId = kBaseTypeIdx;
-    fSubTypeId = kBaseSubTypeIdx;
 }
 
-MeasureUnit::MeasureUnit(const MeasureUnit &other)
-        : fTypeId(other.fTypeId), fSubTypeId(other.fSubTypeId) {
+MeasureUnit::MeasureUnit(const MeasureUnit &other) {
+    *this = other;
+}
+
+MeasureUnit::MeasureUnit(MeasureUnit &&other) noexcept
+        : fId(other.fId),
+        fSubTypeId(other.fSubTypeId),
+        fTypeId(other.fTypeId) {
     uprv_strcpy(fCurrency, other.fCurrency);
+    other.fId = nullptr;
 }
 
 MeasureUnit &MeasureUnit::operator=(const MeasureUnit &other) {
     if (this == &other) {
         return *this;
     }
+    if (other.fId) {
+        auto* id = static_cast<char*>(uprv_malloc(uprv_strlen(other.fId) + 1));
+        if (!id) {
+            // Unrecoverable allocation error; set to the default unit
+            *this = MeasureUnit();
+            return *this;
+        }
+        uprv_strcpy(id, other.fId);
+        fId = id;
+    } else {
+        fId = nullptr;
+    }
+    fTypeId = other.fTypeId;
+    fSubTypeId = other.fSubTypeId;
+    uprv_strcpy(fCurrency, other.fCurrency);
+    return *this;
+}
+
+MeasureUnit &MeasureUnit::operator=(MeasureUnit &&other) noexcept {
+    if (this == &other) {
+        return *this;
+    }
+    fId = other.fId;
+    other.fId = nullptr;
     fTypeId = other.fTypeId;
     fSubTypeId = other.fSubTypeId;
     uprv_strcpy(fCurrency, other.fCurrency);
@@ -2032,6 +2065,8 @@ MeasureUnit *MeasureUnit::clone() const {
 }
 
 MeasureUnit::~MeasureUnit() {
+    uprv_free(const_cast<char*>(fId));
+    fId = nullptr;
 }
 
 const char *MeasureUnit::getType() const {
@@ -2042,6 +2077,10 @@ const char *MeasureUnit::getSubtype() const {
     return fCurrency[0] == 0 ? gSubTypes[getOffset()] : fCurrency;
 }
 
+const char *MeasureUnit::toString() const {
+    return fId ? fId : getSubtype();
+}
+
 UBool MeasureUnit::operator==(const UObject& other) const {
     if (this == &other) {  // Same object, equal
         return TRUE;
@@ -2050,10 +2089,7 @@ UBool MeasureUnit::operator==(const UObject& other) const {
         return FALSE;
     }
     const MeasureUnit &rhs = static_cast<const MeasureUnit&>(other);
-    return (
-            fTypeId == rhs.fTypeId
-            && fSubTypeId == rhs.fSubTypeId
-            && uprv_strcmp(fCurrency, rhs.fCurrency) == 0);
+    return uprv_strcmp(toString(), rhs.toString()) == 0;
 }
 
 int32_t MeasureUnit::getIndex() const {
index ebcf6914fdb377d9c754448abedf39b0c53ecbee..cca944cc8ff888c81d51bd17544b5193c7785528 100644 (file)
 U_NAMESPACE_BEGIN
 
 class StringEnumeration;
+class MeasureUnitFields;
+
+/**
+ * Enumeration for SI prefixes, such as "kilo".
+ *
+ * @draft ICU 67
+ */
+typedef enum UMeasureSIPrefix {
+
+    /**
+     * SI prefix: yotta, 10^24.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_YOTTA = 24,
+
+    /**
+     * SI prefix: zetta, 10^21.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_ZETTA = 21,
+
+    /**
+     * SI prefix: exa, 10^18.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_EXA = 18,
+
+    /**
+     * SI prefix: peta, 10^15.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_PETA = 15,
+
+    /**
+     * SI prefix: tera, 10^12.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_TERA = 12,
+
+    /**
+     * SI prefix: giga, 10^9.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_GIGA = 9,
+
+    /**
+     * SI prefix: mega, 10^6.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_MEGA = 6,
+
+    /**
+     * SI prefix: kilo, 10^3.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_KILO = 3,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_HECTO = 2,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_DEKA = 1,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_ONE = 0,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_DECI = -1,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_CENTI = -2,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_MILLI = -3,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_MICRO = -6,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_NANO = -9,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_PICO = -12,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_FEMTO = -15,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_ATTO = -18,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_ZEPTO = -21,
+
+    /**
+     * SI prefix: FIXME, 10^FIXME.
+     *
+     * @draft ICU 67
+     */
+    UMEASURE_SI_PREFIX_YOCTO = -24
+} UMeasureSIPrefix;
 
 /**
  * A unit such as length, mass, volume, currency, etc.  A unit is
@@ -52,13 +208,39 @@ class U_I18N_API MeasureUnit: public UObject {
      * @stable ICU 3.0
      */
     MeasureUnit(const MeasureUnit &other);
-        
+    
+    /**
+     * Move constructor.
+     * @stable ICU 3.0
+     */
+    MeasureUnit(MeasureUnit &&other) noexcept;
+
+    /**
+     * Construct a MeasureUnit from a CLDR Sequence Unit Identifier, defined in UTS 35.
+     * Validates and canonicalizes the identifier.
+     *
+     * <pre>
+     * MeasureUnit example = MeasureUnit::forIdentifier("furlong-per-nanosecond")
+     * </pre>
+     *
+     * @param id The CLDR Sequence Unit Identifier
+     * @param status Set if the identifier is invalid.
+     * @draft ICU 67
+     */
+    static MeasureUnit forIdentifier(const char* identifier, UErrorCode& status);
+
     /**
-     * Assignment operator.
+     * Copy assignment operator.
      * @stable ICU 3.0
      */
     MeasureUnit &operator=(const MeasureUnit &other);
 
+    /**
+     * Move assignment operator.
+     * @stable ICU 3.0
+     */
+    MeasureUnit &operator=(MeasureUnit &&other) noexcept;
+
     /**
      * Returns a polymorphic clone of this object.  The result will
      * have the same class as returned by getDynamicClassID().
@@ -90,16 +272,177 @@ class U_I18N_API MeasureUnit: public UObject {
 
     /**
      * Get the type.
+     *
+     * If the unit does not have a type, the empty string is returned.
+     *
      * @stable ICU 53
      */
     const char *getType() const;
 
     /**
      * Get the sub type.
+     *
+     * If the unit does not have a subtype, the empty string is returned.
+     *
      * @stable ICU 53
      */
     const char *getSubtype() const;
 
+    /**
+     * Get the CLDR Sequence Unit Identifier for this MeasureUnit, as defined in UTS 35.
+     *
+     * @return The string form of this unit, owned by this MeasureUnit.
+     * @draft ICU 67
+     */
+    const char* toString() const;
+
+    /**
+     * Creates a MeasureUnit which is this MeasureUnit augmented with the specified SI prefix.
+     * For example, UMEASURE_SI_PREFIX_KILO for "kilo".
+     *
+     * There is sufficient locale data to format all standard SI prefixes.
+     *
+     * If the MeasureUnit is composed of multiple simple units, only the first simple unit is
+     * modified. For example, starting at "meter-kilogram-per-second", if you set the SI prefix
+     * to "centi", then you get "centimeter-kilogram-per-second".
+     *
+     * @param prefix The SI prefix, from UMeasureSIPrefix.
+     * @return A new MeasureUnit.
+     */
+    MeasureUnit withSIPrefix(UMeasureSIPrefix prefix) const;
+
+    /**
+     * Gets the current SI prefix of this MeasureUnit. For example, if the unit has the SI prefix
+     * "kilo", then UMEASURE_SI_PREFIX_KILO is returned.
+     *
+     * If the MeasureUnit is composed of multiple simple units, the SI prefix of the first simple
+     * unit is returned. For example, from "centimeter-kilogram-per-second", the SI prefix
+     * UMEASURE_SI_PREFIX_CENTI will be returned.
+     *
+     * @return The SI prefix of the first simple unit, from UMeasureSIPrefix.
+     */
+    UMeasureSIPrefix getSIPrefix() const;
+
+    /**
+     * Creates a MeasureUnit which is this MeasureUnit augmented with the specified power. For
+     * example, if power is 2, the unit will be squared.
+     *
+     * If the MeasureUnit is composed of multiple simple units, only the first simple unit is
+     * modified. For example, starting at "meter-kilogram-per-second", if you set the power to 2,
+     * then you get "square-meter-kilogram-per-second".
+     *
+     * @param power The power.
+     * @return A new MeasureUnit.
+     */
+    MeasureUnit withPower(int8_t power) const;
+
+    /**
+     * Gets the power of this MeasureUnit. For example, if the unit is square, then 2 is returned.
+     *
+     * If the MeasureUnit is composed of multiple simple units, the power of the first simple unit
+     * is returned. For example, from "cubic-meter-per-square-second", 3 is returned.
+     *
+     * @return The power of the first simple unit.
+     */
+    int8_t getPower() const;
+
+    /**
+     * Gets the reciprocal of the unit, with the numerator and denominator flipped.
+     *
+     * For example, if the receiver is "meter-per-second", the unit "second-per-meter" is returned.
+     *
+     * @return The reciprocal of the target unit.
+     */
+    MeasureUnit reciprocal() const;
+
+    /**
+     * Gets the product of this unit with another unit. This is a way to build units from
+     * constituent parts.
+     *
+     * The numerator and denominator are preserved through this operation.
+     *
+     * For example, if the receiver is "kilowatt" and the argument is "hour-per-day", then the
+     * unit "kilowatt-hour-per-day" is returned.
+     *
+     * @return The product of the target unit with the provided unit.
+     */
+    MeasureUnit product(const MeasureUnit& other) const;
+
+    /**
+     * Gets the number of constituent simple units.
+     *
+     * For example, if the receiver is "meter-per-square-second", then 2 is returned, since there
+     * are two simple units: "meter" and "second".
+     *
+     * @return The number of constituent units.
+     */
+    size_t getSimpleUnitCount() const;
+
+    /**
+     * Gets the constituent unit at the given index.
+     * 
+     * For example, to loop over all simple units:
+     * 
+     * <pre>
+     * MeasureUnit unit(u"meter-per-square-second");
+     * for (size_t i = 0; i < unit.getSimpleUnitCount(); i++) {
+     *     std::cout << unit.simpleUnitAt(i).toString() << std::endl;
+     * }
+     * </pre>
+     * 
+     * Expected output: meter, one-per-square-second
+     * 
+     * @param index Zero-based index. If out of range, the dimensionless unit is returned.
+     * @return The constituent simple unit at the specified position.
+     */
+    MeasureUnit simpleUnitAt(size_t index) const;
+
+    /**
+     * Composes this unit with a super unit.
+     * 
+     * A super unit, used for formatting only, should be a larger unit sharing the same dimension.
+     * For example, if the current unit is "inch", a super unit could be "foot", in order to
+     * render 71 inches as "5 feet, 11 inches". If the super unit is invalid, an error will occur
+     * during formatting.
+     *
+     * A unit can have multiple super units; for example, "second" could have both "minute" and
+     * "hour" as super units.
+     * 
+     * Super units are ignored and left untouched in most other methods, such as withSIPrefix,
+     * withPower, and reciprocal.
+     *
+     * @param other The super unit to compose with the target unit.
+     * @return The composition of the given super unit with this unit.
+     */
+    MeasureUnit withSuperUnit(const MeasureUnit& other) const;
+
+    /**
+     * Gets the number of super units in the receiver.
+     *
+     * For example, "foot+inch" has one super unit.
+     *
+     * @return The number of super units.
+     */
+    size_t getSuperUnitCount() const;
+
+    /**
+     * Gets the super unit at the given index.
+     *
+     * For example, to loop over all super units:
+     *
+     * <pre>
+     * MeasureUnit unit(u"hour+minute+second");
+     * for (size_t i = 0; i < unit.getSuperUnitCount(); i++) {
+     *     std::cout << unit.superUnitAt(i).toCoreUnitIdentifier() << std::endl;
+     * }
+     * </pre>
+     *
+     * Expected output: hour, minute
+     *
+     * @return The super unit at the specified position.
+     */
+    MeasureUnit superUnitAt(size_t index) const;
+
     /**
      * getAvailable gets all of the available units.
      * If there are too many units to fit into destCapacity then the
@@ -3353,13 +3696,13 @@ class U_I18N_API MeasureUnit: public UObject {
 #endif  /* U_HIDE_INTERNAL_API */
 
 private:
-    int32_t fTypeId;
-    int32_t fSubTypeId;
+
+    const char* fId;
     char fCurrency[4];
+    int16_t fSubTypeId;
+    int8_t fTypeId;
 
-    MeasureUnit(int32_t typeId, int32_t subTypeId) : fTypeId(typeId), fSubTypeId(subTypeId) {
-        fCurrency[0] = 0;
-    }
+    MeasureUnit(int32_t typeId, int32_t subTypeId);
     void setTo(int32_t typeId, int32_t subTypeId);
     int32_t getOffset() const;
     static MeasureUnit *create(int typeId, int subTypeId, UErrorCode &status);
index 2b53f8bc0249b4c7e1a8e7abe3ecfd8cde831686..94a499e73a4d30e0aca2d6a8b387d0faa6c596ac 100644 (file)
@@ -79,6 +79,7 @@ private:
     void Test20332_PersonUnits();
     void TestNumericTime();
     void TestNumericTimeSomeSpecialFormats();
+    void TestCompoundUnitOperations();
     void verifyFormat(
         const char *description,
         const MeasureFormat &fmt,
@@ -138,6 +139,11 @@ private:
         NumberFormat::EAlignmentFields field,
         int32_t start,
         int32_t end);
+    void verifyUnitParts(
+        const MeasureUnit& unit,
+        UMeasureSIPrefix siPrefix,
+        int8_t power,
+        const char* identifier);
 };
 
 void MeasureFormatTest::runIndexedTest(
@@ -3215,6 +3221,69 @@ void MeasureFormatTest::TestNumericTimeSomeSpecialFormats() {
     verifyFormat("Danish fhoursFminutes", fmtDa, fhoursFminutes, 2, "2.03,877");
 }
 
+void MeasureFormatTest::TestCompoundUnitOperations() {
+    IcuTestErrorCode status(*this, "TestCompoundUnitOperations");
+
+    MeasureUnit kilometer = MeasureUnit::getKilometer();
+    MeasureUnit meter = kilometer.withSIPrefix(UMEASURE_SI_PREFIX_ONE);
+    MeasureUnit centimeter1 = kilometer.withSIPrefix(UMEASURE_SI_PREFIX_CENTI);
+    MeasureUnit centimeter2 = meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI);
+
+    verifyUnitParts(kilometer, UMEASURE_SI_PREFIX_KILO, 0, "kilometer");
+    verifyUnitParts(meter, UMEASURE_SI_PREFIX_ONE, 0, "meter");
+    verifyUnitParts(centimeter1, UMEASURE_SI_PREFIX_CENTI, 0, "centimeter");
+    verifyUnitParts(centimeter2, UMEASURE_SI_PREFIX_CENTI, 0, "centimeter");
+
+    assertTrue("centimeter equality", centimeter1 == centimeter2);
+    assertTrue("kilometer inequality", centimeter1 != kilometer);
+
+    MeasureUnit squareMeter = meter.withPower(2);
+    MeasureUnit overCubicCentimeter = centimeter1.withPower(-3);
+    MeasureUnit quarticKilometer = kilometer.withPower(4);
+    MeasureUnit overQuarticKilometer1 = kilometer.withPower(-4);
+
+    verifyUnitParts(squareMeter, UMEASURE_SI_PREFIX_ONE, 2, "square-meter");
+    verifyUnitParts(overCubicCentimeter, UMEASURE_SI_PREFIX_CENTI, 2, "one-per-cubic-centimeter");
+    verifyUnitParts(quarticKilometer, UMEASURE_SI_PREFIX_ONE, 2, "p4-kilometer");
+    verifyUnitParts(overQuarticKilometer1, UMEASURE_SI_PREFIX_ONE, 2, "one-per-p4-kilometer");
+
+    assertTrue("power inequality", quarticKilometer != overQuarticKilometer1);
+
+    MeasureUnit overQuarticKilometer2 = overQuarticKilometer1.reciprocal();
+    MeasureUnit overQuarticKilometer3 = kilometer.product(kilometer).product(kilometer)
+        .product(kilometer).reciprocal();
+
+    verifyUnitParts(overQuarticKilometer2, UMEASURE_SI_PREFIX_ONE, 2, "one-per-p4-kilometer");
+    verifyUnitParts(overQuarticKilometer3, UMEASURE_SI_PREFIX_ONE, 2, "one-per-p4-kilometer");
+
+    assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer2);
+    assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer3);
+
+    MeasureUnit kiloSquareSecond = MeasureUnit::getSecond()
+        .withPower(2).withSIPrefix(UMEASURE_SI_PREFIX_KILO);
+    MeasureUnit meterSecond = meter.product(kiloSquareSecond);
+    MeasureUnit cubicMeterSecond1 = meter.withPower(3).product(kiloSquareSecond);
+    MeasureUnit cubicMeterSecond2 = meterSecond.withPower(3);
+    MeasureUnit centimeterSecond1 = meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI).product(kiloSquareSecond);
+    MeasureUnit centimeterSecond2 = meterSecond.withSIPrefix(UMEASURE_SI_PREFIX_CENTI);
+    MeasureUnit secondCubicMeter = kiloSquareSecond.product(meter.withPower(3));
+    MeasureUnit secondCentimeter = kiloSquareSecond.product(meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI));
+
+    verifyUnitParts(kiloSquareSecond, UMEASURE_SI_PREFIX_KILO, 2, "square-kilosecond");
+    verifyUnitParts(meterSecond, UMEASURE_SI_PREFIX_ONE, 0, "meter-square-kilosecond");
+    verifyUnitParts(cubicMeterSecond1, UMEASURE_SI_PREFIX_ONE, 2, "cubic-meter-square-kilosecond");
+    verifyUnitParts(cubicMeterSecond2, UMEASURE_SI_PREFIX_ONE, 2, "cubic-meter-square-kilosecond");
+    verifyUnitParts(centimeterSecond1, UMEASURE_SI_PREFIX_CENTI, 0, "centimeter-square-kilosecond");
+    verifyUnitParts(centimeterSecond2, UMEASURE_SI_PREFIX_CENTI, 0, "centimeter-square-kilosecond");
+    verifyUnitParts(secondCubicMeter, UMEASURE_SI_PREFIX_KILO, 2, "square-kilosecond-cubic-meter");
+    verifyUnitParts(secondCentimeter, UMEASURE_SI_PREFIX_KILO, 2, "square-kilosecond-centimeter");
+
+    assertTrue("multipart power equality", cubicMeterSecond1 == cubicMeterSecond2);
+    assertTrue("multipart SI prefix equality", centimeterSecond1 == centimeterSecond2);
+    assertTrue("order matters inequality", cubicMeterSecond1 != secondCubicMeter);
+    assertTrue("additional simple units inequality", secondCubicMeter != secondCentimeter);
+}
+
 
 void MeasureFormatTest::verifyFieldPosition(
         const char *description,
@@ -3286,6 +3355,18 @@ void MeasureFormatTest::verifyFormat(
     }
 }
 
+void MeasureFormatTest::verifyUnitParts(
+        const MeasureUnit& unit,
+        UMeasureSIPrefix siPrefix,
+        int8_t power,
+        const char* identifier) {
+    IcuTestErrorCode status(*this, "verifyUnitParts");
+    assertEquals(UnicodeString(identifier) + ": SI prefix", siPrefix, unit.getSIPrefix());
+    assertEquals(UnicodeString(identifier) + ": Power", power, unit.getPower());
+    assertEquals(UnicodeString(identifier) + ": Identifier", identifier, unit.toString());
+    assertTrue(UnicodeString(identifier) + ": Constructor", unit == MeasureUnit::forIdentifier(identifier, status));
+}
+
 extern IntlTest *createMeasureFormatTest() {
     return new MeasureFormatTest();
 }