From c5e86f87c8c6570c5dde79f591a1cae3be2742be Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Thu, 1 Mar 2018 09:24:37 +0000 Subject: [PATCH] ICU-8610 Full support for skeletons in ICU4J. Needs a few more tests. X-SVN-Rev: 41038 --- .../ibm/icu/number/NumberSkeletonImpl.java | 464 +++++++++++++++--- .../test/number/NumberFormatterApiTest.java | 287 +++++------ .../dev/test/number/NumberSkeletonTest.java | 34 +- 3 files changed, 580 insertions(+), 205 deletions(-) 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 1841ad700c..896c245eb6 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 @@ -12,7 +12,11 @@ import java.util.concurrent.ConcurrentHashMap; import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.MacroProps; +import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; +import com.ibm.icu.number.NumberFormatter.GroupingStrategy; +import com.ibm.icu.number.NumberFormatter.SignDisplay; import com.ibm.icu.number.NumberFormatter.UnitWidth; +import com.ibm.icu.text.NumberingSystem; import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; import com.ibm.icu.util.MeasureUnit; @@ -25,7 +29,25 @@ import com.ibm.icu.util.NoUnit; class NumberSkeletonImpl { static enum StemType { - OTHER, ROUNDER, FRACTION_ROUNDER, MAYBE_INCREMENT_ROUNDER, CURRENCY_ROUNDER, MEASURE_UNIT, UNIT_WIDTH + OTHER, + COMPACT_NOTATION, + SCIENTIFIC_NOTATION, + SIMPLE_NOTATION, + NO_UNIT, + CURRENCY, + MEASURE_UNIT, + PER_MEASURE_UNIT, + ROUNDER, + FRACTION_ROUNDER, + MAYBE_INCREMENT_ROUNDER, + CURRENCY_ROUNDER, + GROUPING, + INTEGER_WIDTH, + LATIN, + NUMBERING_SYSTEM, + UNIT_WIDTH, + SIGN_DISPLAY, + DECIMAL_DISPLAY } static class SkeletonDataStructure { @@ -61,6 +83,16 @@ class NumberSkeletonImpl { static final SkeletonDataStructure skeletonData = new SkeletonDataStructure(); static { + skeletonData.put(StemType.COMPACT_NOTATION, "compact-short", Notation.compactShort()); + skeletonData.put(StemType.COMPACT_NOTATION, "compact-long", Notation.compactLong()); + skeletonData.put(StemType.SCIENTIFIC_NOTATION, "scientific", Notation.scientific()); + skeletonData.put(StemType.SCIENTIFIC_NOTATION, "engineering", Notation.engineering()); + skeletonData.put(StemType.SIMPLE_NOTATION, "simple-notation", Notation.simple()); + + skeletonData.put(StemType.NO_UNIT, "base-unit", NoUnit.BASE); + skeletonData.put(StemType.NO_UNIT, "percent", NoUnit.PERCENT); + skeletonData.put(StemType.NO_UNIT, "permille", NoUnit.PERMILLE); + skeletonData.put(StemType.ROUNDER, "round-integer", Rounder.integer()); skeletonData.put(StemType.ROUNDER, "round-unlimited", Rounder.unlimited()); skeletonData.put(StemType.ROUNDER, @@ -68,11 +100,32 @@ class NumberSkeletonImpl { Rounder.currency(CurrencyUsage.STANDARD)); skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); + skeletonData.put(StemType.GROUPING, "group-off", GroupingStrategy.OFF); + skeletonData.put(StemType.GROUPING, "group-min2", GroupingStrategy.MIN2); + skeletonData.put(StemType.GROUPING, "group-auto", GroupingStrategy.AUTO); + skeletonData.put(StemType.GROUPING, "group-on-aligned", GroupingStrategy.ON_ALIGNED); + skeletonData.put(StemType.GROUPING, "group-thousands", GroupingStrategy.THOUSANDS); + + skeletonData.put(StemType.LATIN, "latin", NumberingSystem.LATIN); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE); skeletonData.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN); + + skeletonData.put(StemType.SIGN_DISPLAY, "sign-auto", SignDisplay.AUTO); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-always", SignDisplay.ALWAYS); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-never", SignDisplay.NEVER); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting", SignDisplay.ACCOUNTING); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-accounting-always", SignDisplay.ACCOUNTING_ALWAYS); + skeletonData.put(StemType.SIGN_DISPLAY, "sign-except-zero", SignDisplay.EXCEPT_ZERO); + skeletonData.put(StemType.SIGN_DISPLAY, + "sign-accounting-except-zero", + SignDisplay.ACCOUNTING_EXCEPT_ZERO); + + skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-auto", DecimalSeparatorDisplay.AUTO); + skeletonData.put(StemType.DECIMAL_DISPLAY, "decimal-always", DecimalSeparatorDisplay.ALWAYS); } private static final Map cache = new ConcurrentHashMap(); @@ -183,6 +236,10 @@ class NumberSkeletonImpl { switch (stem) { case MAYBE_INCREMENT_ROUNDER: case MEASURE_UNIT: + case PER_MEASURE_UNIT: + case CURRENCY: + case INTEGER_WIDTH: + case NUMBERING_SYSTEM: throw new SkeletonSyntaxException("Stem requires an option", segment); default: break; @@ -200,14 +257,40 @@ class NumberSkeletonImpl { if (stem != null) { Object value = skeletonData.stemToValue(content); switch (stem) { + case COMPACT_NOTATION: + case SCIENTIFIC_NOTATION: + case SIMPLE_NOTATION: + checkNull(macros.notation, content); + macros.notation = (Notation) value; + break; + case NO_UNIT: + checkNull(macros.unit, content); + macros.unit = (NoUnit) value; + break; case ROUNDER: checkNull(macros.rounder, content); macros.rounder = (Rounder) value; break; + case GROUPING: + checkNull(macros.grouping, content); + macros.grouping = value; + break; + case LATIN: + checkNull(macros.symbols, content); + macros.symbols = value; + break; case UNIT_WIDTH: checkNull(macros.unitWidth, content); macros.unitWidth = (UnitWidth) value; break; + case SIGN_DISPLAY: + checkNull(macros.sign, content); + macros.sign = (SignDisplay) value; + break; + case DECIMAL_DISPLAY: + checkNull(macros.decimal, content); + macros.decimal = (DecimalSeparatorDisplay) value; + break; default: assert false; } @@ -216,19 +299,35 @@ class NumberSkeletonImpl { // Second try: literal stems that require an option if (content.equals("round-increment")) { + checkNull(macros.rounder, content); return StemType.MAYBE_INCREMENT_ROUNDER; } else if (content.equals("measure-unit")) { + checkNull(macros.unit, content); return StemType.MEASURE_UNIT; + } else if (content.equals("per-measure-unit")) { + checkNull(macros.perUnit, content); + return StemType.PER_MEASURE_UNIT; + } else if (content.equals("currency")) { + checkNull(macros.unit, content); + return StemType.CURRENCY; + } else if (content.equals("integer-width")) { + checkNull(macros.integerWidth, content); + return StemType.INTEGER_WIDTH; + } else if (content.equals("numbering-system")) { + checkNull(macros.symbols, content); + return StemType.NUMBERING_SYSTEM; } - // Second try: stem "blueprint" syntax + // Third try: stem "blueprint" syntax switch (content.charAt(0)) { case '.': stem = StemType.FRACTION_ROUNDER; + checkNull(macros.rounder, content); parseFractionStem(content, macros); break; case '@': stem = StemType.ROUNDER; + checkNull(macros.rounder, content); parseDigitsStem(content, macros); break; } @@ -241,6 +340,43 @@ class NumberSkeletonImpl { } private static StemType parseOption(StemType stem, CharSequence content, MacroProps macros) { + + ///// Required options: ///// + + switch (stem) { + case CURRENCY: + parseCurrencyOption(content, macros); + return StemType.OTHER; + case MEASURE_UNIT: + parseMeasureUnitOption(content, macros); + return StemType.OTHER; + case PER_MEASURE_UNIT: + parseMeasurePerUnitOption(content, macros); + return StemType.OTHER; + case MAYBE_INCREMENT_ROUNDER: + parseIncrementOption(content, macros); + return StemType.ROUNDER; + case INTEGER_WIDTH: + parseIntegerWidthOption(content, macros); + return StemType.OTHER; + case NUMBERING_SYSTEM: + parseNumberingSystemOption(content, macros); + return StemType.OTHER; + } + + ///// Non-required options: ///// + + // Scientific options + switch (stem) { + case SCIENTIFIC_NOTATION: + if (parseExponentWidthOption(content, macros)) { + return StemType.SCIENTIFIC_NOTATION; + } + if (parseExponentSignOption(content, macros)) { + return StemType.SCIENTIFIC_NOTATION; + } + } + // Frac-sig option switch (stem) { case FRACTION_ROUNDER: @@ -249,51 +385,61 @@ class NumberSkeletonImpl { } } - // Increment option - switch (stem) { - case MAYBE_INCREMENT_ROUNDER: - // The increment option is required. - parseIncrementOption(content, macros); - return StemType.ROUNDER; - } - // Rounding mode option switch (stem) { case ROUNDER: case FRACTION_ROUNDER: case CURRENCY_ROUNDER: if (parseRoundingModeOption(content, macros)) { - break; + return StemType.ROUNDER; } } - // Measure unit option - switch (stem) { - case MEASURE_UNIT: - // The measure unit option is required. - parseMeasureUnitOption(content, macros); - return StemType.OTHER; - } - // Unknown option throw new SkeletonSyntaxException("Unknown option", content); } - ///// - private static void generateSkeleton(MacroProps macros, StringBuilder sb) { - if (macros.rounder != null) { - generateRoundingValue(macros, sb); + if (macros.notation != null) { + generateNotationValue(macros, sb); sb.append(' '); } if (macros.unit != null) { generateUnitValue(macros, sb); sb.append(' '); } + if (macros.perUnit != null) { + generatePerUnitValue(macros, sb); + sb.append(' '); + } + if (macros.rounder != null) { + generateRoundingValue(macros, sb); + sb.append(' '); + } + if (macros.grouping != null) { + generateGroupingValue(macros, sb); + sb.append(' '); + } + if (macros.integerWidth != null) { + generateIntegerWidthValue(macros, sb); + sb.append(' '); + } + if (macros.symbols != null) { + generateSymbolsValue(macros, sb); + sb.append(' '); + } if (macros.unitWidth != null) { generateUnitWidthValue(macros, sb); sb.append(' '); } + if (macros.sign != null) { + generateSignValue(macros, sb); + sb.append(' '); + } + if (macros.decimal != null) { + generateDecimalValue(macros, sb); + sb.append(' '); + } // Remove the trailing space if (sb.length() > 0) { @@ -303,6 +449,90 @@ class NumberSkeletonImpl { ///// + private static boolean parseExponentWidthOption(CharSequence content, MacroProps macros) { + if (content.charAt(0) != '+') { + return false; + } + int offset = 1; + int minExp = 0; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == 'e') { + minExp++; + } else { + break; + } + } + if (offset < content.length()) { + return false; + } + // Use the public APIs to enforce bounds checking + macros.notation = ((ScientificNotation) macros.notation).withMinExponentDigits(minExp); + return true; + } + + private static void generateExponentWidthOption(int minInt, int maxInt, StringBuilder sb) { + sb.append('+'); + appendMultiple(sb, 'e', minInt); + } + + private static boolean parseExponentSignOption(CharSequence content, MacroProps macros) { + Object value = skeletonData.stemToValue(content); + if (value != null && value instanceof SignDisplay) { + macros.notation = ((ScientificNotation) macros.notation) + .withExponentSignDisplay((SignDisplay) value); + return true; + } + return false; + } + + private static void generateCurrencyOption(Currency currency, StringBuilder sb) { + sb.append(currency.getCurrencyCode()); + } + + private static void parseCurrencyOption(CharSequence content, MacroProps macros) { + String currencyCode = content.subSequence(0, content.length()).toString(); + try { + macros.unit = Currency.getInstance(currencyCode); + } catch (IllegalArgumentException e) { + throw new SkeletonSyntaxException("Invalid currency", content, e); + } + } + + private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) { + // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) + // http://unicode.org/reports/tr35/#Validity_Data + int firstHyphen = 0; + while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') { + firstHyphen++; + } + if (firstHyphen == content.length()) { + throw new SkeletonSyntaxException("Invalid measure unit option", content); + } + String type = content.subSequence(0, firstHyphen).toString(); + String subType = content.subSequence(firstHyphen + 1, content.length()).toString(); + Set units = MeasureUnit.getAvailable(type); + for (MeasureUnit unit : units) { + if (subType.equals(unit.getSubtype())) { + macros.unit = unit; + return; + } + } + throw new SkeletonSyntaxException("Unknown measure unit", content); + } + + private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { + sb.append(unit.getType() + "-" + unit.getSubtype()); + } + + private static void parseMeasurePerUnitOption(CharSequence content, MacroProps macros) { + // A little bit of a hack: safe the current unit (numerator), call the main measure unit parsing + // code, put back the numerator unit, and put the new unit into per-unit. + MeasureUnit numerator = macros.unit; + parseMeasureUnitOption(content, macros); + macros.perUnit = macros.unit; + macros.unit = numerator; + } + private static void parseFractionStem(CharSequence content, MacroProps macros) { assert content.charAt(0) == '.'; int offset = 1; @@ -412,7 +642,7 @@ class NumberSkeletonImpl { } FractionRounder oldRounder = (FractionRounder) macros.rounder; // A little bit of a hack: parse the option as a digits stem, and extract the min/max sig from - // the new Rounder saved into the macros + // the new Rounder saved into the macros. parseDigitsStem(content, macros); Rounder.SignificantRounderImpl intermediate = (Rounder.SignificantRounderImpl) macros.rounder; if (intermediate.maxSig == -1) { @@ -455,31 +685,134 @@ class NumberSkeletonImpl { sb.append(mode.toString()); } - private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) { - // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) - // http://unicode.org/reports/tr35/#Validity_Data - int firstHyphen = 0; - while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') { - firstHyphen++; + private static void parseIntegerWidthOption(CharSequence content, MacroProps macros) { + int offset = 0; + int minInt = 0; + int maxInt; + if (content.charAt(0) == '+') { + maxInt = -1; + offset++; + } else { + maxInt = 0; } - String type = content.subSequence(0, firstHyphen).toString(); - String subType = content.subSequence(firstHyphen + 1, content.length()).toString(); - Set units = MeasureUnit.getAvailable(type); - for (MeasureUnit unit : units) { - if (subType.equals(unit.getSubtype())) { - macros.unit = unit; - return; + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '#') { + maxInt++; + } else { + break; } } - throw new SkeletonSyntaxException("Unknown unit", content); + if (offset < content.length()) { + for (; offset < content.length(); offset++) { + if (content.charAt(offset) == '0') { + minInt++; + } else { + break; + } + } + } + if (maxInt != -1) { + maxInt += minInt; + } + if (offset < content.length()) { + throw new SkeletonSyntaxException("Invalid integer width stem", content); + } + // Use the public APIs to enforce bounds checking + if (maxInt == -1) { + macros.integerWidth = IntegerWidth.zeroFillTo(minInt); + } else { + macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt); + } } - private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { - sb.append(unit.getType() + "-" + unit.getSubtype()); + private static void generateIntegerWidthOption(int minInt, int maxInt, StringBuilder sb) { + if (maxInt == -1) { + sb.append('+'); + } else { + appendMultiple(sb, '#', maxInt - minInt); + } + appendMultiple(sb, '0', minInt); + } + + private static void parseNumberingSystemOption(CharSequence content, MacroProps macros) { + String nsName = content.subSequence(0, content.length()).toString(); + NumberingSystem ns = NumberingSystem.getInstanceByName(nsName); + if (ns == null) { + throw new SkeletonSyntaxException("Unknown numbering system", content); + } + macros.symbols = ns; + } + + private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) { + sb.append(ns.getName()); } ///// + private static void generateNotationValue(MacroProps macros, StringBuilder sb) { + // Check for literals + String literal = skeletonData.valueToStem(macros.notation); + if (literal != null) { + sb.append(literal); + return; + } + + // Generate the stem + if (macros.notation instanceof CompactNotation) { + // Compact notation generated from custom data (not supported in skeleton) + // The other compact notations are literals + } else if (macros.notation instanceof ScientificNotation) { + ScientificNotation impl = (ScientificNotation) macros.notation; + if (impl.engineeringInterval == 3) { + sb.append("engineering"); + } else { + sb.append("scientific"); + } + if (impl.minExponentDigits > 1) { + sb.append('/'); + generateExponentWidthOption(impl.minExponentDigits, -1, sb); + } + if (impl.exponentSignDisplay != SignDisplay.AUTO) { + sb.append('/'); + sb.append(skeletonData.valueToStem(impl.exponentSignDisplay)); + } + } else { + assert macros.notation instanceof SimpleNotation; + sb.append("notation-simple"); + } + } + + private static void generateUnitValue(MacroProps macros, StringBuilder sb) { + // Check for literals + String literal = skeletonData.valueToStem(macros.unit); + if (literal != null) { + sb.append(literal); + return; + } + + // Generate the stem + if (macros.unit instanceof Currency) { + sb.append("currency/"); + generateCurrencyOption((Currency) macros.unit, sb); + } else if (macros.unit instanceof NoUnit) { + // This should be taken care of by the literals. + assert false; + } else { + sb.append("measure-unit/"); + generateMeasureUnitOption(macros.unit, sb); + } + } + + private static void generatePerUnitValue(MacroProps macros, StringBuilder sb) { + // Per-units are currently expected to be only MeasureUnits. + if (macros.unit instanceof Currency || macros.unit instanceof NoUnit) { + assert false; + } else { + sb.append("per-measure-unit/"); + generateMeasureUnitOption(macros.perUnit, sb); + } + } + private static void generateRoundingValue(MacroProps macros, StringBuilder sb) { // Check for literals String literal = skeletonData.valueToStem(macros.rounder); @@ -527,30 +860,39 @@ class NumberSkeletonImpl { } } - private static void generateUnitValue(MacroProps macros, StringBuilder sb) { - // Check for literals - String literal = skeletonData.valueToStem(macros.unit); - if (literal != null) { - sb.append(literal); - return; - } + private static void generateGroupingValue(MacroProps macros, StringBuilder sb) { + appendExpectedLiteral(macros.grouping, sb); + } - // Generate the stem - if (macros.unit instanceof Currency) { - // TODO - } else if (macros.unit instanceof NoUnit) { - // TODO + private static void generateIntegerWidthValue(MacroProps macros, StringBuilder sb) { + sb.append("integer-width/"); + generateIntegerWidthOption(macros.integerWidth.minInt, macros.integerWidth.maxInt, sb); + } + + private static void generateSymbolsValue(MacroProps macros, StringBuilder sb) { + if (macros.symbols instanceof NumberingSystem) { + NumberingSystem ns = (NumberingSystem) macros.symbols; + if (ns.getName().equals("latn")) { + sb.append("latin"); + } else { + sb.append("numbering-system/"); + generateNumberingSystemOption(ns, sb); + } } else { - sb.append("measure-unit/"); - generateMeasureUnitOption(macros.unit, sb); + // DecimalFormatSymbols (not supported in skeleton) } } private static void generateUnitWidthValue(MacroProps macros, StringBuilder sb) { - // There should be a literal. - String literal = skeletonData.valueToStem(macros.unitWidth); - assert literal != null; - sb.append(literal); + appendExpectedLiteral(macros.unitWidth, sb); + } + + private static void generateSignValue(MacroProps macros, StringBuilder sb) { + appendExpectedLiteral(macros.sign, sb); + } + + private static void generateDecimalValue(MacroProps macros, StringBuilder sb) { + appendExpectedLiteral(macros.decimal, sb); } ///// @@ -566,4 +908,10 @@ class NumberSkeletonImpl { sb.appendCodePoint(cp); } } + + private static void appendExpectedLiteral(Object value, StringBuilder sb) { + String literal = skeletonData.valueToStem(value); + assert literal != null; + sb.append(literal); + } } 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 49569d73d4..050e60dfd5 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 @@ -75,7 +75,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Big Simple", - "", + "simple-notation", NumberFormatter.with().notation(Notation.simple()), ULocale.ENGLISH, "87,650,000", @@ -101,7 +101,7 @@ public class NumberFormatterApiTest { public void notationScientific() { assertFormatDescending( "Scientific", - "E", + "scientific", NumberFormatter.with().notation(Notation.scientific()), ULocale.ENGLISH, "8.765E4", @@ -116,7 +116,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Engineering", - "E3", + "engineering", NumberFormatter.with().notation(Notation.engineering()), ULocale.ENGLISH, "87.65E3", @@ -131,7 +131,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Scientific sign always shown", - "E+", + "scientific/sign-always", NumberFormatter.with().notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)), ULocale.ENGLISH, "8.765E+4", @@ -146,7 +146,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Scientific min exponent digits", - "E00", + "scientific/+ee", NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)), ULocale.ENGLISH, "8.765E04", @@ -161,7 +161,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Scientific Negative", - "E", + "scientific", NumberFormatter.with().notation(Notation.scientific()), ULocale.ENGLISH, -1000000, @@ -172,7 +172,7 @@ public class NumberFormatterApiTest { public void notationCompact() { assertFormatDescendingBig( "Compact Short", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, "88M", @@ -187,7 +187,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Compact Long", - "CC", + "compact-long", NumberFormatter.with().notation(Notation.compactLong()), ULocale.ENGLISH, "88 million", @@ -202,7 +202,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Compact Short Currency", - "C $USD", + "compact-short currency/USD", NumberFormatter.with().notation(Notation.compactShort()).unit(USD), ULocale.ENGLISH, "$88K", @@ -217,7 +217,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Compact Short with ISO Currency", - "C $USD unit-width=ISO_CODE", + "compact-short currency/USD unit-width-iso-code", NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, "USD 88K", @@ -232,7 +232,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Compact Short with Long Name Currency", - "C $USD unit-width=FULL_NAME", + "compact-short currency/USD unit-width-full-name", NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "88K US dollars", @@ -249,7 +249,7 @@ public class NumberFormatterApiTest { // This test case should be fixed when proper compact long currency patterns are added. assertFormatDescending( "Compact Long Currency", - "CC $USD", + "compact-long currency/USD", NumberFormatter.with().notation(Notation.compactLong()).unit(USD), ULocale.ENGLISH, "$88K", // should be something like "$88 thousand" @@ -266,7 +266,7 @@ public class NumberFormatterApiTest { // This test case should be fixed when proper compact long currency patterns are added. assertFormatDescending( "Compact Long with ISO Currency", - "CC $USD unit-width=ISO_CODE", + "compact-long currency/USD unit-width-iso-code", NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, "USD 88K", // should be something like "USD 88 thousand" @@ -282,7 +282,7 @@ public class NumberFormatterApiTest { // TODO: This behavior could be improved and should be revisited. assertFormatDescending( "Compact Long with Long Name Currency", - "CC $USD unit-width=FULL_NAME", + "compact-long currency/USD unit-width-full-name", NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "88 thousand US dollars", @@ -297,7 +297,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Plural One", - "CC", + "compact-long", NumberFormatter.with().notation(Notation.compactLong()), ULocale.forLanguageTag("es"), 1000000, @@ -305,7 +305,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Plural Other", - "CC", + "compact-long", NumberFormatter.with().notation(Notation.compactLong()), ULocale.forLanguageTag("es"), 2000000, @@ -313,7 +313,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact with Negative Sign", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, -9876543.21, @@ -321,7 +321,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 990000, @@ -329,7 +329,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 999000, @@ -337,7 +337,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 999900, @@ -345,7 +345,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 9900000, @@ -353,7 +353,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Compact Rounding", - "C", + "compact-short", NumberFormatter.with().notation(Notation.compactShort()), ULocale.ENGLISH, 9990000, @@ -366,7 +366,7 @@ public class NumberFormatterApiTest { compactCustomData.put("1000", entry); assertFormatSingle( "Compact Somali No Figure", - "", + null, // feature not supported in skeleton NumberFormatter.with().notation(CompactNotation.forCustomData(compactCustomData)), ULocale.ENGLISH, 1000, @@ -423,7 +423,7 @@ public class NumberFormatterApiTest { assertFormatSingleMeasure( "Meters with Measure Input", - "unit-width=FULL_NAME", + "unit-width-full-name", NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, new Measure(5.43, MeasureUnit.METER), @@ -431,7 +431,7 @@ public class NumberFormatterApiTest { assertFormatSingleMeasure( "Measure format method takes precedence over fluent chain", - "U:length:meter", + "measure-unit/length-meter", NumberFormatter.with().unit(MeasureUnit.METER), ULocale.ENGLISH, new Measure(5.43, USD), @@ -439,7 +439,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Meters with Negative Sign", - "U:length:meter", + "measure-unit/length-meter", NumberFormatter.with().unit(MeasureUnit.METER), ULocale.ENGLISH, -9876543.21, @@ -448,7 +448,7 @@ public class NumberFormatterApiTest { // The locale string "सान" appears only in brx.txt: assertFormatSingle( "Interesting Data Fallback 1", - "U:duration:day unit-width=FULL_NAME", + "measure-unit/duration-day unit-width-full-name", NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.FULL_NAME), ULocale.forLanguageTag("brx"), 5.43, @@ -457,7 +457,7 @@ public class NumberFormatterApiTest { // Requires following the alias from unitsNarrow to unitsShort: assertFormatSingle( "Interesting Data Fallback 2", - "U:duration:day unit-width=NARROW", + "measure-unit/duration-day unit-width-narrow", NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("brx"), 5.43, @@ -467,7 +467,7 @@ public class NumberFormatterApiTest { // requiring fallback to the root. assertFormatSingle( "Interesting Data Fallback 3", - "U:area:square-meter unit-width=NARROW", + "measure-unit/area-square-meter unit-width-narrow", NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("en-GB"), 5.43, @@ -477,7 +477,7 @@ public class NumberFormatterApiTest { // NOTE: This example is in the documentation. assertFormatSingle( "MeasureUnit Difference between Narrow and Short (Narrow Version)", - "", + "measure-unit/temperature-fahrenheit unit-width-narrow", NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("es-US"), 5.43, @@ -485,7 +485,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "MeasureUnit Difference between Narrow and Short (Short Version)", - "", + "measure-unit/temperature-fahrenheit unit-width-short", NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("es-US"), 5.43, @@ -493,7 +493,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "MeasureUnit form without {0} in CLDR pattern", - "", + "measure-unit/temperature-kelvin unit-width-full-name", NumberFormatter.with().unit(MeasureUnit.KELVIN).unitWidth(UnitWidth.FULL_NAME), ULocale.forLanguageTag("es-MX"), 1, @@ -501,9 +501,9 @@ public class NumberFormatterApiTest { assertFormatSingle( "MeasureUnit form without {0} in CLDR pattern and wide base form", - "", + "measure-unit/temperature-kelvin .0000000000 unit-width-full-name", NumberFormatter.with() - .rounding(Rounder.fixedFraction(20)) + .rounding(Rounder.fixedFraction(10)) .unit(MeasureUnit.KELVIN) .unitWidth(UnitWidth.FULL_NAME), ULocale.forLanguageTag("es-MX"), @@ -515,7 +515,7 @@ public class NumberFormatterApiTest { public void unitCompoundMeasure() { assertFormatDescending( "Meters Per Second Short (unit that simplifies)", - "", + "measure-unit/length-meter per-measure-unit/duration-second", NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND), ULocale.ENGLISH, "87,650 m/s", @@ -530,7 +530,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Pounds Per Square Mile Short (secondary unit has per-format)", - "", + "measure-unit/mass-pound per-measure-unit/area-square-mile", NumberFormatter.with().unit(MeasureUnit.POUND).perUnit(MeasureUnit.SQUARE_MILE), ULocale.ENGLISH, "87,650 lb/mi²", @@ -545,7 +545,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Joules Per Furlong Short (unit with no simplifications or special patterns)", - "", + "measure-unit/energy-joule per-measure-unit/length-furlong", NumberFormatter.with().unit(MeasureUnit.JOULE).perUnit(MeasureUnit.FURLONG), ULocale.ENGLISH, "87,650 J/fur", @@ -563,7 +563,7 @@ public class NumberFormatterApiTest { public void unitCurrency() { assertFormatDescending( "Currency", - "$GBP", + "currency/GBP", NumberFormatter.with().unit(GBP), ULocale.ENGLISH, "£87,650.00", @@ -578,7 +578,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency ISO", - "$GBP unit-width=ISO_CODE", + "currency/GBP unit-width-iso-code", NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, "GBP 87,650.00", @@ -593,7 +593,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Long Name", - "$GBP unit-width=FULL_NAME", + "currency/GBP unit-width-full-name", NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "87,650.00 British pounds", @@ -608,7 +608,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Hidden", - "$GBP unit-width=HIDDEN", + "currency/GBP unit-width-hidden", NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.HIDDEN), ULocale.ENGLISH, "87,650.00", @@ -631,7 +631,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency Long Name from Pattern Syntax", - "$GBP F0 grouping=none integer-width=1- symbols=loc:en sign=AUTO decimal=AUTO", + null, NumberFormatter.fromDecimalFormat( PatternStringParser.parseToProperties("0 ¤¤¤"), DecimalFormatSymbols.getInstance(ULocale.ENGLISH), @@ -642,7 +642,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency with Negative Sign", - "$GBP", + "currency/GBP", NumberFormatter.with().unit(GBP), ULocale.ENGLISH, -9876543.21, @@ -652,7 +652,7 @@ public class NumberFormatterApiTest { // NOTE: This example is in the documentation. assertFormatSingle( "Currency Difference between Narrow and Short (Narrow Version)", - "", + "currency/USD unit-width-narrow", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("en-CA"), 5.43, @@ -660,7 +660,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency Difference between Narrow and Short (Short Version)", - "", + "currency/USD unit-width-short", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("en-CA"), 5.43, @@ -668,7 +668,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent format (Control)", - "", + "currency/USD unit-width-short", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("ca"), 444444.55, @@ -676,7 +676,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent format (Test)", - "", + "currency/ESP unit-width-short", NumberFormatter.with().unit(ESP).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("ca"), 444444.55, @@ -684,7 +684,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent symbols (Control)", - "", + "currency/USD unit-width-short", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("pt-PT"), 444444.55, @@ -694,7 +694,7 @@ public class NumberFormatterApiTest { // width space), and they set the decimal separator to the $ symbol. assertFormatSingle( "Currency-dependent symbols (Test)", - "", + "currency/PTE unit-width-short", NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.SHORT), ULocale.forLanguageTag("pt-PT"), 444444.55, @@ -702,7 +702,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent symbols (Test)", - "", + "currency/PTE unit-width-narrow", NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW), ULocale.forLanguageTag("pt-PT"), 444444.55, @@ -710,7 +710,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency-dependent symbols (Test)", - "", + "currency/PTE unit-width-iso-code", NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.ISO_CODE), ULocale.forLanguageTag("pt-PT"), 444444.55, @@ -721,7 +721,7 @@ public class NumberFormatterApiTest { public void unitPercent() { assertFormatDescending( "Percent", - "%", + "percent", NumberFormatter.with().unit(NoUnit.PERCENT), ULocale.ENGLISH, "87,650%", @@ -736,7 +736,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Permille", - "%%", + "permille", NumberFormatter.with().unit(NoUnit.PERMILLE), ULocale.ENGLISH, "87,650‰", @@ -751,7 +751,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "NoUnit Base", - "B", + "base-unit", NumberFormatter.with().unit(NoUnit.BASE), ULocale.ENGLISH, 51423, @@ -759,7 +759,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Percent with Negative Sign", - "%", + "percent", NumberFormatter.with().unit(NoUnit.PERCENT), ULocale.ENGLISH, -98.7654321, @@ -1023,7 +1023,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Standard", - "round-currency-standard", + "currency/CZK round-currency-standard", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.STANDARD)).unit(CZK), ULocale.ENGLISH, "CZK 87,650.00", @@ -1038,7 +1038,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Cash", - "round-currency-cash", + "currency/CZK round-currency-cash", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CZK), ULocale.ENGLISH, "CZK 87,650", @@ -1053,7 +1053,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency Cash with Nickel Rounding", - "round-currency-cash", + "currency/CAD round-currency-cash", NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CAD), ULocale.ENGLISH, "CA$87,650.00", @@ -1068,7 +1068,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Currency not in top-level fluent chain", - "round-currency-cash/CZK", + "round-integer", // calling .withCurrency() applies currency rounding rules immediately NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH).withCurrency(CZK)), ULocale.ENGLISH, "87,650", @@ -1083,8 +1083,8 @@ public class NumberFormatterApiTest { // NOTE: Other tests cover the behavior of the other rounding modes. assertFormatDescending( + "Rounding Mode CEILING", "round-integer/CEILING", - "", NumberFormatter.with().rounding(Rounder.integer().withMode(RoundingMode.CEILING)), ULocale.ENGLISH, "87,650", @@ -1102,7 +1102,7 @@ public class NumberFormatterApiTest { public void grouping() { assertFormatDescendingBig( "Western Grouping", - "grouping=defaults", + "group-auto", NumberFormatter.with().grouping(GroupingStrategy.AUTO), ULocale.ENGLISH, "87,650,000", @@ -1117,7 +1117,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Indic Grouping", - "grouping=defaults", + "group-auto", NumberFormatter.with().grouping(GroupingStrategy.AUTO), new ULocale("en-IN"), "8,76,50,000", @@ -1132,7 +1132,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Western Grouping, Min 2", - "grouping=min2", + "group-min2", NumberFormatter.with().grouping(GroupingStrategy.MIN2), ULocale.ENGLISH, "87,650,000", @@ -1147,7 +1147,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Indic Grouping, Min 2", - "grouping=min2", + "group-min2", NumberFormatter.with().grouping(GroupingStrategy.MIN2), new ULocale("en-IN"), "8,76,50,000", @@ -1162,7 +1162,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "No Grouping", - "grouping=none", + "group-off", NumberFormatter.with().grouping(GroupingStrategy.OFF), new ULocale("en-IN"), "87650000", @@ -1177,7 +1177,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Indic locale with THOUSANDS grouping", - "", + "group-thousands", NumberFormatter.with().grouping(GroupingStrategy.THOUSANDS), new ULocale("en-IN"), "87,650,000", @@ -1194,7 +1194,7 @@ public class NumberFormatterApiTest { // If this test breaks due to data changes, find another locale that has minimumGroupingDigits. assertFormatDescendingBig( "Hungarian Grouping", - "", + "group-auto", NumberFormatter.with().grouping(GroupingStrategy.AUTO), new ULocale("hu"), "87 650 000", @@ -1209,7 +1209,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Hungarian Grouping, Min 2", - "", + "group-min2", NumberFormatter.with().grouping(GroupingStrategy.MIN2), new ULocale("hu"), "87 650 000", @@ -1224,7 +1224,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Hungarian Grouping, Always", - "", + "group-on-aligned", NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED), new ULocale("hu"), "87 650 000", @@ -1241,7 +1241,7 @@ public class NumberFormatterApiTest { // If this test breaks due to data changes, find another locale that has no default grouping. assertFormatDescendingBig( "Bulgarian Currency Grouping", - "", + "currency/USD group-auto", NumberFormatter.with().grouping(GroupingStrategy.AUTO).unit(USD), new ULocale("bg"), "87650000,00 щ.д.", @@ -1256,7 +1256,7 @@ public class NumberFormatterApiTest { assertFormatDescendingBig( "Bulgarian Currency Grouping, Always", - "", + "currency/USD group-on-aligned", NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED).unit(USD), new ULocale("bg"), "87 650 000,00 щ.д.", @@ -1273,7 +1273,7 @@ public class NumberFormatterApiTest { macros.grouping = Grouper.getInstance((short) 4, (short) 1, (short) 3); assertFormatDescendingBig( "Custom Grouping via Internal API", - "", + null, NumberFormatter.with().macros(macros), ULocale.ENGLISH, "8,7,6,5,0000", @@ -1291,7 +1291,7 @@ public class NumberFormatterApiTest { public void padding() { assertFormatDescending( "Padding", - "", + null, NumberFormatter.with().padding(Padder.none()), ULocale.ENGLISH, "87,650", @@ -1306,7 +1306,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Padding", - "", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)), ULocale.ENGLISH, "**87,650", @@ -1321,7 +1321,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Padding with code points", - "", + null, NumberFormatter.with().padding(Padder.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)), ULocale.ENGLISH, "𐇤𐇤87,650", @@ -1336,7 +1336,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Padding with wide digits", - "symbols=ns:mathsanb", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)) .symbols(NumberingSystem.getInstanceByName("mathsanb")), ULocale.ENGLISH, @@ -1352,7 +1352,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Padding with currency spacing", - "$GBP unit-width=ISO_CODE", + null, NumberFormatter.with().padding(Padder.codePoints('*', 10, PadPosition.AFTER_PREFIX)).unit(GBP) .unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, @@ -1368,7 +1368,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Pad Before Prefix", - "", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_PREFIX)), ULocale.ENGLISH, -88.88, @@ -1376,7 +1376,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Pad After Prefix", - "", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)), ULocale.ENGLISH, -88.88, @@ -1384,7 +1384,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Pad Before Suffix", - "%", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX)) .unit(NoUnit.PERCENT), ULocale.ENGLISH, @@ -1393,7 +1393,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Pad After Suffix", - "%", + null, NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX)) .unit(NoUnit.PERCENT), ULocale.ENGLISH, @@ -1402,7 +1402,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency Spacing with Zero Digit Padding Broken", - "$GBP unit-width=ISO_CODE", + null, NumberFormatter.with().padding(Padder.codePoints('0', 12, PadPosition.AFTER_PREFIX)).unit(GBP) .unitWidth(UnitWidth.ISO_CODE), ULocale.ENGLISH, @@ -1414,7 +1414,7 @@ public class NumberFormatterApiTest { public void integerWidth() { assertFormatDescending( "Integer Width Default", - "integer-width=1-", + "integer-width/+0", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1)), ULocale.ENGLISH, "87,650", @@ -1429,7 +1429,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Integer Width Zero Fill 0", - "integer-width=0-", + "integer-width/+", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(0)), ULocale.ENGLISH, "87,650", @@ -1444,7 +1444,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Integer Width Zero Fill 3", - "integer-width=3-", + "integer-width/+000", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(3)), ULocale.ENGLISH, "87,650", @@ -1459,7 +1459,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Integer Width Max 3", - "integer-width=1-3", + "integer-width/##0", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1).truncateAt(3)), ULocale.ENGLISH, "650", @@ -1474,7 +1474,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Integer Width Fixed 2", - "integer-width=2", + "integer-width/00", NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)), ULocale.ENGLISH, "50", @@ -1492,7 +1492,7 @@ public class NumberFormatterApiTest { public void symbols() { assertFormatDescending( "French Symbols with Japanese Data 1", - "symbols=loc:fr", + null, NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)), ULocale.JAPAN, "87 650", @@ -1507,7 +1507,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "French Symbols with Japanese Data 2", - "C symbols=loc:fr", + null, NumberFormatter.with().notation(Notation.compactShort()) .symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)), ULocale.JAPAN, @@ -1516,7 +1516,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Latin Numbering System with Arabic Data", - "$USD symbols=ns:latn", + "currency/USD latin", NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), new ULocale("ar"), "US$ 87,650.00", @@ -1531,7 +1531,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Math Numbering System with French Data", - "symbols=ns:mathsanb", + "numbering-system/mathsanb", NumberFormatter.with().symbols(NumberingSystem.getInstanceByName("mathsanb")), ULocale.FRENCH, "𝟴𝟳 𝟲𝟱𝟬", @@ -1546,7 +1546,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Swiss Symbols (used in documentation)", - "symbols=loc:de_CH", + null, NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("de-CH"))), ULocale.ENGLISH, 12345.67, @@ -1554,7 +1554,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Myanmar Symbols (used in documentation)", - "symbols=loc:my_MY", + null, NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("my_MY"))), ULocale.ENGLISH, 12345.67, @@ -1564,7 +1564,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency symbol should precede number in ar with NS latn", - "", + "currency/USD latin", NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), new ULocale("ar"), 12345.67, @@ -1572,7 +1572,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency symbol should precede number in ar@numbers=latn", - "", + "currency/USD", NumberFormatter.with().unit(USD), new ULocale("ar@numbers=latn"), 12345.67, @@ -1580,7 +1580,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency symbol should follow number in ar-EG with NS arab", - "", + "currency/USD", NumberFormatter.with().unit(USD), new ULocale("ar-EG"), 12345.67, @@ -1588,7 +1588,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Currency symbol should follow number in ar@numbers=arab", - "", + "currency/USD", NumberFormatter.with().unit(USD), new ULocale("ar@numbers=arab"), 12345.67, @@ -1596,7 +1596,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "NumberingSystem in API should win over @numbers keyword", - "", + "currency/USD latin", NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), new ULocale("ar@numbers=arab"), 12345.67, @@ -1615,7 +1615,7 @@ public class NumberFormatterApiTest { symbols.setGroupingSeparatorString("!"); assertFormatSingle( "Symbols object should be copied", - "symbols=loc:de_CH", + null, f, ULocale.ENGLISH, 12345.67, @@ -1623,7 +1623,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "The last symbols setter wins", - "symbols=ns:latn", + "latin", NumberFormatter.with().symbols(symbols).symbols(NumberingSystem.LATIN), ULocale.ENGLISH, 12345.67, @@ -1631,7 +1631,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "The last symbols setter wins", - "symbols=loc:de_CH", + null, NumberFormatter.with().symbols(NumberingSystem.LATIN).symbols(symbols), ULocale.ENGLISH, 12345.67, @@ -1657,7 +1657,7 @@ public class NumberFormatterApiTest { public void sign() { assertFormatSingle( "Sign Auto Positive", - "sign=AUTO", + "sign-auto", NumberFormatter.with().sign(SignDisplay.AUTO), ULocale.ENGLISH, 444444, @@ -1665,7 +1665,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Auto Negative", - "sign=AUTO", + "sign-auto", NumberFormatter.with().sign(SignDisplay.AUTO), ULocale.ENGLISH, -444444, @@ -1673,7 +1673,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Auto Zero", - "", + "sign-auto", NumberFormatter.with().sign(SignDisplay.AUTO), ULocale.ENGLISH, 0, @@ -1681,7 +1681,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Always Positive", - "sign=ALWAYS", + "sign-always", NumberFormatter.with().sign(SignDisplay.ALWAYS), ULocale.ENGLISH, 444444, @@ -1689,7 +1689,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Always Negative", - "sign=ALWAYS", + "sign-always", NumberFormatter.with().sign(SignDisplay.ALWAYS), ULocale.ENGLISH, -444444, @@ -1697,7 +1697,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Always Zero", - "", + "sign-always", NumberFormatter.with().sign(SignDisplay.ALWAYS), ULocale.ENGLISH, 0, @@ -1705,7 +1705,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Never Positive", - "sign=NEVER", + "sign-never", NumberFormatter.with().sign(SignDisplay.NEVER), ULocale.ENGLISH, 444444, @@ -1713,7 +1713,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Never Negative", - "sign=NEVER", + "sign-never", NumberFormatter.with().sign(SignDisplay.NEVER), ULocale.ENGLISH, -444444, @@ -1721,7 +1721,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Never Zero", - "", + "sign-never", NumberFormatter.with().sign(SignDisplay.NEVER), ULocale.ENGLISH, 0, @@ -1729,7 +1729,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting Positive", - "$USD sign=ACCOUNTING", + "currency/USD sign-accounting", NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), ULocale.ENGLISH, 444444, @@ -1737,7 +1737,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting Negative", - "$USD sign=ACCOUNTING", + "currency/USD sign-accounting", NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), ULocale.ENGLISH, -444444, @@ -1745,7 +1745,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting Zero", - "", + "currency/USD sign-accounting", NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), ULocale.ENGLISH, 0, @@ -1753,7 +1753,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Always Positive", - "$USD sign=ACCOUNTING_ALWAYS", + "currency/USD sign-accounting-always", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), ULocale.ENGLISH, 444444, @@ -1761,7 +1761,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Always Negative", - "$USD sign=ACCOUNTING_ALWAYS", + "currency/USD sign-accounting-always", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), ULocale.ENGLISH, -444444, @@ -1769,7 +1769,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Always Zero", - "", + "currency/USD sign-accounting-always", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), ULocale.ENGLISH, 0, @@ -1777,7 +1777,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Except-Zero Positive", - "", + "sign-except-zero", NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), ULocale.ENGLISH, 444444, @@ -1785,7 +1785,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Always Negative", - "", + "sign-except-zero", NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), ULocale.ENGLISH, -444444, @@ -1793,7 +1793,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Except-Zero Zero", - "", + "sign-except-zero", NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), ULocale.ENGLISH, 0, @@ -1801,7 +1801,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Except-Zero Positive", - "$USD sign=ACCOUNTING_ALWAYS", + "currency/USD sign-accounting-except-zero", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), ULocale.ENGLISH, 444444, @@ -1809,7 +1809,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Except-Zero Negative", - "$USD sign=ACCOUNTING_ALWAYS", + "currency/USD sign-accounting-except-zero", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), ULocale.ENGLISH, -444444, @@ -1817,7 +1817,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting-Except-Zero Zero", - "", + "currency/USD sign-accounting-except-zero", NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), ULocale.ENGLISH, 0, @@ -1825,7 +1825,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Sign Accounting Negative Hidden", - "$USD unit-width=HIDDEN sign=ACCOUNTING", + "currency/USD unit-width-hidden sign-accounting", NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.HIDDEN), ULocale.ENGLISH, -444444, @@ -1836,7 +1836,7 @@ public class NumberFormatterApiTest { public void decimal() { assertFormatDescending( "Decimal Default", - "decimal=AUTO", + "decimal-auto", NumberFormatter.with().decimal(DecimalSeparatorDisplay.AUTO), ULocale.ENGLISH, "87,650", @@ -1851,7 +1851,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Decimal Always Shown", - "decimal=ALWAYS", + "decimal-always", NumberFormatter.with().decimal(DecimalSeparatorDisplay.ALWAYS), ULocale.ENGLISH, "87,650.", @@ -1880,7 +1880,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Plural 1", - "$USD F0 unit-width=FULL_NAME", + "currency/USD round-integer unit-width-full-name", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).rounding(Rounder.fixedFraction(0)), ULocale.ENGLISH, 1, @@ -1888,7 +1888,7 @@ public class NumberFormatterApiTest { assertFormatSingle( "Plural 1.00", - "$USD F2 unit-width=FULL_NAME", + "currency/USD .00 unit-width-full-name", NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).rounding(Rounder.fixedFraction(2)), ULocale.ENGLISH, 1, @@ -2036,18 +2036,23 @@ public class NumberFormatterApiTest { double[] inputs, String... expected) { assert expected.length == 9; - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); for (int i = 0; i < 9; i++) { double d = inputs[i]; String actual1 = l1.format(d).toString(); assertEquals(message + ": Unsafe Path: " + d, expected[i], actual1); String actual2 = l2.format(d).toString(); assertEquals(message + ": Safe Path: " + d, expected[i], actual2); - String actual3 = l3.format(d).toString(); - assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3); + } + if (skeleton != null) { // if null, skeleton is declared as undefined. + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + for (int i = 0; i < 9; i++) { + double d = inputs[i]; + String actual3 = l3.format(d).toString(); + assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3); + } } } @@ -2058,16 +2063,18 @@ public class NumberFormatterApiTest { ULocale locale, Number input, String expected) { - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); String actual1 = l1.format(input).toString(); assertEquals(message + ": Unsafe Path: " + input, expected, actual1); String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); - String actual3 = l3.format(input).toString(); - assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + if (skeleton != null) { // if null, skeleton is declared as undefined. + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + String actual3 = l3.format(input).toString(); + assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + } } private static void assertFormatSingleMeasure( @@ -2077,15 +2084,17 @@ public class NumberFormatterApiTest { ULocale locale, Measure input, String expected) { - assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation - LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); String actual1 = l1.format(input).toString(); assertEquals(message + ": Unsafe Path: " + input, expected, actual1); String actual2 = l2.format(input).toString(); assertEquals(message + ": Safe Path: " + input, expected, actual2); - String actual3 = l3.format(input).toString(); - assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + if (skeleton != null) { // if null, skeleton is declared as undefined. + assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton()); + LocalizedNumberFormatter l3 = NumberFormatter.fromSkeleton(skeleton).locale(locale); + String actual3 = l3.format(input).toString(); + assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + } } } 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 6735271ab4..6bb084c48b 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 @@ -38,7 +38,11 @@ public class NumberSkeletonTest { "@#+", "round-increment/xxx", "round-increment/0.1.2", - }; + "currency/dummy", + "measure-unit/foo", + "integer-width/xxx", + "integer-width/0+", + "integer-width/+0#", }; for (String cas : cases) { try { @@ -51,19 +55,33 @@ public class NumberSkeletonTest { } @Test - public void stemsRequiringOption() { - String[] cases = { - "round-increment", - "round-increment/", - "round-increment scientific", - }; + public void unknownTokens() { + String[] cases = { "measure-unit/foo-bar", "numbering-system/dummy" }; for (String cas : cases) { try { NumberFormatter.fromSkeleton(cas); fail(); } catch (SkeletonSyntaxException expected) { - assertTrue(expected.getMessage(), expected.getMessage().contains("requires an option")); + assertTrue(expected.getMessage(), expected.getMessage().contains("Unknown")); + } + } + } + + @Test + public void stemsRequiringOption() { + String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", }; + String[] suffixes = { "", "/", " scientific", "/ scientific" }; + + for (String stem : stems) { + for (String suffix : suffixes) { + try { + NumberFormatter.fromSkeleton(stem + suffix); + fail(); + } catch (SkeletonSyntaxException expected) { + assertTrue(expected.getMessage(), + expected.getMessage().contains("requires an option")); + } } } }