ICU-13568 ICU-13400 ICU-13389 ICU-13075 Merging fixes from branch to trunk.

X-SVN-Rev: 40839
This commit is contained in:
Shane Carr 2018-02-06 03:15:37 +00:00
commit 5315ad603e
48 changed files with 1459 additions and 518 deletions

View File

@ -17,6 +17,7 @@
#include "unicode/ustring.h"
#include "unicode/parsepos.h"
#include "ustr_imp.h"
#include "charstr.h"
#include "cmemory.h"
#include "cstring.h"
#include "uassert.h"
@ -28,9 +29,12 @@
#include "uinvchar.h"
#include "uresimp.h"
#include "ulist.h"
#include "uresimp.h"
#include "ureslocs.h"
#include "ulocimp.h"
using namespace icu;
//#define UCURR_DEBUG_EQUIV 1
#ifdef UCURR_DEBUG_EQUIV
#include "stdio.h"
@ -104,6 +108,7 @@ static const char VAR_DELIM_STR[] = "_";
// Tag for localized display names (symbols) of currencies
static const char CURRENCIES[] = "Currencies";
static const char CURRENCIES_NARROW[] = "Currencies%narrow";
static const char CURRENCYPLURALS[] = "CurrencyPlurals";
static const UChar EUR_STR[] = {0x0045,0x0055,0x0052,0};
@ -698,7 +703,7 @@ ucurr_getName(const UChar* currency,
}
int32_t choice = (int32_t) nameStyle;
if (choice < 0 || choice > 1) {
if (choice < 0 || choice > 2) {
*ec = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
@ -731,15 +736,19 @@ ucurr_getName(const UChar* currency,
const UChar* s = NULL;
ec2 = U_ZERO_ERROR;
UResourceBundle* rb = ures_open(U_ICUDATA_CURR, loc, &ec2);
LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_CURR, loc, &ec2));
rb = ures_getByKey(rb, CURRENCIES, rb, &ec2);
// Fetch resource with multi-level resource inheritance fallback
rb = ures_getByKeyWithFallback(rb, buf, rb, &ec2);
s = ures_getStringByIndex(rb, choice, len, &ec2);
ures_close(rb);
if (nameStyle == UCURR_NARROW_SYMBOL_NAME) {
CharString key;
key.append(CURRENCIES_NARROW, ec2);
key.append("/", ec2);
key.append(buf, ec2);
s = ures_getStringByKeyWithFallback(rb.getAlias(), key.data(), len, &ec2);
} else {
ures_getByKey(rb.getAlias(), CURRENCIES, rb.getAlias(), &ec2);
ures_getByKeyWithFallback(rb.getAlias(), buf, rb.getAlias(), &ec2);
s = ures_getStringByIndex(rb.getAlias(), choice, len, &ec2);
}
// If we've succeeded we're done. Otherwise, try to fallback.
// If that fails (because we are already at root) then exit.

View File

@ -102,7 +102,17 @@ typedef enum UCurrNameStyle {
* currency, such as "US Dollar" for USD.
* @stable ICU 2.6
*/
UCURR_LONG_NAME
UCURR_LONG_NAME,
/**
* Selector for getName() indicating the narrow currency symbol.
* The narrow currency symbol is similar to the regular currency
* symbol, but it always takes the shortest form: for example,
* "$" instead of "US$" for USD in en-CA.
*
* @draft ICU 61
*/
UCURR_NARROW_SYMBOL_NAME
} UCurrNameStyle;
#if !UCONFIG_NO_SERVICE

View File

@ -262,7 +262,6 @@ void CompactHandler::precomputeAllModifiers(MutablePatternModifier &buildReferen
buildReference.setPatternInfo(&patternInfo);
info.mod = buildReference.createImmutable(status);
if (U_FAILURE(status)) { return; }
info.numDigits = patternInfo.positive.integerTotal;
info.patternString = patternString;
}
}
@ -286,7 +285,6 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
StandardPlural::Form plural = quantity.getStandardPlural(rules);
const UChar *patternString = data.getPattern(magnitude, plural);
int numDigits = -1;
if (patternString == nullptr) {
// Use the default (non-compact) modifier.
// No need to take any action.
@ -299,7 +297,6 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
const CompactModInfo &info = precomputedMods[i];
if (u_strcmp(patternString, info.patternString) == 0) {
info.mod->applyToMicros(micros, quantity);
numDigits = info.numDigits;
break;
}
}
@ -313,12 +310,8 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
PatternParser::parseToPatternInfo(UnicodeString(patternString), patternInfo, status);
static_cast<MutablePatternModifier*>(const_cast<Modifier*>(micros.modMiddle))
->setPatternInfo(&patternInfo);
numDigits = patternInfo.positive.integerTotal;
}
// FIXME: Deal with numDigits == 0 (Awaiting a test case)
(void)numDigits;
// We already performed rounding. Do not perform it again.
micros.rounding = Rounder::constructPassThrough();
}

View File

