diff --git a/docs/userguide/format_parse/numbers/skeletons.md b/docs/userguide/format_parse/numbers/skeletons.md index fa30ba1eddc..3ef41c2834d 100644 --- a/docs/userguide/format_parse/numbers/skeletons.md +++ b/docs/userguide/format_parse/numbers/skeletons.md @@ -299,6 +299,7 @@ integer digits): | `integer-width/##0` | - | Between 1 and 3
integer digits | `IntegerWidth::zeroFillTo(1)`
`.truncateAt(3)` | `integer-width/00` | - | Exactly 2
integer digits | `IntegerWidth::zeroFillTo(2)`
`.truncateAt(2)` | | `integer-width/*` | - | Zero or more
integer digits | `IntegerWidth::zeroFillTo(0) ` +| `integer-width-trunc` | - | Zero integer digits | `IntegerWidth::zeroFillTo(0)`
`.truncateAt(0)` The long-form option starts with either a single `*` symbol, signaling no limit on the number of integer digits (no *`truncateAt`*), or zero or more `#` symbols. @@ -310,6 +311,8 @@ symbols plus the number of `0` symbols. The concise skeleton is simply one or more `0` characters. This supports minimum integer digits but not maximum integer digits. +The special stem `integer-width-trunc` covers the case when both *`truncateAt`* and *`zeroFillTo`* are zero. + ***Prior to ICU 67***, use the symbol `+` instead of `*`. ### Scale diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 97d74303a43..0aa03f5aeb3 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -74,6 +74,7 @@ void U_CALLCONV initNumberSkeletons(UErrorCode& status) { b.add(u"rounding-mode-half-down", STEM_ROUNDING_MODE_HALF_DOWN, status); b.add(u"rounding-mode-half-up", STEM_ROUNDING_MODE_HALF_UP, status); b.add(u"rounding-mode-unnecessary", STEM_ROUNDING_MODE_UNNECESSARY, status); + b.add(u"integer-width-trunc", STEM_INTEGER_WIDTH_TRUNC, status); b.add(u"group-off", STEM_GROUP_OFF, status); b.add(u"group-min2", STEM_GROUP_MIN2, status); b.add(u"group-auto", STEM_GROUP_AUTO, status); @@ -700,6 +701,11 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se macros.roundingMode = stem_to_object::roundingMode(stem); return STATE_NULL; + case STEM_INTEGER_WIDTH_TRUNC: + CHECK_NULL(seen, integerWidth, status); + macros.integerWidth = IntegerWidth::zeroFillTo(0).truncateAt(0); + return STATE_NULL; + case STEM_GROUP_OFF: case STEM_GROUP_MIN2: case STEM_GROUP_AUTO: @@ -1677,10 +1683,15 @@ bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, // Error or Default return false; } + const auto& minMaxInt = macros.integerWidth.fUnion.minMaxInt; + if (minMaxInt.fMinInt == 0 && minMaxInt.fMaxInt == 0) { + sb.append(u"integer-width-trunc", -1); + return true; + } sb.append(u"integer-width/", -1); blueprint_helpers::generateIntegerWidthOption( - macros.integerWidth.fUnion.minMaxInt.fMinInt, - macros.integerWidth.fUnion.minMaxInt.fMaxInt, + minMaxInt.fMinInt, + minMaxInt.fMaxInt, sb, status); return true; diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index af636504283..a34e4242095 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -92,6 +92,7 @@ enum StemEnum { STEM_ROUNDING_MODE_HALF_DOWN, STEM_ROUNDING_MODE_HALF_UP, STEM_ROUNDING_MODE_UNNECESSARY, + STEM_INTEGER_WIDTH_TRUNC, STEM_GROUP_OFF, STEM_GROUP_MIN2, STEM_GROUP_AUTO, diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index b0aa5b45284..cc07d857951 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -3838,6 +3838,41 @@ void NumberFormatterApiTest::integerWidth() { // Note: this double produces all 17 significant digits 10000000000000002000.0, u"00"); + + assertFormatDescending( + u"Integer Width Double Zero (ICU-21590)", + u"integer-width-trunc", + u"integer-width-trunc", + NumberFormatter::with() + .integerWidth(IntegerWidth::zeroFillTo(0).truncateAt(0)), + Locale::getEnglish(), + u"0", + u"0", + u".5", + u".65", + u".765", + u".8765", + u".08765", + u".008765", + u"0"); + + assertFormatDescending( + u"Integer Width Double Zero with minFraction (ICU-21590)", + u"integer-width-trunc .0*", + u"integer-width-trunc .0*", + NumberFormatter::with() + .integerWidth(IntegerWidth::zeroFillTo(0).truncateAt(0)) + .precision(Precision::minFraction(1)), + Locale::getEnglish(), + u".0", + u".0", + u".5", + u".65", + u".765", + u".8765", + u".08765", + u".008765", + u".0"); } void NumberFormatterApiTest::symbols() { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java index 54555d88c24..c9e0369c5f9 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java @@ -96,6 +96,7 @@ class NumberSkeletonImpl { STEM_ROUNDING_MODE_HALF_DOWN, STEM_ROUNDING_MODE_HALF_UP, STEM_ROUNDING_MODE_UNNECESSARY, + STEM_INTEGER_WIDTH_TRUNC, STEM_GROUP_OFF, STEM_GROUP_MIN2, STEM_GROUP_AUTO, @@ -174,6 +175,7 @@ class NumberSkeletonImpl { b.add("rounding-mode-half-down", StemEnum.STEM_ROUNDING_MODE_HALF_DOWN.ordinal()); b.add("rounding-mode-half-up", StemEnum.STEM_ROUNDING_MODE_HALF_UP.ordinal()); b.add("rounding-mode-unnecessary", StemEnum.STEM_ROUNDING_MODE_UNNECESSARY.ordinal()); + b.add("integer-width-trunc", StemEnum.STEM_INTEGER_WIDTH_TRUNC.ordinal()); b.add("group-off", StemEnum.STEM_GROUP_OFF.ordinal()); b.add("group-min2", StemEnum.STEM_GROUP_MIN2.ordinal()); b.add("group-auto", StemEnum.STEM_GROUP_AUTO.ordinal()); @@ -751,6 +753,11 @@ class NumberSkeletonImpl { macros.roundingMode = StemToObject.roundingMode(stem); return ParseState.STATE_NULL; + case STEM_INTEGER_WIDTH_TRUNC: + checkNull(macros.integerWidth, segment); + macros.integerWidth = IntegerWidth.zeroFillTo(0).truncateAt(0); + return ParseState.STATE_NULL; + case STEM_GROUP_OFF: case STEM_GROUP_MIN2: case STEM_GROUP_AUTO: @@ -1623,6 +1630,10 @@ class NumberSkeletonImpl { if (macros.integerWidth.equals(IntegerWidth.DEFAULT)) { return false; // Default } + if (macros.integerWidth.minInt == 0 && macros.integerWidth.maxInt == 0) { + sb.append("integer-width-trunc"); + return true; + } sb.append("integer-width/"); BlueprintHelpers.generateIntegerWidthOption(macros.integerWidth.minInt, macros.integerWidth.maxInt, diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java index 4f910daad9b..9cf6e614982 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java @@ -3799,6 +3799,41 @@ public class NumberFormatterApiTest extends TestFmwk { // Note: this double produces all 17 significant digits 10000000000000002000.0, "00"); + + assertFormatDescending( + "Integer Width Double Zero (ICU-21590)", + "integer-width-trunc", + "integer-width-trunc", + NumberFormatter.with() + .integerWidth(IntegerWidth.zeroFillTo(0).truncateAt(0)), + ULocale.ENGLISH, + "0", + "0", + ".5", + ".65", + ".765", + ".8765", + ".08765", + ".008765", + "0"); + + assertFormatDescending( + "Integer Width Double Zero with minFraction (ICU-21590)", + "integer-width-trunc .0*", + "integer-width-trunc .0*", + NumberFormatter.with() + .integerWidth(IntegerWidth.zeroFillTo(0).truncateAt(0)) + .precision(Precision.minFraction(1)), + ULocale.ENGLISH, + ".0", + ".0", + ".5", + ".65", + ".765", + ".8765", + ".08765", + ".008765", + ".0"); } @Test