ICU-20418 Number skeletons: implement star wildcard; user guide fixes
See #1060
diff --git a/docs/userguide/format_parse/numbers/skeletons.md b/docs/userguide/format_parse/numbers/skeletons.md
index 70a9d39..1c5e9c5 100644
--- a/docs/userguide/format_parse/numbers/skeletons.md
+++ b/docs/userguide/format_parse/numbers/skeletons.md
@@ -95,14 +95,15 @@
following optional options:
- `/sign-xxx` sets the sign display option for the exponent; see [Sign](#sign).
-- `/+ee` sets exponent digits to "at least 2"; use `/+eee` for at least 3 digits, etc.
+- `/*ee` sets exponent digits to "at least 2"; use `/*eee` for at least 3 digits, etc.
+ - ***Prior to ICU 67***, use `/+ee` instead of `/*ee`.
For example, all of the following skeletons are valid:
- `scientific`
- `scientific/sign-always`
-- `scientific/+ee`
-- `scientific/+ee/sign-always`
+- `scientific/*ee`
+- `scientific/*ee/sign-always`
#### Scientific and Engineering Notation: Concise Form
@@ -111,14 +112,14 @@
| Concise Skeleton | Equivalent Long-Form Skeleton |
|---|---|
| `E0` | `scientific` |
-| `E00` | `scientific/+ee` |
-| `EE+0` | `engineering/sign-always` |
+| `E00` | `scientific/*ee` |
+| `EE+!0` | `engineering/sign-always` |
| `E+?00` | `scientific/sign-except-zero/+ee` |
More precisely:
1. Start with `E` for scientific or `EE` for engineering.
-2. Allow either `+` or `+?` as a concise sign display option.
+2. Allow either `+!` or `+?` as a concise sign display option.
3. Expect one or more `0`s. If more than one, set minimum integer digits.
### Unit
@@ -192,13 +193,13 @@
| Stem | Explanation | Equivalent C++ Code |
|---|---|---|
| `.00` | Exactly 2 fraction digits | `Precision::fixedFraction(2) ` |
-| `.00+` | At least 2 fraction digits | `Precision::minFraction(2)` |
+| `.00*` | At least 2 fraction digits | `Precision::minFraction(2)` |
| `.##` | At most 2 fraction digits | `Precision::maxFraction(2) ` |
| `.0#` | Between 1 and 2 fraction digits | `Precision::minMaxFraction(1, 2)` |
More precisely, the fraction precision stem starts with `.`, then contains
zero or more `0` symbols, which implies the minimum fraction digits. Then it
-contains either a `+`, for unlimited maximum fraction digits, or zero or more
+contains either a `*`, for unlimited maximum fraction digits, or zero or more
`#` symbols, which implies the minimum fraction digits when added to the `0`
symbols.
@@ -211,11 +212,11 @@
| Skeleton | Explanation | Equivalent C++ Code |
|---|---|---|
-| `.##/@@@+` | At most 2 fraction digits, but guarantee <br/> at least 3 significant digits | `Precision::maxFraction(2)` <br/> `.withMinDigits(3)` |
+| `.##/@@@*` | At most 2 fraction digits, but guarantee <br/> at least 3 significant digits | `Precision::maxFraction(2)` <br/> `.withMinDigits(3)` |
| `.00/@##` | Exactly 2 fraction digits, but do not <br/> display more than 3 significant digits | `Precision::fixedFraction(2)` <br/> `.withMaxDigits(3)` |
Precisely, the option starts with one or more `@` symbols. Then it contains
-either a `+`, for `::withMinDigits`, or one or more `#` symbols, for
+either a `*`, for `::withMinDigits`, or one or more `#` symbols, for
`::withMaxDigits`. If a `#` symbol is present, there must be only one `@`
symbol.
@@ -226,16 +227,22 @@
| Stem | Explanation | Equivalent C++ Code|
|---|---|---|
| `@@@` | Exactly 3 significant digits | `Precision::fixedSignificantDigits(3)` |
-| `@@@+` | At least 3 significant digits | `Precision::minSignificantDigits(3)` |
+| `@@@*` | At least 3 significant digits | `Precision::minSignificantDigits(3)` |
| `@##` | At most 3 significant digits | `Precision::maxSignificantDigits(3)` |
| `@@#` | Between 2 and 3 significant digits | `...::minMaxSignificantDigits(2, 3)` |
The precise syntax is very similar to fraction precision. The blueprint stem
starts with one or more `@` symbols, which implies the minimum significant
-digits. Then it contains either a `+`, for unlimited maximum significant
+digits. Then it contains either a `*`, for unlimited maximum significant
digits, or zero or more `#` symbols, which implies the minimum significant
digits when added to the `@` symbols.
+#### Wildcard Character
+
+***Prior to ICU 67***, the symbol `+` was used for unlimited precision, instead
+of `*` (for example, `.00+`). For backwards compatibility, either `+` or `*` is
+accepted. This applies for both fraction digits and significant digits.
+
### Rounding Mode
The rounding mode can be specified by the following stems:
@@ -259,21 +266,23 @@
| Long Form | Concise Form | Explanation | Equivalent C++ Code |
|---|---|---|---|
-| `integer-width/+000` | `000` | At least 3 <br/> integer digits | `IntegerWidth::zeroFillTo(3)` |
+| `integer-width/*000` | `000` | At least 3 <br/> integer digits | `IntegerWidth::zeroFillTo(3)` |
| `integer-width/##0` | - | Between 1 and 3 <br/> integer digits | `IntegerWidth::zeroFillTo(1)` <br/> `.truncateAt(3)`
| `integer-width/00` | - | Exactly 2 <br/> integer digits | `IntegerWidth::zeroFillTo(2)` <br/> `.truncateAt(2)` |
-| `integer-width/+` | - | Zero or more <br/> integer digits | `IntegerWidth::zeroFillTo(0) `
+| `integer-width/*` | - | Zero or more <br/> integer digits | `IntegerWidth::zeroFillTo(0) `
-The long-form option starts with either a single `+` symbol, signaling no limit
+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.
It should then be followed by zero or more `0` symbols, indicating the minimum
-integer digits (the argument to *zeroFillTo*). If there is no `+` symbol, the
+integer digits (the argument to *zeroFillTo*). If there is no `*` symbol, the
maximum integer digits (the argument to *truncateAt*) is the number of `#`
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.
+***Prior to ICU 67***, use the symbol `+` instead of `*`.
+
### Scale
To specify the scale, use the following stem and option:
@@ -303,7 +312,7 @@
- `group-min2` or `,?` (concise)
- `group-auto` (or omit since this is the default)
- `group-on-aligned` or `,!` (concise)
-- `group-thousands` or `,=` (concise)
+- `group-thousands` (no concise equivalent)
For more details, see
[UNumberGroupingStrategy](http://icu-project.org/apiref/icu4c/unumberformatter_8h.html).
diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
index 5b0fe2b..4ba2647 100644
--- a/icu4c/source/i18n/number_skeletons.cpp
+++ b/icu4c/source/i18n/number_skeletons.cpp
@@ -897,7 +897,7 @@
bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros,
UErrorCode&) {
- if (segment.charAt(0) != u'+') {
+ if (!isWildcardChar(segment.charAt(0))) {
return false;
}
int32_t offset = 1;
@@ -919,7 +919,7 @@
void
blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) {
- sb.append(u'+');
+ sb.append(kWildcardChar);
appendMultiple(sb, u'e', minExponentDigits);
}
@@ -1071,7 +1071,7 @@
}
}
if (offset < segment.length()) {
- if (segment.charAt(offset) == u'+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxFrac = -1;
offset++;
} else {
@@ -1113,7 +1113,7 @@
sb.append(u'.');
appendMultiple(sb, u'0', minFrac);
if (maxFrac == -1) {
- sb.append(u'+');
+ sb.append(kWildcardChar);
} else {
appendMultiple(sb, u'#', maxFrac - minFrac);
}
@@ -1133,7 +1133,7 @@
}
}
if (offset < segment.length()) {
- if (segment.charAt(offset) == u'+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxSig = -1;
offset++;
} else {
@@ -1166,7 +1166,7 @@
blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) {
appendMultiple(sb, u'@', minSig);
if (maxSig == -1) {
- sb.append(u'+');
+ sb.append(kWildcardChar);
} else {
appendMultiple(sb, u'#', maxSig - minSig);
}
@@ -1262,7 +1262,7 @@
// Invalid: @, @@, @@@
// Invalid: @@#, @@##, @@@#
if (offset < segment.length()) {
- if (segment.charAt(offset) == u'+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxSig = -1;
offset++;
} else if (minSig > 1) {
@@ -1351,7 +1351,7 @@
int32_t offset = 0;
int32_t minInt = 0;
int32_t maxInt;
- if (segment.charAt(0) == u'+') {
+ if (isWildcardChar(segment.charAt(0))) {
maxInt = -1;
offset++;
} else {
@@ -1392,7 +1392,7 @@
void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb,
UErrorCode&) {
if (maxInt == -1) {
- sb.append(u'+');
+ sb.append(kWildcardChar);
} else {
appendMultiple(sb, u'#', maxInt - minInt);
}
diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h
index 3c03d73..d9b2c0e 100644
--- a/icu4c/source/i18n/number_skeletons.h
+++ b/icu4c/source/i18n/number_skeletons.h
@@ -118,6 +118,17 @@
STEM_SCALE,
};
+/** Default wildcard char, accepted on input and printed in output */
+constexpr char16_t kWildcardChar = u'*';
+
+/** Alternative wildcard char, accept on input but not printed in output */
+constexpr char16_t kAltWildcardChar = u'+';
+
+/** Checks whether the char is a wildcard on input */
+inline bool isWildcardChar(char16_t c) {
+ return c == kWildcardChar || c == kAltWildcardChar;
+}
+
/**
* Creates a NumberFormatter corresponding to the given skeleton string.
*
diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h
index 30615b9..2597aa8 100644
--- a/icu4c/source/test/intltest/numbertest.h
+++ b/icu4c/source/test/intltest/numbertest.h
@@ -257,6 +257,7 @@
void stemsRequiringOption();
void defaultTokens();
void flexibleSeparators();
+ void wildcardCharacters();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index 9652ade..c586603 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -209,7 +209,7 @@
assertFormatDescending(
u"Scientific min exponent digits",
- u"scientific/+ee",
+ u"scientific/*ee",
u"E00",
NumberFormatter::with().notation(Notation::scientific().withMinExponentDigits(2)),
Locale::getEnglish(),
@@ -1039,7 +1039,7 @@
assertFormatDescending(
u"Min Fraction",
- u".0+",
+ u".0*",
u".0+",
NumberFormatter::with().precision(Precision::minFraction(1)),
Locale::getEnglish(),
@@ -1116,7 +1116,7 @@
assertFormatSingle(
u"Min Significant",
- u"@@+",
+ u"@@*",
u"@@+",
NumberFormatter::with().precision(Precision::minSignificantDigits(2)),
Locale::getEnglish(),
@@ -1153,7 +1153,7 @@
assertFormatSingle(
u"Fixed Significant on zero with zero integer width",
- u"@ integer-width/+",
+ u"@ integer-width/*",
u"@ integer-width/+",
NumberFormatter::with().precision(Precision::fixedSignificantDigits(1))
.integerWidth(IntegerWidth::zeroFillTo(0)),
@@ -1181,7 +1181,7 @@
assertFormatDescending(
u"FracSig minMaxFrac minSig",
- u".0#/@@@+",
+ u".0#/@@@*",
u".0#/@@@+",
NumberFormatter::with().precision(Precision::minMaxFraction(1, 2).withMinDigits(3)),
Locale::getEnglish(),
@@ -1229,7 +1229,7 @@
assertFormatSingle(
u"FracSig with trailing zeros A",
- u".00/@@@+",
+ u".00/@@@*",
u".00/@@@+",
NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)),
Locale::getEnglish(),
@@ -1238,7 +1238,7 @@
assertFormatSingle(
u"FracSig with trailing zeros B",
- u".00/@@@+",
+ u".00/@@@*",
u".00/@@@+",
NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)),
Locale::getEnglish(),
@@ -1807,7 +1807,7 @@
assertFormatDescending(
u"Integer Width Zero Fill 0",
- u"integer-width/+",
+ u"integer-width/*",
u"integer-width/+",
NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(0)),
Locale::getEnglish(),
diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp
index e8ab127..3aad601 100644
--- a/icu4c/source/test/intltest/numbertest_skeletons.cpp
+++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp
@@ -29,6 +29,7 @@
TESTCASE_AUTO(stemsRequiringOption);
TESTCASE_AUTO(defaultTokens);
TESTCASE_AUTO(flexibleSeparators);
+ TESTCASE_AUTO(wildcardCharacters);
TESTCASE_AUTO_END;
}
@@ -41,26 +42,35 @@
u"precision-integer",
u"precision-unlimited",
u"@@@##",
+ u"@@*",
u"@@+",
u".000##",
+ u".00*",
u".00+",
u".",
+ u".*",
u".+",
u".######",
+ u".00/@@*",
u".00/@@+",
u".00/@##",
u"precision-increment/3.14",
u"precision-currency-standard",
u"precision-integer rounding-mode-half-up",
u".00# rounding-mode-ceiling",
+ u".00/@@* rounding-mode-floor",
u".00/@@+ rounding-mode-floor",
u"scientific",
+ u"scientific/*ee",
u"scientific/+ee",
u"scientific/sign-always",
+ u"scientific/*ee/sign-always",
u"scientific/+ee/sign-always",
+ u"scientific/sign-always/*ee",
u"scientific/sign-always/+ee",
u"scientific/sign-except-zero",
u"engineering",
+ u"engineering/*eee",
u"engineering/+eee",
u"compact-short",
u"compact-long",
@@ -81,6 +91,7 @@
u"group-thousands",
u"integer-width/00",
u"integer-width/#0",
+ u"integer-width/*00",
u"integer-width/+00",
u"sign-always",
u"sign-auto",
@@ -136,16 +147,22 @@
static const char16_t* cases[] = {
u".00x",
u".00##0",
+ u".##*",
+ u".00##*",
+ u".0#*",
+ u"@#*",
u".##+",
u".00##+",
u".0#+",
+ u"@#+",
u"@@x",
u"@@##0",
- u"@#+",
u".00/@",
u".00/@@",
u".00/@@x",
u".00/@@#",
+ u".00/@@#*",
+ u".00/floor/@@*", // wrong order
u".00/@@#+",
u".00/floor/@@+", // wrong order
u"precision-increment/français", // non-invariant characters for C++
@@ -161,6 +178,10 @@
u"currency/ççç", // three characters but not ASCII
u"measure-unit/foo",
u"integer-width/xxx",
+ u"integer-width/0*",
+ u"integer-width/*0#",
+ u"integer-width/*#",
+ u"integer-width/*#0",
u"integer-width/0+",
u"integer-width/+0#",
u"integer-width/+#",
@@ -177,6 +198,7 @@
u"EEE",
u"EEE0",
u"001",
+ u"00*",
u"00+",
};
@@ -304,6 +326,32 @@
}
}
+void NumberSkeletonTest::wildcardCharacters() {
+ IcuTestErrorCode status(*this, "wildcardCharacters");
+
+ struct TestCase {
+ const char16_t* star;
+ const char16_t* plus;
+ } cases[] = {
+ { u".00*", u".00+" },
+ { u"@@*", u"@@+" },
+ { u".00/@@*", u".00/@@+" },
+ { u"scientific/*ee", u"scientific/+ee" },
+ { u"integer-width/*00", u"integer-width/+00" },
+ };
+
+ for (const auto& cas : cases) {
+ UnicodeString star(cas.star);
+ UnicodeString plus(cas.plus);
+ status.setScope(star);
+
+ UnicodeString normalized = NumberFormatter::forSkeleton(plus, status)
+ .toSkeleton(status);
+ assertEquals("Plus should normalize to star", star, normalized);
+ status.errIfFailureAndReset();
+ }
+}
+
// In C++, there is no distinguishing between "invalid", "unknown", and "unexpected" tokens.
void NumberSkeletonTest::expectedErrorSkeleton(const char16_t** cases, int32_t casesLen) {
for (int32_t i = 0; i < casesLen; i++) {
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 492f6b5..3897ca3 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
@@ -122,6 +122,17 @@
STEM_SCALE,
};
+ /** Default wildcard char, accepted on input and printed in output */
+ static final char WILDCARD_CHAR = '*';
+
+ /** Alternative wildcard char, accept on input but not printed in output */
+ static final char ALT_WILDCARD_CHAR = '+';
+
+ /** Checks whether the char is a wildcard on input */
+ static boolean isWildcardChar(char c) {
+ return c == WILDCARD_CHAR || c == ALT_WILDCARD_CHAR;
+ }
+
/** For mapping from ordinal back to StemEnum in Java. */
static final StemEnum[] STEM_ENUM_VALUES = StemEnum.values();
@@ -924,7 +935,7 @@
/** @return Whether we successfully found and parsed an exponent width option. */
private static boolean parseExponentWidthOption(StringSegment segment, MacroProps macros) {
- if (segment.charAt(0) != '+') {
+ if (!isWildcardChar(segment.charAt(0))) {
return false;
}
int offset = 1;
@@ -945,7 +956,7 @@
}
private static void generateExponentWidthOption(int minExponentDigits, StringBuilder sb) {
- sb.append('+');
+ sb.append(WILDCARD_CHAR);
appendMultiple(sb, 'e', minExponentDigits);
}
@@ -1044,7 +1055,7 @@
}
}
if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxFrac = -1;
offset++;
} else {
@@ -1083,7 +1094,7 @@
sb.append('.');
appendMultiple(sb, '0', minFrac);
if (maxFrac == -1) {
- sb.append('+');
+ sb.append(WILDCARD_CHAR);
} else {
appendMultiple(sb, '#', maxFrac - minFrac);
}
@@ -1102,7 +1113,7 @@
}
}
if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxSig = -1;
offset++;
} else {
@@ -1132,7 +1143,7 @@
private static void generateDigitsStem(int minSig, int maxSig, StringBuilder sb) {
appendMultiple(sb, '@', minSig);
if (maxSig == -1) {
- sb.append('+');
+ sb.append(WILDCARD_CHAR);
} else {
appendMultiple(sb, '#', maxSig - minSig);
}
@@ -1224,7 +1235,7 @@
// Invalid: @, @@, @@@
// Invalid: @@#, @@##, @@@#
if (offset < segment.length()) {
- if (segment.charAt(offset) == '+') {
+ if (isWildcardChar(segment.charAt(offset))) {
maxSig = -1;
offset++;
} else if (minSig > 1) {
@@ -1278,7 +1289,7 @@
int offset = 0;
int minInt = 0;
int maxInt;
- if (segment.charAt(0) == '+') {
+ if (isWildcardChar(segment.charAt(0))) {
maxInt = -1;
offset++;
} else {
@@ -1316,7 +1327,7 @@
private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) {
if (maxInt == -1) {
- sb.append('+');
+ sb.append(WILDCARD_CHAR);
} else {
appendMultiple(sb, '#', maxInt - minInt);
}
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 ae3eab4..2a51b7d 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
@@ -165,7 +165,7 @@
assertFormatDescending(
"Scientific min exponent digits",
- "scientific/+ee",
+ "scientific/*ee",
"E00",
NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)),
ULocale.ENGLISH,
@@ -966,7 +966,7 @@
assertFormatDescending(
"Min Fraction",
- ".0+",
+ ".0*",
".0+",
NumberFormatter.with().precision(Precision.minFraction(1)),
ULocale.ENGLISH,
@@ -1044,7 +1044,7 @@
assertFormatSingle(
"Min Significant",
- "@@+",
+ "@@*",
"@@+",
NumberFormatter.with().precision(Precision.minSignificantDigits(2)),
ULocale.ENGLISH,
@@ -1071,7 +1071,7 @@
assertFormatSingle(
"Fixed Significant on zero with zero integer width",
- "@ integer-width/+",
+ "@ integer-width/*",
"@ integer-width/+",
NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(0)),
ULocale.ENGLISH,
@@ -1108,7 +1108,7 @@
assertFormatDescending(
"FracSig minMaxFrac minSig",
- ".0#/@@@+",
+ ".0#/@@@*",
".0#/@@@+",
NumberFormatter.with().precision(Precision.minMaxFraction(1, 2).withMinDigits(3)),
ULocale.ENGLISH,
@@ -1172,7 +1172,7 @@
assertFormatSingle(
"FracSig with trailing zeros A",
- ".00/@@@+",
+ ".00/@@@*",
".00/@@@+",
NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)),
ULocale.ENGLISH,
@@ -1181,7 +1181,7 @@
assertFormatSingle(
"FracSig with trailing zeros B",
- ".00/@@@+",
+ ".00/@@@*",
".00/@@@+",
NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)),
ULocale.ENGLISH,
@@ -1726,7 +1726,7 @@
assertFormatDescending(
"Integer Width Zero Fill 0",
- "integer-width/+",
+ "integer-width/*",
"integer-width/+",
NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(0)),
ULocale.ENGLISH,
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
index a2c9553..03caedc 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
@@ -28,26 +28,35 @@
"precision-integer",
"precision-unlimited",
"@@@##",
+ "@@*",
"@@+",
".000##",
+ ".00*",
".00+",
".",
+ ".*",
".+",
".######",
+ ".00/@@*",
".00/@@+",
".00/@##",
"precision-increment/3.14",
"precision-currency-standard",
"precision-integer rounding-mode-half-up",
".00# rounding-mode-ceiling",
+ ".00/@@* rounding-mode-floor",
".00/@@+ rounding-mode-floor",
"scientific",
+ "scientific/*ee",
"scientific/+ee",
"scientific/sign-always",
+ "scientific/*ee/sign-always",
"scientific/+ee/sign-always",
+ "scientific/sign-always/*ee",
"scientific/sign-always/+ee",
"scientific/sign-except-zero",
"engineering",
+ "engineering/*eee",
"engineering/+eee",
"compact-short",
"compact-long",
@@ -68,6 +77,7 @@
"group-thousands",
"integer-width/00",
"integer-width/#0",
+ "integer-width/*00",
"integer-width/+00",
"sign-always",
"sign-auto",
@@ -122,16 +132,22 @@
String[] cases = {
".00x",
".00##0",
+ ".##*",
+ ".00##*",
+ ".0#*",
+ "@#*",
".##+",
".00##+",
".0#+",
+ "@#+",
"@@x",
"@@##0",
- "@#+",
".00/@",
".00/@@",
".00/@@x",
".00/@@#",
+ ".00/@@#*",
+ ".00/floor/@@*", // wrong order
".00/@@#+",
".00/floor/@@+", // wrong order
"precision-increment/français", // non-invariant characters for C++
@@ -147,6 +163,10 @@
"currency/ççç", // three characters but not ASCII
"measure-unit/foo",
"integer-width/xxx",
+ "integer-width/0*",
+ "integer-width/*0#",
+ "integer-width/*#",
+ "integer-width/*#0",
"integer-width/0+",
"integer-width/+0#",
"integer-width/+#",
@@ -163,6 +183,7 @@
"EEE",
"EEE0",
"001",
+ "00*",
"00+",
};
@@ -297,6 +318,26 @@
}
@Test
+ public void wildcardCharacters() {
+ String[][] cases = {
+ { ".00*", ".00+" },
+ { "@@*", "@@+" },
+ { ".00/@@*", ".00/@@+" },
+ { "scientific/*ee", "scientific/+ee" },
+ { "integer-width/*00", "integer-width/+00" },
+ };
+
+ for (String[] cas : cases) {
+ String star = cas[0];
+ String plus = cas[1];
+
+ String normalized = NumberFormatter.forSkeleton(plus)
+ .toSkeleton();
+ assertEquals("Plus should normalize to star", star, normalized);
+ }
+ }
+
+ @Test
public void roundingModeNames() {
for (RoundingMode mode : RoundingMode.values()) {
if (mode == RoundingMode.HALF_EVEN) {