@ -52,7 +52,6 @@ class CompactData : public MultiplierProducer {
struct CompactModInfo {
const ImmutablePatternModifier *mod;
const UChar* patternString;
int32_t numDigits;
};
class CompactHandler : public MicroPropsGenerator, public UMemory {

View File

@ -73,9 +73,11 @@ Derived NumberFormatterSettings<Derived>::rounding(const Rounder &rounder) const
}
template<typename Derived>
Derived NumberFormatterSettings<Derived>::grouping(const Grouper &grouper) const {
Derived NumberFormatterSettings<Derived>::grouping(const UGroupingStrategy &strategy) const {
Derived copy(*this);
copy.fMacros.grouper = grouper;
// NOTE: This is slightly different than how the setting is stored in Java
// because we want to put it on the stack.
copy.fMacros.grouper = Grouper::forStrategy(strategy);
return copy;
}

View File

@ -17,6 +17,8 @@
#include "unicode/dcfmtsym.h"
#include "number_scientific.h"
#include "number_compact.h"
#include "uresimp.h"
#include "ureslocs.h"
using namespace icu;
using namespace icu::number;
@ -88,6 +90,37 @@ const char16_t *getPatternForStyle(const Locale &locale, const char *nsName, Cld
return pattern;
}
struct CurrencyFormatInfoResult {
bool exists;
const char16_t* pattern;
const char16_t* decimalSeparator;
const char16_t* groupingSeparator;
};
CurrencyFormatInfoResult getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) {
// TODO: Load this data in a centralized location like ICU4J?
// TODO: Parts of this same data are loaded in dcfmtsym.cpp; should clean up.
CurrencyFormatInfoResult result = { false, nullptr, nullptr, nullptr };
if (U_FAILURE(status)) return result;
CharString key;
key.append("Currencies/", status);
key.append(isoCode, status);
UErrorCode localStatus = status;
LocalUResourceBundlePointer bundle(ures_open(U_ICUDATA_CURR, locale.getName(), &localStatus));
ures_getByKeyWithFallback(bundle.getAlias(), key.data(), bundle.getAlias(), &localStatus);
if (U_SUCCESS(localStatus) && ures_getSize(bundle.getAlias())>2) { // the length is 3 if more data is present
ures_getByIndex(bundle.getAlias(), 2, bundle.getAlias(), &localStatus);
int32_t dummy;
result.exists = true;
result.pattern = ures_getStringByIndex(bundle.getAlias(), 0, &dummy, &localStatus);
result.decimalSeparator = ures_getStringByIndex(bundle.getAlias(), 1, &dummy, &localStatus);
result.groupingSeparator = ures_getStringByIndex(bundle.getAlias(), 2, &dummy, &localStatus);
status = localStatus;
} else if (localStatus != U_MISSING_RESOURCE_ERROR) {
status = localStatus;
}
return result;
}
inline bool unitIsCurrency(const MeasureUnit &unit) {
return uprv_strcmp("currency", unit.getType()) == 0;
}
@ -186,7 +219,37 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe,
}
const char *nsName = U_SUCCESS(status) ? ns->getName() : "latn";
// Resolve the symbols. Do this here because currency may need to customize them.
if (macros.symbols.isDecimalFormatSymbols()) {
fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
} else {
fMicros.symbols = new DecimalFormatSymbols(macros.locale, *ns, status);
// Give ownership to the NumberFormatterImpl.
fSymbols.adoptInstead(fMicros.symbols);
}
// Load and parse the pattern string. It is used for grouping sizes and affixes only.
// If we are formatting currency, check for a currency-specific pattern.
const char16_t* pattern = nullptr;
if (isCurrency) {
CurrencyFormatInfoResult info = getCurrencyFormatInfo(macros.locale, currency.getSubtype(), status);
if (info.exists) {
pattern = info.pattern;
// It's clunky to clone an object here, but this code is not frequently executed.
DecimalFormatSymbols* symbols = new DecimalFormatSymbols(*fMicros.symbols);
fMicros.symbols = symbols;
fSymbols.adoptInstead(symbols);
symbols->setSymbol(
DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol,
UnicodeString(info.decimalSeparator),
FALSE);
symbols->setSymbol(
DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol,
UnicodeString(info.groupingSeparator),
FALSE);
}
}
if (pattern == nullptr) {
CldrPatternStyle patternStyle;
if (isPercent || isPermille) {
patternStyle = CLDR_PATTERN_STYLE_PERCENT;
@ -199,7 +262,8 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe,
} else {
patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
}
const char16_t *pattern = getPatternForStyle(macros.locale, nsName, patternStyle, status);
pattern = getPatternForStyle(macros.locale, nsName, patternStyle, status);
}
auto patternInfo = new ParsedPatternInfo();
fPatternInfo.adoptInstead(patternInfo);
PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
@ -208,15 +272,6 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe,
/// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
/////////////////////////////////////////////////////////////////////////////////////
// Symbols
if (macros.symbols.isDecimalFormatSymbols()) {
fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
} else {
fMicros.symbols = new DecimalFormatSymbols(macros.locale, *ns, status);
// Give ownership to the NumberFormatterImpl.
fSymbols.adoptInstead(fMicros.symbols);
}
// Rounding strategy
if (!macros.rounder.isBogus()) {
fMicros.rounding = macros.rounder;
@ -234,11 +289,11 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps &macros, bool safe,
fMicros.grouping = macros.grouper;
} else if (macros.notation.fType == Notation::NTN_COMPACT) {
// Compact notation uses minGrouping by default since ICU 59
fMicros.grouping = Grouper::minTwoDigits();
fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2);
} else {
fMicros.grouping = Grouper::defaults();
fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO);
}
fMicros.grouping.setLocaleData(*fPatternInfo);
fMicros.grouping.setLocaleData(*fPatternInfo, macros.locale);
// Padding strategy
if (!macros.padder.isBogus()) {

View File

@ -7,36 +7,70 @@
#include "unicode/numberformatter.h"
#include "number_patternstring.h"
#include "uresimp.h"
using namespace icu;
using namespace icu::number;
using namespace icu::number::impl;
Grouper Grouper::defaults() {
return {-2, -2, false};
namespace {
int16_t getMinGroupingForLocale(const Locale& locale) {
// TODO: Cache this?
UErrorCode localStatus = U_ZERO_ERROR;
LocalUResourceBundlePointer bundle(ures_open(NULL, locale.getName(), &localStatus));
int32_t resultLen = 0;
const char16_t* result = ures_getStringByKeyWithFallback(
bundle.getAlias(),
"NumberElements/minimumGroupingDigits",
&resultLen,
&localStatus);
// TODO: Is it safe to assume resultLen == 1? Would locales set minGrouping >= 10?
if (U_FAILURE(localStatus) || resultLen != 1) {
return 1;
}
return result[0] - u'0';
}
Grouper Grouper::minTwoDigits() {
return {-2, -2, true};
}
Grouper Grouper::none() {
return {-1, -1, false};
Grouper Grouper::forStrategy(UGroupingStrategy grouping) {
switch (grouping) {
case UNUM_GROUPING_OFF:
return {-1, -1, -2};
case UNUM_GROUPING_AUTO:
return {-2, -2, -2};
case UNUM_GROUPING_MIN2:
return {-2, -2, -3};
case UNUM_GROUPING_ON_ALIGNED:
return {-4, -4, 1};
case UNUM_GROUPING_WESTERN:
return {3, 3, 1};
default:
U_ASSERT(FALSE);
}
}
void Grouper::setLocaleData(const impl::ParsedPatternInfo &patternInfo) {
if (fGrouping1 != -2) {
void Grouper::setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale) {
if (fGrouping1 != -2 && fGrouping2 != -4) {
return;
}
auto grouping1 = static_cast<int8_t> (patternInfo.positive.groupingSizes & 0xffff);
auto grouping2 = static_cast<int8_t> ((patternInfo.positive.groupingSizes >> 16) & 0xffff);
auto grouping3 = static_cast<int8_t> ((patternInfo.positive.groupingSizes >> 32) & 0xffff);
auto grouping1 = static_cast<int16_t> (patternInfo.positive.groupingSizes & 0xffff);
auto grouping2 = static_cast<int16_t> ((patternInfo.positive.groupingSizes >> 16) & 0xffff);
auto grouping3 = static_cast<int16_t> ((patternInfo.positive.groupingSizes >> 32) & 0xffff);
if (grouping2 == -1) {
grouping1 = -1;
grouping1 = fGrouping1 == -4 ? (short) 3 : (short) -1;
}
if (grouping3 == -1) {
grouping2 = grouping1;
}
if (fMinGrouping == -2) {
fMinGrouping = getMinGroupingForLocale(locale);
} else if (fMinGrouping == -3) {
fMinGrouping = uprv_max(2, getMinGroupingForLocale(locale));
} else {
// leave fMinGrouping alone
}
fGrouping1 = grouping1;
fGrouping2 = grouping2;
}
@ -49,7 +83,7 @@ bool Grouper::groupAtPosition(int32_t position, const impl::DecimalQuantity &val
}
position -= fGrouping1;
return position >= 0 && (position % fGrouping2) == 0
&& value.getUpperDisplayMagnitude() - fGrouping1 + 1 >= (fMin2 ? 2 : 1);
&& value.getUpperDisplayMagnitude() - fGrouping1 + 1 >= fMinGrouping;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -155,9 +155,15 @@ SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startI
int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
UErrorCode &status) const {
// Insert the suffix first since inserting the prefix will change the rightIndex
int32_t length = output.insert(rightIndex, fSuffix, status);
length += output.insert(leftIndex, fPrefix, status);
int32_t length = output.insert(leftIndex, fPrefix, status);
if (fOverwrite) {
length += output.splice(
leftIndex + length,
rightIndex + length,
UnicodeString(), 0, 0,
UNUM_FIELD_COUNT, status);
}
length += output.insert(rightIndex + length, fSuffix, status);
return length;
}
@ -177,10 +183,11 @@ bool ConstantMultiFieldModifier::isStrong() const {
CurrencySpacingEnabledModifier::CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix,
const NumberStringBuilder &suffix,
bool overwrite,
bool strong,
const DecimalFormatSymbols &symbols,
UErrorCode &status)
: ConstantMultiFieldModifier(prefix, suffix, strong) {
: ConstantMultiFieldModifier(prefix, suffix, overwrite, strong) {
// Check for currency spacing. Do not build the UnicodeSets unless there is
// a currency code point at a boundary.
if (prefix.length() > 0 && prefix.fieldAt(prefix.length() - 1) == UNUM_CURRENCY_FIELD) {

View File

@ -103,8 +103,15 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
*/
class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
public:
ConstantMultiFieldModifier(const NumberStringBuilder &prefix, const NumberStringBuilder &suffix,
bool strong) : fPrefix(prefix), fSuffix(suffix), fStrong(strong) {}
ConstantMultiFieldModifier(
const NumberStringBuilder &prefix,
const NumberStringBuilder &suffix,
bool overwrite,
bool strong)
: fPrefix(prefix),
fSuffix(suffix),
fOverwrite(overwrite),
fStrong(strong) {}
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const U_OVERRIDE;
@ -120,6 +127,7 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
// value and is treated internally as immutable.
NumberStringBuilder fPrefix;
NumberStringBuilder fSuffix;
bool fOverwrite;
bool fStrong;
};
@ -127,8 +135,13 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
class U_I18N_API CurrencySpacingEnabledModifier : public ConstantMultiFieldModifier {
public:
/** Safe code path */
CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix, const NumberStringBuilder &suffix,
bool strong, const DecimalFormatSymbols &symbols, UErrorCode &status);
CurrencySpacingEnabledModifier(
const NumberStringBuilder &prefix,
const NumberStringBuilder &suffix,
bool overwrite,
bool strong,
const DecimalFormatSymbols &symbols,
UErrorCode &status);
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const U_OVERRIDE;

View File

@ -109,9 +109,9 @@ ConstantMultiFieldModifier *MutablePatternModifier::createConstantModifier(UErro
insertPrefix(a, 0, status);
insertSuffix(b, 0, status);
if (patternInfo->hasCurrencySign()) {
return new CurrencySpacingEnabledModifier(a, b, fStrong, *symbols, status);
return new CurrencySpacingEnabledModifier(a, b, !patternInfo->hasBody(), fStrong, *symbols, status);
} else {
return new ConstantMultiFieldModifier(a, b, fStrong);
return new ConstantMultiFieldModifier(a, b, !patternInfo->hasBody(), fStrong);
}
}
@ -167,9 +167,23 @@ int32_t MutablePatternModifier::apply(NumberStringBuilder &output, int32_t leftI
auto nonConstThis = const_cast<MutablePatternModifier *>(this);
int32_t prefixLen = nonConstThis->insertPrefix(output, leftIndex, status);
int32_t suffixLen = nonConstThis->insertSuffix(output, rightIndex + prefixLen, status);
// If the pattern had no decimal stem body (like #,##0.00), overwrite the value.
int32_t overwriteLen = 0;
if (!patternInfo->hasBody()) {
overwriteLen = output.splice(
leftIndex + prefixLen, rightIndex + prefixLen,
UnicodeString(), 0, 0, UNUM_FIELD_COUNT,
status);
}
CurrencySpacingEnabledModifier::applyCurrencySpacing(
output, leftIndex, prefixLen, rightIndex + prefixLen, suffixLen, *symbols, status);
return prefixLen + suffixLen;
output,
leftIndex,
prefixLen,
rightIndex + overwriteLen + prefixLen,
suffixLen,
*symbols,
status);
return prefixLen + overwriteLen + suffixLen;
}
int32_t MutablePatternModifier::getPrefixLength(UErrorCode &status) const {
@ -234,13 +248,16 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const {
} else if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN) {
return UnicodeString();
} else {
UCurrNameStyle selector = (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW)
? UCurrNameStyle::UCURR_NARROW_SYMBOL_NAME
: UCurrNameStyle::UCURR_SYMBOL_NAME;
UErrorCode status = U_ZERO_ERROR;
UBool isChoiceFormat = FALSE;
int32_t symbolLen = 0;
const char16_t *symbol = ucurr_getName(
currencyCode,
symbols->getLocale().getName(),
UCurrNameStyle::UCURR_SYMBOL_NAME,
selector,
&isChoiceFormat,
&symbolLen,
&status);

View File

@ -95,6 +95,10 @@ bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode &st
return AffixUtils::containsType(UnicodeStringCharSequence(pattern), type, status);
}
bool ParsedPatternInfo::hasBody() const {
return positive.integerTotal > 0;
}
/////////////////////////////////////////////////////
/// BEGIN RECURSIVE DESCENT PARSER IMPLEMENTATION ///
/////////////////////////////////////////////////////

View File

@ -84,6 +84,8 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor
bool containsSymbolType(AffixPatternType type, UErrorCode &status) const U_OVERRIDE;
bool hasBody() const U_OVERRIDE;
private:
struct U_I18N_API ParserState {
const UnicodeString &pattern; // reference to the parent

View File

@ -142,6 +142,13 @@ class U_I18N_API AffixPatternProvider {
virtual bool negativeHasMinusSign() const = 0;
virtual bool containsSymbolType(AffixPatternType, UErrorCode &) const = 0;
/**
* True if the pattern has a number placeholder like "0" or "#,##0.00"; false if the pattern does not
* have one. This is used in cases like compact notation, where the pattern replaces the entire
* number instead of rendering the number.
*/
virtual bool hasBody() const = 0;
};
/**

View File

@ -88,10 +88,6 @@
* </ul>
*
* <p>
* * The narrow format for currencies is not currently supported; this is a known issue that will be fixed in a
* future version. See #11666 for more information.
*
* <p>
* This enum is similar to {@link com.ibm.icu.text.MeasureFormat.FormatWidth}.
*
* @draft ICU 60
@ -165,6 +161,97 @@ typedef enum UNumberUnitWidth {
UNUM_UNIT_WIDTH_COUNT
} UNumberUnitWidth;
/**
* An enum declaring the strategy for when and how to display grouping separators (i.e., the
* separator, often a comma or period, after every 2-3 powers of ten). The choices are several
* pre-built strategies for different use cases that employ locale data whenever possible. Example
* outputs for 1234 and 1234567 in <em>en-IN</em>:
*
* <ul>
* <li>OFF: 1234 and 12345
* <li>MIN2: 1234 and 12,34,567
* <li>AUTO: 1,234 and 12,34,567
* <li>ON_ALIGNED: 1,234 and 12,34,567
* <li>WESTERN: 1,234 and 1,234,567
* </ul>
*
* <p>
* The default is AUTO, which displays grouping separators unless the locale data says that grouping
* is not customary. To force grouping for all numbers greater than 1000 consistently across locales,
* use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2
* or OFF. See the docs of each option for details.
*
* <p>
* Note: This enum specifies the strategy for grouping sizes. To set which character to use as the
* grouping separator, use the "symbols" setter.
*
* @draft ICU 61
*/
typedef enum UGroupingStrategy {
/**
* Do not display grouping separators in any locale.
*
* @draft ICU 61
*/
UNUM_GROUPING_OFF,
/**
* Display grouping using locale defaults, except do not show grouping on values smaller than
* 10000 (such that there is a <em>minimum of two digits</em> before the first separator).
*
* <p>
* Note that locales may restrict grouping separators to be displayed only on 1 million or
* greater (for example, ee and hu) or disable grouping altogether (for example, bg currency).
*
* <p>
* Locale data is used to determine whether to separate larger numbers into groups of 2
* (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
*
* @draft ICU 61
*/
UNUM_GROUPING_MIN2,
/**
* Display grouping using the default strategy for all locales. This is the default behavior.
*
* <p>
* Note that locales may restrict grouping separators to be displayed only on 1 million or
* greater (for example, ee and hu) or disable grouping altogether (for example, bg currency).
*
* <p>
* Locale data is used to determine whether to separate larger numbers into groups of 2
* (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
*
* @draft ICU 61
*/
UNUM_GROUPING_AUTO,
/**
* Always display the grouping separator on values of at least 1000.
*
* <p>
* This option ignores the locale data that restricts or disables grouping, described in MIN2 and
* AUTO. This option may be useful to normalize the alignment of numbers, such as in a
* spreadsheet.
*
* <p>
* Locale data is used to determine whether to separate larger numbers into groups of 2
* (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
*
* @draft ICU 61
*/
UNUM_GROUPING_ON_ALIGNED,
/**
* Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use
* locale data for determining the grouping strategy.
*
* @draft ICU 61
*/
UNUM_GROUPING_WESTERN
} UGroupingStrategy;
/**
* An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123 and -123 in
* <em>en-US</em>:
@ -303,7 +390,6 @@ class Rounder;
class FractionRounder;
class CurrencyRounder;
class IncrementRounder;
class Grouper;
class IntegerWidth;
namespace impl {
@ -1036,53 +1122,6 @@ class U_I18N_API IncrementRounder : public Rounder {
friend class Rounder;
};
/**
* @internal This API is a technical preview. It is likely to change in an upcoming release.
*/
class U_I18N_API Grouper : public UMemory {
public:
/**
* @internal This API is a technical preview. It is likely to change in an upcoming release.
*/
static Grouper defaults();
/**
* @internal This API is a technical preview. It is likely to change in an upcoming release.
*/
static Grouper minTwoDigits();
/**
* @internal This API is a technical preview. It is likely to change in an upcoming release.
*/
static Grouper none();
private:
int8_t fGrouping1; // -3 means "bogus"; -2 means "needs locale data"; -1 means "no grouping"
int8_t fGrouping2;
bool fMin2;
Grouper(int8_t grouping1, int8_t grouping2, bool min2)
: fGrouping1(grouping1), fGrouping2(grouping2), fMin2(min2) {}
Grouper() : fGrouping1(-3) {};
bool isBogus() const {
return fGrouping1 == -3;
}
/** NON-CONST: mutates the current instance. */
void setLocaleData(const impl::ParsedPatternInfo &patternInfo);
bool groupAtPosition(int32_t position, const impl::DecimalQuantity &value) const;
// To allow MacroProps/MicroProps to initialize empty instances:
friend struct impl::MacroProps;
friend struct impl::MicroProps;
// To allow NumberFormatterImpl to access isBogus() and perform other operations:
friend class impl::NumberFormatterImpl;
};
/**
* A class that defines the strategy for padding and truncating integers before the decimal separator.
*
@ -1252,6 +1291,58 @@ class U_I18N_API SymbolsWrapper : public UMemory {
void doCleanup();
};
/** @internal */
class U_I18N_API Grouper : public UMemory {
public:
/** @internal */
static Grouper forStrategy(UGroupingStrategy grouping);
// Future: static Grouper forProperties(DecimalFormatProperties& properties);
/** @internal */
Grouper(int16_t grouping1, int16_t grouping2, int16_t minGrouping)
: fGrouping1(grouping1), fGrouping2(grouping2), fMinGrouping(minGrouping) {}
private:
/**
* The grouping sizes, with the following special values:
* <ul>
* <li>-1 = no grouping
* <li>-2 = needs locale data
* <li>-4 = fall back to Western grouping if not in locale
* </ul>
*/
int16_t fGrouping1;
int16_t fGrouping2;
/**
* The minimum gropuing size, with the following special values:
* <ul>
* <li>-2 = needs locale data
* <li>-3 = no less than 2
* </ul>
*/
int16_t fMinGrouping;
Grouper() : fGrouping1(-3) {};
bool isBogus() const {
return fGrouping1 == -3;
}
/** NON-CONST: mutates the current instance. */
void setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale);
bool groupAtPosition(int32_t position, const impl::DecimalQuantity &value) const;
// To allow MacroProps/MicroProps to initialize empty instances:
friend struct MacroProps;
friend struct MicroProps;
// To allow NumberFormatterImpl to access isBogus() and perform other operations:
friend class NumberFormatterImpl;
};
/** @internal */
class U_I18N_API Padder : public UMemory {
public:
@ -1531,8 +1622,6 @@ class U_I18N_API NumberFormatterSettings {
*/
Derived rounding(const Rounder &rounder) const;
#ifndef U_HIDE_INTERNAL_API
/**
* Specifies the grouping strategy to use when formatting numbers.
*
@ -1546,25 +1635,21 @@ class U_I18N_API NumberFormatterSettings {
* The exact grouping widths will be chosen based on the locale.
*
* <p>
* Pass this method the return value of one of the factory methods on {@link Grouper}. For example:
* Pass this method an element from the {@link UGroupingStrategy} enum. For example:
*
* <pre>
* NumberFormatter::with().grouping(Grouper::min2())
* NumberFormatter::with().grouping(UNUM_GROUPING_MIN2)
* </pre>
*
* The default is to perform grouping without concern for the minimum grouping digits.
* The default is to perform grouping according to locale data; most locales, but not all locales,
* enable it by default.
*
* @param grouper
* @param strategy
* The grouping strategy to use.
* @return The fluent chain.
* @see Grouper
* @see Notation
* @internal
* @internal ICU 60: This API is technical preview.
* @draft ICU 61
*/
Derived grouping(const Grouper &grouper) const;
#endif /* U_HIDE_INTERNAL_API */
Derived grouping(const UGroupingStrategy &strategy) const;
/**
* Specifies the minimum and maximum number of digits to render before the decimal mark.

View File

@ -71,6 +71,8 @@ class NumberFormatterApiTest : public IntlTest {
CurrencyUnit GBP;
CurrencyUnit CZK;
CurrencyUnit CAD;
CurrencyUnit ESP;
CurrencyUnit PTE;
MeasureUnit METER;
MeasureUnit DAY;
@ -139,6 +141,7 @@ class ModifiersTest : public IntlTest {
class PatternModifierTest : public IntlTest {
public:
void testBasic();
void testPatternWithNoPlaceholder();
void testMutableEqualsImmutable();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);

View File

@ -22,6 +22,7 @@ NumberFormatterApiTest::NumberFormatterApiTest()
NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode &status)
: USD(u"USD", status), GBP(u"GBP", status),
CZK(u"CZK", status), CAD(u"CAD", status),
ESP(u"ESP", status), PTE(u"PTE", status),
FRENCH_SYMBOLS(Locale::getFrench(), status),
SWISS_SYMBOLS(Locale("de-CH"), status),
MYANMAR_SYMBOLS(Locale("my"), status) {
@ -349,6 +350,9 @@ void NumberFormatterApiTest::notationCompact() {
Locale::getEnglish(),
9990000,
u"10M");
// NOTE: There is no API for compact custom data in C++
// and thus no "Compact Somali No Figure" test
}
void NumberFormatterApiTest::unitMeasure() {
@ -608,6 +612,66 @@ void NumberFormatterApiTest::unitCurrency() {
Locale::getEnglish(),
-9876543.21,
u"-£9,876,543.21");
// The full currency symbol is not shown in NARROW format.
// NOTE: This example is in the documentation.
assertFormatSingle(
u"Currency Difference between Narrow and Short (Narrow Version)",
NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_NARROW),
Locale("en-CA"),
5.43,
u"$5.43");
assertFormatSingle(
u"Currency Difference between Narrow and Short (Short Version)",
NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
Locale("en-CA"),
5.43,
u"US$5.43");
assertFormatSingle(
u"Currency-dependent format (Control)",
NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
Locale("ca"),
444444.55,
u"444.444,55 USD");
assertFormatSingle(
u"Currency-dependent format (Test)",
NumberFormatter::with().unit(ESP).unitWidth(UNUM_UNIT_WIDTH_SHORT),
Locale("ca"),
444444.55,
u" 444.445");
assertFormatSingle(
u"Currency-dependent symbols (Control)",
NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
Locale("pt-PT"),
444444.55,
u"444 444,55 US$");
// NOTE: This is a bit of a hack on CLDR's part. They set the currency symbol to U+200B (zero-
// width space), and they set the decimal separator to the $ symbol.
assertFormatSingle(
u"Currency-dependent symbols (Test Short)",
NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_SHORT),
Locale("pt-PT"),
444444.55,
u"444,444$55 \u200B");
assertFormatSingle(
u"Currency-dependent symbols (Test Narrow)",
NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_NARROW),
Locale("pt-PT"),
444444.55,
u"444,444$55 PTE");
assertFormatSingle(
u"Currency-dependent symbols (Test ISO Code)",
NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_ISO_CODE),
Locale("pt-PT"),
444444.55,
u"444,444$55 PTE");
}
void NumberFormatterApiTest::unitPercent() {
@ -826,6 +890,20 @@ void NumberFormatterApiTest::roundingFractionFigures() {
u"0.09",
u"0.01",
u"0.00");
assertFormatSingle(
"FracSig with trailing zeros A",
NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMinDigits(3)),
Locale::getEnglish(),
0.1,
u"0.10");
assertFormatSingle(
"FracSig with trailing zeros B",
NumberFormatter::with().rounding(Rounder::fixedFraction(2).withMinDigits(3)),
Locale::getEnglish(),
0.0999999,
u"0.10");
}
void NumberFormatterApiTest::roundingOther() {
@ -950,7 +1028,7 @@ void NumberFormatterApiTest::roundingOther() {
void NumberFormatterApiTest::grouping() {
assertFormatDescendingBig(
u"Western Grouping",
NumberFormatter::with().grouping(Grouper::defaults()),
NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
Locale::getEnglish(),
u"87,650,000",
u"8,765,000",
@ -964,7 +1042,7 @@ void NumberFormatterApiTest::grouping() {
assertFormatDescendingBig(
u"Indic Grouping",
NumberFormatter::with().grouping(Grouper::defaults()),
NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
Locale("en-IN"),
u"8,76,50,000",
u"87,65,000",
@ -978,7 +1056,7 @@ void NumberFormatterApiTest::grouping() {
assertFormatDescendingBig(
u"Western Grouping, Wide",
NumberFormatter::with().grouping(Grouper::minTwoDigits()),
NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
Locale::getEnglish(),
u"87,650,000",
u"8,765,000",
@ -992,7 +1070,7 @@ void NumberFormatterApiTest::grouping() {
assertFormatDescendingBig(
u"Indic Grouping, Wide",
NumberFormatter::with().grouping(Grouper::minTwoDigits()),
NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
Locale("en-IN"),
u"8,76,50,000",
u"87,65,000",
@ -1006,7 +1084,7 @@ void NumberFormatterApiTest::grouping() {
assertFormatDescendingBig(
u"No Grouping",
NumberFormatter::with().grouping(Grouper::none()),
NumberFormatter::with().grouping(UNUM_GROUPING_OFF),
Locale("en-IN"),
u"87650000",
u"8765000",
@ -1017,6 +1095,97 @@ void NumberFormatterApiTest::grouping() {
u"87.65",
u"8.765",
u"0");
// NOTE: Hungarian is interesting because it has minimumGroupingDigits=4 in locale data
// If this test breaks due to data changes, find another locale that has minimumGroupingDigits.
assertFormatDescendingBig(
u"Hungarian Grouping",
NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
Locale("hu"),
u"87 650 000",
u"8 765 000",
u"876500",
u"87650",
u"8765",
u"876,5",
u"87,65",
u"8,765",
u"0");
assertFormatDescendingBig(
u"Hungarian Grouping, Min 2",
NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
Locale("hu"),
u"87 650 000",
u"8 765 000",
u"876500",
u"87650",
u"8765",
u"876,5",
u"87,65",
u"8,765",
u"0");
assertFormatDescendingBig(
u"Hungarian Grouping, Always",
NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED),
Locale("hu"),
u"87 650 000",
u"8 765 000",
u"876 500",
u"87 650",
u"8 765",
u"876,5",
u"87,65",
u"8,765",
u"0");
// NOTE: Bulgarian is interesting because it has no grouping in the default currency format.
// If this test breaks due to data changes, find another locale that has no default grouping.
assertFormatDescendingBig(
u"Bulgarian Currency Grouping",
NumberFormatter::with().grouping(UNUM_GROUPING_AUTO).unit(USD),
Locale("bg"),
u"87650000,00 щ.д.",
u"8765000,00 щ.д.",
u"876500,00 щ.д.",
u"87650,00 щ.д.",
u"8765,00 щ.д.",
u"876,50 щ.д.",
u"87,65 щ.д.",
u"8,76 щ.д.",
u"0,00 щ.д.");
assertFormatDescendingBig(
u"Bulgarian Currency Grouping, Always",
NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED).unit(USD),
Locale("bg"),
u"87 650 000,00 щ.д.",
u"8 765 000,00 щ.д.",
u"876 500,00 щ.д.",
u"87 650,00 щ.д.",
u"8 765,00 щ.д.",
u"876,50 щ.д.",
u"87,65 щ.д.",
u"8,76 щ.д.",
u"0,00 щ.д.");
// TODO: Enable this test when macro-setter is available in C++
// MacroProps macros;
// macros.grouping = Grouper(4, 1, 3);
// assertFormatDescendingBig(
// u"Custom Grouping via Internal API",
// NumberFormatter::with().macros(macros),
// Locale::getEnglish(),
// u"8,7,6,5,0000",
// u"8,7,6,5000",
// u"876500",
// u"87650",
// u"8765",
// u"876.5",
// u"87.65",
// u"8.765",
// u"0");
}
void NumberFormatterApiTest::padding() {

View File

@ -38,13 +38,13 @@ void ModifiersTest::testConstantMultiFieldModifier() {
UErrorCode status = U_ZERO_ERROR;
NumberStringBuilder prefix;
NumberStringBuilder suffix;
ConstantMultiFieldModifier mod1(prefix, suffix, true);
ConstantMultiFieldModifier mod1(prefix, suffix, false, true);
assertModifierEquals(mod1, 0, true, u"|", u"n", status);
assertSuccess("Spot 1", status);
prefix.append(u"a📻", UNUM_PERCENT_FIELD, status);
suffix.append(u"b", UNUM_CURRENCY_FIELD, status);
ConstantMultiFieldModifier mod2(prefix, suffix, true);
ConstantMultiFieldModifier mod2(prefix, suffix, false, true);
assertModifierEquals(mod2, 3, true, u"a📻|b", u"%%%n$", status);
assertSuccess("Spot 2", status);
@ -105,14 +105,14 @@ void ModifiersTest::testCurrencySpacingEnabledModifier() {
NumberStringBuilder prefix;
NumberStringBuilder suffix;
CurrencySpacingEnabledModifier mod1(prefix, suffix, true, symbols, status);
CurrencySpacingEnabledModifier mod1(prefix, suffix, false, true, symbols, status);
assertSuccess("Spot 2", status);
assertModifierEquals(mod1, 0, true, u"|", u"n", status);
assertSuccess("Spot 3", status);
prefix.append(u"USD", UNUM_CURRENCY_FIELD, status);
assertSuccess("Spot 4", status);
CurrencySpacingEnabledModifier mod2(prefix, suffix, true, symbols, status);
CurrencySpacingEnabledModifier mod2(prefix, suffix, false, true, symbols, status);
assertSuccess("Spot 5", status);
assertModifierEquals(mod2, 3, true, u"USD|", u"$$$n", status);
assertSuccess("Spot 6", status);
@ -138,7 +138,7 @@ void ModifiersTest::testCurrencySpacingEnabledModifier() {
symbols.setPatternForCurrencySpacing(UNUM_CURRENCY_SURROUNDING_MATCH, true, u"[|]");
suffix.append("XYZ", UNUM_CURRENCY_FIELD, status);
assertSuccess("Spot 11", status);
CurrencySpacingEnabledModifier mod3(prefix, suffix, true, symbols, status);
CurrencySpacingEnabledModifier mod3(prefix, suffix, false, true, symbols, status);
assertSuccess("Spot 12", status);
assertModifierEquals(mod3, 3, true, u"USD|\u00A0XYZ", u"$$$nn$$$", status);
assertSuccess("Spot 13", status);

View File

@ -14,6 +14,7 @@ void PatternModifierTest::runIndexedTest(int32_t index, UBool exec, const char *
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testBasic);
TESTCASE_AUTO(testPatternWithNoPlaceholder);
TESTCASE_AUTO(testMutableEqualsImmutable);
TESTCASE_AUTO_END;
}
@ -78,6 +79,42 @@ void PatternModifierTest::testBasic() {
assertSuccess("Spot 5", status);
}
void PatternModifierTest::testPatternWithNoPlaceholder() {
UErrorCode status = U_ZERO_ERROR;
MutablePatternModifier mod(false);
ParsedPatternInfo patternInfo;
PatternParser::parseToPatternInfo(u"abc", patternInfo, status);
assertSuccess("Spot 1", status);
mod.setPatternInfo(&patternInfo);
mod.setPatternAttributes(UNUM_SIGN_AUTO, false);
DecimalFormatSymbols symbols(Locale::getEnglish(), status);
CurrencyUnit currency(u"USD", status);
assertSuccess("Spot 2", status);
mod.setSymbols(&symbols, currency, UNUM_UNIT_WIDTH_SHORT, nullptr);
mod.setNumberProperties(1, StandardPlural::Form::COUNT);
// Unsafe Code Path
NumberStringBuilder nsb;
nsb.append(u"x123y", UNUM_FIELD_COUNT, status);
assertSuccess("Spot 3", status);
mod.apply(nsb, 1, 4, status);
assertSuccess("Spot 4", status);
assertEquals("Unsafe Path", u"xabcy", nsb.toUnicodeString());
// Safe Code Path
nsb.clear();
nsb.append(u"x123y", UNUM_FIELD_COUNT, status);
assertSuccess("Spot 5", status);
MicroProps micros;
LocalPointer<ImmutablePatternModifier> imod(mod.createImmutable(status));
assertSuccess("Spot 6", status);
DecimalQuantity quantity;
imod->applyToMicros(micros, quantity);
micros.modMiddle->apply(nsb, 1, 4, status);
assertSuccess("Spot 7", status);
assertEquals("Safe Path", u"xabcy", nsb.toUnicodeString());
}
void PatternModifierTest::testMutableEqualsImmutable() {
UErrorCode status = U_ZERO_ERROR;
MutablePatternModifier mod(false);

View File

@ -623,6 +623,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
TESTCASE_AUTO(Test11649_toPatternWithMultiCurrency);
TESTCASE_AUTO(Test13327_numberingSystemBufferOverflow);
TESTCASE_AUTO(Test13391_chakmaParsing);
TESTCASE_AUTO(Test11035_FormatCurrencyAmount);
TESTCASE_AUTO_END;
}
@ -2311,30 +2312,54 @@ void NumberFormatTest::TestCurrencyNames(void) {
const UBool possibleDataError = TRUE;
// Warning: HARD-CODED LOCALE DATA in this test. If it fails, CHECK
// THE LOCALE DATA before diving into the code.
assertEquals("USD.getName(SYMBOL_NAME)",
assertEquals("USD.getName(SYMBOL_NAME, en)",
UnicodeString("$"),
UnicodeString(ucurr_getName(USD, "en",
UCURR_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("USD.getName(LONG_NAME)",
assertEquals("USD.getName(NARROW_SYMBOL_NAME, en)",
UnicodeString("$"),
UnicodeString(ucurr_getName(USD, "en",
UCURR_NARROW_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("USD.getName(LONG_NAME, en)",
UnicodeString("US Dollar"),
UnicodeString(ucurr_getName(USD, "en",
UCURR_LONG_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("CAD.getName(SYMBOL_NAME)",
assertEquals("CAD.getName(SYMBOL_NAME, en)",
UnicodeString("CA$"),
UnicodeString(ucurr_getName(CAD, "en",
UCURR_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("CAD.getName(SYMBOL_NAME)",
assertEquals("CAD.getName(NARROW_SYMBOL_NAME, en)",
UnicodeString("$"),
UnicodeString(ucurr_getName(CAD, "en",
UCURR_NARROW_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("CAD.getName(SYMBOL_NAME, en_CA)",
UnicodeString("$"),
UnicodeString(ucurr_getName(CAD, "en_CA",
UCURR_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("USD.getName(SYMBOL_NAME, en_CA)",
UnicodeString("US$"),
UnicodeString(ucurr_getName(USD, "en_CA",
UCURR_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("USD.getName(NARROW_SYMBOL_NAME, en_CA)",
UnicodeString("$"),
UnicodeString(ucurr_getName(USD, "en_CA",
UCURR_NARROW_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("USD.getName(SYMBOL_NAME) in en_NZ",
UnicodeString("US$"),
UnicodeString(ucurr_getName(USD, "en_NZ",
@ -2347,6 +2372,18 @@ void NumberFormatTest::TestCurrencyNames(void) {
UCURR_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("USX.getName(SYMBOL_NAME)",
UnicodeString("USX"),
UnicodeString(ucurr_getName(USX, "en_US",
UCURR_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("USX.getName(NARROW_SYMBOL_NAME)",
UnicodeString("USX"),
UnicodeString(ucurr_getName(USX, "en_US",
UCURR_NARROW_SYMBOL_NAME,
&isChoiceFormat, &len, &ec)),
possibleDataError);
assertEquals("USX.getName(LONG_NAME)",
UnicodeString("USX"),
UnicodeString(ucurr_getName(USX, "en_US",
@ -8885,4 +8922,28 @@ void NumberFormatTest::checkExceptionIssue11735() {
assertEquals("Issue11735 ppos", 0, ppos.getIndex());
}
void NumberFormatTest::Test11035_FormatCurrencyAmount() {
UErrorCode status;
double amount = 12345.67;
const char16_t* expected = u"12,345$67 ";
// Test two ways to set a currency via API
Locale loc1 = Locale("pt_PT");
NumberFormat* fmt1 = NumberFormat::createCurrencyInstance(loc1, status);
fmt1->setCurrency(u"PTE", status);
UnicodeString actualSetCurrency;
fmt1->format(amount, actualSetCurrency);
Locale loc2 = Locale("pt_PT@currency=PTE");
NumberFormat* fmt2 = NumberFormat::createCurrencyInstance(loc2, status);
UnicodeString actualLocaleString;
fmt2->format(amount, actualLocaleString);
// TODO: The following test fill fail until DecimalFormat wraps NumberFormatter.
if (!logKnownIssue("13574")) {
assertEquals("Custom Currency Pattern, Set Currency", expected, actualSetCurrency);
}
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -219,6 +219,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
void Test13391_chakmaParsing();
void checkExceptionIssue11735();
void Test11035_FormatCurrencyAmount();
private:
UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);

View File

@ -29,7 +29,6 @@ public class CurrencyData {
public abstract Map<String, String> getUnitPatterns();
public abstract CurrencyFormatInfo getFormatInfo(String isoCode);
public abstract CurrencySpacingInfo getSpacingInfo();
public abstract String getNarrowSymbol(String isoCode);
}
public static final class CurrencyFormatInfo {

View File

@ -31,4 +31,11 @@ public interface AffixPatternProvider {
public boolean negativeHasMinusSign();
public boolean containsSymbolType(int type);
/**
* True if the pattern has a number placeholder like "0" or "#,##0.00"; false if the pattern does not
* have one. This is used in cases like compact notation, where the pattern replaces the entire
* number instead of rendering the number.
*/
public boolean hasBody();
}

View File

@ -17,24 +17,29 @@ public class ConstantMultiFieldModifier implements Modifier {
protected final char[] suffixChars;
protected final Field[] prefixFields;
protected final Field[] suffixFields;
private final boolean overwrite;
private final boolean strong;
public ConstantMultiFieldModifier(
NumberStringBuilder prefix,
NumberStringBuilder suffix,
boolean overwrite,
boolean strong) {
prefixChars = prefix.toCharArray();
suffixChars = suffix.toCharArray();
prefixFields = prefix.toFieldArray();
suffixFields = suffix.toFieldArray();
this.overwrite = overwrite;
this.strong = strong;
}
@Override
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
// Insert the suffix first since inserting the prefix will change the rightIndex
int length = output.insert(rightIndex, suffixChars, suffixFields);
length += output.insert(leftIndex, prefixChars, prefixFields);
int length = output.insert(leftIndex, prefixChars, prefixFields);
if (overwrite) {
length += output.splice(leftIndex + length, rightIndex + length, "", 0, 0, null);
}
length += output.insert(rightIndex + length, suffixChars, suffixFields);
return length;
}

View File

@ -59,4 +59,9 @@ public class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
public boolean containsSymbolType(int type) {
return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
}
@Override
public boolean hasBody() {
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasBody();
}
}

View File

@ -30,9 +30,10 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
public CurrencySpacingEnabledModifier(
NumberStringBuilder prefix,
NumberStringBuilder suffix,
boolean overwrite,
boolean strong,
DecimalFormatSymbols symbols) {
super(prefix, suffix, strong);
super(prefix, suffix, overwrite, strong);
// Check for currency spacing. Do not build the UnicodeSets unless there is
// a currency code point at a boundary.

View File

@ -0,0 +1,164 @@
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
/**
* A full options object for grouping sizes.
*/
public class Grouper {
private static final Grouper GROUPER_NEVER = new Grouper((short) -1, (short) -1, (short) -2);
private static final Grouper GROUPER_MIN2 = new Grouper((short) -2, (short) -2, (short) -3);
private static final Grouper GROUPER_AUTO = new Grouper((short) -2, (short) -2, (short) -2);
private static final Grouper GROUPER_ON_ALIGNED = new Grouper((short) -4, (short) -4, (short) 1);
private static final Grouper GROUPER_WESTERN = new Grouper((short) 3, (short) 3, (short) 1);
private static final Grouper GROUPER_INDIC = new Grouper((short) 3, (short) 2, (short) 1);
private static final Grouper GROUPER_WESTERN_MIN2 = new Grouper((short) 3, (short) 3, (short) 2);
private static final Grouper GROUPER_INDIC_MIN2 = new Grouper((short) 3, (short) 2, (short) 2);
/**
* Convert from the GroupingStrategy enum to a Grouper object.
*/
public static Grouper forStrategy(GroupingStrategy grouping) {
switch (grouping) {
case OFF:
return GROUPER_NEVER;
case MIN2:
return GROUPER_MIN2;
case AUTO:
return GROUPER_AUTO;
case ON_ALIGNED:
return GROUPER_ON_ALIGNED;
case WESTERN:
return GROUPER_WESTERN;
default:
throw new AssertionError();
}
}
/**
* Resolve the values in Properties to a Grouper object.
*/
public static Grouper forProperties(DecimalFormatProperties properties) {
short grouping1 = (short) properties.getGroupingSize();
short grouping2 = (short) properties.getSecondaryGroupingSize();
short minGrouping = (short) properties.getMinimumGroupingDigits();
grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1;
grouping2 = grouping2 > 0 ? grouping2 : grouping1;
return getInstance(grouping1, grouping2, minGrouping);
}
public static Grouper getInstance(short grouping1, short grouping2, short minGrouping) {
if (grouping1 == -1) {
return GROUPER_NEVER;
} else if (grouping1 == 3 && grouping2 == 3 && minGrouping == 1) {
return GROUPER_WESTERN;
} else if (grouping1 == 3 && grouping2 == 2 && minGrouping == 1) {
return GROUPER_INDIC;
} else if (grouping1 == 3 && grouping2 == 3 && minGrouping == 1) {
return GROUPER_WESTERN_MIN2;
} else if (grouping1 == 3 && grouping2 == 2 && minGrouping == 1) {
return GROUPER_INDIC_MIN2;
} else {
return new Grouper(grouping1, grouping2, minGrouping);
}
}
private static short getMinGroupingForLocale(ULocale locale) {
// TODO: Cache this?
ICUResourceBundle resource = (ICUResourceBundle) UResourceBundle
.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
String result = resource.getStringWithFallback("NumberElements/minimumGroupingDigits");
return Short.valueOf(result);
}
/**
* The primary grouping size, with the following special values:
* <ul>
* <li>-1 = no grouping
* <li>-2 = needs locale data
* <li>-4 = fall back to Western grouping if not in locale
* </ul>
*/
private final short grouping1;
/**
* The secondary grouping size, with the following special values:
* <ul>
* <li>-1 = no grouping
* <li>-2 = needs locale data
* <li>-4 = fall back to Western grouping if not in locale
* </ul>
*/
private final short grouping2;
/**
* The minimum gropuing size, with the following special values:
* <ul>
* <li>-2 = needs locale data
* <li>-3 = no less than 2
* </ul>
*/
private final short minGrouping;
private Grouper(short grouping1, short grouping2, short minGrouping) {
this.grouping1 = grouping1;
this.grouping2 = grouping2;
this.minGrouping = minGrouping;
}
public Grouper withLocaleData(ULocale locale, ParsedPatternInfo patternInfo) {
if (this.grouping1 != -2 && this.grouping1 != -4) {
return this;
}
short grouping1 = (short) (patternInfo.positive.groupingSizes & 0xffff);
short grouping2 = (short) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
short grouping3 = (short) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
if (grouping2 == -1) {
grouping1 = this.grouping1 == -4 ? (short) 3 : (short) -1;
}
if (grouping3 == -1) {
grouping2 = grouping1;
}
short minGrouping;
if (this.minGrouping == -2) {
minGrouping = getMinGroupingForLocale(locale);
} else if (this.minGrouping == -3) {
minGrouping = (short) Math.max(2, getMinGroupingForLocale(locale));
} else {
minGrouping = this.minGrouping;
}
return getInstance(grouping1, grouping2, minGrouping);
}
public boolean groupAtPosition(int position, DecimalQuantity value) {
assert grouping1 != -2 && grouping1 != -4;
if (grouping1 == -1 || grouping1 == 0) {
// Either -1 or 0 means "no grouping"
return false;
}
position -= grouping1;
return position >= 0
&& (position % grouping2) == 0
&& value.getUpperDisplayMagnitude() - grouping1 + 1 >= minGrouping;
}
public short getPrimary() {
return grouping1;
}
public short getSecondary() {
return grouping2;
}
}

View File

@ -3,7 +3,6 @@
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.number.Grouper;
import com.ibm.icu.number.IntegerWidth;
import com.ibm.icu.number.Notation;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
@ -19,7 +18,7 @@ public class MacroProps implements Cloneable {
public MeasureUnit unit;
public MeasureUnit perUnit;
public Rounder rounder;
public Grouper grouper;
public Object grouping;
public Padder padder;
public IntegerWidth integerWidth;
public Object symbols;
@ -47,8 +46,8 @@ public class MacroProps implements Cloneable {
perUnit = fallback.perUnit;
if (rounder == null)
rounder = fallback.rounder;
if (grouper == null)
grouper = fallback.grouper;
if (grouping == null)
grouping = fallback.grouping;
if (padder == null)
padder = fallback.padder;
if (integerWidth == null)
@ -77,7 +76,7 @@ public class MacroProps implements Cloneable {
unit,
perUnit,
rounder,
grouper,
grouping,
padder,
integerWidth,
symbols,
@ -103,7 +102,7 @@ public class MacroProps implements Cloneable {
&& Utility.equals(unit, other.unit)
&& Utility.equals(perUnit, other.perUnit)
&& Utility.equals(rounder, other.rounder)
&& Utility.equals(grouper, other.grouper)
&& Utility.equals(grouping, other.grouping)
&& Utility.equals(padder, other.padder)
&& Utility.equals(integerWidth, other.integerWidth)
&& Utility.equals(symbols, other.symbols)

View File

@ -2,7 +2,6 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.number.Grouper;
import com.ibm.icu.number.IntegerWidth;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.SignDisplay;

View File

@ -206,9 +206,9 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
insertPrefix(a.clear(), 0);
insertSuffix(b.clear(), 0);
if (patternInfo.hasCurrencySign()) {
return new CurrencySpacingEnabledModifier(a, b, isStrong, symbols);
return new CurrencySpacingEnabledModifier(a, b, !patternInfo.hasBody(), isStrong, symbols);
} else {
return new ConstantMultiFieldModifier(a, b, isStrong);
return new ConstantMultiFieldModifier(a, b, !patternInfo.hasBody(), isStrong);
}
}
@ -271,13 +271,18 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
int prefixLen = insertPrefix(output, leftIndex);
int suffixLen = insertSuffix(output, rightIndex + prefixLen);
// If the pattern had no decimal stem body (like #,##0.00), overwrite the value.
int overwriteLen = 0;
if (!patternInfo.hasBody()) {
overwriteLen = output.splice(leftIndex + prefixLen, rightIndex + prefixLen, "", 0, 0, null);
}
CurrencySpacingEnabledModifier.applyCurrencySpacing(output,
leftIndex,
prefixLen,
rightIndex + prefixLen,
rightIndex + prefixLen + overwriteLen,
suffixLen,
symbols);
return prefixLen + suffixLen;
return prefixLen + overwriteLen + suffixLen;
}
@Override
@ -355,10 +360,10 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
return currency.getCurrencyCode();
} else if (unitWidth == UnitWidth.HIDDEN) {
return "";
} else if (unitWidth == UnitWidth.NARROW) {
return currency.getName(symbols.getULocale(), Currency.NARROW_SYMBOL_NAME, null);
} else {
return currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
int selector = unitWidth == UnitWidth.NARROW ? Currency.NARROW_SYMBOL_NAME
: Currency.SYMBOL_NAME;
return currency.getName(symbols.getULocale(), selector, null);
}
case AffixUtils.TYPE_CURRENCY_DOUBLE:
return currency.getCurrencyCode();

View File

@ -169,6 +169,11 @@ public class PatternStringParser {
public boolean containsSymbolType(int type) {
return AffixUtils.containsType(pattern, type);
}
@Override
public boolean hasBody() {
return positive.integerTotal > 0;
}
}
public static class ParsedSubpatternInfo {

View File

@ -127,6 +127,11 @@ public class PropertiesAffixPatternProvider implements AffixPatternProvider {
|| AffixUtils.containsType(negPrefix, type) || AffixUtils.containsType(negSuffix, type);
}
@Override
public boolean hasBody() {
return true;
}
@Override
public String toString() {
return super.toString()

View File

@ -3,9 +3,9 @@
package com.ibm.icu.impl.number.parse;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.parse.UnicodeSetStaticCache.Key;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.number.Grouper;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.UnicodeSet;

View File

@ -11,11 +11,12 @@ import java.util.List;
import com.ibm.icu.impl.number.AffixPatternProvider;
import com.ibm.icu.impl.number.CustomSymbolCurrency;
import com.ibm.icu.impl.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.number.Grouper;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.UnicodeSet;
import com.ibm.icu.util.Currency;
@ -92,7 +93,7 @@ public class NumberParserImpl {
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern);
AffixMatcher.newGenerate(patternInfo, parser, factory, ignorables, parseFlags);
Grouper grouper = Grouper.defaults().withLocaleData(patternInfo);
Grouper grouper = Grouper.forStrategy(GroupingStrategy.AUTO).withLocaleData(locale, patternInfo);
parser.addMatcher(ignorables);
parser.addMatcher(DecimalMatcher.getInstance(symbols, grouper, parseFlags));
@ -165,7 +166,7 @@ public class NumberParserImpl {
AffixPatternProvider patternInfo = new PropertiesAffixPatternProvider(properties);
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
boolean isStrict = properties.getParseMode() == ParseMode.STRICT;
Grouper grouper = Grouper.defaults().withProperties(properties);
Grouper grouper = Grouper.forProperties(properties);
int parseFlags = 0;
// Fraction grouping is disabled by default because it has never been supported in DecimalFormat
parseFlags |= ParsingUtils.PARSE_FLAG_FRACTION_GROUPING_DISABLED;

View File

@ -2,7 +2,7 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number.parse;
import com.ibm.icu.number.Grouper;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.UnicodeSet;

View File

@ -10,6 +10,7 @@ import java.util.Set;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.CompactData;
import com.ibm.icu.impl.number.CompactData.CompactType;
import com.ibm.icu.impl.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.MicroProps;
import com.ibm.icu.impl.number.MicroPropsGenerator;
@ -38,6 +39,17 @@ public class CompactNotation extends Notation {
final CompactStyle compactStyle;
final Map<String, Map<String, String>> compactCustomData;
/**
* Create a compact notation with custom data.
* @internal
* @deprecated This API is ICU internal only.
* @see DecimalFormatProperties#setCompactCustomData
*/
@Deprecated
public static CompactNotation forCustomData(Map<String, Map<String, String>> compactCustomData) {
return new CompactNotation(compactCustomData);
}
/* package-private */ CompactNotation(CompactStyle compactStyle) {
compactCustomData = null;
this.compactStyle = compactStyle;
@ -61,14 +73,9 @@ public class CompactNotation extends Notation {
private static class CompactHandler implements MicroPropsGenerator {
private static class CompactModInfo {
public ImmutablePatternModifier mod;
public int numDigits;
}
final PluralRules rules;
final MicroPropsGenerator parent;
final Map<String, CompactModInfo> precomputedMods;
final Map<String, ImmutablePatternModifier> precomputedMods;
final CompactData data;
private CompactHandler(
@ -89,7 +96,7 @@ public class CompactNotation extends Notation {
}
if (buildReference != null) {
// Safe code path
precomputedMods = new HashMap<String, CompactModInfo>();
precomputedMods = new HashMap<String, ImmutablePatternModifier>();
precomputeAllModifiers(buildReference);
} else {
// Unsafe code path
@ -103,12 +110,9 @@ public class CompactNotation extends Notation {
data.getUniquePatterns(allPatterns);
for (String patternString : allPatterns) {
CompactModInfo info = new CompactModInfo();
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(patternString);
buildReference.setPatternInfo(patternInfo);
info.mod = buildReference.createImmutable();
info.numDigits = patternInfo.positive.integerTotal;
precomputedMods.put(patternString, info);
precomputedMods.put(patternString, buildReference.createImmutable());
}
}
@ -131,28 +135,22 @@ public class CompactNotation extends Notation {
StandardPlural plural = quantity.getStandardPlural(rules);
String patternString = data.getPattern(magnitude, plural);
@SuppressWarnings("unused") // see #13075
int numDigits = -1;
if (patternString == null) {
// Use the default (non-compact) modifier.
// No need to take any action.
} else if (precomputedMods != null) {
// Safe code path.
// Java uses a hash set here for O(1) lookup. C++ uses a linear search.
CompactModInfo info = precomputedMods.get(patternString);
info.mod.applyToMicros(micros, quantity);
numDigits = info.numDigits;
ImmutablePatternModifier mod = precomputedMods.get(patternString);
mod.applyToMicros(micros, quantity);
} else {
// Unsafe code path.
// Overwrite the PatternInfo in the existing modMiddle.
assert micros.modMiddle instanceof MutablePatternModifier;
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(patternString);
((MutablePatternModifier) micros.modMiddle).setPatternInfo(patternInfo);
numDigits = patternInfo.positive.integerTotal;
}
// FIXME: Deal with numDigits == 0 (Awaiting a test case)
// We already performed rounding. Do not perform it again.
micros.rounding = Rounder.constructPassThrough();

View File

@ -1,157 +0,0 @@
// © 2017 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.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
/**
* @internal
* @deprecated This API is a technical preview. It is likely to change in an upcoming release.
*/
@Deprecated
public class Grouper {
// Conveniences for Java handling of bytes
private static final byte N2 = -2;
private static final byte N1 = -1;
private static final byte B2 = 2;
private static final byte B3 = 3;
private static final Grouper DEFAULTS = new Grouper(N2, N2, false);
private static final Grouper MIN2 = new Grouper(N2, N2, true);
private static final Grouper NONE = new Grouper(N1, N1, false);
private final byte grouping1; // -2 means "needs locale data"; -1 means "no grouping"
private final byte grouping2;
private final boolean min2;
private Grouper(byte grouping1, byte grouping2, boolean min2) {
this.grouping1 = grouping1;
this.grouping2 = grouping2;
this.min2 = min2;
}
/**
* @internal
* @deprecated This API is a technical preview. It is likely to change in an upcoming release.
*/
@Deprecated
public static Grouper defaults() {
return DEFAULTS;
}
/**
* @internal
* @deprecated This API is a technical preview. It is likely to change in an upcoming release.
*/
@Deprecated
public static Grouper minTwoDigits() {
return MIN2;
}
/**
* @internal
* @deprecated This API is a technical preview. It is likely to change in an upcoming release.
*/
@Deprecated
public static Grouper none() {
return NONE;
}
//////////////////////////
// PACKAGE-PRIVATE APIS //
//////////////////////////
private static final Grouper GROUPING_3 = new Grouper(B3, B3, false);
private static final Grouper GROUPING_3_2 = new Grouper(B3, B2, false);
private static final Grouper GROUPING_3_MIN2 = new Grouper(B3, B3, true);
private static final Grouper GROUPING_3_2_MIN2 = new Grouper(B3, B2, true);
static Grouper getInstance(byte grouping1, byte grouping2, boolean min2) {
if (grouping1 == -1) {
return NONE;
} else if (!min2 && grouping1 == 3 && grouping2 == 3) {
return GROUPING_3;
} else if (!min2 && grouping1 == 3 && grouping2 == 2) {
return GROUPING_3_2;
} else if (min2 && grouping1 == 3 && grouping2 == 3) {
return GROUPING_3_MIN2;
} else if (min2 && grouping1 == 3 && grouping2 == 2) {
return GROUPING_3_2_MIN2;
} else {
return new Grouper(grouping1, grouping2, min2);
}
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public Grouper withProperties(DecimalFormatProperties properties) {
if (grouping1 != -2) {
return this;
}
byte grouping1 = (byte) properties.getGroupingSize();
byte grouping2 = (byte) properties.getSecondaryGroupingSize();
int minGrouping = properties.getMinimumGroupingDigits();
grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1;
grouping2 = grouping2 > 0 ? grouping2 : grouping1;
// TODO: Is it important to handle minGrouping > 2?
return getInstance(grouping1, grouping2, minGrouping == 2);
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public Grouper withLocaleData(ParsedPatternInfo patternInfo) {
if (grouping1 != -2) {
return this;
}
// TODO: short or byte?
byte grouping1 = (byte) (patternInfo.positive.groupingSizes & 0xffff);
byte grouping2 = (byte) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
byte grouping3 = (byte) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
if (grouping2 == -1) {
grouping1 = -1;
}
if (grouping3 == -1) {
grouping2 = grouping1;
}
return getInstance(grouping1, grouping2, min2);
}
boolean groupAtPosition(int position, DecimalQuantity value) {
assert grouping1 != -2;
if (grouping1 == -1 || grouping1 == 0) {
// Either -1 or 0 means "no grouping"
return false;
}
position -= grouping1;
return position >= 0
&& (position % grouping2) == 0
&& value.getUpperDisplayMagnitude() - grouping1 + 1 >= (min2 ? 2 : 1);
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public byte getPrimary() {
return grouping1;
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public byte getSecondary() {
return grouping2;
}
}

View File

@ -161,15 +161,119 @@ public final class NumberFormatter {
}
/**
* An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123
* and -123 in <em>en-US</em>:
* An enum declaring the strategy for when and how to display grouping separators (i.e., the
* separator, often a comma or period, after every 2-3 powers of ten). The choices are several
* pre-built strategies for different use cases that employ locale data whenever possible. Example
* outputs for 1234 and 1234567 in <em>en-IN</em>:
*
* <ul>
* <li>AUTO: "123" and "-123"
* <li>ALWAYS: "+123" and "-123"
* <li>NEVER: "123" and "123"
* <li>ACCOUNTING: "$123" and "($123)"
* <li>ACCOUNTING_ALWAYS: "+$123" and "($123)"
* <li>OFF: 1234 and 12345
* <li>MIN2: 1234 and 12,34,567
* <li>AUTO: 1,234 and 12,34,567
* <li>ON_ALIGNED: 1,234 and 12,34,567
* <li>WESTERN: 1,234 and 1,234,567
* </ul>
*
* <p>
* The default is AUTO, which displays grouping separators unless the locale data says that grouping
* is not customary. To force grouping for all numbers greater than 1000 consistently across locales,
* use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2
* or OFF. See the docs of each option for details.
*
* <p>
* Note: This enum specifies the strategy for grouping sizes. To set which character to use as the
* grouping separator, use the "symbols" setter.
*
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
*/
public static enum GroupingStrategy {
/**
* Do not display grouping separators in any locale.
*
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
*/
OFF,
/**
* Display grouping using locale defaults, except do not show grouping on values smaller than
* 10000 (such that there is a <em>minimum of two digits</em> before the first separator).
*
* <p>
* Note that locales may restrict grouping separators to be displayed only on 1 million or
* greater (for example, ee and hu) or disable grouping altogether (for example, bg currency).
*
* <p>
* Locale data is used to determine whether to separate larger numbers into groups of 2
* (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
*
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
*/
MIN2,
/**
* Display grouping using the default strategy for all locales. This is the default behavior.
*
* <p>
* Note that locales may restrict grouping separators to be displayed only on 1 million or
* greater (for example, ee and hu) or disable grouping altogether (for example, bg currency).
*
* <p>
* Locale data is used to determine whether to separate larger numbers into groups of 2
* (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
*
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
*/
AUTO,
/**
* Always display the grouping separator on values of at least 1000.
*
* <p>
* This option ignores the locale data that restricts or disables grouping, described in MIN2 and
* AUTO. This option may be useful to normalize the alignment of numbers, such as in a
* spreadsheet.
*
* <p>
* Locale data is used to determine whether to separate larger numbers into groups of 2
* (customary in South Asia) or groups of 3 (customary in Europe and the Americas).
*
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
*/
ON_ALIGNED,
/**
* Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use
* locale data for determining the grouping strategy.
*
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
*/
WESTERN
}
/**
* An enum declaring how to denote positive and negative numbers. Example outputs when formatting
* 123, 0, and -123 in <em>en-US</em>:
*
* <ul>
* <li>AUTO: "123", "0", and "-123"
* <li>ALWAYS: "+123", "+0", and "-123"
* <li>NEVER: "123", "0", and "123"
* <li>ACCOUNTING: "$123", "$0", and "($123)"
* <li>ACCOUNTING_ALWAYS: "+$123", "+$0", and "($123)"
* <li>EXCEPT_ZERO: "+123", "0", and "-123"
* <li>ACCOUNTING_EXCEPT_ZERO: "+$123", "$0", and "($123)"
* </ul>
*
* <p>

View File

@ -2,9 +2,12 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.number;
import com.ibm.icu.impl.CurrencyData;
import com.ibm.icu.impl.CurrencyData.CurrencyFormatInfo;
import com.ibm.icu.impl.number.CompactData.CompactType;
import com.ibm.icu.impl.number.ConstantAffixModifier;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.LongNameHandler;
import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.impl.number.MicroProps;
@ -15,6 +18,7 @@ import com.ibm.icu.impl.number.Padder;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.DecimalFormatSymbols;
@ -134,35 +138,49 @@ class NumberFormatterImpl {
}
String nsName = ns.getName();
// Resolve the symbols. Do this here because currency may need to customize them.
if (macros.symbols instanceof DecimalFormatSymbols) {
micros.symbols = (DecimalFormatSymbols) macros.symbols;
} else {
micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns);
}
// Load and parse the pattern string. It is used for grouping sizes and affixes only.
// If we are formatting currency, check for a currency-specific pattern.
String pattern = null;
if (isCurrency) {
CurrencyFormatInfo info = CurrencyData.provider.getInstance(macros.loc, true)
.getFormatInfo(currency.getCurrencyCode());
if (info != null) {
pattern = info.currencyPattern;
// It's clunky to clone an object here, but this code is not frequently executed.
micros.symbols = (DecimalFormatSymbols) micros.symbols.clone();
micros.symbols.setMonetaryDecimalSeparatorString(info.monetaryDecimalSeparator);
micros.symbols.setMonetaryGroupingSeparatorString(info.monetaryGroupingSeparator);
}
}
if (pattern == null) {
int patternStyle;
if (isPercent || isPermille) {
patternStyle = NumberFormat.PERCENTSTYLE;
} else if (!isCurrency || unitWidth == UnitWidth.FULL_NAME) {
patternStyle = NumberFormat.NUMBERSTYLE;
} else if (isAccounting) {
// NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right
// now,
// the API contract allows us to add support to other units in the future.
// NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies
// right now, the API contract allows us to add support to other units in the future.
patternStyle = NumberFormat.ACCOUNTINGCURRENCYSTYLE;
} else {
patternStyle = NumberFormat.CURRENCYSTYLE;
}
String pattern = NumberFormat
pattern = NumberFormat
.getPatternForStyleAndNumberingSystem(macros.loc, nsName, patternStyle);
}
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern);
/////////////////////////////////////////////////////////////////////////////////////
/// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
/////////////////////////////////////////////////////////////////////////////////////
// Symbols
if (macros.symbols instanceof DecimalFormatSymbols) {
micros.symbols = (DecimalFormatSymbols) macros.symbols;
} else {
micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns);
}
// Multiplier (compatibility mode value).
if (macros.multiplier != null) {
chain = macros.multiplier.copyAndChain(chain);
@ -181,15 +199,17 @@ class NumberFormatterImpl {
micros.rounding = micros.rounding.withLocaleData(currency);
// Grouping strategy
if (macros.grouper != null) {
micros.grouping = macros.grouper;
if (macros.grouping instanceof Grouper) {
micros.grouping = (Grouper) macros.grouping;
} else if (macros.grouping instanceof GroupingStrategy) {
micros.grouping = Grouper.forStrategy((GroupingStrategy) macros.grouping);
} else if (macros.notation instanceof CompactNotation) {
// Compact notation uses minGrouping by default since ICU 59
micros.grouping = Grouper.minTwoDigits();
micros.grouping = Grouper.forStrategy(GroupingStrategy.MIN2);
} else {
micros.grouping = Grouper.defaults();
micros.grouping = Grouper.forStrategy(GroupingStrategy.AUTO);
}
micros.grouping = micros.grouping.withLocaleData(patternInfo);
micros.grouping = micros.grouping.withLocaleData(macros.loc, patternInfo);
// Padding strategy
if (macros.padder != null) {

View File

@ -5,6 +5,7 @@ package com.ibm.icu.number;
import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.impl.number.Padder;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.DecimalFormatSymbols;
@ -31,7 +32,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
static final int KEY_NOTATION = 2;
static final int KEY_UNIT = 3;
static final int KEY_ROUNDER = 4;
static final int KEY_GROUPER = 5;
static final int KEY_GROUPING = 5;
static final int KEY_PADDER = 6;
static final int KEY_INTEGER = 7;
static final int KEY_SYMBOLS = 8;
@ -220,25 +221,24 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
* The exact grouping widths will be chosen based on the locale.
*
* <p>
* Pass this method the return value of one of the factory methods on {@link Grouper}. For example:
* Pass this method an element from the {@link GroupingStrategy} enum. For example:
*
* <pre>
* NumberFormatter.with().grouping(Grouper.min2())
* NumberFormatter.with().grouping(GroupingStrategy.MIN2)
* </pre>
*
* The default is to perform grouping without concern for the minimum grouping digits.
* The default is to perform grouping according to locale data; most locales, but not all locales,
* enable it by default.
*
* @param grouper
* @param strategy
* The grouping strategy to use.
* @return The fluent chain.
* @see Grouper
* @see Notation
* @internal
* @deprecated ICU 60 This API is technical preview; see #7861.
* @see GroupingStrategy
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
*/
@Deprecated
public T grouping(Grouper grouper) {
return create(KEY_GROUPER, grouper);
public T grouping(GroupingStrategy strategy) {
return create(KEY_GROUPING, strategy);
}
/**
@ -512,9 +512,9 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
macros.rounder = (Rounder) current.value;
}
break;
case KEY_GROUPER:
if (macros.grouper == null) {
macros.grouper = (Grouper) current.value;
case KEY_GROUPING:
if (macros.grouping == null) {
macros.grouping = /* (Object) */ current.value;
}
break;
case KEY_PADDER:

View File

@ -9,6 +9,7 @@ import com.ibm.icu.impl.number.AffixPatternProvider;
import com.ibm.icu.impl.number.CurrencyPluralInfoAffixProvider;
import com.ibm.icu.impl.number.CustomSymbolCurrency;
import com.ibm.icu.impl.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.impl.number.MultiplierImpl;
import com.ibm.icu.impl.number.Padder;
@ -193,7 +194,7 @@ final class NumberPropertyMapper {
// GROUPING STRATEGY //
///////////////////////
macros.grouper = Grouper.defaults().withProperties(properties);
macros.grouping = Grouper.forProperties(properties);
/////////////
// PADDING //

View File

@ -111,17 +111,31 @@ public abstract class CurrencyDisplayNames {
/**
* Returns the symbol for the currency with the provided ISO code. If
* there is no data for the ISO code, substitutes isoCode or returns null.
* there is no data for the ISO code, substitutes isoCode, or returns null
* if noSubstitute was set in the factory method.
*
* @param isoCode the three-letter ISO code.
* @return the display name.
* @return the symbol.
* @stable ICU 4.4
*/
public abstract String getSymbol(String isoCode);
/**
* Returns the narrow symbol for the currency with the provided ISO code.
* If there is no data for narrow symbol, substitutes isoCode, or returns
* null if noSubstitute was set in the factory method.
*
* @param isoCode the three-letter ISO code.
* @return the narrow symbol.
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
*/
public abstract String getNarrowSymbol(String isoCode);
/**
* Returns the 'long name' for the currency with the provided ISO code.
* If there is no data for the ISO code, substitutes isoCode or returns null.
* If there is no data for the ISO code, substitutes isoCode, or returns null
* if noSubstitute was set in the factory method.
*
* @param isoCode the three-letter ISO code
* @return the display name

View File

@ -24,7 +24,6 @@ import java.util.MissingResourceException;
import java.util.Set;
import com.ibm.icu.impl.CacheBase;
import com.ibm.icu.impl.CurrencyData.CurrencyDisplayInfo;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUDebug;
@ -92,15 +91,11 @@ public class Currency extends MeasureUnit {
* Selector for getName() indicating the narrow currency symbol.
* The narrow currency symbol is similar to the regular currency
* symbol, but it always takes the shortest form: for example,
* "$" instead of "US$".
* "$" instead of "US$" for USD in en-CA.
*
* This method assumes that the currency data provider is the ICU4J
* built-in data provider. If it is not, an exception is thrown.
*
* @internal
* @deprecated ICU 60: This API is ICU internal only.
* @draft ICU 61
* @provisional This API might change or be removed in a future release.
*/
@Deprecated
public static final int NARROW_SYMBOL_NAME = 3;
private static final EquivalenceRelation<String> EQUIVALENT_CURRENCY_SYMBOLS =
@ -568,8 +563,8 @@ public class Currency extends MeasureUnit {
* currency object in the en_US locale is "$".
* @param locale locale in which to display currency
* @param nameStyle selector for which kind of name to return.
* The nameStyle should be either SYMBOL_NAME or
* LONG_NAME. Otherwise, throw IllegalArgumentException.
* The nameStyle should be SYMBOL_NAME, NARROW_SYMBOL_NAME,
* or LONG_NAME. Otherwise, throw IllegalArgumentException.
* @param isChoiceFormat fill-in; isChoiceFormat[0] is set to true
* if the returned value is a ChoiceFormat pattern; otherwise it
* is set to false
@ -597,13 +592,7 @@ public class Currency extends MeasureUnit {
case SYMBOL_NAME:
return names.getSymbol(subType);
case NARROW_SYMBOL_NAME:
// CurrencyDisplayNames is the public interface.
// CurrencyDisplayInfo is ICU's standard implementation.
if (!(names instanceof CurrencyDisplayInfo)) {
throw new UnsupportedOperationException(
"Cannot get narrow symbol from custom currency display name provider");
}
return ((CurrencyDisplayInfo) names).getNarrowSymbol(subType);
return names.getNarrowSymbol(subType);
case LONG_NAME:
return names.getName(subType);
default:

View File

@ -465,9 +465,8 @@ output grouping breaks grouping2 minGroupingDigits
1,2345,6789 4
1,23,45,6789 4 K 2
1,23,45,6789 4 K 2 2
// Q only supports minGrouping<=2
123,456789 6 6 3
123456789 6 JKQ 6 4
123456789 6 JK 6 4
test multiplier setters
set locale en_US

View File

@ -5331,6 +5331,33 @@ public class NumberFormatTest extends TestFmwk {
assertEquals("Grouping should be off", false, df.isGroupingUsed());
}
@Test
public void Test11035_FormatCurrencyAmount() {
double amount = 12345.67;
String expected = "12,345$67 ";
Currency cur = Currency.getInstance("PTE");
// Test three ways to set currency via API
ULocale loc1 = new ULocale("pt_PT");
NumberFormat fmt1 = NumberFormat.getCurrencyInstance(loc1);
fmt1.setCurrency(cur);
String actualSetCurrency = fmt1.format(amount);
ULocale loc2 = new ULocale("pt_PT@currency=PTE");
NumberFormat fmt2 = NumberFormat.getCurrencyInstance(loc2);
String actualLocaleString = fmt2.format(amount);
ULocale loc3 = new ULocale("pt_PT");
NumberFormat fmt3 = NumberFormat.getCurrencyInstance(loc3);
CurrencyAmount curAmt = new CurrencyAmount(amount, cur);
String actualCurrencyAmount = fmt3.format(curAmt);
assertEquals("Custom Currency Pattern, Set Currency", expected, actualSetCurrency);
assertEquals("Custom Currency Pattern, Locale String", expected, actualCurrencyAmount);
assertEquals("Custom Currency Pattern, CurrencyAmount", expected, actualLocaleString);
}
@Test
public void testPercentZero() {
DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();

View File

@ -31,12 +31,12 @@ public class ModifierTest {
public void testConstantMultiFieldModifier() {
NumberStringBuilder prefix = new NumberStringBuilder();
NumberStringBuilder suffix = new NumberStringBuilder();
Modifier mod1 = new ConstantMultiFieldModifier(prefix, suffix, true);
Modifier mod1 = new ConstantMultiFieldModifier(prefix, suffix, false, true);
assertModifierEquals(mod1, 0, true, "|", "n");
prefix.append("a📻", NumberFormat.Field.PERCENT);
suffix.append("b", NumberFormat.Field.CURRENCY);
Modifier mod2 = new ConstantMultiFieldModifier(prefix, suffix, true);
Modifier mod2 = new ConstantMultiFieldModifier(prefix, suffix, false, true);
assertModifierEquals(mod2, 3, true, "a📻|b", "%%%n$");
// Make sure the first modifier is still the same (that it stayed constant)
@ -91,11 +91,11 @@ public class ModifierTest {
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
NumberStringBuilder prefix = new NumberStringBuilder();
NumberStringBuilder suffix = new NumberStringBuilder();
Modifier mod1 = new CurrencySpacingEnabledModifier(prefix, suffix, true, symbols);
Modifier mod1 = new CurrencySpacingEnabledModifier(prefix, suffix, false, true, symbols);
assertModifierEquals(mod1, 0, true, "|", "n");
prefix.append("USD", NumberFormat.Field.CURRENCY);
Modifier mod2 = new CurrencySpacingEnabledModifier(prefix, suffix, true, symbols);
Modifier mod2 = new CurrencySpacingEnabledModifier(prefix, suffix, false, true, symbols);
assertModifierEquals(mod2, 3, true, "USD|", "$$$n");
// Test the default currency spacing rules
@ -116,7 +116,7 @@ public class ModifierTest {
true,
"[|]");
suffix.append("XYZ", NumberFormat.Field.CURRENCY);
Modifier mod3 = new CurrencySpacingEnabledModifier(prefix, suffix, true, symbols);
Modifier mod3 = new CurrencySpacingEnabledModifier(prefix, suffix, false, true, symbols);
assertModifierEquals(mod3, 3, true, "USD|\u00A0XYZ", "$$$nn$$$");
}

View File

@ -103,6 +103,32 @@ public class MutablePatternModifierTest {
assertFalse(nsb1 + " vs. " + nsb3, nsb1.contentEquals(nsb3));
}
@Test
public void patternWithNoPlaceholder() {
MutablePatternModifier mod = new MutablePatternModifier(false);
mod.setPatternInfo(PatternStringParser.parseToPatternInfo("abc"));
mod.setPatternAttributes(SignDisplay.AUTO, false);
mod.setSymbols(DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
Currency.getInstance("USD"),
UnitWidth.SHORT,
null);
mod.setNumberProperties(1, null);
// Unsafe Code Path
NumberStringBuilder nsb = new NumberStringBuilder();
nsb.append("x123y", null);
mod.apply(nsb, 1, 4);
assertEquals("Unsafe Path", "xabcy", nsb.toString());
// Safe Code Path
nsb.clear();
nsb.append("x123y", null);
MicroProps micros = new MicroProps(false);
mod.createImmutable().applyToMicros(micros, new DecimalQuantity_DualStorageBCD());
micros.modMiddle.apply(nsb, 1, 4);
assertEquals("Safe Path", "xabcy", nsb.toString());
}
private static String getPrefix(MutablePatternModifier mod) {
NumberStringBuilder nsb = new NumberStringBuilder();
mod.apply(nsb, 0, 0);

View File

@ -19,17 +19,20 @@ import java.util.Set;
import org.junit.Ignore;
import org.junit.Test;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.impl.number.Padder;
import com.ibm.icu.impl.number.Padder.PadPosition;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.number.CompactNotation;
import com.ibm.icu.number.FormattedNumber;
import com.ibm.icu.number.FractionRounder;
import com.ibm.icu.number.Grouper;
import com.ibm.icu.number.IntegerWidth;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.Notation;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.number.Rounder;
@ -51,6 +54,8 @@ public class NumberFormatterApiTest {
private static final Currency GBP = Currency.getInstance("GBP");
private static final Currency CZK = Currency.getInstance("CZK");
private static final Currency CAD = Currency.getInstance("CAD");
private static final Currency ESP = Currency.getInstance("ESP");
private static final Currency PTE = Currency.getInstance("PTE");
@Test
public void notationSimple() {
@ -354,6 +359,19 @@ public class NumberFormatterApiTest {
ULocale.ENGLISH,
9990000,
"10M");
Map<String, Map<String, String>> compactCustomData = new HashMap<String, Map<String, String>>();
Map<String, String> entry = new HashMap<String, String>();
entry.put("one", "Kun");
entry.put("other", "0KK");
compactCustomData.put("1000", entry);
assertFormatSingle(
"Compact Somali No Figure",
"",
NumberFormatter.with().notation(CompactNotation.forCustomData(compactCustomData)),
ULocale.ENGLISH,
1000,
"Kun");
}
@Test
@ -645,9 +663,59 @@ public class NumberFormatterApiTest {
"Currency Difference between Narrow and Short (Short Version)",
"",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("en_CA"),
ULocale.forLanguageTag("en-CA"),
5.43,
"US$ 5.43");
"US$5.43");
assertFormatSingle(
"Currency-dependent format (Control)",
"",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("ca"),
444444.55,
"444.444,55 USD");
assertFormatSingle(
"Currency-dependent format (Test)",
"",
NumberFormatter.with().unit(ESP).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("ca"),
444444.55,
" 444.445");
assertFormatSingle(
"Currency-dependent symbols (Control)",
"",
NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("pt-PT"),
444444.55,
"444 444,55 US$");
// NOTE: This is a bit of a hack on CLDR's part. They set the currency symbol to U+200B (zero-
// width space), and they set the decimal separator to the $ symbol.
assertFormatSingle(
"Currency-dependent symbols (Test)",
"",
NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.SHORT),
ULocale.forLanguageTag("pt-PT"),
444444.55,
"444,444$55 \u200B");
assertFormatSingle(
"Currency-dependent symbols (Test)",
"",
NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW),
ULocale.forLanguageTag("pt-PT"),
444444.55,
"444,444$55 PTE");
assertFormatSingle(
"Currency-dependent symbols (Test)",
"",
NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.ISO_CODE),
ULocale.forLanguageTag("pt-PT"),
444444.55,
"444,444$55 PTE");
}
@Test
@ -889,6 +957,22 @@ public class NumberFormatterApiTest {
"0.09",
"0.01",
"0.00");
assertFormatSingle(
"FracSig with trailing zeros A",
"",
NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMinDigits(3)),
ULocale.ENGLISH,
0.1,
"0.10");
assertFormatSingle(
"FracSig with trailing zeros B",
"",
NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMinDigits(3)),
ULocale.ENGLISH,
0.0999999,
"0.10");
}
@Test
@ -1020,7 +1104,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Western Grouping",
"grouping=defaults",
NumberFormatter.with().grouping(Grouper.defaults()),
NumberFormatter.with().grouping(GroupingStrategy.AUTO),
ULocale.ENGLISH,
"87,650,000",
"8,765,000",
@ -1035,7 +1119,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Indic Grouping",
"grouping=defaults",
NumberFormatter.with().grouping(Grouper.defaults()),
NumberFormatter.with().grouping(GroupingStrategy.AUTO),
new ULocale("en-IN"),
"8,76,50,000",
"87,65,000",
@ -1050,7 +1134,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Western Grouping, Min 2",
"grouping=min2",
NumberFormatter.with().grouping(Grouper.minTwoDigits()),
NumberFormatter.with().grouping(GroupingStrategy.MIN2),
ULocale.ENGLISH,
"87,650,000",
"8,765,000",
@ -1065,7 +1149,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"Indic Grouping, Min 2",
"grouping=min2",
NumberFormatter.with().grouping(Grouper.minTwoDigits()),
NumberFormatter.with().grouping(GroupingStrategy.MIN2),
new ULocale("en-IN"),
"8,76,50,000",
"87,65,000",
@ -1080,7 +1164,7 @@ public class NumberFormatterApiTest {
assertFormatDescendingBig(
"No Grouping",
"grouping=none",
NumberFormatter.with().grouping(Grouper.none()),
NumberFormatter.with().grouping(GroupingStrategy.OFF),
new ULocale("en-IN"),
"87650000",
"8765000",
@ -1091,6 +1175,102 @@ public class NumberFormatterApiTest {
"87.65",
"8.765",
"0");
// NOTE: Hungarian is interesting because it has minimumGroupingDigits=4 in locale data
// If this test breaks due to data changes, find another locale that has minimumGroupingDigits.
assertFormatDescendingBig(
"Hungarian Grouping",
"",
NumberFormatter.with().grouping(GroupingStrategy.AUTO),
new ULocale("hu"),
"87 650 000",
"8 765 000",
"876500",
"87650",
"8765",
"876,5",
"87,65",
"8,765",
"0");
assertFormatDescendingBig(
"Hungarian Grouping, Min 2",
"",
NumberFormatter.with().grouping(GroupingStrategy.MIN2),
new ULocale("hu"),
"87 650 000",
"8 765 000",
"876500",
"87650",
"8765",
"876,5",
"87,65",
"8,765",
"0");
assertFormatDescendingBig(
"Hungarian Grouping, Always",
"",
NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED),
new ULocale("hu"),
"87 650 000",
"8 765 000",
"876 500",
"87 650",
"8 765",
"876,5",
"87,65",
"8,765",
"0");
// NOTE: Bulgarian is interesting because it has no grouping in the default currency format.
// If this test breaks due to data changes, find another locale that has no default grouping.
assertFormatDescendingBig(
"Bulgarian Currency Grouping",
"",
NumberFormatter.with().grouping(GroupingStrategy.AUTO).unit(USD),
new ULocale("bg"),
"87650000,00 щ.д.",
"8765000,00 щ.д.",
"876500,00 щ.д.",
"87650,00 щ.д.",
"8765,00 щ.д.",
"876,50 щ.д.",
"87,65 щ.д.",
"8,76 щ.д.",
"0,00 щ.д.");
assertFormatDescendingBig(
"Bulgarian Currency Grouping, Always",
"",
NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED).unit(USD),
new ULocale("bg"),
"87 650 000,00 щ.д.",
"8 765 000,00 щ.д.",
"876 500,00 щ.д.",
"87 650,00 щ.д.",
"8 765,00 щ.д.",
"876,50 щ.д.",
"87,65 щ.д.",
"8,76 щ.д.",
"0,00 щ.д.");
MacroProps macros = new MacroProps();
macros.grouping = Grouper.getInstance((short) 4, (short) 1, (short) 3);
assertFormatDescendingBig(
"Custom Grouping via Internal API",
"",
NumberFormatter.with().macros(macros),
ULocale.ENGLISH,
"8,7,6,5,0000",
"8,7,6,5000",
"876500",
"87650",
"8765",
"876.5",
"87.65",
"8.765",
"0");
}
@Test

View File

@ -190,17 +190,54 @@ public class CurrencyTest extends TestFmwk {
// Do a basic check of getName()
// USD { "US$", "US Dollar" } // 04/04/1792-
ULocale en = ULocale.ENGLISH;
ULocale en_CA = ULocale.forLanguageTag("en-CA");
ULocale en_US = ULocale.forLanguageTag("en-US");
ULocale en_NZ = ULocale.forLanguageTag("en-NZ");
boolean[] isChoiceFormat = new boolean[1];
Currency usd = Currency.getInstance("USD");
Currency USD = Currency.getInstance("USD");
Currency CAD = Currency.getInstance("CAD");
Currency USX = Currency.getInstance("USX");
// Warning: HARD-CODED LOCALE DATA in this test. If it fails, CHECK
// THE LOCALE DATA before diving into the code.
assertEquals("USD.getName(SYMBOL_NAME)",
assertEquals("USD.getName(SYMBOL_NAME, en)",
"$",
usd.getName(en, Currency.SYMBOL_NAME, isChoiceFormat));
assertEquals("USD.getName(LONG_NAME)",
USD.getName(en, Currency.SYMBOL_NAME, isChoiceFormat));
assertEquals("USD.getName(NARROW_SYMBOL_NAME, en)",
"$",
USD.getName(en, Currency.NARROW_SYMBOL_NAME, isChoiceFormat));
assertEquals("USD.getName(LONG_NAME, en)",
"US Dollar",
usd.getName(en, Currency.LONG_NAME, isChoiceFormat));
// TODO add more tests later
USD.getName(en, Currency.LONG_NAME, isChoiceFormat));
assertEquals("CAD.getName(SYMBOL_NAME, en)",
"CA$",
CAD.getName(en, Currency.SYMBOL_NAME, isChoiceFormat));
assertEquals("CAD.getName(NARROW_SYMBOL_NAME, en)",
"$",
CAD.getName(en, Currency.NARROW_SYMBOL_NAME, isChoiceFormat));
assertEquals("CAD.getName(SYMBOL_NAME, en_CA)",
"$",
CAD.getName(en_CA, Currency.SYMBOL_NAME, isChoiceFormat));
assertEquals("USD.getName(SYMBOL_NAME, en_CA)",
"US$",
USD.getName(en_CA, Currency.SYMBOL_NAME, isChoiceFormat));
assertEquals("USD.getName(NARROW_SYMBOL_NAME, en_CA)",
"$",
USD.getName(en_CA, Currency.NARROW_SYMBOL_NAME, isChoiceFormat));
assertEquals("USD.getName(SYMBOL_NAME) in en_NZ",
"US$",
USD.getName(en_NZ, Currency.SYMBOL_NAME, isChoiceFormat));
assertEquals("CAD.getName(SYMBOL_NAME)",
"CA$",
CAD.getName(en_NZ, Currency.SYMBOL_NAME, isChoiceFormat));
assertEquals("USX.getName(SYMBOL_NAME)",
"USX",
USX.getName(en_US, Currency.SYMBOL_NAME, isChoiceFormat));
assertEquals("USX.getName(NARROW_SYMBOL_NAME)",
"USX",
USX.getName(en_US, Currency.NARROW_SYMBOL_NAME, isChoiceFormat));
assertEquals("USX.getName(LONG_NAME)",
"USX",
USX.getName(en_US, Currency.LONG_NAME, isChoiceFormat));
}
@Test