mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-13 08:53:20 +00:00
ICU-11276 Adding Java NumberRangeFormatter implementation.
This commit is contained in:
parent
57f448e93c
commit
55974b2fb6
23 changed files with 1334 additions and 195 deletions
|
@ -1154,8 +1154,22 @@ const char16_t* DecimalQuantity::checkHealth() const {
|
|||
}
|
||||
|
||||
bool DecimalQuantity::operator==(const DecimalQuantity& other) const {
|
||||
// FIXME: Make a faster implementation.
|
||||
return toString() == other.toString();
|
||||
bool basicEquals = scale == other.scale && precision == other.precision && flags == other.flags
|
||||
&& lOptPos == other.lOptPos && lReqPos == other.lReqPos && rReqPos == other.rReqPos
|
||||
&& rOptPos == other.rOptPos;
|
||||
if (!basicEquals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (precision == 0) {
|
||||
return true;
|
||||
}
|
||||
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
|
||||
if (getDigit(m) != other.getDigit(m)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
UnicodeString DecimalQuantity::toString() const {
|
||||
|
|
|
@ -82,9 +82,10 @@ int32_t ScientificModifier::getPrefixLength() const {
|
|||
}
|
||||
|
||||
int32_t ScientificModifier::getCodePointCount() const {
|
||||
// This method is not used for strong modifiers.
|
||||
U_ASSERT(false);
|
||||
return 0;
|
||||
// NOTE: This method is only called one place, NumberRangeFormatterImpl.
|
||||
// The call site only cares about != 0 and != 1.
|
||||
// Return a very large value so that if this method is used elsewhere, we should notice.
|
||||
return 999;
|
||||
}
|
||||
|
||||
bool ScientificModifier::isStrong() const {
|
||||
|
|
|
@ -18,11 +18,20 @@ using namespace icu::number;
|
|||
using namespace icu::number::impl;
|
||||
|
||||
|
||||
// This function needs to be declared in this namespace so it can be friended.
|
||||
// NOTE: In Java, this logic is handled in the resolve() function.
|
||||
void icu::number::impl::touchRangeLocales(RangeMacroProps& macros) {
|
||||
macros.formatter1.fMacros.locale = macros.locale;
|
||||
macros.formatter2.fMacros.locale = macros.locale;
|
||||
}
|
||||
|
||||
|
||||
template<typename Derived>
|
||||
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(const UnlocalizedNumberFormatter& formatter) const& {
|
||||
Derived copy(*this);
|
||||
copy.fMacros.formatter1 = formatter;
|
||||
copy.fMacros.singleFormatter = true;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
@ -31,6 +40,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(const Unlocal
|
|||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter1 = formatter;
|
||||
move.fMacros.singleFormatter = true;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
|
@ -39,6 +49,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(UnlocalizedNu
|
|||
Derived copy(*this);
|
||||
copy.fMacros.formatter1 = std::move(formatter);
|
||||
copy.fMacros.singleFormatter = true;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
@ -47,6 +58,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(UnlocalizedNu
|
|||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter1 = std::move(formatter);
|
||||
move.fMacros.singleFormatter = true;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
|
@ -55,6 +67,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(const Unloca
|
|||
Derived copy(*this);
|
||||
copy.fMacros.formatter1 = formatter;
|
||||
copy.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
@ -63,6 +76,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(const Unloca
|
|||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter1 = formatter;
|
||||
move.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
|
@ -71,6 +85,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(UnlocalizedN
|
|||
Derived copy(*this);
|
||||
copy.fMacros.formatter1 = std::move(formatter);
|
||||
copy.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
@ -79,6 +94,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(UnlocalizedN
|
|||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter1 = std::move(formatter);
|
||||
move.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
|
@ -87,6 +103,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(const Unloc
|
|||
Derived copy(*this);
|
||||
copy.fMacros.formatter2 = formatter;
|
||||
copy.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
@ -95,6 +112,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(const Unloc
|
|||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter2 = formatter;
|
||||
move.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
|
@ -103,6 +121,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(Unlocalized
|
|||
Derived copy(*this);
|
||||
copy.fMacros.formatter2 = std::move(formatter);
|
||||
copy.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(copy.fMacros);
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
@ -111,6 +130,7 @@ Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(Unlocalized
|
|||
Derived move(std::move(*this));
|
||||
move.fMacros.formatter2 = std::move(formatter);
|
||||
move.fMacros.singleFormatter = false;
|
||||
touchRangeLocales(move.fMacros);
|
||||
return move;
|
||||
}
|
||||
|
||||
|
@ -228,11 +248,13 @@ LocalizedNumberRangeFormatter::~LocalizedNumberRangeFormatter() {
|
|||
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(const RangeMacroProps& macros, const Locale& locale) {
|
||||
fMacros = macros;
|
||||
fMacros.locale = locale;
|
||||
touchRangeLocales(fMacros);
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(RangeMacroProps&& macros, const Locale& locale) {
|
||||
fMacros = std::move(macros);
|
||||
fMacros.locale = locale;
|
||||
touchRangeLocales(fMacros);
|
||||
}
|
||||
|
||||
LocalizedNumberRangeFormatter UnlocalizedNumberRangeFormatter::locale(const Locale& locale) const& {
|
||||
|
|
|
@ -65,7 +65,7 @@ void getNumberRangeData(const char* localeName, const char* nsName, NumberRangeD
|
|||
ures_getAllItemsWithFallback(rb.getAlias(), dataPath.data(), sink, status);
|
||||
if (U_FAILURE(status)) { return; }
|
||||
|
||||
// TODO: Is it necessary to maually fall back to latn, or does the data sink take care of that?
|
||||
// TODO: Is it necessary to manually fall back to latn, or does the data sink take care of that?
|
||||
|
||||
if (data.rangePattern.getArgumentLimit() == 0) {
|
||||
// No data!
|
||||
|
@ -107,11 +107,10 @@ void NumberRangeFormatterImpl::format(UFormattedNumberRangeData& data, bool equa
|
|||
|
||||
MicroProps micros1;
|
||||
MicroProps micros2;
|
||||
formatterImpl1.preProcess(data.quantity1, micros1, status);
|
||||
if (fSameFormatters) {
|
||||
formatterImpl1.preProcess(data.quantity1, micros1, status);
|
||||
formatterImpl1.preProcess(data.quantity2, micros2, status);
|
||||
} else {
|
||||
formatterImpl1.preProcess(data.quantity1, micros1, status);
|
||||
formatterImpl2.preProcess(data.quantity2, micros2, status);
|
||||
}
|
||||
|
||||
|
@ -124,6 +123,7 @@ void NumberRangeFormatterImpl::format(UFormattedNumberRangeData& data, bool equa
|
|||
|| !(*micros1.modMiddle == *micros2.modMiddle)
|
||||
|| !(*micros1.modOuter == *micros2.modOuter)) {
|
||||
formatRange(data, micros1, micros2, status);
|
||||
data.identityResult = UNUM_IDENTITY_RESULT_NOT_EQUAL;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -182,8 +182,8 @@ void NumberRangeFormatterImpl::formatSingleValue(UFormattedNumberRangeData& data
|
|||
UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (fSameFormatters) {
|
||||
int32_t length = formatterImpl1.writeNumber(micros1, data.quantity1, data.string, 0, status);
|
||||
formatterImpl1.writeAffixes(micros1, data.string, 0, length, status);
|
||||
int32_t length = NumberFormatterImpl::writeNumber(micros1, data.quantity1, data.string, 0, status);
|
||||
NumberFormatterImpl::writeAffixes(micros1, data.string, 0, length, status);
|
||||
} else {
|
||||
formatRange(data, micros1, micros2, status);
|
||||
}
|
||||
|
@ -195,8 +195,8 @@ void NumberRangeFormatterImpl::formatApproximately (UFormattedNumberRangeData& d
|
|||
UErrorCode& status) const {
|
||||
if (U_FAILURE(status)) { return; }
|
||||
if (fSameFormatters) {
|
||||
int32_t length = formatterImpl1.writeNumber(micros1, data.quantity1, data.string, 0, status);
|
||||
length += formatterImpl1.writeAffixes(micros1, data.string, 0, length, status);
|
||||
int32_t length = NumberFormatterImpl::writeNumber(micros1, data.quantity1, data.string, 0, status);
|
||||
length += NumberFormatterImpl::writeAffixes(micros1, data.string, 0, length, status);
|
||||
fApproximatelyModifier.apply(data.string, 0, length, status);
|
||||
} else {
|
||||
formatRange(data, micros1, micros2, status);
|
||||
|
@ -242,9 +242,7 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
|
|||
// (could disable collapsing of the middle modifier)
|
||||
// The modifiers are equal by this point, so we can look at just one of them.
|
||||
const Modifier* mm = micros1.modMiddle;
|
||||
if (mm == nullptr) {
|
||||
// pass
|
||||
} else if (fCollapse == UNUM_RANGE_COLLAPSE_UNIT) {
|
||||
if (fCollapse == UNUM_RANGE_COLLAPSE_UNIT) {
|
||||
// Only collapse if the modifier is a unit.
|
||||
// TODO: Make a better way to check for a unit?
|
||||
// TODO: Handle case where the modifier has both notation and unit (compact currency)?
|
||||
|
@ -321,6 +319,7 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
|
|||
// TODO: Support padding?
|
||||
|
||||
if (collapseInner) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
lengthInfix += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
|
||||
} else {
|
||||
length1 += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
|
||||
|
@ -328,6 +327,7 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
|
|||
}
|
||||
|
||||
if (collapseMiddle) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
lengthInfix += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
|
||||
} else {
|
||||
length1 += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
|
||||
|
@ -335,6 +335,7 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
|
|||
}
|
||||
|
||||
if (collapseOuter) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
lengthInfix += micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
|
||||
} else {
|
||||
length1 += micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
|
||||
|
|
|
@ -145,6 +145,8 @@ class CurrencySymbols;
|
|||
class GeneratorHelpers;
|
||||
class DecNum;
|
||||
class NumberRangeFormatterImpl;
|
||||
struct RangeMacroProps;
|
||||
void touchRangeLocales(impl::RangeMacroProps& macros);
|
||||
|
||||
} // namespace impl
|
||||
|
||||
|
@ -2112,6 +2114,7 @@ class U_I18N_API NumberFormatterSettings {
|
|||
friend class UnlocalizedNumberFormatter;
|
||||
|
||||
// Give NumberRangeFormatter access to the MacroProps
|
||||
friend void impl::touchRangeLocales(impl::RangeMacroProps& macros);
|
||||
friend class impl::NumberRangeFormatterImpl;
|
||||
};
|
||||
|
||||
|
|
|
@ -107,6 +107,54 @@ void NumberRangeFormatterTest::testBasic() {
|
|||
u"4,999 m – 5,001 km",
|
||||
u"5,000 m – 5,000 km",
|
||||
u"5,000 m – 5,000,000 km");
|
||||
|
||||
assertFormatRange(
|
||||
u"Basic long unit",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
|
||||
Locale("en-us"),
|
||||
u"1 meter – 5 meters", // TODO: This doesn't collapse because the plurals are different. Fix?
|
||||
u"~5 meters",
|
||||
u"~5 meters",
|
||||
u"0–3 meters", // Note: It collapses when the plurals are the same
|
||||
u"~0 meters",
|
||||
u"3–3,000 meters",
|
||||
u"3,000–5,000 meters",
|
||||
u"4,999–5,001 meters",
|
||||
u"~5,000 meters",
|
||||
u"5,000–5,000,000 meters");
|
||||
|
||||
assertFormatRange(
|
||||
u"Non-English locale and unit",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
|
||||
Locale("fr-FR"),
|
||||
u"1 degré Fahrenheit – 5 degrés Fahrenheit",
|
||||
u"~5 degrés Fahrenheit",
|
||||
u"~5 degrés Fahrenheit",
|
||||
u"0 degré Fahrenheit – 3 degrés Fahrenheit",
|
||||
u"~0 degré Fahrenheit",
|
||||
u"3–3 000 degrés Fahrenheit",
|
||||
u"3 000–5 000 degrés Fahrenheit",
|
||||
u"4 999–5 001 degrés Fahrenheit",
|
||||
u"~5 000 degrés Fahrenheit",
|
||||
u"5 000–5 000 000 degrés Fahrenheit");
|
||||
|
||||
assertFormatRange(
|
||||
u"Portuguese currency",
|
||||
NumberRangeFormatter::with()
|
||||
.numberFormatterBoth(NumberFormatter::with().unit(PTE)),
|
||||
Locale("pt-PT"),
|
||||
u"1$00 - 5$00 \u200B",
|
||||
u"~5$00 \u200B",
|
||||
u"~5$00 \u200B",
|
||||
u"0$00 - 3$00 \u200B",
|
||||
u"~0$00 \u200B",
|
||||
u"3$00 - 3000$00 \u200B",
|
||||
u"3000$00 - 5000$00 \u200B",
|
||||
u"4999$00 - 5001$00 \u200B",
|
||||
u"~5000$00 \u200B",
|
||||
u"5000$00 - 5,000,000$00 \u200B");
|
||||
}
|
||||
|
||||
void NumberRangeFormatterTest::testCollapse() {
|
||||
|
@ -392,6 +440,40 @@ void NumberRangeFormatterTest::testCollapse() {
|
|||
u"~5K m",
|
||||
u"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
u"No collapse on scientific notation",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_NONE)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
|
||||
Locale("en-us"),
|
||||
u"1E0 – 5E0",
|
||||
u"~5E0",
|
||||
u"~5E0",
|
||||
u"0E0 – 3E0",
|
||||
u"~0E0",
|
||||
u"3E0 – 3E3",
|
||||
u"3E3 – 5E3",
|
||||
u"4.999E3 – 5.001E3",
|
||||
u"~5E3",
|
||||
u"5E3 – 5E6");
|
||||
|
||||
assertFormatRange(
|
||||
u"All collapse on scientific notation",
|
||||
NumberRangeFormatter::with()
|
||||
.collapse(UNUM_RANGE_COLLAPSE_ALL)
|
||||
.numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
|
||||
Locale("en-us"),
|
||||
u"1–5E0",
|
||||
u"~5E0",
|
||||
u"~5E0",
|
||||
u"0–3E0",
|
||||
u"~0E0",
|
||||
u"3E0 – 3E3",
|
||||
u"3–5E3",
|
||||
u"4.999–5.001E3",
|
||||
u"~5E3",
|
||||
u"5E3 – 5E6");
|
||||
|
||||
// TODO: Test compact currency?
|
||||
// The code is not smart enough to differentiate the notation from the unit.
|
||||
}
|
||||
|
|
|
@ -74,6 +74,23 @@ public class ConstantAffixModifier implements Modifier {
|
|||
return strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not currently used.
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsModifier(Modifier other) {
|
||||
if (!(other instanceof ConstantAffixModifier)) {
|
||||
return false;
|
||||
}
|
||||
ConstantAffixModifier _other = (ConstantAffixModifier) other;
|
||||
return prefix.equals(_other.prefix) && suffix.equals(_other.suffix) && field == _other.field
|
||||
&& strong == _other.strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("<ConstantAffixModifier prefix:'%s' suffix:'%s'>", prefix, suffix);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
|
@ -59,6 +61,32 @@ public class ConstantMultiFieldModifier implements Modifier {
|
|||
return strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
for (int i = 0; i < prefixFields.length; i++) {
|
||||
if (prefixFields[i] == field) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < suffixFields.length; i++) {
|
||||
if (suffixFields[i] == field) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsModifier(Modifier other) {
|
||||
if (!(other instanceof ConstantMultiFieldModifier)) {
|
||||
return false;
|
||||
}
|
||||
ConstantMultiFieldModifier _other = (ConstantMultiFieldModifier) other;
|
||||
return Arrays.equals(prefixChars, _other.prefixChars) && Arrays.equals(prefixFields, _other.prefixFields)
|
||||
&& Arrays.equals(suffixChars, _other.suffixChars) && Arrays.equals(suffixFields, _other.suffixFields)
|
||||
&& overwrite == _other.overwrite && strong == _other.strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
NumberStringBuilder temp = new NumberStringBuilder();
|
||||
|
|
|
@ -1014,6 +1014,37 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(other instanceof DecimalQuantity_AbstractBCD)) {
|
||||
return false;
|
||||
}
|
||||
DecimalQuantity_AbstractBCD _other = (DecimalQuantity_AbstractBCD) other;
|
||||
|
||||
boolean basicEquals = scale == _other.scale && precision == _other.precision && flags == _other.flags
|
||||
&& lOptPos == _other.lOptPos && lReqPos == _other.lReqPos && rReqPos == _other.rReqPos
|
||||
&& rOptPos == _other.rOptPos;
|
||||
if (!basicEquals) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (precision == 0) {
|
||||
return true;
|
||||
}
|
||||
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
|
||||
if (getDigit(m) != _other.getDigit(m)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single digit from the BCD list. No internal state is changed by calling this method.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied
|
||||
* to the string builder. A Modifier usually contains a prefix and a suffix that are applied, but it
|
||||
|
@ -48,4 +50,14 @@ public interface Modifier {
|
|||
* @return Whether the modifier is strong.
|
||||
*/
|
||||
public boolean isStrong();
|
||||
|
||||
/**
|
||||
* Whether the modifier contains at least one occurrence of the given field.
|
||||
*/
|
||||
public boolean containsField(Field currency);
|
||||
|
||||
/**
|
||||
* Returns whether the affixes owned by this modifier are equal to the ones owned by the given modifier.
|
||||
*/
|
||||
public boolean equalsModifier(Modifier other);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.ibm.icu.impl.number.AffixUtils.SymbolProvider;
|
|||
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
||||
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.Currency;
|
||||
|
||||
|
@ -319,6 +320,20 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
|
|||
return isStrong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not currently used. (unsafe path not used in range formatting)
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsModifier(Modifier other) {
|
||||
// This method is not currently used. (unsafe path not used in range formatting)
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
private int insertPrefix(NumberStringBuilder sb, int position) {
|
||||
prepareAffix(true);
|
||||
int length = AffixUtils.unescape(currentAffix, sb, position, this);
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
package com.ibm.icu.impl.number;
|
||||
|
||||
import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
import com.ibm.icu.util.ICUException;
|
||||
|
||||
/**
|
||||
* The second primary implementation of {@link Modifier}, this one consuming a
|
||||
|
@ -80,6 +82,22 @@ public class SimpleModifier implements Modifier {
|
|||
return strong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not currently used.
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsModifier(Modifier other) {
|
||||
if (!(other instanceof SimpleModifier)) {
|
||||
return false;
|
||||
}
|
||||
SimpleModifier _other = (SimpleModifier) other;
|
||||
return compiledPattern.equals(_other.compiledPattern) && field == _other.field && strong == _other.strong;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
|
||||
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not
|
||||
|
@ -123,4 +141,66 @@ public class SimpleModifier implements Modifier {
|
|||
return prefixLength + suffixLength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Like above, this belongs with the rest of the SimpleFormatterImpl code.
|
||||
* I put it here so that the SimpleFormatter uses in NumberStringBuilder are near each other.
|
||||
*
|
||||
* <p>
|
||||
* Applies the compiled two-argument pattern to the NumberStringBuilder.
|
||||
*
|
||||
* <p>
|
||||
* This method is optimized for the case where the prefix and suffix are often empty, such as
|
||||
* in the range pattern like "{0}-{1}".
|
||||
*/
|
||||
public static void formatTwoArgPattern(String compiledPattern, NumberStringBuilder result, int index, PrefixInfixSuffixLengthHelper h,
|
||||
Field field) {
|
||||
int argLimit = SimpleFormatterImpl.getArgumentLimit(compiledPattern);
|
||||
if (argLimit != 2) {
|
||||
throw new ICUException();
|
||||
}
|
||||
int offset = 1; // offset into compiledPattern
|
||||
int length = 0; // chars added to result
|
||||
|
||||
int prefixLength = compiledPattern.charAt(offset);
|
||||
offset++;
|
||||
if (prefixLength < ARG_NUM_LIMIT) {
|
||||
// No prefix
|
||||
prefixLength = 0;
|
||||
} else {
|
||||
prefixLength -= ARG_NUM_LIMIT;
|
||||
result.insert(index + length, compiledPattern, offset, offset + prefixLength, field);
|
||||
offset += prefixLength;
|
||||
length += prefixLength;
|
||||
offset++;
|
||||
}
|
||||
|
||||
int infixLength = compiledPattern.charAt(offset);
|
||||
offset++;
|
||||
if (infixLength < ARG_NUM_LIMIT) {
|
||||
// No infix
|
||||
infixLength = 0;
|
||||
} else {
|
||||
infixLength -= ARG_NUM_LIMIT;
|
||||
result.insert(index + length, compiledPattern, offset, offset + infixLength, field);
|
||||
offset += infixLength;
|
||||
length += infixLength;
|
||||
offset++;
|
||||
}
|
||||
|
||||
int suffixLength;
|
||||
if (offset == compiledPattern.length()) {
|
||||
// No suffix
|
||||
suffixLength = 0;
|
||||
} else {
|
||||
suffixLength = compiledPattern.charAt(offset) - ARG_NUM_LIMIT;
|
||||
offset++;
|
||||
result.insert(index + length, compiledPattern, offset, offset + suffixLength, field);
|
||||
length += suffixLength;
|
||||
}
|
||||
|
||||
h.lengthPrefix = prefixLength;
|
||||
h.lengthInfix = infixLength;
|
||||
h.lengthSuffix = suffixLength;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.impl.number.range;
|
||||
|
||||
/**
|
||||
* A small, mutable internal helper class for keeping track of offsets on range patterns.
|
||||
*/
|
||||
public class PrefixInfixSuffixLengthHelper {
|
||||
public int lengthPrefix = 0;
|
||||
public int length1 = 0;
|
||||
public int lengthInfix = 0;
|
||||
public int length2 = 0;
|
||||
public int lengthSuffix = 0;
|
||||
|
||||
public int index0() {
|
||||
return lengthPrefix;
|
||||
}
|
||||
|
||||
public int index1() {
|
||||
return lengthPrefix + length1;
|
||||
}
|
||||
|
||||
public int index2() {
|
||||
return lengthPrefix + length1 + lengthInfix;
|
||||
}
|
||||
|
||||
public int index3() {
|
||||
return lengthPrefix + length1 + lengthInfix + length2;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import com.ibm.icu.util.ULocale;
|
|||
public class RangeMacroProps {
|
||||
public UnlocalizedNumberFormatter formatter1;
|
||||
public UnlocalizedNumberFormatter formatter2;
|
||||
public int sameFormatters = -1; // -1 for unset, 0 for false, 1 for true
|
||||
public RangeCollapse collapse;
|
||||
public RangeIdentityFallback identityFallback;
|
||||
public ULocale loc;
|
||||
|
|
|
@ -23,16 +23,16 @@ import com.ibm.icu.util.ICUUncheckedIOException;
|
|||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public class FormattedNumberRange {
|
||||
final NumberStringBuilder nsb;
|
||||
final DecimalQuantity first;
|
||||
final DecimalQuantity second;
|
||||
final NumberStringBuilder string;
|
||||
final DecimalQuantity quantity1;
|
||||
final DecimalQuantity quantity2;
|
||||
final RangeIdentityResult identityResult;
|
||||
|
||||
FormattedNumberRange(NumberStringBuilder nsb, DecimalQuantity first, DecimalQuantity second,
|
||||
FormattedNumberRange(NumberStringBuilder string, DecimalQuantity quantity1, DecimalQuantity quantity2,
|
||||
RangeIdentityResult identityResult) {
|
||||
this.nsb = nsb;
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
this.string = string;
|
||||
this.quantity1 = quantity1;
|
||||
this.quantity2 = quantity2;
|
||||
this.identityResult = identityResult;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ public class FormattedNumberRange {
|
|||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return nsb.toString();
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +67,7 @@ public class FormattedNumberRange {
|
|||
*/
|
||||
public <A extends Appendable> A appendTo(A appendable) {
|
||||
try {
|
||||
appendable.append(nsb);
|
||||
appendable.append(string);
|
||||
} catch (IOException e) {
|
||||
// Throw as an unchecked exception to avoid users needing try/catch
|
||||
throw new ICUUncheckedIOException(e);
|
||||
|
@ -105,7 +105,7 @@ public class FormattedNumberRange {
|
|||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public boolean nextFieldPosition(FieldPosition fieldPosition) {
|
||||
return nsb.nextFieldPosition(fieldPosition);
|
||||
return string.nextFieldPosition(fieldPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,7 +124,7 @@ public class FormattedNumberRange {
|
|||
* @see NumberRangeFormatter
|
||||
*/
|
||||
public AttributedCharacterIterator toCharacterIterator() {
|
||||
return nsb.toCharacterIterator();
|
||||
return string.toCharacterIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,7 +138,7 @@ public class FormattedNumberRange {
|
|||
* @see #getSecondBigDecimal
|
||||
*/
|
||||
public BigDecimal getFirstBigDecimal() {
|
||||
return first.toBigDecimal();
|
||||
return quantity1.toBigDecimal();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,7 +152,7 @@ public class FormattedNumberRange {
|
|||
* @see #getFirstBigDecimal
|
||||
*/
|
||||
public BigDecimal getSecondBigDecimal() {
|
||||
return second.toBigDecimal();
|
||||
return quantity2.toBigDecimal();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,8 +180,8 @@ public class FormattedNumberRange {
|
|||
public int hashCode() {
|
||||
// NumberStringBuilder and BigDecimal are mutable, so we can't call
|
||||
// #equals() or #hashCode() on them directly.
|
||||
return Arrays.hashCode(nsb.toCharArray()) ^ Arrays.hashCode(nsb.toFieldArray())
|
||||
^ first.toBigDecimal().hashCode() ^ second.toBigDecimal().hashCode();
|
||||
return Arrays.hashCode(string.toCharArray()) ^ Arrays.hashCode(string.toFieldArray())
|
||||
^ quantity1.toBigDecimal().hashCode() ^ quantity2.toBigDecimal().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,9 +201,9 @@ public class FormattedNumberRange {
|
|||
// NumberStringBuilder and BigDecimal are mutable, so we can't call
|
||||
// #equals() or #hashCode() on them directly.
|
||||
FormattedNumberRange _other = (FormattedNumberRange) other;
|
||||
return Arrays.equals(nsb.toCharArray(), _other.nsb.toCharArray())
|
||||
&& Arrays.equals(nsb.toFieldArray(), _other.nsb.toFieldArray())
|
||||
&& first.toBigDecimal().equals(_other.first.toBigDecimal())
|
||||
&& second.toBigDecimal().equals(_other.second.toBigDecimal());
|
||||
return Arrays.equals(string.toCharArray(), _other.string.toCharArray())
|
||||
&& Arrays.equals(string.toFieldArray(), _other.string.toFieldArray())
|
||||
&& quantity1.toBigDecimal().equals(_other.quantity1.toBigDecimal())
|
||||
&& quantity2.toBigDecimal().equals(_other.quantity2.toBigDecimal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,9 +152,9 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
|||
public FormattedNumber format(DecimalQuantity fq) {
|
||||
NumberStringBuilder string = new NumberStringBuilder();
|
||||
if (computeCompiled()) {
|
||||
compiled.apply(fq, string);
|
||||
compiled.format(fq, string);
|
||||
} else {
|
||||
NumberFormatterImpl.applyStatic(resolve(), fq, string);
|
||||
NumberFormatterImpl.formatStatic(resolve(), fq, string);
|
||||
}
|
||||
return new FormattedNumber(string, fq);
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
|
|||
// Further benchmarking is required.
|
||||
long currentCount = callCount.incrementAndGet(this);
|
||||
if (currentCount == macros.threshold.longValue()) {
|
||||
compiled = NumberFormatterImpl.fromMacros(macros);
|
||||
compiled = new NumberFormatterImpl(macros);
|
||||
return true;
|
||||
} else if (compiled != null) {
|
||||
return true;
|
||||
|
|
|
@ -4,9 +4,6 @@ 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.NumberRangeFormatter.RangeIdentityResult;
|
||||
|
||||
/**
|
||||
* A NumberRangeFormatter that has a locale associated with it; this means .formatRange() methods are available.
|
||||
|
@ -18,6 +15,8 @@ import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityResult;
|
|||
*/
|
||||
public class LocalizedNumberRangeFormatter extends NumberRangeFormatterSettings<LocalizedNumberRangeFormatter> {
|
||||
|
||||
private volatile NumberRangeFormatterImpl fImpl;
|
||||
|
||||
LocalizedNumberRangeFormatter(NumberRangeFormatterSettings<?> parent, int key, Object value) {
|
||||
super(parent, key, value);
|
||||
}
|
||||
|
@ -85,28 +84,10 @@ public class LocalizedNumberRangeFormatter extends NumberRangeFormatterSettings<
|
|||
}
|
||||
|
||||
FormattedNumberRange formatImpl(DecimalQuantity first, DecimalQuantity second, boolean equalBeforeRounding) {
|
||||
// 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 (fImpl == null) {
|
||||
fImpl = new NumberRangeFormatterImpl(resolve());
|
||||
}
|
||||
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(r1.nsb);
|
||||
nsb.append(" --- ", null);
|
||||
nsb.append(r2.nsb);
|
||||
RangeIdentityResult identityResult = equalBeforeRounding ? RangeIdentityResult.EQUAL_BEFORE_ROUNDING
|
||||
: RangeIdentityResult.NOT_EQUAL;
|
||||
return new FormattedNumberRange(nsb, first, second, identityResult);
|
||||
return fImpl.format(first, second, equalBeforeRounding);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -42,21 +42,21 @@ import com.ibm.icu.util.MeasureUnit;
|
|||
class NumberFormatterImpl {
|
||||
|
||||
/** Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly. */
|
||||
public static NumberFormatterImpl fromMacros(MacroProps macros) {
|
||||
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, true);
|
||||
return new NumberFormatterImpl(microPropsGenerator);
|
||||
public NumberFormatterImpl(MacroProps macros) {
|
||||
this(macrosToMicroGenerator(macros, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
|
||||
*/
|
||||
public static void applyStatic(
|
||||
public static int formatStatic(
|
||||
MacroProps macros,
|
||||
DecimalQuantity inValue,
|
||||
NumberStringBuilder outString) {
|
||||
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
|
||||
MicroProps micros = microPropsGenerator.processQuantity(inValue);
|
||||
microsToString(micros, inValue, outString);
|
||||
MicroProps micros = preProcessUnsafe(macros, inValue);
|
||||
int length = writeNumber(micros, inValue, outString, 0);
|
||||
length += writeAffixes(micros, outString, 0, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,9 +82,40 @@ class NumberFormatterImpl {
|
|||
this.microPropsGenerator = microPropsGenerator;
|
||||
}
|
||||
|
||||
public void apply(DecimalQuantity inValue, NumberStringBuilder outString) {
|
||||
/**
|
||||
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
|
||||
*/
|
||||
public int format(DecimalQuantity inValue, NumberStringBuilder outString) {
|
||||
MicroProps micros = preProcess(inValue);
|
||||
int length = writeNumber(micros, inValue, outString, 0);
|
||||
length += writeAffixes(micros, outString, 0, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like format(), but saves the result into an output MicroProps without additional processing.
|
||||
*/
|
||||
public MicroProps preProcess(DecimalQuantity inValue) {
|
||||
MicroProps micros = microPropsGenerator.processQuantity(inValue);
|
||||
microsToString(micros, inValue, outString);
|
||||
micros.rounder.apply(inValue);
|
||||
if (micros.integerWidth.maxInt == -1) {
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
|
||||
} else {
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
|
||||
}
|
||||
return micros;
|
||||
}
|
||||
|
||||
private static MicroProps preProcessUnsafe(MacroProps macros, DecimalQuantity inValue) {
|
||||
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
|
||||
MicroProps micros = microPropsGenerator.processQuantity(inValue);
|
||||
micros.rounder.apply(inValue);
|
||||
if (micros.integerWidth.maxInt == -1) {
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
|
||||
} else {
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
|
||||
}
|
||||
return micros;
|
||||
}
|
||||
|
||||
public int getPrefixSuffix(byte signum, StandardPlural plural, NumberStringBuilder output) {
|
||||
|
@ -350,64 +381,55 @@ class NumberFormatterImpl {
|
|||
//////////
|
||||
|
||||
/**
|
||||
* Synthesizes the output string from a MicroProps and DecimalQuantity.
|
||||
*
|
||||
* @param micros
|
||||
* The MicroProps after the quantity has been consumed. Will not be mutated.
|
||||
* @param quantity
|
||||
* The DecimalQuantity to be rendered. May be mutated.
|
||||
* @param string
|
||||
* The output string. Will be mutated.
|
||||
* Adds the affixes. Intended to be called immediately after formatNumber.
|
||||
*/
|
||||
private static void microsToString(
|
||||
public static int writeAffixes(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
micros.rounder.apply(quantity);
|
||||
if (micros.integerWidth.maxInt == -1) {
|
||||
quantity.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
|
||||
} else {
|
||||
quantity.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
|
||||
}
|
||||
int length = writeNumber(micros, quantity, string);
|
||||
// NOTE: When range formatting is added, these modifiers can bubble up.
|
||||
// For now, apply them all here at once.
|
||||
NumberStringBuilder string,
|
||||
int start,
|
||||
int end) {
|
||||
// Always apply the inner modifier (which is "strong").
|
||||
length += micros.modInner.apply(string, 0, length);
|
||||
int length = micros.modInner.apply(string, start, end);
|
||||
if (micros.padding.isValid()) {
|
||||
micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, 0, length);
|
||||
micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, start, end + length);
|
||||
} else {
|
||||
length += micros.modMiddle.apply(string, 0, length);
|
||||
length += micros.modOuter.apply(string, 0, length);
|
||||
length += micros.modMiddle.apply(string, start, end + length);
|
||||
length += micros.modOuter.apply(string, start, end + length);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int writeNumber(
|
||||
/**
|
||||
* Synthesizes the output string from a MicroProps and DecimalQuantity.
|
||||
* This method formats only the main number, not affixes.
|
||||
*/
|
||||
public static int writeNumber(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
NumberStringBuilder string,
|
||||
int index) {
|
||||
int length = 0;
|
||||
if (quantity.isInfinite()) {
|
||||
length += string.insert(length, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
|
||||
length += string.insert(length + index, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
|
||||
|
||||
} else if (quantity.isNaN()) {
|
||||
length += string.insert(length, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
|
||||
length += string.insert(length + index, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
|
||||
|
||||
} else {
|
||||
// Add the integer digits
|
||||
length += writeIntegerDigits(micros, quantity, string);
|
||||
length += writeIntegerDigits(micros, quantity, string, length + index);
|
||||
|
||||
// Add the decimal point
|
||||
if (quantity.getLowerDisplayMagnitude() < 0
|
||||
|| micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
|
||||
length += string.insert(length,
|
||||
length += string.insert(length + index,
|
||||
micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
|
||||
: micros.symbols.getDecimalSeparatorString(),
|
||||
NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
}
|
||||
|
||||
// Add the fraction digits
|
||||
length += writeFractionDigits(micros, quantity, string);
|
||||
length += writeFractionDigits(micros, quantity, string, length + index);
|
||||
}
|
||||
|
||||
return length;
|
||||
|
@ -416,13 +438,14 @@ class NumberFormatterImpl {
|
|||
private static int writeIntegerDigits(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
NumberStringBuilder string,
|
||||
int index) {
|
||||
int length = 0;
|
||||
int integerCount = quantity.getUpperDisplayMagnitude() + 1;
|
||||
for (int i = 0; i < integerCount; i++) {
|
||||
// Add grouping separator
|
||||
if (micros.grouping.groupAtPosition(i, quantity)) {
|
||||
length += string.insert(0,
|
||||
length += string.insert(index,
|
||||
micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
|
||||
: micros.symbols.getGroupingSeparatorString(),
|
||||
NumberFormat.Field.GROUPING_SEPARATOR);
|
||||
|
@ -431,11 +454,11 @@ class NumberFormatterImpl {
|
|||
// Get and append the next digit value
|
||||
byte nextDigit = quantity.getDigit(i);
|
||||
if (micros.symbols.getCodePointZero() != -1) {
|
||||
length += string.insertCodePoint(0,
|
||||
length += string.insertCodePoint(index,
|
||||
micros.symbols.getCodePointZero() + nextDigit,
|
||||
NumberFormat.Field.INTEGER);
|
||||
} else {
|
||||
length += string.insert(0,
|
||||
length += string.insert(index,
|
||||
micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
NumberFormat.Field.INTEGER);
|
||||
}
|
||||
|
@ -446,17 +469,18 @@ class NumberFormatterImpl {
|
|||
private static int writeFractionDigits(
|
||||
MicroProps micros,
|
||||
DecimalQuantity quantity,
|
||||
NumberStringBuilder string) {
|
||||
NumberStringBuilder string,
|
||||
int index) {
|
||||
int length = 0;
|
||||
int fractionCount = -quantity.getLowerDisplayMagnitude();
|
||||
for (int i = 0; i < fractionCount; i++) {
|
||||
// Get and append the next digit value
|
||||
byte nextDigit = quantity.getDigit(-i - 1);
|
||||
if (micros.symbols.getCodePointZero() != -1) {
|
||||
length += string.appendCodePoint(micros.symbols.getCodePointZero() + nextDigit,
|
||||
length += string.insertCodePoint(length + index, micros.symbols.getCodePointZero() + nextDigit,
|
||||
NumberFormat.Field.FRACTION);
|
||||
} else {
|
||||
length += string.append(micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
length += string.insert(length + index, micros.symbols.getDigitStringsLocal()[nextDigit],
|
||||
NumberFormat.Field.FRACTION);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,10 +47,10 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
|
|||
static final int KEY_PER_UNIT = 15;
|
||||
static final int KEY_MAX = 16;
|
||||
|
||||
final NumberFormatterSettings<?> parent;
|
||||
final int key;
|
||||
final Object value;
|
||||
volatile MacroProps resolvedMacros;
|
||||
private final NumberFormatterSettings<?> parent;
|
||||
private final int key;
|
||||
private final Object value;
|
||||
private volatile MacroProps resolvedMacros;
|
||||
|
||||
NumberFormatterSettings(NumberFormatterSettings<?> parent, int key, Object value) {
|
||||
this.parent = parent;
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
// © 2018 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.number;
|
||||
|
||||
import com.ibm.icu.impl.ICUData;
|
||||
import com.ibm.icu.impl.ICUResourceBundle;
|
||||
import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.impl.UResource;
|
||||
import com.ibm.icu.impl.number.DecimalQuantity;
|
||||
import com.ibm.icu.impl.number.MicroProps;
|
||||
import com.ibm.icu.impl.number.Modifier;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.impl.number.SimpleModifier;
|
||||
import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
|
||||
import com.ibm.icu.impl.number.range.RangeMacroProps;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityResult;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
/**
|
||||
* Business logic behind NumberRangeFormatter.
|
||||
*/
|
||||
class NumberRangeFormatterImpl {
|
||||
|
||||
NumberFormatterImpl formatterImpl1;
|
||||
NumberFormatterImpl formatterImpl2;
|
||||
boolean fSameFormatters;
|
||||
|
||||
NumberRangeFormatter.RangeCollapse fCollapse;
|
||||
NumberRangeFormatter.RangeIdentityFallback fIdentityFallback;
|
||||
|
||||
String fRangePattern;
|
||||
SimpleModifier fApproximatelyModifier;
|
||||
|
||||
// Helper function for 2-dimensional switch statement
|
||||
int identity2d(RangeIdentityFallback a, RangeIdentityResult b) {
|
||||
return a.ordinal() | (b.ordinal() << 4);
|
||||
}
|
||||
|
||||
private static final class NumberRangeDataSink extends UResource.Sink {
|
||||
|
||||
String rangePattern;
|
||||
String approximatelyPattern;
|
||||
|
||||
// For use with SimpleFormatterImpl
|
||||
StringBuilder sb;
|
||||
|
||||
NumberRangeDataSink(StringBuilder sb) {
|
||||
this.sb = sb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
|
||||
UResource.Table miscTable = value.getTable();
|
||||
for (int i = 0; miscTable.getKeyAndValue(i, key, value); ++i) {
|
||||
if (key.contentEquals("range") && rangePattern == null) {
|
||||
String pattern = value.getString();
|
||||
rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
|
||||
}
|
||||
if (key.contentEquals("approximately") && approximatelyPattern == null) {
|
||||
String pattern = value.getString();
|
||||
approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void getNumberRangeData(
|
||||
ULocale locale,
|
||||
String nsName,
|
||||
NumberRangeFormatterImpl out) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
NumberRangeDataSink sink = new NumberRangeDataSink(sb);
|
||||
ICUResourceBundle resource;
|
||||
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||||
sb.append("NumberElements/");
|
||||
sb.append(nsName);
|
||||
sb.append("/miscPatterns");
|
||||
String key = sb.toString();
|
||||
resource.getAllItemsWithFallback(key, sink);
|
||||
|
||||
// TODO: Is it necessary to manually fall back to latn, or does the data sink take care of that?
|
||||
|
||||
if (sink.rangePattern == null) {
|
||||
sink.rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments("{0} --- {1}", sb, 2, 2);
|
||||
}
|
||||
if (sink.approximatelyPattern == null) {
|
||||
sink.approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments("~{0}", sb, 1, 1);
|
||||
}
|
||||
|
||||
out.fRangePattern = sink.rangePattern;
|
||||
out.fApproximatelyModifier = new SimpleModifier(sink.approximatelyPattern, null, false);
|
||||
}
|
||||
|
||||
public NumberRangeFormatterImpl(RangeMacroProps macros) {
|
||||
formatterImpl1 = new NumberFormatterImpl(macros.formatter1 != null ? macros.formatter1.resolve()
|
||||
: NumberFormatter.withLocale(macros.loc).resolve());
|
||||
formatterImpl2 = new NumberFormatterImpl(macros.formatter2 != null ? macros.formatter2.resolve()
|
||||
: NumberFormatter.withLocale(macros.loc).resolve());
|
||||
fSameFormatters = macros.sameFormatters != 0;
|
||||
fCollapse = macros.collapse != null ? macros.collapse : NumberRangeFormatter.RangeCollapse.AUTO;
|
||||
fIdentityFallback = macros.identityFallback != null ? macros.identityFallback
|
||||
: NumberRangeFormatter.RangeIdentityFallback.APPROXIMATELY;
|
||||
|
||||
// TODO: As of this writing (ICU 63), there is no locale that has different number miscPatterns
|
||||
// based on numbering system. Therefore, data is loaded only from latn. If this changes,
|
||||
// this part of the code should be updated to load from the local numbering system.
|
||||
// The numbering system could come from the one specified in the NumberFormatter passed to
|
||||
// numberFormatterBoth() or similar.
|
||||
|
||||
getNumberRangeData(macros.loc, "latn", this);
|
||||
}
|
||||
|
||||
public FormattedNumberRange format(DecimalQuantity quantity1, DecimalQuantity quantity2, boolean equalBeforeRounding) {
|
||||
NumberStringBuilder string = new NumberStringBuilder();
|
||||
MicroProps micros1 = formatterImpl1.preProcess(quantity1);
|
||||
MicroProps micros2;
|
||||
if (fSameFormatters) {
|
||||
micros2 = formatterImpl1.preProcess(quantity2);
|
||||
} else {
|
||||
micros2 = formatterImpl2.preProcess(quantity2);
|
||||
}
|
||||
|
||||
// If any of the affixes are different, an identity is not possible
|
||||
// and we must use formatRange().
|
||||
// TODO: Write this as MicroProps operator==() ?
|
||||
// TODO: Avoid the redundancy of these equality operations with the
|
||||
// ones in formatRange?
|
||||
if (!micros1.modInner.equalsModifier(micros2.modInner)
|
||||
|| !micros1.modMiddle.equalsModifier(micros2.modMiddle)
|
||||
|| !micros1.modOuter.equalsModifier(micros2.modOuter)) {
|
||||
formatRange(quantity1, quantity2, string, micros1, micros2);
|
||||
return new FormattedNumberRange(string, quantity1, quantity2, RangeIdentityResult.NOT_EQUAL);
|
||||
}
|
||||
|
||||
// Check for identity
|
||||
RangeIdentityResult identityResult;
|
||||
if (equalBeforeRounding) {
|
||||
identityResult = RangeIdentityResult.EQUAL_BEFORE_ROUNDING;
|
||||
} else if (quantity1.equals(quantity2)) {
|
||||
identityResult = RangeIdentityResult.EQUAL_AFTER_ROUNDING;
|
||||
} else {
|
||||
identityResult = RangeIdentityResult.NOT_EQUAL;
|
||||
}
|
||||
|
||||
// Java does not let us use a constexpr like C++;
|
||||
// we need to expand identity2d calls.
|
||||
switch (identity2d(fIdentityFallback, identityResult)) {
|
||||
case (3 | (2 << 4)): // RANGE, NOT_EQUAL
|
||||
case (3 | (1 << 4)): // RANGE, EQUAL_AFTER_ROUNDING
|
||||
case (3 | (0 << 4)): // RANGE, EQUAL_BEFORE_ROUNDING
|
||||
case (2 | (2 << 4)): // APPROXIMATELY, NOT_EQUAL
|
||||
case (1 | (2 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, NOT_EQUAL
|
||||
case (0 | (2 << 4)): // SINGLE_VALUE, NOT_EQUAL
|
||||
formatRange(quantity1, quantity2, string, micros1, micros2);
|
||||
break;
|
||||
|
||||
case (2 | (1 << 4)): // APPROXIMATELY, EQUAL_AFTER_ROUNDING
|
||||
case (2 | (0 << 4)): // APPROXIMATELY, EQUAL_BEFORE_ROUNDING
|
||||
case (1 | (1 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, EQUAL_AFTER_ROUNDING
|
||||
formatApproximately(quantity1, quantity2, string, micros1, micros2);
|
||||
break;
|
||||
|
||||
case (1 | (0 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, EQUAL_BEFORE_ROUNDING
|
||||
case (0 | (1 << 4)): // SINGLE_VALUE, EQUAL_AFTER_ROUNDING
|
||||
case (0 | (0 << 4)): // SINGLE_VALUE, EQUAL_BEFORE_ROUNDING
|
||||
formatSingleValue(quantity1, quantity2, string, micros1, micros2);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert false;
|
||||
break;
|
||||
}
|
||||
|
||||
return new FormattedNumberRange(string, quantity1, quantity2, identityResult);
|
||||
}
|
||||
|
||||
private void formatSingleValue(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
|
||||
MicroProps micros1, MicroProps micros2) {
|
||||
if (fSameFormatters) {
|
||||
int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
|
||||
NumberFormatterImpl.writeAffixes(micros1, string, 0, length);
|
||||
} else {
|
||||
formatRange(quantity1, quantity2, string, micros1, micros2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void formatApproximately(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
|
||||
MicroProps micros1, MicroProps micros2) {
|
||||
if (fSameFormatters) {
|
||||
int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
|
||||
length += NumberFormatterImpl.writeAffixes(micros1, string, 0, length);
|
||||
fApproximatelyModifier.apply(string, 0, length);
|
||||
} else {
|
||||
formatRange(quantity1, quantity2, string, micros1, micros2);
|
||||
}
|
||||
}
|
||||
|
||||
private void formatRange(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
|
||||
MicroProps micros1, MicroProps micros2) {
|
||||
// modInner is always notation (scientific); collapsable in ALL.
|
||||
// modOuter is always units; collapsable in ALL, AUTO, and UNIT.
|
||||
// modMiddle could be either; collapsable in ALL and sometimes AUTO and UNIT.
|
||||
// Never collapse an outer mod but not an inner mod.
|
||||
boolean collapseOuter, collapseMiddle, collapseInner;
|
||||
switch (fCollapse) {
|
||||
case ALL:
|
||||
case AUTO:
|
||||
case UNIT:
|
||||
{
|
||||
// OUTER MODIFIER
|
||||
collapseOuter = micros1.modOuter.equalsModifier(micros2.modOuter);
|
||||
|
||||
if (!collapseOuter) {
|
||||
// Never collapse inner mods if outer mods are not collapsable
|
||||
collapseMiddle = false;
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// MIDDLE MODIFIER
|
||||
collapseMiddle = micros1.modMiddle.equalsModifier(micros2.modMiddle);
|
||||
|
||||
if (!collapseMiddle) {
|
||||
// Never collapse inner mods if outer mods are not collapsable
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// MIDDLE MODIFIER HEURISTICS
|
||||
// (could disable collapsing of the middle modifier)
|
||||
// The modifiers are equal by this point, so we can look at just one of them.
|
||||
Modifier mm = micros1.modMiddle;
|
||||
if (fCollapse == RangeCollapse.UNIT) {
|
||||
// Only collapse if the modifier is a unit.
|
||||
// TODO: Make a better way to check for a unit?
|
||||
// TODO: Handle case where the modifier has both notation and unit (compact currency)?
|
||||
if (mm.containsField(NumberFormat.Field.CURRENCY) && mm.containsField(NumberFormat.Field.PERCENT)) {
|
||||
collapseMiddle = false;
|
||||
}
|
||||
} else if (fCollapse == RangeCollapse.AUTO) {
|
||||
// Heuristic as of ICU 63: collapse only if the modifier is more than one code point.
|
||||
if (mm.getCodePointCount() <= 1) {
|
||||
collapseMiddle = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collapseMiddle || fCollapse != RangeCollapse.ALL) {
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// INNER MODIFIER
|
||||
collapseInner = micros1.modInner.equalsModifier(micros2.modInner);
|
||||
|
||||
// All done checking for collapsability.
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
collapseOuter = false;
|
||||
collapseMiddle = false;
|
||||
collapseInner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Java doesn't have macros, constexprs, or stack objects.
|
||||
// Use a helper object instead.
|
||||
PrefixInfixSuffixLengthHelper h = new PrefixInfixSuffixLengthHelper();
|
||||
|
||||
SimpleModifier.formatTwoArgPattern(fRangePattern, string, 0, h, null);
|
||||
|
||||
// SPACING HEURISTIC
|
||||
// Add spacing unless all modifiers are collapsed.
|
||||
// TODO: add API to control this?
|
||||
{
|
||||
boolean repeatInner = !collapseInner && micros1.modInner.getCodePointCount() > 0;
|
||||
boolean repeatMiddle = !collapseMiddle && micros1.modMiddle.getCodePointCount() > 0;
|
||||
boolean repeatOuter = !collapseOuter && micros1.modOuter.getCodePointCount() > 0;
|
||||
if (repeatInner || repeatMiddle || repeatOuter) {
|
||||
// Add spacing
|
||||
h.lengthInfix += string.insertCodePoint(h.index1(), '\u0020', null);
|
||||
h.lengthInfix += string.insertCodePoint(h.index2(), '\u0020', null);
|
||||
}
|
||||
}
|
||||
|
||||
h.length1 += NumberFormatterImpl.writeNumber(micros1, quantity1, string, h.index0());
|
||||
h.length2 += NumberFormatterImpl.writeNumber(micros2, quantity2, string, h.index2());
|
||||
|
||||
// TODO: Support padding?
|
||||
|
||||
if (collapseInner) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
h.lengthInfix += micros1.modInner.apply(string, h.index0(), h.index3());
|
||||
} else {
|
||||
h.length1 += micros1.modInner.apply(string, h.index0(), h.index1());
|
||||
h.length2 += micros2.modInner.apply(string, h.index2(), h.index3());
|
||||
}
|
||||
|
||||
if (collapseMiddle) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
h.lengthInfix += micros1.modMiddle.apply(string, h.index0(), h.index3());
|
||||
} else {
|
||||
h.length1 += micros1.modMiddle.apply(string, h.index0(), h.index1());
|
||||
h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index3());
|
||||
}
|
||||
|
||||
if (collapseOuter) {
|
||||
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
|
||||
h.lengthInfix += micros1.modOuter.apply(string, h.index0(), h.index3());
|
||||
} else {
|
||||
h.length1 += micros1.modOuter.apply(string, h.index0(), h.index1());
|
||||
h.length2 += micros2.modOuter.apply(string, h.index2(), h.index3());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,14 +23,15 @@ public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatte
|
|||
static final int KEY_LOCALE = 1;
|
||||
static final int KEY_FORMATTER_1 = 2;
|
||||
static final int KEY_FORMATTER_2 = 3;
|
||||
static final int KEY_COLLAPSE = 4;
|
||||
static final int KEY_IDENTITY_FALLBACK = 5;
|
||||
static final int KEY_MAX = 6;
|
||||
static final int KEY_SAME_FORMATTERS = 4;
|
||||
static final int KEY_COLLAPSE = 5;
|
||||
static final int KEY_IDENTITY_FALLBACK = 6;
|
||||
static final int KEY_MAX = 7;
|
||||
|
||||
final NumberRangeFormatterSettings<?> parent;
|
||||
final int key;
|
||||
final Object value;
|
||||
volatile RangeMacroProps resolvedMacros;
|
||||
private final NumberRangeFormatterSettings<?> parent;
|
||||
private final int key;
|
||||
private final Object value;
|
||||
private volatile RangeMacroProps resolvedMacros;
|
||||
|
||||
NumberRangeFormatterSettings(NumberRangeFormatterSettings<?> parent, int key, Object value) {
|
||||
this.parent = parent;
|
||||
|
@ -55,7 +56,7 @@ public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatte
|
|||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T numberFormatterBoth(UnlocalizedNumberFormatter formatter) {
|
||||
return (T) numberFormatterFirst(formatter).numberFormatterSecond(formatter);
|
||||
return (T) create(KEY_SAME_FORMATTERS, true).create(KEY_FORMATTER_1, formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,8 +73,9 @@ public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatte
|
|||
* @see NumberFormatter
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T numberFormatterFirst(UnlocalizedNumberFormatter formatterFirst) {
|
||||
return create(KEY_FORMATTER_1, formatterFirst);
|
||||
return (T) create(KEY_SAME_FORMATTERS, false).create(KEY_FORMATTER_1, formatterFirst);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,8 +92,9 @@ public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatte
|
|||
* @see NumberFormatter
|
||||
* @see NumberRangeFormatter
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public T numberFormatterSecond(UnlocalizedNumberFormatter formatterSecond) {
|
||||
return create(KEY_FORMATTER_2, formatterSecond);
|
||||
return (T) create(KEY_SAME_FORMATTERS, false).create(KEY_FORMATTER_2, formatterSecond);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -173,6 +176,11 @@ public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatte
|
|||
macros.formatter2 = (UnlocalizedNumberFormatter) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_SAME_FORMATTERS:
|
||||
if (macros.sameFormatters == -1) {
|
||||
macros.sameFormatters = (boolean) current.value ? 1 : 0;
|
||||
}
|
||||
break;
|
||||
case KEY_COLLAPSE:
|
||||
if (macros.collapse == null) {
|
||||
macros.collapse = (RangeCollapse) current.value;
|
||||
|
@ -188,6 +196,13 @@ public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatte
|
|||
}
|
||||
current = current.parent;
|
||||
}
|
||||
// Copy the locale into the children (see touchRangeLocales in C++)
|
||||
if (macros.formatter1 != null) {
|
||||
macros.formatter1.resolve().loc = macros.loc;
|
||||
}
|
||||
if (macros.formatter2 != null) {
|
||||
macros.formatter2.resolve().loc = macros.loc;
|
||||
}
|
||||
resolvedMacros = macros;
|
||||
return macros;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
|||
import com.ibm.icu.number.Precision.SignificantRounderImpl;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
|
||||
/**
|
||||
* A class that defines the scientific notation style to be used when formatting numbers in
|
||||
|
@ -221,8 +222,10 @@ public class ScientificNotation extends Notation implements Cloneable {
|
|||
|
||||
@Override
|
||||
public int getCodePointCount() {
|
||||
// This method is not used for strong modifiers.
|
||||
throw new AssertionError();
|
||||
// NOTE: This method is only called one place, NumberRangeFormatterImpl.
|
||||
// The call site only cares about != 0 and != 1.
|
||||
// Return a very large value so that if this method is used elsewhere, we should notice.
|
||||
return 999;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -231,6 +234,20 @@ public class ScientificNotation extends Notation implements Cloneable {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not currently used. (unsafe path not used in range formatting)
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsModifier(Modifier other) {
|
||||
// This method is not currently used. (unsafe path not used in range formatting)
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
return doApply(exponent, output, rightIndex);
|
||||
|
@ -288,5 +305,22 @@ public class ScientificNotation extends Notation implements Cloneable {
|
|||
// Scientific is always strong
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsField(Field field) {
|
||||
// This method is not used for inner modifiers.
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsModifier(Modifier other) {
|
||||
if (!(other instanceof ScientificHandler)) {
|
||||
return false;
|
||||
}
|
||||
ScientificHandler _other = (ScientificHandler) other;
|
||||
// TODO: Check for locale symbols and settings as well? Could be less efficient.
|
||||
return exponent == _other.exponent;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,11 +9,16 @@ import java.util.Locale;
|
|||
import org.junit.Test;
|
||||
|
||||
import com.ibm.icu.number.LocalizedNumberRangeFormatter;
|
||||
import com.ibm.icu.number.Notation;
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
|
||||
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
||||
import com.ibm.icu.number.NumberRangeFormatter;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
|
||||
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
|
||||
import com.ibm.icu.number.Precision;
|
||||
import com.ibm.icu.number.UnlocalizedNumberRangeFormatter;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
|
@ -24,6 +29,7 @@ public class NumberRangeFormatterTest {
|
|||
|
||||
private static final Currency USD = Currency.getInstance("USD");
|
||||
private static final Currency GBP = Currency.getInstance("GBP");
|
||||
private static final Currency PTE = Currency.getInstance("PTE");
|
||||
|
||||
@Test
|
||||
public void testSanity() {
|
||||
|
@ -42,86 +48,507 @@ public class NumberRangeFormatterTest {
|
|||
@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");
|
||||
"Basic",
|
||||
NumberRangeFormatter.with(),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"~5",
|
||||
"~5",
|
||||
"0–3",
|
||||
"~0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,001",
|
||||
"~5,000",
|
||||
"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic with units",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3,000 m",
|
||||
"3,000–5,000 m",
|
||||
"4,999–5,001 m",
|
||||
"~5,000 m",
|
||||
"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic with different units",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterFirst(NumberFormatter.with().unit(MeasureUnit.METER))
|
||||
.numberFormatterSecond(NumberFormatter.with().unit(MeasureUnit.KILOMETER)),
|
||||
new ULocale("en-us"),
|
||||
"1 m – 5 km",
|
||||
"5 m – 5 km",
|
||||
"5 m – 5 km",
|
||||
"0 m – 3 km",
|
||||
"0 m – 0 km",
|
||||
"3 m – 3,000 km",
|
||||
"3,000 m – 5,000 km",
|
||||
"4,999 m – 5,001 km",
|
||||
"5,000 m – 5,000 km",
|
||||
"5,000 m – 5,000,000 km");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic long unit",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME)),
|
||||
new ULocale("en-us"),
|
||||
"1 meter – 5 meters", // TODO: This doesn't collapse because the plurals are different. Fix?
|
||||
"~5 meters",
|
||||
"~5 meters",
|
||||
"0–3 meters", // Note: It collapses when the plurals are the same
|
||||
"~0 meters",
|
||||
"3–3,000 meters",
|
||||
"3,000–5,000 meters",
|
||||
"4,999–5,001 meters",
|
||||
"~5,000 meters",
|
||||
"5,000–5,000,000 meters");
|
||||
|
||||
assertFormatRange(
|
||||
"Non-English locale and unit",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.FULL_NAME)),
|
||||
new ULocale("fr-FR"),
|
||||
"1 degré Fahrenheit – 5 degrés Fahrenheit",
|
||||
"~5 degrés Fahrenheit",
|
||||
"~5 degrés Fahrenheit",
|
||||
"0 degré Fahrenheit – 3 degrés Fahrenheit",
|
||||
"~0 degré Fahrenheit",
|
||||
"3–3 000 degrés Fahrenheit",
|
||||
"3 000–5 000 degrés Fahrenheit",
|
||||
"4 999–5 001 degrés Fahrenheit",
|
||||
"~5 000 degrés Fahrenheit",
|
||||
"5 000–5 000 000 degrés Fahrenheit");
|
||||
|
||||
assertFormatRange(
|
||||
"Portuguese currency",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(PTE)),
|
||||
new ULocale("pt-PT"),
|
||||
"1$00 - 5$00 \u200B",
|
||||
"~5$00 \u200B",
|
||||
"~5$00 \u200B",
|
||||
"0$00 - 3$00 \u200B",
|
||||
"~0$00 \u200B",
|
||||
"3$00 - 3000$00 \u200B",
|
||||
"3000$00 - 5000$00 \u200B",
|
||||
"4999$00 - 5001$00 \u200B",
|
||||
"~5000$00 \u200B",
|
||||
"5000$00 - 5,000,000$00 \u200B");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullBehavior() {
|
||||
public void testCollapse() {
|
||||
assertFormatRange(
|
||||
"Basic",
|
||||
NumberRangeFormatter.with().numberFormatterBoth(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");
|
||||
"Default collapse on currency (default rounding)",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD)),
|
||||
new ULocale("en-us"),
|
||||
"$1.00 – $5.00",
|
||||
"~$5.00",
|
||||
"~$5.00",
|
||||
"$0.00 – $3.00",
|
||||
"~$0.00",
|
||||
"$3.00 – $3,000.00",
|
||||
"$3,000.00 – $5,000.00",
|
||||
"$4,999.00 – $5,001.00",
|
||||
"~$5,000.00",
|
||||
"$5,000.00 – $5,000,000.00");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic",
|
||||
NumberRangeFormatter.with().numberFormatterFirst(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");
|
||||
"Default collapse on currency",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD).precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"$1 – $5",
|
||||
"~$5",
|
||||
"~$5",
|
||||
"$0 – $3",
|
||||
"~$0",
|
||||
"$3 – $3,000",
|
||||
"$3,000 – $5,000",
|
||||
"$4,999 – $5,001",
|
||||
"~$5,000",
|
||||
"$5,000 – $5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterFirst(NumberFormatter.with().grouping(GroupingStrategy.OFF))
|
||||
.numberFormatterSecond(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");
|
||||
"No collapse on currency",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD).precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"$1 – $5",
|
||||
"~$5",
|
||||
"~$5",
|
||||
"$0 – $3",
|
||||
"~$0",
|
||||
"$3 – $3,000",
|
||||
"$3,000 – $5,000",
|
||||
"$4,999 – $5,001",
|
||||
"~$5,000",
|
||||
"$5,000 – $5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Basic",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterFirst(null)
|
||||
.numberFormatterSecond(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");
|
||||
"Unit collapse on currency",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD).precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"$1–5",
|
||||
"~$5",
|
||||
"~$5",
|
||||
"$0–3",
|
||||
"~$0",
|
||||
"$3–3,000",
|
||||
"$3,000–5,000",
|
||||
"$4,999–5,001",
|
||||
"~$5,000",
|
||||
"$5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on currency",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(USD).precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"$1–5",
|
||||
"~$5",
|
||||
"~$5",
|
||||
"$0–3",
|
||||
"~$0",
|
||||
"$3–3,000",
|
||||
"$3,000–5,000",
|
||||
"$4,999–5,001",
|
||||
"~$5,000",
|
||||
"$5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Default collapse on currency ISO code",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.ISO_CODE)
|
||||
.precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"GBP 1–5",
|
||||
"~GBP 5", // TODO: Fix this at some point
|
||||
"~GBP 5",
|
||||
"GBP 0–3",
|
||||
"~GBP 0",
|
||||
"GBP 3–3,000",
|
||||
"GBP 3,000–5,000",
|
||||
"GBP 4,999–5,001",
|
||||
"~GBP 5,000",
|
||||
"GBP 5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on currency ISO code",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.ISO_CODE)
|
||||
.precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"GBP 1 – GBP 5",
|
||||
"~GBP 5", // TODO: Fix this at some point
|
||||
"~GBP 5",
|
||||
"GBP 0 – GBP 3",
|
||||
"~GBP 0",
|
||||
"GBP 3 – GBP 3,000",
|
||||
"GBP 3,000 – GBP 5,000",
|
||||
"GBP 4,999 – GBP 5,001",
|
||||
"~GBP 5,000",
|
||||
"GBP 5,000 – GBP 5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Unit collapse on currency ISO code",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.ISO_CODE)
|
||||
.precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"GBP 1–5",
|
||||
"~GBP 5", // TODO: Fix this at some point
|
||||
"~GBP 5",
|
||||
"GBP 0–3",
|
||||
"~GBP 0",
|
||||
"GBP 3–3,000",
|
||||
"GBP 3,000–5,000",
|
||||
"GBP 4,999–5,001",
|
||||
"~GBP 5,000",
|
||||
"GBP 5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on currency ISO code",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with()
|
||||
.unit(GBP)
|
||||
.unitWidth(UnitWidth.ISO_CODE)
|
||||
.precision(Precision.integer())),
|
||||
new ULocale("en-us"),
|
||||
"GBP 1–5",
|
||||
"~GBP 5", // TODO: Fix this at some point
|
||||
"~GBP 5",
|
||||
"GBP 0–3",
|
||||
"~GBP 0",
|
||||
"GBP 3–3,000",
|
||||
"GBP 3,000–5,000",
|
||||
"GBP 4,999–5,001",
|
||||
"~GBP 5,000",
|
||||
"GBP 5,000–5,000,000");
|
||||
|
||||
// Default collapse on measurement unit is in testBasic()
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on measurement unit",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1 m – 5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0 m – 3 m",
|
||||
"~0 m",
|
||||
"3 m – 3,000 m",
|
||||
"3,000 m – 5,000 m",
|
||||
"4,999 m – 5,001 m",
|
||||
"~5,000 m",
|
||||
"5,000 m – 5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
"Unit collapse on measurement unit",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3,000 m",
|
||||
"3,000–5,000 m",
|
||||
"4,999–5,001 m",
|
||||
"~5,000 m",
|
||||
"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on measurement unit",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with().unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3,000 m",
|
||||
"3,000–5,000 m",
|
||||
"4,999–5,001 m",
|
||||
"~5,000 m",
|
||||
"5,000–5,000,000 m");
|
||||
|
||||
assertFormatRange(
|
||||
"Default collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort()).unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3K m",
|
||||
"3K – 5K m",
|
||||
"~5K m",
|
||||
"~5K m",
|
||||
"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort()).unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1 m – 5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0 m – 3 m",
|
||||
"~0 m",
|
||||
"3 m – 3K m",
|
||||
"3K m – 5K m",
|
||||
"~5K m",
|
||||
"~5K m",
|
||||
"5K m – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
"Unit collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.UNIT)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort()).unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3K m",
|
||||
"3K – 5K m",
|
||||
"~5K m",
|
||||
"~5K m",
|
||||
"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on measurement unit with compact-short notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort()).unit(MeasureUnit.METER)),
|
||||
new ULocale("en-us"),
|
||||
"1–5 m",
|
||||
"~5 m",
|
||||
"~5 m",
|
||||
"0–3 m",
|
||||
"~0 m",
|
||||
"3–3K m",
|
||||
"3–5K m", // this one is the key use case for ALL
|
||||
"~5K m",
|
||||
"~5K m",
|
||||
"5K – 5M m");
|
||||
|
||||
assertFormatRange(
|
||||
"No collapse on scientific notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.NONE)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.scientific())),
|
||||
new ULocale("en-us"),
|
||||
"1E0 – 5E0",
|
||||
"~5E0",
|
||||
"~5E0",
|
||||
"0E0 – 3E0",
|
||||
"~0E0",
|
||||
"3E0 – 3E3",
|
||||
"3E3 – 5E3",
|
||||
"4.999E3 – 5.001E3",
|
||||
"~5E3",
|
||||
"5E3 – 5E6");
|
||||
|
||||
assertFormatRange(
|
||||
"All collapse on scientific notation",
|
||||
NumberRangeFormatter.with()
|
||||
.collapse(RangeCollapse.ALL)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.scientific())),
|
||||
new ULocale("en-us"),
|
||||
"1–5E0",
|
||||
"~5E0",
|
||||
"~5E0",
|
||||
"0–3E0",
|
||||
"~0E0",
|
||||
"3E0 – 3E3",
|
||||
"3–5E3",
|
||||
"4.999–5.001E3",
|
||||
"~5E3",
|
||||
"5E3 – 5E6");
|
||||
|
||||
// TODO: Test compact currency?
|
||||
// The code is not smart enough to differentiate the notation from the unit.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdentity() {
|
||||
assertFormatRange(
|
||||
"Identity fallback Range",
|
||||
NumberRangeFormatter.with().identityFallback(RangeIdentityFallback.RANGE),
|
||||
new ULocale("en-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(
|
||||
"Identity fallback Approximately or Single Value",
|
||||
NumberRangeFormatter.with().identityFallback(RangeIdentityFallback.APPROXIMATELY_OR_SINGLE_VALUE),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"~5",
|
||||
"5",
|
||||
"0–3",
|
||||
"0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,001",
|
||||
"5,000",
|
||||
"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Identity fallback Single Value",
|
||||
NumberRangeFormatter.with().identityFallback(RangeIdentityFallback.SINGLE_VALUE),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"5",
|
||||
"5",
|
||||
"0–3",
|
||||
"0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,001",
|
||||
"5,000",
|
||||
"5,000–5,000,000");
|
||||
|
||||
assertFormatRange(
|
||||
"Identity fallback Approximately or Single Value with compact notation",
|
||||
NumberRangeFormatter.with()
|
||||
.identityFallback(RangeIdentityFallback.APPROXIMATELY_OR_SINGLE_VALUE)
|
||||
.numberFormatterBoth(NumberFormatter.with().notation(Notation.compactShort())),
|
||||
new ULocale("en-us"),
|
||||
"1–5",
|
||||
"~5",
|
||||
"5",
|
||||
"0–3",
|
||||
"0",
|
||||
"3–3K",
|
||||
"3K – 5K",
|
||||
"~5K",
|
||||
"5K",
|
||||
"5K – 5M");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentFormatters() {
|
||||
assertFormatRange(
|
||||
"Different rounding rules",
|
||||
NumberRangeFormatter.with()
|
||||
.numberFormatterFirst(NumberFormatter.with().precision(Precision.integer()))
|
||||
.numberFormatterSecond(NumberFormatter.with().precision(Precision.fixedDigits(2))),
|
||||
new ULocale("en-us"),
|
||||
"1–5.0",
|
||||
"5–5.0",
|
||||
"5–5.0",
|
||||
"0–3.0",
|
||||
"0–0.0",
|
||||
"3–3,000",
|
||||
"3,000–5,000",
|
||||
"4,999–5,000",
|
||||
"5,000–5,000", // TODO: Should this one be ~5,000?
|
||||
"5,000–5,000,000");
|
||||
}
|
||||
|
||||
static void assertFormatRange(
|
||||
|
|
Loading…
Add table
Reference in a new issue