ICU-13568 ICU-13400 ICU-13389 ICU-13075 Merging fixes from branch to trunk.
X-SVN-Rev: 40839
This commit is contained in:
commit
5315ad603e
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 ¯os, 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 ¯os, 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 ¯os, 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 ¯os, 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()) {
|
||||
|
@ -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 */
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 ///
|
||||
/////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
164
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Grouper.java
Normal file
164
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Grouper.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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:
|
||||
|
@ -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 //
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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$$$");
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user