diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/RangeMacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/RangeMacroProps.java index 9595e7d55a..800dcf6ab9 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/RangeMacroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/RangeMacroProps.java @@ -4,9 +4,9 @@ package com.ibm.icu.impl.number.range; import java.util.Objects; -import com.ibm.icu.number.NumberFormatterSettings; import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse; import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback; +import com.ibm.icu.number.UnlocalizedNumberFormatter; import com.ibm.icu.util.ULocale; /** @@ -14,8 +14,8 @@ import com.ibm.icu.util.ULocale; * */ public class RangeMacroProps { - public NumberFormatterSettings formatter1; - public NumberFormatterSettings formatter2; + public UnlocalizedNumberFormatter formatter1; + public UnlocalizedNumberFormatter formatter2; public RangeCollapse collapse; public RangeIdentityFallback identityFallback; public ULocale loc; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberRangeFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberRangeFormatter.java index 0721ab71bb..87d94941bb 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberRangeFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberRangeFormatter.java @@ -5,8 +5,8 @@ package com.ibm.icu.number; import com.ibm.icu.impl.number.DecimalQuantity; import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD; import com.ibm.icu.impl.number.NumberStringBuilder; +import com.ibm.icu.impl.number.range.RangeMacroProps; import com.ibm.icu.number.FormattedNumberRange.RangeIdentityType; -import com.ibm.icu.text.NumberFormat; /** * A NumberRangeFormatter that has a locale associated with it; this means .formatRange() methods are available. @@ -36,16 +36,72 @@ public class LocalizedNumberRangeFormatter extends NumberRangeFormatterSettings< * @see NumberRangeFormatter */ public FormattedNumberRange formatRange(int first, int second) { - // TODO: This is a placeholder implementation. DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first); DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second); + return formatImpl(dq1, dq2); + } + + /** + * Format the given doubles to a string using the settings specified in the NumberRangeFormatter fluent setting + * chain. + * + * @param first + * The first number in the range, usually to the left in LTR locales. + * @param second + * The second number in the range, usually to the right in LTR locales. + * @return A FormattedNumber object; call .toString() to get the string. + * @draft ICU 63 + * @provisional This API might change or be removed in a future release. + * @see NumberRangeFormatter + */ + public FormattedNumberRange formatRange(double first, double second) { + DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first); + DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second); + return formatImpl(dq1, dq2); + } + + /** + * Format the given Numbers to a string using the settings specified in the NumberRangeFormatter fluent setting + * chain. + * + * @param first + * The first number in the range, usually to the left in LTR locales. + * @param second + * The second number in the range, usually to the right in LTR locales. + * @return A FormattedNumber object; call .toString() to get the string. + * @draft ICU 63 + * @provisional This API might change or be removed in a future release. + * @see NumberRangeFormatter + */ + public FormattedNumberRange formatRange(Number first, Number second) { + DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first); + DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second); + return formatImpl(dq1, dq2); + } + + FormattedNumberRange formatImpl(DecimalQuantity first, DecimalQuantity second) { + // TODO: This is a placeholder implementation. + RangeMacroProps macros = resolve(); + LocalizedNumberFormatter f1 , f2; + if (macros.formatter1 != null) { + f1 = macros.formatter1.locale(macros.loc); + } else { + f1 = NumberFormatter.withLocale(macros.loc); + } + if (macros.formatter2 != null) { + f2 = macros.formatter2.locale(macros.loc); + } else { + f2 = NumberFormatter.withLocale(macros.loc); + } + FormattedNumber r1 = f1.format(first); + FormattedNumber r2 = f2.format(second); NumberStringBuilder nsb = new NumberStringBuilder(); - nsb.append(dq1.toPlainString(), NumberFormat.Field.INTEGER); + nsb.append(r1.nsb); nsb.append(" --- ", null); - nsb.append(dq2.toPlainString(), NumberFormat.Field.INTEGER); + nsb.append(r2.nsb); RangeIdentityType identityType = (first == second) ? RangeIdentityType.EQUAL_BEFORE_ROUNDING : RangeIdentityType.NOT_EQUAL; - return new FormattedNumberRange(nsb, dq1, dq2, identityType); + return new FormattedNumberRange(nsb, first, second, identityType); } @Override diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatter.java index 8c351b35cf..da6618b562 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatter.java @@ -2,6 +2,10 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.number; +import java.util.Locale; + +import com.ibm.icu.util.ULocale; + /** * The main entrypoint to the formatting of ranges of numbers, including currencies and other units of measurement. * @@ -16,4 +20,18 @@ public abstract class NumberRangeFormatter { public static enum RangeIdentityFallback {} + private static final UnlocalizedNumberRangeFormatter BASE = new UnlocalizedNumberRangeFormatter(); + + public static UnlocalizedNumberRangeFormatter with() { + return BASE; + } + + public static LocalizedNumberRangeFormatter withLocale(Locale locale) { + return BASE.locale(locale); + } + + public static LocalizedNumberRangeFormatter withLocale(ULocale locale) { + return BASE.locale(locale); + } + } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterSettings.java index d7725e8a2f..23de02a071 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterSettings.java @@ -38,11 +38,11 @@ public abstract class NumberRangeFormatterSettings formatter) { + public T numberFormatter(UnlocalizedNumberFormatter formatter) { return numberFormatters(formatter, formatter); } - public T numberFormatters(NumberFormatterSettings formatterFirst, NumberFormatterSettings formatterSecond) { + public T numberFormatters(UnlocalizedNumberFormatter formatterFirst, UnlocalizedNumberFormatter formatterSecond) { T intermediate = create(KEY_FORMATTER_1, formatterFirst); return (T) intermediate.create(KEY_FORMATTER_2, formatterSecond); } @@ -79,12 +79,12 @@ public abstract class NumberRangeFormatterSettings) current.value; + macros.formatter1 = (UnlocalizedNumberFormatter) current.value; } break; case KEY_FORMATTER_2: if (macros.formatter2 == null) { - macros.formatter2 = (NumberFormatterSettings) current.value; + macros.formatter2 = (UnlocalizedNumberFormatter) current.value; } break; case KEY_COLLAPSE: @@ -131,9 +131,9 @@ public abstract class NumberRangeFormatterSettings) other).resolve()); + return resolve().equals(((NumberRangeFormatterSettings) other).resolve()); } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberRangeFormatterTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberRangeFormatterTest.java new file mode 100644 index 0000000000..c0cfce5c1f --- /dev/null +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberRangeFormatterTest.java @@ -0,0 +1,162 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License +package com.ibm.icu.dev.test.number; + +import static org.junit.Assert.assertEquals; + +import java.util.Locale; + +import org.junit.Test; + +import com.ibm.icu.number.LocalizedNumberRangeFormatter; +import com.ibm.icu.number.NumberFormatter; +import com.ibm.icu.number.NumberFormatter.GroupingStrategy; +import com.ibm.icu.number.NumberRangeFormatter; +import com.ibm.icu.number.UnlocalizedNumberRangeFormatter; +import com.ibm.icu.util.Currency; +import com.ibm.icu.util.ULocale; + +/** + * @author sffc + * + */ +public class NumberRangeFormatterTest { + + private static final Currency USD = Currency.getInstance("USD"); + private static final Currency GBP = Currency.getInstance("GBP"); + + @Test + public void testSanity() { + LocalizedNumberRangeFormatter lnrf1 = NumberRangeFormatter.withLocale(ULocale.US); + LocalizedNumberRangeFormatter lnrf2 = NumberRangeFormatter.with().locale(ULocale.US); + LocalizedNumberRangeFormatter lnrf3 = NumberRangeFormatter.withLocale(Locale.US); + LocalizedNumberRangeFormatter lnrf4 = NumberRangeFormatter.with().locale(Locale.US); + assertEquals("Formatters should be equal 1", lnrf1, lnrf2); + assertEquals("Formatters should be equal 2", lnrf2, lnrf3); + assertEquals("Formatters should be equal 3", lnrf3, lnrf4); + assertEquals("Formatters should have same behavior 1", lnrf1.formatRange(4, 6), lnrf2.formatRange(4, 6)); + assertEquals("Formatters should have same behavior 2", lnrf2.formatRange(4, 6), lnrf3.formatRange(4, 6)); + assertEquals("Formatters should have same behavior 3", lnrf3.formatRange(4, 6), lnrf4.formatRange(4, 6)); + } + + @Test + public void testBasic() { + assertFormatRange( + "Basic", + NumberRangeFormatter.with(), + ULocale.US, + "1 --- 5", + "5 --- 5", + "5 --- 5", + "0 --- 3", + "0 --- 0", + "3 --- 3,000", + "3,000 --- 5,000", + "4,999 --- 5,001", + "5,000 --- 5,000", + "5,000 --- 5,000,000"); + } + + @Test + public void testNullBehavior() { + assertFormatRange( + "Basic", + NumberRangeFormatter.with().numberFormatter(null), + ULocale.US, + "1 --- 5", + "5 --- 5", + "5 --- 5", + "0 --- 3", + "0 --- 0", + "3 --- 3,000", + "3,000 --- 5,000", + "4,999 --- 5,001", + "5,000 --- 5,000", + "5,000 --- 5,000,000"); + + assertFormatRange( + "Basic", + NumberRangeFormatter.with().numberFormatters(null, null), + ULocale.US, + "1 --- 5", + "5 --- 5", + "5 --- 5", + "0 --- 3", + "0 --- 0", + "3 --- 3,000", + "3,000 --- 5,000", + "4,999 --- 5,001", + "5,000 --- 5,000", + "5,000 --- 5,000,000"); + + assertFormatRange( + "Basic", + NumberRangeFormatter.with().numberFormatters( + NumberFormatter.with().grouping(GroupingStrategy.OFF), + null + ), + ULocale.US, + "1 --- 5", + "5 --- 5", + "5 --- 5", + "0 --- 3", + "0 --- 0", + "3 --- 3,000", + "3000 --- 5,000", + "4999 --- 5,001", + "5000 --- 5,000", + "5000 --- 5,000,000"); + + assertFormatRange( + "Basic", + NumberRangeFormatter.with().numberFormatters( + null, + NumberFormatter.with().grouping(GroupingStrategy.OFF) + ), + ULocale.US, + "1 --- 5", + "5 --- 5", + "5 --- 5", + "0 --- 3", + "0 --- 0", + "3 --- 3000", + "3,000 --- 5000", + "4,999 --- 5001", + "5,000 --- 5000", + "5,000 --- 5000000"); + } + + static void assertFormatRange( + String message, + UnlocalizedNumberRangeFormatter f, + ULocale locale, + String expected_10_50, + String expected_49_51, + String expected_50_50, + String expected_00_30, + String expected_00_00, + String expected_30_3K, + String expected_30K_50K, + String expected_49K_51K, + String expected_50K_50K, + String expected_50K_50M) { + LocalizedNumberRangeFormatter l = f.locale(locale); + assertFormattedRangeEquals(message, l, 1, 5, expected_10_50); + assertFormattedRangeEquals(message, l, 4.9999999, 5.0000001, expected_49_51); + assertFormattedRangeEquals(message, l, 5, 5, expected_50_50); + assertFormattedRangeEquals(message, l, 0, 3, expected_00_30); + assertFormattedRangeEquals(message, l, 0, 0, expected_00_00); + assertFormattedRangeEquals(message, l, 3, 3000, expected_30_3K); + assertFormattedRangeEquals(message, l, 3000, 5000, expected_30K_50K); + assertFormattedRangeEquals(message, l, 4999, 5001, expected_49K_51K); + assertFormattedRangeEquals(message, l, 5000, 5000, expected_50K_50K); + assertFormattedRangeEquals(message, l, 5e3, 5e6, expected_50K_50M); + } + + private static void assertFormattedRangeEquals(String message, LocalizedNumberRangeFormatter l, Number first, + Number second, String expected) { + String actual1 = l.formatRange(first, second).toString(); + assertEquals(message + ": " + first + ", " + second, expected, actual1); + } + +}