ICU-20119 63rc BRS, merge current unicode-icu master into cldr34a-integration branch

This commit is contained in:
Peter Edberg 2018-09-18 23:18:27 -07:00 committed by Shane Carr
commit f5defe757b
No known key found for this signature in database
GPG Key ID: FCED3B24AAB18B5C
117 changed files with 8561 additions and 838 deletions

View File

@ -126,15 +126,21 @@ char *CharString::getAppendBuffer(int32_t minCapacity,
}
CharString &CharString::appendInvariantChars(const UnicodeString &s, UErrorCode &errorCode) {
return appendInvariantChars(s.getBuffer(), s.length(), errorCode);
}
CharString &CharString::appendInvariantChars(const UChar* uchars, int32_t ucharsLen, UErrorCode &errorCode) {
if(U_FAILURE(errorCode)) {
return *this;
}
if (!uprv_isInvariantUnicodeString(s)) {
if (!uprv_isInvariantUString(uchars, ucharsLen)) {
errorCode = U_INVARIANT_CONVERSION_ERROR;
return *this;
}
if(ensureCapacity(len+s.length()+1, 0, errorCode)) {
len+=s.extract(0, 0x7fffffff, buffer.getAlias()+len, buffer.getCapacity()-len, US_INV);
if(ensureCapacity(len+ucharsLen+1, 0, errorCode)) {
u_UCharsToChars(uchars, buffer.getAlias()+len, ucharsLen);
len += ucharsLen;
buffer[len] = 0;
}
return *this;
}

View File

@ -123,6 +123,7 @@ public:
UErrorCode &errorCode);
CharString &appendInvariantChars(const UnicodeString &s, UErrorCode &errorCode);
CharString &appendInvariantChars(const UChar* uchars, int32_t ucharsLen, UErrorCode& errorCode);
/**
* Appends a filename/path part, e.g., a directory name.

View File

@ -279,6 +279,10 @@ inline T *LocalMemory<T>::allocateInsteadAndCopy(int32_t newCapacity, int32_t le
*
* Unlike LocalMemory and LocalArray, this class never adopts
* (takes ownership of) another array.
*
* WARNING: MaybeStackArray only works with primitive (plain-old data) types.
* It does NOT know how to call a destructor! If you work with classes with
* destructors, consider LocalArray in localpointer.h.
*/
template<typename T, int32_t stackCapacity>
class MaybeStackArray {

View File

@ -31,6 +31,7 @@
******************************************************************************
*/
#include <utility>
#include "unicode/bytestream.h"
#include "unicode/locid.h"
@ -45,9 +46,11 @@
#include "cstring.h"
#include "uassert.h"
#include "uhash.h"
#include "ulocimp.h"
#include "ucln_cmn.h"
#include "ustr_imp.h"
#include "charstr.h"
#include "bytesinkutil.h"
U_CDECL_BEGIN
static UBool U_CALLCONV locale_cleanup(void);
@ -426,56 +429,70 @@ Locale::Locale(const Locale &other)
*this = other;
}
Locale &Locale::operator=(const Locale &other)
{
Locale::Locale(Locale&& other) U_NOEXCEPT
: UObject(other), fullName(fullNameBuffer), baseName(fullName) {
*this = std::move(other);
}
Locale& Locale::operator=(const Locale& other) {
if (this == &other) {
return *this;
}
/* Free our current storage */
if (baseName != fullName) {
uprv_free(baseName);
}
baseName = NULL;
if(fullName != fullNameBuffer) {
uprv_free(fullName);
fullName = fullNameBuffer;
setToBogus();
if (other.fullName == other.fullNameBuffer) {
uprv_strcpy(fullNameBuffer, other.fullNameBuffer);
} else if (other.fullName == nullptr) {
fullName = nullptr;
} else {
fullName = uprv_strdup(other.fullName);
if (fullName == nullptr) return *this;
}
/* Allocate the full name if necessary */
if(other.fullName != other.fullNameBuffer) {
fullName = (char *)uprv_malloc(sizeof(char)*(uprv_strlen(other.fullName)+1));
if (fullName == NULL) {
// if memory allocation fails, set this object to bogus.
fIsBogus = TRUE;
return *this;
}
}
/* Copy the full name */
uprv_strcpy(fullName, other.fullName);
/* Copy the baseName if it differs from fullName. */
if (other.baseName == other.fullName) {
baseName = fullName;
} else {
if (other.baseName) {
baseName = uprv_strdup(other.baseName);
if (baseName == nullptr) {
// if memory allocation fails, set this object to bogus.
fIsBogus = TRUE;
return *this;
}
}
} else if (other.baseName != nullptr) {
baseName = uprv_strdup(other.baseName);
if (baseName == nullptr) return *this;
}
/* Copy the language and country fields */
uprv_strcpy(language, other.language);
uprv_strcpy(script, other.script);
uprv_strcpy(country, other.country);
/* The variantBegin is an offset, just copy it */
variantBegin = other.variantBegin;
fIsBogus = other.fIsBogus;
return *this;
}
Locale& Locale::operator=(Locale&& other) U_NOEXCEPT {
if (baseName != fullName) uprv_free(baseName);
if (fullName != fullNameBuffer) uprv_free(fullName);
if (other.fullName == other.fullNameBuffer) {
uprv_strcpy(fullNameBuffer, other.fullNameBuffer);
fullName = fullNameBuffer;
} else {
fullName = other.fullName;
}
if (other.baseName == other.fullName) {
baseName = fullName;
} else {
baseName = other.baseName;
}
uprv_strcpy(language, other.language);
uprv_strcpy(script, other.script);
uprv_strcpy(country, other.country);
variantBegin = other.variantBegin;
fIsBogus = other.fIsBogus;
other.baseName = other.fullName = other.fullNameBuffer;
return *this;
}
@ -713,6 +730,126 @@ Locale::setDefault( const Locale& newLocale,
locale_set_default_internal(localeID, status);
}
void
Locale::addLikelySubtags(UErrorCode& status) {
if (U_FAILURE(status)) {
return;
}
// The maximized locale ID string is often longer, but there is no good
// heuristic to estimate just how much longer. Leave that to CharString.
CharString maximizedLocaleID;
int32_t maximizedLocaleIDCapacity = uprv_strlen(fullName);
char* buffer;
int32_t reslen;
for (;;) {
buffer = maximizedLocaleID.getAppendBuffer(
/*minCapacity=*/maximizedLocaleIDCapacity,
/*desiredCapacityHint=*/maximizedLocaleIDCapacity,
maximizedLocaleIDCapacity,
status);
if (U_FAILURE(status)) {
return;
}
reslen = uloc_addLikelySubtags(
fullName,
buffer,
maximizedLocaleIDCapacity,
&status);
if (status != U_BUFFER_OVERFLOW_ERROR) {
break;
}
maximizedLocaleIDCapacity = reslen;
status = U_ZERO_ERROR;
}
if (U_FAILURE(status)) {
return;
}
maximizedLocaleID.append(buffer, reslen, status);
if (status == U_STRING_NOT_TERMINATED_WARNING) {
status = U_ZERO_ERROR; // Terminators provided by CharString.
}
if (U_FAILURE(status)) {
return;
}
init(maximizedLocaleID.data(), /*canonicalize=*/FALSE);
if (isBogus()) {
status = U_ILLEGAL_ARGUMENT_ERROR;
}
}
void
Locale::minimizeSubtags(UErrorCode& status) {
if (U_FAILURE(status)) {
return;
}
// Except for a few edge cases (like the empty string, that is minimized to
// "en__POSIX"), minimized locale ID strings will be either the same length
// or shorter than their input.
CharString minimizedLocaleID;
int32_t minimizedLocaleIDCapacity = uprv_strlen(fullName);
char* buffer;
int32_t reslen;
for (;;) {
buffer = minimizedLocaleID.getAppendBuffer(
/*minCapacity=*/minimizedLocaleIDCapacity,
/*desiredCapacityHint=*/minimizedLocaleIDCapacity,
minimizedLocaleIDCapacity,
status);
if (U_FAILURE(status)) {
return;
}
reslen = uloc_minimizeSubtags(
fullName,
buffer,
minimizedLocaleIDCapacity,
&status);
if (status != U_BUFFER_OVERFLOW_ERROR) {
break;
}
// Because of the internal minimal buffer size of CharString, I can't
// think of any input data for which this could possibly ever happen.
// Maybe it would be better replaced with an assertion instead?
minimizedLocaleIDCapacity = reslen;
status = U_ZERO_ERROR;
}
if (U_FAILURE(status)) {
return;
}
minimizedLocaleID.append(buffer, reslen, status);
if (status == U_STRING_NOT_TERMINATED_WARNING) {
status = U_ZERO_ERROR; // Terminators provided by CharString.
}
if (U_FAILURE(status)) {
return;
}
init(minimizedLocaleID.data(), /*canonicalize=*/FALSE);
if (isBogus()) {
status = U_ILLEGAL_ARGUMENT_ERROR;
}
}
Locale U_EXPORT2
Locale::forLanguageTag(StringPiece tag, UErrorCode& status)
{
@ -722,12 +859,6 @@ Locale::forLanguageTag(StringPiece tag, UErrorCode& status)
return result;
}
// TODO: Remove the need for a const char* to a NUL terminated buffer.
const CharString tag_nul(tag, status);
if (U_FAILURE(status)) {
return result;
}
// If a BCP-47 language tag is passed as the language parameter to the
// normal Locale constructor, it will actually fall back to invoking
// uloc_forLanguageTag() to parse it if it somehow is able to detect that
@ -757,8 +888,9 @@ Locale::forLanguageTag(StringPiece tag, UErrorCode& status)
return result;
}
reslen = uloc_forLanguageTag(
tag_nul.data(),
reslen = ulocimp_forLanguageTag(
tag.data(),
tag.length(),
buffer,
resultCapacity,
&parsedLength,
@ -1174,20 +1306,79 @@ KeywordEnumeration::~KeywordEnumeration() {
uprv_free(keywords);
}
// A wrapper around KeywordEnumeration that calls uloc_toUnicodeLocaleKey() in
// the next() method for each keyword before returning it.
class UnicodeKeywordEnumeration : public KeywordEnumeration {
public:
using KeywordEnumeration::KeywordEnumeration;
virtual ~UnicodeKeywordEnumeration() = default;
virtual const char* next(int32_t* resultLength, UErrorCode& status) {
const char* legacy_key = KeywordEnumeration::next(nullptr, status);
if (U_SUCCESS(status) && legacy_key != nullptr) {
const char* key = uloc_toUnicodeLocaleKey(legacy_key);
if (key == nullptr) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else {
if (resultLength != nullptr) *resultLength = uprv_strlen(key);
return key;
}
}
if (resultLength != nullptr) *resultLength = 0;
return nullptr;
}
};
StringEnumeration *
Locale::createKeywords(UErrorCode &status) const
{
char keywords[256];
int32_t keywordCapacity = 256;
int32_t keywordCapacity = sizeof keywords;
StringEnumeration *result = NULL;
if (U_FAILURE(status)) {
return result;
}
const char* variantStart = uprv_strchr(fullName, '@');
const char* assignment = uprv_strchr(fullName, '=');
if(variantStart) {
if(assignment > variantStart) {
int32_t keyLen = locale_getKeywords(variantStart+1, '@', keywords, keywordCapacity, NULL, 0, NULL, FALSE, &status);
if(keyLen) {
if(U_SUCCESS(status) && keyLen) {
result = new KeywordEnumeration(keywords, keyLen, 0, status);
if (!result) {
status = U_MEMORY_ALLOCATION_ERROR;
}
}
} else {
status = U_INVALID_FORMAT_ERROR;
}
}
return result;
}
StringEnumeration *
Locale::createUnicodeKeywords(UErrorCode &status) const
{
char keywords[256];
int32_t keywordCapacity = sizeof keywords;
StringEnumeration *result = NULL;
if (U_FAILURE(status)) {
return result;
}
const char* variantStart = uprv_strchr(fullName, '@');
const char* assignment = uprv_strchr(fullName, '=');
if(variantStart) {
if(assignment > variantStart) {
int32_t keyLen = locale_getKeywords(variantStart+1, '@', keywords, keywordCapacity, NULL, 0, NULL, FALSE, &status);
if(U_SUCCESS(status) && keyLen) {
result = new UnicodeKeywordEnumeration(keywords, keyLen, 0, status);
if (!result) {
status = U_MEMORY_ALLOCATION_ERROR;
}
}
} else {
status = U_INVALID_FORMAT_ERROR;
@ -1202,6 +1393,105 @@ Locale::getKeywordValue(const char* keywordName, char *buffer, int32_t bufLen, U
return uloc_getKeywordValue(fullName, keywordName, buffer, bufLen, &status);
}
void
Locale::getKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
if (fIsBogus) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
// TODO: Remove the need for a const char* to a NUL terminated buffer.
const CharString keywordName_nul(keywordName, status);
if (U_FAILURE(status)) {
return;
}
LocalMemory<char> scratch;
int32_t scratch_capacity = 16; // Arbitrarily chosen default size.
char* buffer;
int32_t result_capacity, reslen;
for (;;) {
if (scratch.allocateInsteadAndReset(scratch_capacity) == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
buffer = sink.GetAppendBuffer(
/*min_capacity=*/scratch_capacity,
/*desired_capacity_hint=*/scratch_capacity,
scratch.getAlias(),
scratch_capacity,
&result_capacity);
reslen = uloc_getKeywordValue(
fullName,
keywordName_nul.data(),
buffer,
result_capacity,
&status);
if (status != U_BUFFER_OVERFLOW_ERROR) {
break;
}
scratch_capacity = reslen;
status = U_ZERO_ERROR;
}
if (U_FAILURE(status)) {
return;
}
sink.Append(buffer, reslen);
if (status == U_STRING_NOT_TERMINATED_WARNING) {
status = U_ZERO_ERROR; // Terminators not used.
}
}
void
Locale::getUnicodeKeywordValue(StringPiece keywordName,
ByteSink& sink,
UErrorCode& status) const {
// TODO: Remove the need for a const char* to a NUL terminated buffer.
const CharString keywordName_nul(keywordName, status);
if (U_FAILURE(status)) {
return;
}
const char* legacy_key = uloc_toLegacyKey(keywordName_nul.data());
if (legacy_key == nullptr) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
CharString legacy_value;
{
CharStringByteSink sink(&legacy_value);
getKeywordValue(legacy_key, sink, status);
}
if (U_FAILURE(status)) {
return;
}
const char* unicode_value = uloc_toUnicodeLocaleType(
keywordName_nul.data(), legacy_value.data());
if (unicode_value == nullptr) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
sink.Append(unicode_value, uprv_strlen(unicode_value));
}
void
Locale::setKeywordValue(const char* keywordName, const char* keywordValue, UErrorCode &status)
{
@ -1212,6 +1502,46 @@ Locale::setKeywordValue(const char* keywordName, const char* keywordValue, UErro
}
}
void
Locale::setKeywordValue(StringPiece keywordName,
StringPiece keywordValue,
UErrorCode& status) {
// TODO: Remove the need for a const char* to a NUL terminated buffer.
const CharString keywordName_nul(keywordName, status);
const CharString keywordValue_nul(keywordValue, status);
setKeywordValue(keywordName_nul.data(), keywordValue_nul.data(), status);
}
void
Locale::setUnicodeKeywordValue(StringPiece keywordName,
StringPiece keywordValue,
UErrorCode& status) {
// TODO: Remove the need for a const char* to a NUL terminated buffer.
const CharString keywordName_nul(keywordName, status);
const CharString keywordValue_nul(keywordValue, status);
if (U_FAILURE(status)) {
return;
}
const char* legacy_key = uloc_toLegacyKey(keywordName_nul.data());
if (legacy_key == nullptr) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
const char* legacy_value =
uloc_toLegacyType(keywordName_nul.data(), keywordValue_nul.data());
if (legacy_value == nullptr) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
setKeywordValue(legacy_key, legacy_value, status);
}
const char *
Locale::getBaseName() const {
return baseName;

View File

@ -1668,7 +1668,8 @@ The leftmost codepage (.xxx) wins.
/* Note that we scan the *uncorrected* ID. */
if ((p = uprv_strrchr(posixID, '@')) != NULL) {
if (correctedPOSIXLocale == NULL) {
correctedPOSIXLocale = static_cast<char *>(uprv_malloc(uprv_strlen(posixID)+1));
/* new locale can be 1 char longer than old one if @ -> __ */
correctedPOSIXLocale = static_cast<char *>(uprv_malloc(uprv_strlen(posixID)+2));
/* Exit on memory allocation error. */
if (correctedPOSIXLocale == NULL) {
return NULL;
@ -1685,7 +1686,7 @@ The leftmost codepage (.xxx) wins.
}
if (uprv_strchr(correctedPOSIXLocale,'_') == NULL) {
uprv_strcat(correctedPOSIXLocale, "__"); /* aa@b -> aa__b */
uprv_strcat(correctedPOSIXLocale, "__"); /* aa@b -> aa__b (note this can make the new locale 1 char longer) */
}
else {
uprv_strcat(correctedPOSIXLocale, "_"); /* aa_CC@b -> aa_CC_b */

View File

@ -94,7 +94,7 @@ typedef size_t uintptr_t;
# define U_NL_LANGINFO_CODESET CODESET
#endif
#ifdef U_TZSET
#if defined(U_TZSET) || defined(U_HAVE_TZSET)
/* Use the predefined value. */
#elif U_PLATFORM_USES_ONLY_WIN32_API
// UWP doesn't support tzset or environment variables for tz
@ -132,7 +132,7 @@ typedef size_t uintptr_t;
# define U_TIMEZONE timezone
#endif
#ifdef U_TZNAME
#if defined(U_TZNAME) || defined(U_HAVE_TZNAME)
/* Use the predefined value. */
#elif U_PLATFORM_USES_ONLY_WIN32_API
/* not usable on all windows platforms */

View File

@ -53,22 +53,6 @@ uprv_isInvariantString(const char *s, int32_t length);
U_INTERNAL UBool U_EXPORT2
uprv_isInvariantUString(const UChar *s, int32_t length);
#ifdef __cplusplus
/**
* Check if a UnicodeString only contains invariant characters.
* See utypes.h for details.
*
* @param s Input string.
* @return TRUE if s contains only invariant characters.
*/
U_INTERNAL inline UBool U_EXPORT2
uprv_isInvariantUnicodeString(const icu::UnicodeString &s) {
return uprv_isInvariantUString(icu::toUCharPtr(s.getBuffer()), s.length());
}
#endif /* __cplusplus */
/**
* \def U_UPPER_ORDINAL
* Get the ordinal number of an uppercase invariant character

View File

@ -1460,9 +1460,9 @@ _appendLDMLExtensionAsKeywords(const char* ldmlext, ExtensionListEntry** appendT
kwd->value = pType;
if (!_addExtensionToList(&kwdFirst, kwd, FALSE)) {
*status = U_ILLEGAL_ARGUMENT_ERROR;
// duplicate keyword is allowed, Only the first
// is honored.
uprv_free(kwd);
goto cleanup;
}
}
@ -2416,6 +2416,23 @@ uloc_forLanguageTag(const char* langtag,
int32_t localeIDCapacity,
int32_t* parsedLength,
UErrorCode* status) {
return ulocimp_forLanguageTag(
langtag,
-1,
localeID,
localeIDCapacity,
parsedLength,
status);
}
U_CAPI int32_t U_EXPORT2
ulocimp_forLanguageTag(const char* langtag,
int32_t tagLen,
char* localeID,
int32_t localeIDCapacity,
int32_t* parsedLength,
UErrorCode* status) {
ULanguageTag *lt;
int32_t reslen = 0;
const char *subtag, *p;
@ -2423,7 +2440,7 @@ uloc_forLanguageTag(const char* langtag,
int32_t i, n;
UBool noRegion = TRUE;
lt = ultag_parse(langtag, -1, parsedLength, status);
lt = ultag_parse(langtag, tagLen, parsedLength, status);
if (U_FAILURE(*status)) {
return 0;
}

View File

@ -61,6 +61,38 @@ ulocimp_getCountry(const char *localeID,
char *country, int32_t countryCapacity,
const char **pEnd);
/**
* Returns a locale ID for the specified BCP47 language tag string.
* If the specified language tag contains any ill-formed subtags,
* the first such subtag and all following subtags are ignored.
* <p>
* This implements the 'Language-Tag' production of BCP47, and so
* supports grandfathered (regular and irregular) as well as private
* use language tags. Private use tags are represented as 'x-whatever',
* and grandfathered tags are converted to their canonical replacements
* where they exist. Note that a few grandfathered tags have no modern
* replacement, these will be converted using the fallback described in
* the first paragraph, so some information might be lost.
* @param langtag the input BCP47 language tag.
* @param tagLen the length of langtag, or -1 to call uprv_strlen().
* @param localeID the output buffer receiving a locale ID for the
* specified BCP47 language tag.
* @param localeIDCapacity the size of the locale ID output buffer.
* @param parsedLength if not NULL, successfully parsed length
* for the input language tag is set.
* @param err error information if receiving the locald ID
* failed.
* @return the length of the locale ID.
* @internal ICU 63
*/
U_STABLE int32_t U_EXPORT2
ulocimp_forLanguageTag(const char* langtag,
int32_t tagLen,
char* localeID,
int32_t localeIDCapacity,
int32_t* parsedLength,
UErrorCode* err);
/**
* Get the region to use for supplemental data lookup. Uses
* (1) any region specified by locale tag "rg"; if none then

View File

@ -32,6 +32,8 @@
#define LOCID_H
#include "unicode/bytestream.h"
#include "unicode/localpointer.h"
#include "unicode/strenum.h"
#include "unicode/stringpiece.h"
#include "unicode/utypes.h"
#include "unicode/uobject.h"
@ -282,6 +284,14 @@ public:
*/
Locale(const Locale& other);
/**
* Move constructor; might leave source in bogus state.
* This locale will have the same contents that the source locale had.
*
* @param other The Locale object being moved in.
* @draft ICU 63
*/
Locale(Locale&& other) U_NOEXCEPT;
/**
* Destructor
@ -298,6 +308,17 @@ public:
*/
Locale& operator=(const Locale& other);
/**
* Move assignment operator; might leave source in bogus state.
* This locale will have the same contents that the source locale had.
* The behavior is undefined if *this and the source are the same object.
*
* @param other The Locale object being moved in.
* @return *this
* @draft ICU 63
*/
Locale& operator=(Locale&& other) U_NOEXCEPT;
/**
* Checks if two locale keys are the same.
*
@ -483,6 +504,69 @@ public:
*/
const char * getBaseName() const;
#ifndef U_HIDE_DRAFT_API
/**
* Add the likely subtags for this Locale, per the algorithm described
* in the following CLDR technical report:
*
* http://www.unicode.org/reports/tr35/#Likely_Subtags
*
* If this Locale is already in the maximal form, or not valid, or there is
* no data available for maximization, the Locale will be unchanged.
*
* For example, "und-Zzzz" cannot be maximized, since there is no
* reasonable maximization.
*
* Examples:
*
* "en" maximizes to "en_Latn_US"
*
* "de" maximizes to "de_Latn_US"
*
* "sr" maximizes to "sr_Cyrl_RS"
*
* "sh" maximizes to "sr_Latn_RS" (Note this will not reverse.)
*
* "zh_Hani" maximizes to "zh_Hans_CN" (Note this will not reverse.)
*
* @param status error information if maximizing this Locale failed.
* If this Locale is not well-formed, the error code is
* U_ILLEGAL_ARGUMENT_ERROR.
* @draft ICU 63
*/
void addLikelySubtags(UErrorCode& status);
/**
* Minimize the subtags for this Locale, per the algorithm described
* in the following CLDR technical report:
*
* http://www.unicode.org/reports/tr35/#Likely_Subtags
*
* If this Locale is already in the minimal form, or not valid, or there is
* no data available for minimization, the Locale will be unchanged.
*
* Since the minimization algorithm relies on proper maximization, see the
* comments for addLikelySubtags for reasons why there might not be any
* data.
*
* Examples:
*
* "en_Latn_US" minimizes to "en"
*
* "de_Latn_US" minimizes to "de"
*
* "sr_Cyrl_RS" minimizes to "sr"
*
* "zh_Hant_TW" minimizes to "zh_TW" (The region is preferred to the
* script, and minimizing to "zh" would imply "zh_Hans_CN".)
*
* @param status error information if maximizing this Locale failed.
* If this Locale is not well-formed, the error code is
* U_ILLEGAL_ARGUMENT_ERROR.
* @draft ICU 63
*/
void minimizeSubtags(UErrorCode& status);
#endif // U_HIDE_DRAFT_API
/**
* Gets the list of keywords for the specified locale.
@ -490,13 +574,64 @@ public:
* @param status the status code
* @return pointer to StringEnumeration class, or NULL if there are no keywords.
* Client must dispose of it by calling delete.
* @see getKeywords
* @stable ICU 2.8
*/
StringEnumeration * createKeywords(UErrorCode &status) const;
#ifndef U_HIDE_DRAFT_API
/**
* Gets the list of Unicode keywords for the specified locale.
*
* @param status the status code
* @return pointer to StringEnumeration class, or NULL if there are no keywords.
* Client must dispose of it by calling delete.
* @see getUnicodeKeywords
* @draft ICU 63
*/
StringEnumeration * createUnicodeKeywords(UErrorCode &status) const;
/**
* Gets the set of keywords for this Locale.
*
* A wrapper to call createKeywords() and write the resulting
* keywords as standard strings (or compatible objects) into any kind of
* container that can be written to by an STL style output iterator.
*
* @param iterator an STL style output iterator to write the keywords to.
* @param status error information if creating set of keywords failed.
* @return a set of strings with all keywords.
* @draft ICU 63
*/
template<typename StringClass, typename OutputIterator>
inline void getKeywords(OutputIterator iterator, UErrorCode& status) const;
/**
* Gets the set of Unicode keywords for this Locale.
*
* A wrapper to call createUnicodeKeywords() and write the resulting
* keywords as standard strings (or compatible objects) into any kind of
* container that can be written to by an STL style output iterator.
*
* @param iterator an STL style output iterator to write the keywords to.
* @param status error information if creating set of keywords failed.
* @return a set of strings with all keywords.
* @draft ICU 63
*/
template<typename StringClass, typename OutputIterator>
inline void getUnicodeKeywords(OutputIterator iterator, UErrorCode& status) const;
#endif // U_HIDE_DRAFT_API
/**
* Gets the value for a keyword.
*
* This uses legacy keyword=value pairs, like "collation=phonebook".
*
* ICU4C doesn't do automatic conversion between legacy and Unicode
* keywords and values in getters and setters (as opposed to ICU4J).
*
* @param keywordName name of the keyword for which we want the value. Case insensitive.
* @param buffer The buffer to receive the keyword value.
* @param bufferCapacity The capacity of receiving buffer
@ -507,12 +642,81 @@ public:
*/
int32_t getKeywordValue(const char* keywordName, char *buffer, int32_t bufferCapacity, UErrorCode &status) const;
#ifndef U_HIDE_DRAFT_API
/**
* Gets the value for a keyword.
*
* This uses legacy keyword=value pairs, like "collation=phonebook".
*
* ICU4C doesn't do automatic conversion between legacy and Unicode
* keywords and values in getters and setters (as opposed to ICU4J).
*
* @param keywordName name of the keyword for which we want the value.
* @param sink the sink to receive the keyword value.
* @param status error information if getting the value failed.
* @draft ICU 63
*/
void getKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const;
/**
* Gets the value for a keyword.
*
* This uses legacy keyword=value pairs, like "collation=phonebook".
*
* ICU4C doesn't do automatic conversion between legacy and Unicode
* keywords and values in getters and setters (as opposed to ICU4J).
*
* @param keywordName name of the keyword for which we want the value.
* @param status error information if getting the value failed.
* @return the keyword value.
* @draft ICU 63
*/
template<typename StringClass>
inline StringClass getKeywordValue(StringPiece keywordName, UErrorCode& status) const;
/**
* Gets the Unicode value for a Unicode keyword.
*
* This uses Unicode key-value pairs, like "co-phonebk".
*
* ICU4C doesn't do automatic conversion between legacy and Unicode
* keywords and values in getters and setters (as opposed to ICU4J).
*
* @param keywordName name of the keyword for which we want the value.
* @param sink the sink to receive the keyword value.
* @param status error information if getting the value failed.
* @draft ICU 63
*/
void getUnicodeKeywordValue(StringPiece keywordName, ByteSink& sink, UErrorCode& status) const;
/**
* Gets the Unicode value for a Unicode keyword.
*
* This uses Unicode key-value pairs, like "co-phonebk".
*
* ICU4C doesn't do automatic conversion between legacy and Unicode
* keywords and values in getters and setters (as opposed to ICU4J).
*
* @param keywordName name of the keyword for which we want the value.
* @param status error information if getting the value failed.
* @return the keyword value.
* @draft ICU 63
*/
template<typename StringClass>
inline StringClass getUnicodeKeywordValue(StringPiece keywordName, UErrorCode& status) const;
#endif // U_HIDE_DRAFT_API
/**
* Sets or removes the value for a keyword.
*
* For removing all keywords, use getBaseName(),
* and construct a new Locale if it differs from getName().
*
* This uses legacy keyword=value pairs, like "collation=phonebook".
*
* ICU4C doesn't do automatic conversion between legacy and Unicode
* keywords and values in getters and setters (as opposed to ICU4J).
*
* @param keywordName name of the keyword to be set. Case insensitive.
* @param keywordValue value of the keyword to be set. If 0-length or
* NULL, will result in the keyword being removed. No error is given if
@ -523,6 +727,48 @@ public:
*/
void setKeywordValue(const char* keywordName, const char* keywordValue, UErrorCode &status);
#ifndef U_HIDE_DRAFT_API
/**
* Sets or removes the value for a keyword.
*
* For removing all keywords, use getBaseName(),
* and construct a new Locale if it differs from getName().
*
* This uses legacy keyword=value pairs, like "collation=phonebook".
*
* ICU4C doesn't do automatic conversion between legacy and Unicode
* keywords and values in getters and setters (as opposed to ICU4J).
*
* @param keywordName name of the keyword to be set.
* @param keywordValue value of the keyword to be set. If 0-length or
* NULL, will result in the keyword being removed. No error is given if
* that keyword does not exist.
* @param status Returns any error information while performing this operation.
* @draft ICU 63
*/
void setKeywordValue(StringPiece keywordName, StringPiece keywordValue, UErrorCode& status);
/**
* Sets or removes the Unicode value for a Unicode keyword.
*
* For removing all keywords, use getBaseName(),
* and construct a new Locale if it differs from getName().
*
* This uses Unicode key-value pairs, like "co-phonebk".
*
* ICU4C doesn't do automatic conversion between legacy and Unicode
* keywords and values in getters and setters (as opposed to ICU4J).
*
* @param keywordName name of the keyword to be set.
* @param keywordValue value of the keyword to be set. If 0-length or
* NULL, will result in the keyword being removed. No error is given if
* that keyword does not exist.
* @param status Returns any error information while performing this operation.
* @draft ICU 63
*/
void setUnicodeKeywordValue(StringPiece keywordName, StringPiece keywordValue, UErrorCode& status);
#endif // U_HIDE_DRAFT_API
/**
* returns the locale's three-letter language code, as specified
* in ISO draft standard ISO-639-2.
@ -867,6 +1113,62 @@ Locale::getName() const
return fullName;
}
#ifndef U_HIDE_DRAFT_API
template<typename StringClass, typename OutputIterator> inline void
Locale::getKeywords(OutputIterator iterator, UErrorCode& status) const
{
LocalPointer<StringEnumeration> keys(createKeywords(status));
if (U_FAILURE(status)) {
return;
}
for (;;) {
int32_t resultLength;
const char* buffer = keys->next(&resultLength, status);
if (U_FAILURE(status) || buffer == nullptr) {
return;
}
*iterator++ = StringClass(buffer, resultLength);
}
}
template<typename StringClass, typename OutputIterator> inline void
Locale::getUnicodeKeywords(OutputIterator iterator, UErrorCode& status) const
{
LocalPointer<StringEnumeration> keys(createUnicodeKeywords(status));
if (U_FAILURE(status)) {
return;
}
for (;;) {
int32_t resultLength;
const char* buffer = keys->next(&resultLength, status);
if (U_FAILURE(status) || buffer == nullptr) {
return;
}
*iterator++ = StringClass(buffer, resultLength);
}
}
template<typename StringClass> inline StringClass
Locale::getKeywordValue(StringPiece keywordName, UErrorCode& status) const
{
StringClass result;
StringByteSink<StringClass> sink(&result);
getKeywordValue(keywordName, sink, status);
return result;
}
template<typename StringClass> inline StringClass
Locale::getUnicodeKeywordValue(StringPiece keywordName, UErrorCode& status) const
{
StringClass result;
StringByteSink<StringClass> sink(&result);
getUnicodeKeywordValue(keywordName, sink, status);
return result;
}
#endif // U_HIDE_DRAFT_API
inline UBool
Locale::isBogus(void) const {
return fIsBogus;

View File

@ -420,6 +420,9 @@
#ifndef __has_cpp_attribute
# define __has_cpp_attribute(x) 0
#endif
#ifndef __has_declspec_attribute
# define __has_declspec_attribute(x) 0
#endif
#ifndef __has_builtin
# define __has_builtin(x) 0
#endif
@ -783,6 +786,8 @@ namespace std {
/* Use the predefined value. */
#elif defined(U_STATIC_IMPLEMENTATION)
# define U_EXPORT
#elif defined(_MSC_VER) || (__has_declspec_attribute(dllexport) && __has_declspec_attribute(dllimport))
# define U_EXPORT __declspec(dllexport)
#elif defined(__GNUC__)
# define U_EXPORT __attribute__((visibility("default")))
#elif (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x550) \
@ -790,8 +795,6 @@ namespace std {
# define U_EXPORT __global
/*#elif defined(__HP_aCC) || defined(__HP_cc)
# define U_EXPORT __declspec(dllexport)*/
#elif defined(_MSC_VER)
# define U_EXPORT __declspec(dllexport)
#else
# define U_EXPORT
#endif
@ -807,7 +810,7 @@ namespace std {
#ifdef U_IMPORT
/* Use the predefined value. */
#elif defined(_MSC_VER)
#elif defined(_MSC_VER) || (__has_declspec_attribute(dllexport) && __has_declspec_attribute(dllimport))
/* Windows needs to export/import data. */
# define U_IMPORT __declspec(dllimport)
#else

View File

@ -59,7 +59,7 @@
</taskdef>
</target>
<!-- target for generating ICU data -->
<target name="all" depends="locales, collation, rbnf, supplementalData, metadata, metaZones, windowsZones, likelySubtags, plurals, numberingSystems, translit, brkitr, keyTypeData, genderList, dayPeriods" />
<target name="all" depends="locales, collation, rbnf, supplementalData, metadata, metaZones, windowsZones, likelySubtags, plurals, pluralRanges, numberingSystems, translit, brkitr, keyTypeData, genderList, dayPeriods" />
<!-- parallel target -->
<target name="pall" depends="init">
<parallel threadsPerProcessor ="1">
@ -258,6 +258,18 @@
</run>
</cldr-build>
</target>
<target name="pluralRanges" depends="init,setup" description="builds pluralRanges.txt from pluralRanges.xml">
<cldr-build toolName="org.unicode.cldr.icu.NewLdml2IcuConverter" destFile="pluralRanges.txt" noArgs="true">
<!-- launch the tool and generate the data after reading the config file -->
<run>
<args>
<arg name="-s" value="${env.CLDR_DIR}/common/supplemental" />
<arg name="-d" value="${env.ICU4C_DIR}/source/data/misc"/>
<arg name="-t" value="pluralRanges"/>
</args>
</run>
</cldr-build>
</target>
<target name="numberingSystems" depends="init,setup" description="builds numberingSystems.txt from numberingSystems.xml">
<cldr-build toolName="org.unicode.cldr.icu.NewLdml2IcuConverter" destFile="numberingSystems.txt" noArgs="true">
<!-- launch the tool and generate the data after reading the config file -->
@ -414,6 +426,9 @@
<fileset id="plurals" dir="${env.ICU4C_DIR}/source/data/misc">
<include name="plurals.txt" />
</fileset>
<fileset id="pluralRanges" dir="${env.ICU4C_DIR}/source/data/misc">
<include name="pluralRanges.txt" />
</fileset>
<fileset id="numberingSystems" dir="${env.ICU4C_DIR}/source/data/misc">
<include name="numberingSystems.txt" />
</fileset>

View File

@ -6,7 +6,7 @@
// Corporation and others. All Rights Reserved.
//---------------------------------------------------------
// Build tool: com.ibm.icu.dev.tool.currency.NumericCodeData
// Build date: 2018-06-06T22:39:58Z
// Build date: 2018-09-17T17:52:05Z
//---------------------------------------------------------
// >> !!! >> THIS IS A MACHINE-GENERATED FILE << !!! <<
// >> !!! >>> DO NOT EDIT <<< !!! <<
@ -270,9 +270,11 @@ currencyNumericCodes:table(nofallback){
UYN:int{858}
UYP:int{858}
UYU:int{858}
UYW:int{927}
UZS:int{860}
VEB:int{862}
VEF:int{937}
VES:int{928}
VNC:int{704}
VND:int{704}
VUV:int{548}

View File

@ -28,4 +28,4 @@ MISC_SOURCE = \
zoneinfo64.txt supplementalData.txt likelySubtags.txt plurals.txt \
numberingSystems.txt icuver.txt icustd.txt metadata.txt metaZones.txt \
windowsZones.txt keyTypeData.txt timezoneTypes.txt currencyNumericCodes.txt \
genderList.txt dayPeriods.txt
genderList.txt dayPeriods.txt pluralRanges.txt

View File

@ -0,0 +1,981 @@
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
pluralRanges:table(nofallback){
locales{
af{"set05"}
ak{"set02"}
am{"set01"}
ar{"set18"}
as{"set01"}
az{"set04"}
be{"set15"}
bg{"set05"}
bn{"set01"}
bs{"set11"}
ca{"set05"}
cs{"set14"}
cy{"set17"}
da{"set06"}
de{"set04"}
el{"set04"}
en{"set05"}
es{"set05"}
et{"set05"}
eu{"set05"}
fa{"set02"}
fi{"set05"}
fil{"set06"}
fr{"set01"}
ga{"set16"}
gl{"set04"}
gsw{"set04"}
gu{"set01"}
he{"set13"}
hi{"set01"}
hr{"set11"}
hu{"set04"}
hy{"set01"}
id{"set00"}
io{"set05"}
is{"set06"}
it{"set04"}
ja{"set00"}
ka{"set03"}
kk{"set04"}
km{"set00"}
kn{"set01"}
ko{"set00"}
ky{"set04"}
lo{"set00"}
lt{"set15"}
lv{"set09"}
mk{"set08"}
ml{"set04"}
mn{"set04"}
mr{"set01"}
ms{"set00"}
my{"set00"}
nb{"set05"}
ne{"set04"}
nl{"set04"}
or{"set02"}
pa{"set06"}
pl{"set14"}
ps{"set01"}
pt{"set01"}
ro{"set10"}
ru{"set15"}
scn{"set04"}
sd{"set02"}
si{"set07"}
sk{"set14"}
sl{"set12"}
sq{"set04"}
sr{"set11"}
sv{"set05"}
sw{"set04"}
ta{"set04"}
te{"set04"}
th{"set00"}
tk{"set04"}
tr{"set04"}
ug{"set04"}
uk{"set15"}
ur{"set05"}
uz{"set04"}
vi{"set00"}
yue{"set00"}
zh{"set00"}
zu{"set01"}
}
rules{
set00{
{
"other",
"other",
"other",
}
}
set01{
{
"one",
"one",
"one",
}
{
"one",
"other",
"other",
}
{
"other",
"other",
"other",
}
}
set02{
{
"one",
"one",
"other",
}
{
"one",
"other",
"other",
}
{
"other",
"other",
"other",
}
}
set03{
{
"one",
"other",
"one",
}
{
"other",
"one",
"other",
}
{
"other",
"other",
"other",
}
}
set04{
{
"one",
"other",
"other",
}
{
"other",
"one",
"one",
}
{
"other",
"other",
"other",
}
}
set05{
{
"one",
"other",
"other",
}
{
"other",
"one",
"other",
}
{
"other",
"other",
"other",
}
}
set06{
{
"one",
"one",
"one",
}
{
"one",
"other",
"other",
}
{
"other",
"one",
"one",
}
{
"other",
"other",
"other",
}
}
set07{
{
"one",
"one",
"one",
}
{
"one",
"other",
"other",
}
{
"other",
"one",
"other",
}
{
"other",
"other",
"other",
}
}
set08{
{
"one",
"one",
"other",
}
{
"one",
"other",
"other",
}
{
"other",
"one",
"other",
}
{
"other",
"other",
"other",
}
}
set09{
{
"zero",
"zero",
"other",
}
{
"zero",
"one",
"one",
}
{
"zero",
"other",
"other",
}
{
"one",
"zero",
"other",
}
{
"one",
"one",
"one",
}
{
"one",
"other",
"other",
}
{
"other",
"zero",
"other",
}
{
"other",
"one",
"one",
}
{
"other",
"other",
"other",
}
}
set10{
{
"one",
"few",
"few",
}
{
"one",
"other",
"other",
}
{
"few",
"one",
"few",
}
{
"few",
"few",
"few",
}
{
"few",
"other",
"other",
}
{
"other",
"few",
"few",
}
{
"other",
"other",
"other",
}
}
set11{
{
"one",
"one",
"one",
}
{
"one",
"few",
"few",
}
{
"one",
"other",
"other",
}
{
"few",
"one",
"one",
}
{
"few",
"few",
"few",
}
{
"few",
"other",
"other",
}
{
"other",
"one",
"one",
}
{
"other",
"few",
"few",
}
{
"other",
"other",
"other",
}
}
set12{
{
"one",
"one",
"few",
}
{
"one",
"two",
"two",
}
{
"one",
"few",
"few",
}
{
"one",
"other",
"other",
}
{
"two",
"one",
"few",
}
{
"two",
"two",
"two",
}
{
"two",
"few",
"few",
}
{
"two",
"other",
"other",
}
{
"few",
"one",
"few",
}
{
"few",
"two",
"two",
}
{
"few",
"few",
"few",
}
{
"few",
"other",
"other",
}
{
"other",
"one",
"few",
}
{
"other",
"two",
"two",
}
{
"other",
"few",
"few",
}
{
"other",
"other",
"other",
}
}
set13{
{
"one",
"two",
"other",
}
{
"one",
"many",
"many",
}
{
"one",
"other",
"other",
}
{
"two",
"many",
"other",
}
{
"two",
"other",
"other",
}
{
"many",
"many",
"many",
}
{
"many",
"other",
"many",
}
{
"other",
"one",
"other",
}
{
"other",
"two",
"other",
}
{
"other",
"many",
"many",
}
{
"other",
"other",
"other",
}
}
set14{
{
"one",
"few",
"few",
}
{
"one",
"many",
"many",
}
{
"one",
"other",
"other",
}
{
"few",
"few",
"few",
}
{
"few",
"many",
"many",
}
{
"few",
"other",
"other",
}
{
"many",
"one",
"one",
}
{
"many",
"few",
"few",
}
{
"many",
"many",
"many",
}
{
"many",
"other",
"other",
}
{
"other",
"one",
"one",
}
{
"other",
"few",
"few",
}
{
"other",
"many",
"many",
}
{
"other",
"other",
"other",
}
}
set15{
{
"one",
"one",
"one",
}
{
"one",
"few",
"few",
}
{
"one",
"many",
"many",
}
{
"one",
"other",
"other",
}
{
"few",
"one",
"one",
}
{
"few",
"few",
"few",
}
{
"few",
"many",
"many",
}
{
"few",
"other",
"other",
}
{
"many",
"one",
"one",
}
{
"many",
"few",
"few",
}
{
"many",
"many",
"many",
}
{
"many",
"other",
"other",
}
{
"other",
"one",
"one",
}
{
"other",
"few",
"few",
}
{
"other",
"many",
"many",
}
{
"other",
"other",
"other",
}
}
set16{
{
"one",
"two",
"two",
}
{
"one",
"few",
"few",
}
{
"one",
"many",
"many",
}
{
"one",
"other",
"other",
}
{
"two",
"few",
"few",
}
{
"two",
"many",
"many",
}
{
"two",
"other",
"other",
}
{
"few",
"few",
"few",
}
{
"few",
"many",
"many",
}
{
"few",
"other",
"other",
}
{
"many",
"many",
"many",
}
{
"many",
"other",
"other",
}
{
"other",
"one",
"one",
}
{
"other",
"two",
"two",
}
{
"other",
"few",
"few",
}
{
"other",
"many",
"many",
}
{
"other",
"other",
"other",
}
}
set17{
{
"zero",
"one",
"one",
}
{
"zero",
"two",
"two",
}
{
"zero",
"few",
"few",
}
{
"zero",
"many",
"many",
}
{
"zero",
"other",
"other",
}
{
"one",
"two",
"two",
}
{
"one",
"few",
"few",
}
{
"one",
"many",
"many",
}
{
"one",
"other",
"other",
}
{
"two",
"few",
"few",
}
{
"two",
"many",
"many",
}
{
"two",
"other",
"other",
}
{
"few",
"many",
"many",
}
{
"few",
"other",
"other",
}
{
"many",
"other",
"other",
}
{
"other",
"one",
"one",
}
{
"other",
"two",
"two",
}
{
"other",
"few",
"few",
}
{
"other",
"many",
"many",
}
{
"other",
"other",
"other",
}
}
set18{
{
"zero",
"one",
"zero",
}
{
"zero",
"two",
"zero",
}
{
"zero",
"few",
"few",
}
{
"zero",
"many",
"many",
}
{
"zero",
"other",
"other",
}
{
"one",
"two",
"other",
}
{
"one",
"few",
"few",
}
{
"one",
"many",
"many",
}
{
"one",
"other",
"other",
}
{
"two",
"few",
"few",
}
{
"two",
"many",
"many",
}
{
"two",
"other",
"other",
}
{
"few",
"few",
"few",
}
{
"few",
"many",
"many",
}
{
"few",
"other",
"other",
}
{
"many",
"few",
"few",
}
{
"many",
"many",
"many",
}
{
"many",
"other",
"other",
}
{
"other",
"one",
"other",
}
{
"other",
"two",
"other",
}
{
"other",
"few",
"few",
}
{
"other",
"many",
"many",
}
{
"other",
"other",
"other",
}
}
}
}

View File

@ -111,9 +111,9 @@ double-conversion-fast-dtoa.o double-conversion-strtod.o \
numparse_stringsegment.o numparse_parsednumber.o numparse_impl.o \
numparse_symbols.o numparse_decimal.o numparse_scientific.o numparse_currency.o \
numparse_affixes.o numparse_compositions.o numparse_validators.o \
numrange_fluent.o numrange_impl.o \
erarules.o
## Header files to install
HEADERS = $(srcdir)/unicode/*.h

View File

@ -3797,7 +3797,7 @@ Calendar::setWeekData(const Locale& desiredLocale, const char *type, UErrorCode&
Locale max = Locale::createFromName(maxLocaleID);
useLocale = Locale(max.getLanguage(),max.getCountry());
} else {
useLocale = Locale(desiredLocale);
useLocale = desiredLocale;
}
/* The code here is somewhat of a hack, since week data and weekend data aren't really tied to

View File

@ -1057,12 +1057,19 @@ UBool DecimalFormat::areSignificantDigitsUsed() const {
void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) {
// These are the default values from the old implementation.
if (useSignificantDigits) {
if (fields->properties->minimumSignificantDigits != -1 ||
fields->properties->maximumSignificantDigits != -1) {
return;
}
} else {
if (fields->properties->minimumSignificantDigits == -1 &&
fields->properties->maximumSignificantDigits == -1) {
return;
}
}
int32_t minSig = useSignificantDigits ? 1 : -1;
int32_t maxSig = useSignificantDigits ? 6 : -1;
if (fields->properties->minimumSignificantDigits == minSig &&
fields->properties->maximumSignificantDigits == maxSig) {
return;
}
fields->properties->minimumSignificantDigits = minSig;
fields->properties->maximumSignificantDigits = maxSig;
touchNoError();

View File

@ -99,13 +99,13 @@ static int32_t compareEncodedDateWithYMD(int encoded, int year, int month, int d
}
}
EraRules::EraRules(int32_t *startDates, int32_t numEras)
: startDates(startDates), numEras(numEras) {
EraRules::EraRules(LocalArray<int32_t>& eraStartDates, int32_t numEras)
: numEras(numEras) {
startDates.moveFrom(eraStartDates);
initCurrentEra();
}
EraRules::~EraRules() {
uprv_free(startDates);
}
EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEra, UErrorCode& status) {
@ -116,7 +116,7 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
ures_getByKey(rb.getAlias(), "calendarData", rb.getAlias(), &status);
ures_getByKey(rb.getAlias(), calType, rb.getAlias(), &status);
ures_getByKey(rb.getAlias(), "eras", rb.getAlias(), &status);
if (U_FAILURE(status)) {
return nullptr;
}
@ -124,33 +124,32 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
int32_t numEras = ures_getSize(rb.getAlias());
int32_t firstTentativeIdx = MAX_INT32;
int32_t *startDates = (int32_t*)uprv_malloc(numEras * sizeof(int32_t));
if (startDates == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
LocalArray<int32_t> startDates(new int32_t[numEras], status);
if (U_FAILURE(status)) {
return nullptr;
}
uprv_memset(startDates, 0, numEras * sizeof(int32_t));
uprv_memset(startDates.getAlias(), 0 , numEras * sizeof(int32_t));
while (ures_hasNext(rb.getAlias())) {
LocalUResourceBundlePointer eraRuleRes(ures_getNextResource(rb.getAlias(), nullptr, &status));
if (U_FAILURE(status)) {
goto error;
return nullptr;
}
const char *eraIdxStr = ures_getKey(eraRuleRes.getAlias());
char *endp;
int32_t eraIdx = (int32_t)strtol(eraIdxStr, &endp, 10);
if ((size_t)(endp - eraIdxStr) != uprv_strlen(eraIdxStr)) {
status = U_INVALID_FORMAT_ERROR;
goto error;
return nullptr;
}
if (eraIdx < 0 || eraIdx >= numEras) {
status = U_INVALID_FORMAT_ERROR;
goto error;
return nullptr;
}
if (isSet(startDates[eraIdx])) {
// start date of the index was already set
status = U_INVALID_FORMAT_ERROR;
goto error;
return nullptr;
}
UBool hasName = TRUE;
@ -159,17 +158,17 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
while (ures_hasNext(eraRuleRes.getAlias())) {
LocalUResourceBundlePointer res(ures_getNextResource(eraRuleRes.getAlias(), nullptr, &status));
if (U_FAILURE(status)) {
goto error;
return nullptr;
}
const char *key = ures_getKey(res.getAlias());
if (uprv_strcmp(key, "start") == 0) {
const int32_t *fields = ures_getIntVector(res.getAlias(), &len, &status);
if (U_FAILURE(status)) {
goto error;
return nullptr;
}
if (len != 3 || !isValidRuleStartDate(fields[0], fields[1], fields[2])) {
status = U_INVALID_FORMAT_ERROR;
goto error;
return nullptr;
}
startDates[eraIdx] = encodeDate(fields[0], fields[1], fields[2]);
} else if (uprv_strcmp(key, "named") == 0) {
@ -193,20 +192,20 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
// This implementation does not support end only rule for eras other than
// the first one.
status = U_INVALID_FORMAT_ERROR;
goto error;
return nullptr;
}
U_ASSERT(eraIdx == 0);
startDates[eraIdx] = MIN_ENCODED_START;
} else {
status = U_INVALID_FORMAT_ERROR;
goto error;
return nullptr;
}
}
if (hasName) {
if (eraIdx >= firstTentativeIdx) {
status = U_INVALID_FORMAT_ERROR;
goto error;
return nullptr;
}
} else {
if (eraIdx < firstTentativeIdx) {
@ -226,10 +225,6 @@ EraRules* EraRules::createInstance(const char *calType, UBool includeTentativeEr
status = U_MEMORY_ALLOCATION_ERROR;
}
return result;
error:
uprv_free(startDates);
return nullptr;
}
void EraRules::getStartDate(int32_t eraIdx, int32_t (&fields)[3], UErrorCode& status) const {

View File

@ -12,6 +12,16 @@
U_NAMESPACE_BEGIN
// Export an explicit template instantiation of LocalArray used as a data member of EraRules.
// When building DLLs for Windows this is required even though no direct access leaks out of the i18n library.
// See digitlst.h, pluralaffix.h, datefmt.h, and others for similar examples.
#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
// Ignore warning 4661 as LocalPointerBase does not use operator== or operator!=
#pragma warning(suppress: 4661)
template class U_I18N_API LocalPointerBase<int32_t>;
template class U_I18N_API LocalArray<int32_t>;
#endif
class U_I18N_API EraRules : public UMemory {
public:
~EraRules();
@ -66,11 +76,11 @@ public:
}
private:
EraRules(int32_t *startDates, int32_t numEra);
EraRules(LocalArray<int32_t>& eraStartDates, int32_t numEra);
void initCurrentEra();
int32_t *startDates;
LocalArray<int32_t> startDates;
int32_t numEras;
int32_t currentEra;
};

View File

@ -289,6 +289,8 @@
<ClCompile Include="numparse_affixes.cpp" />
<ClCompile Include="numparse_compositions.cpp" />
<ClCompile Include="numparse_validators.cpp" />
<ClCompile Include="numrange_fluent.cpp" />
<ClCompile Include="numrange_impl.cpp" />
<ClCompile Include="numfmt.cpp" />
<ClCompile Include="numsys.cpp" />
<ClCompile Include="olsontz.cpp" />
@ -552,6 +554,7 @@
<ClInclude Include="numparse_validators.h" />
<ClInclude Include="numparse_types.h" />
<ClInclude Include="numparse_utils.h" />
<ClInclude Include="numrange_impl.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="i18n.rc" />

View File

@ -624,6 +624,12 @@
<ClCompile Include="numparse_validators.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numrange_fluent.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numrange_impl.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="dayperiodrules.cpp">
<Filter>formatting</Filter>
</ClCompile>
@ -914,6 +920,9 @@
<ClInclude Include="numparse_utils.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="numrange_impl.h">
<Filter>formatting</Filter>
</ClInclude>
<ClInclude Include="olsontz.h">
<Filter>formatting</Filter>
</ClInclude>

View File

@ -396,6 +396,8 @@
<ClCompile Include="numparse_affixes.cpp" />
<ClCompile Include="numparse_compositions.cpp" />
<ClCompile Include="numparse_validators.cpp" />
<ClCompile Include="numrange_fluent.cpp" />
<ClCompile Include="numrange_impl.cpp" />
<ClCompile Include="numfmt.cpp" />
<ClCompile Include="numsys.cpp" />
<ClCompile Include="olsontz.cpp" />
@ -657,6 +659,7 @@
<ClInclude Include="numparse_validators.h" />
<ClInclude Include="numparse_types.h" />
<ClInclude Include="numparse_utils.h" />
<ClInclude Include="numrange_impl.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="i18n.rc" />

View File

@ -347,12 +347,15 @@ IndianCalendar::inDaylightTime(UErrorCode& status) const
return (UBool)(U_SUCCESS(status) ? (internalGet(UCAL_DST_OFFSET) != 0) : FALSE);
}
// default century
const UDate IndianCalendar::fgSystemDefaultCentury = DBL_MIN;
const int32_t IndianCalendar::fgSystemDefaultCenturyYear = -1;
UDate IndianCalendar::fgSystemDefaultCenturyStart = DBL_MIN;
int32_t IndianCalendar::fgSystemDefaultCenturyStartYear = -1;
/**
* The system maintains a static default century start date and Year. They are
* initialized the first time they are used. Once the system default century date
* and year are set, they do not change.
*/
static UDate gSystemDefaultCenturyStart = DBL_MIN;
static int32_t gSystemDefaultCenturyStartYear = -1;
static icu::UInitOnce gSystemDefaultCenturyInit = U_INITONCE_INITIALIZER;
UBool IndianCalendar::haveDefaultCentury() const
@ -360,88 +363,46 @@ UBool IndianCalendar::haveDefaultCentury() const
return TRUE;
}
UDate IndianCalendar::defaultCenturyStart() const
{
return internalGetDefaultCenturyStart();
}
int32_t IndianCalendar::defaultCenturyStartYear() const
{
return internalGetDefaultCenturyStartYear();
}
UDate
IndianCalendar::internalGetDefaultCenturyStart() const
{
// lazy-evaluate systemDefaultCenturyStart
UBool needsUpdate;
{
Mutex m;
needsUpdate = (fgSystemDefaultCenturyStart == fgSystemDefaultCentury);
}
if (needsUpdate) {
initializeSystemDefaultCentury();
}
// use defaultCenturyStart unless it's the flag value;
// then use systemDefaultCenturyStart
return fgSystemDefaultCenturyStart;
}
int32_t
IndianCalendar::internalGetDefaultCenturyStartYear() const
{
// lazy-evaluate systemDefaultCenturyStartYear
UBool needsUpdate;
{
Mutex m;
needsUpdate = (fgSystemDefaultCenturyStart == fgSystemDefaultCentury);
}
if (needsUpdate) {
initializeSystemDefaultCentury();
}
// use defaultCenturyStart unless it's the flag value;
// then use systemDefaultCenturyStartYear
return fgSystemDefaultCenturyStartYear;
}
void
IndianCalendar::initializeSystemDefaultCentury()
static void U_CALLCONV
initializeSystemDefaultCentury()
{
// initialize systemDefaultCentury and systemDefaultCenturyYear based
// on the current time. They'll be set to 80 years before
// the current time.
// No point in locking as it should be idempotent.
if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) {
UErrorCode status = U_ZERO_ERROR;
UErrorCode status = U_ZERO_ERROR;
IndianCalendar calendar(Locale("@calendar=Indian"),status);
if (U_SUCCESS(status)) {
calendar.setTime(Calendar::getNow(), status);
calendar.add(UCAL_YEAR, -80, status);
IndianCalendar calendar ( Locale ( "@calendar=Indian" ), status);
if ( U_SUCCESS ( status ) ) {
calendar.setTime ( Calendar::getNow(), status );
calendar.add ( UCAL_YEAR, -80, status );
UDate newStart = calendar.getTime(status);
int32_t newYear = calendar.get(UCAL_YEAR, status);
UDate newStart = calendar.getTime ( status );
int32_t newYear = calendar.get ( UCAL_YEAR, status );
{
Mutex m;
fgSystemDefaultCenturyStart = newStart;
fgSystemDefaultCenturyStartYear = newYear;
}
}
// We have no recourse upon failure unless we want to propagate the failure
// out.
gSystemDefaultCenturyStart = newStart;
gSystemDefaultCenturyStartYear = newYear;
}
// We have no recourse upon failure.
}
UDate
IndianCalendar::defaultCenturyStart() const
{
// lazy-evaluate systemDefaultCenturyStart
umtx_initOnce(gSystemDefaultCenturyInit, &initializeSystemDefaultCentury);
return gSystemDefaultCenturyStart;
}
int32_t
IndianCalendar::defaultCenturyStartYear() const
{
// lazy-evaluate systemDefaultCenturyStartYear
umtx_initOnce(gSystemDefaultCenturyInit, &initializeSystemDefaultCentury);
return gSystemDefaultCenturyStartYear;
}
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(IndianCalendar)
U_NAMESPACE_END

View File

@ -68,7 +68,7 @@ U_NAMESPACE_BEGIN
*/
class IndianCalendar : public Calendar {
class U_I18N_API IndianCalendar : public Calendar {
public:
/**
* Useful constants for IndianCalendar.
@ -274,10 +274,10 @@ public:
* @return The class ID for all objects of this class.
* @internal
*/
U_I18N_API static UClassID U_EXPORT2 getStaticClassID(void);
static UClassID U_EXPORT2 getStaticClassID(void);
/**
* return the calendar type, "buddhist".
* return the calendar type, "indian".
*
* @return calendar type
* @internal
@ -320,49 +320,6 @@ protected:
* @internal
*/
virtual int32_t defaultCenturyStartYear() const;
private: // default century stuff.
/**
* The system maintains a static default century start date. This is initialized
* the first time it is used. Before then, it is set to SYSTEM_DEFAULT_CENTURY to
* indicate an uninitialized state. Once the system default century date and year
* are set, they do not change.
*/
static UDate fgSystemDefaultCenturyStart;
/**
* See documentation for systemDefaultCenturyStart.
*/
static int32_t fgSystemDefaultCenturyStartYear;
/**
* Default value that indicates the defaultCenturyStartYear is unitialized
*/
static const int32_t fgSystemDefaultCenturyYear;
/**
* start of default century, as a date
*/
static const UDate fgSystemDefaultCentury;
/**
* Returns the beginning date of the 100-year window that dates
* with 2-digit years are considered to fall within.
*/
UDate internalGetDefaultCenturyStart(void) const;
/**
* Returns the first year of the 100-year window that dates with
* 2-digit years are considered to fall within.
*/
int32_t internalGetDefaultCenturyStartYear(void) const;
/**
* Initializes the 100-year window that dates with 2-digit years
* are considered to fall within so that its start date is 80 years
* before the current time.
*/
static void initializeSystemDefaultCentury(void);
};
U_NAMESPACE_END

View File

@ -59,11 +59,19 @@ static void U_CALLCONV initializeEras(UErrorCode &status) {
// By default, such tentative era is disabled.
// 1. Environment variable ICU_ENABLE_TENTATIVE_ERA=true or false
// 2. Windows registry (TBD)
UBool includeTentativeEra = FALSE;
#if U_PLATFORM_HAS_WINUWP_API == 0
#if U_PLATFORM_HAS_WINUWP_API == 1
// UWP doesn't allow access to getenv(), but we can call GetEnvironmentVariableW to do the same thing.
UChar varName[26] = {};
u_charsToUChars(TENTATIVE_ERA_VAR_NAME, varName, static_cast<int32_t>(uprv_strlen(TENTATIVE_ERA_VAR_NAME)));
WCHAR varValue[5] = {};
DWORD ret = GetEnvironmentVariableW(reinterpret_cast<WCHAR*>(varName), varValue, UPRV_LENGTHOF(varValue));
if ((ret == 4) && (_wcsicmp(varValue, L"true") == 0)) {
includeTentativeEra = TRUE;
}
#else
char *envVarVal = getenv(TENTATIVE_ERA_VAR_NAME);
if (envVarVal != NULL && uprv_stricmp(envVarVal, "true") == 0) {
includeTentativeEra = TRUE;

View File

@ -288,8 +288,7 @@ ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& e
}
ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *style, UErrorCode& errorCode) {
Locale tempLocale = locale;
const ListFormatInternal* listFormatInternal = getListFormatInternal(tempLocale, style, errorCode);
const ListFormatInternal* listFormatInternal = getListFormatInternal(locale, style, errorCode);
if (U_FAILURE(errorCode)) {
return nullptr;
}

View File

@ -273,13 +273,13 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr
if (U_FAILURE(status)) { return; }
// Treat zero as if it had magnitude 0
int magnitude;
int32_t magnitude;
if (quantity.isZero()) {
magnitude = 0;
micros.rounder.apply(quantity, status);
} else {
// TODO: Revisit chooseMultiplierAndApply
int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data, status);
int32_t multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data, status);
magnitude = quantity.isZero() ? 0 : quantity.getMagnitude();
magnitude -= multiplier;
}

View File

@ -1154,8 +1154,31 @@ const char16_t* DecimalQuantity::checkHealth() const {
}
bool DecimalQuantity::operator==(const DecimalQuantity& other) const {
// FIXME: Make a faster implementation.
return toString() == other.toString();
bool basicEquals =
scale == other.scale
&& precision == other.precision
&& flags == other.flags
&& lOptPos == other.lOptPos
&& lReqPos == other.lReqPos
&& rReqPos == other.rReqPos
&& rOptPos == other.rOptPos
&& isApproximate == other.isApproximate;
if (!basicEquals) {
return false;
}
if (precision == 0) {
return true;
} else if (isApproximate) {
return origDouble == other.origDouble && origDelta == other.origDelta;
} else {
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
if (getDigit(m) != other.getDigit(m)) {
return false;
}
}
return true;
}
}
UnicodeString DecimalQuantity::toString() const {

View File

@ -363,6 +363,7 @@ UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(const NFS<UNF>& other)
// No additional fields to assign
}
// Make default copy constructor call the NumberFormatterSettings copy constructor.
UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(UNF&& src) U_NOEXCEPT
: UNF(static_cast<NFS<UNF>&&>(src)) {}
@ -383,6 +384,7 @@ UnlocalizedNumberFormatter& UnlocalizedNumberFormatter::operator=(UNF&& src) U_N
return *this;
}
// Make default copy constructor call the NumberFormatterSettings copy constructor.
LocalizedNumberFormatter::LocalizedNumberFormatter(const LNF& other)
: LNF(static_cast<const NFS<LNF>&>(other)) {}
@ -405,7 +407,8 @@ LocalizedNumberFormatter::LocalizedNumberFormatter(NFS<LNF>&& src) U_NOEXCEPT
LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(const LNF& other) {
NFS<LNF>::operator=(static_cast<const NFS<LNF>&>(other));
// No additional fields to assign (let call count and compiled formatter reset to defaults)
// Reset to default values.
clear();
return *this;
}
@ -417,20 +420,26 @@ LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(LNF&& src) U_NOEXC
// Formatter is compiled
lnfMoveHelper(static_cast<LNF&&>(src));
} else {
// Reset to default values.
auto* callCount = reinterpret_cast<u_atomic_int32_t*>(fUnsafeCallCount);
umtx_storeRelease(*callCount, 0);
fCompiled = nullptr;
clear();
}
return *this;
}
void LocalizedNumberFormatter::clear() {
// Reset to default values.
auto* callCount = reinterpret_cast<u_atomic_int32_t*>(fUnsafeCallCount);
umtx_storeRelease(*callCount, 0);
delete fCompiled;
fCompiled = nullptr;
}
void LocalizedNumberFormatter::lnfMoveHelper(LNF&& src) {
// Copy over the compiled formatter and set call count to INT32_MIN as in computeCompiled().
// Don't copy the call count directly because doing so requires a loadAcquire/storeRelease.
// The bits themselves appear to be platform-dependent, so copying them might not be safe.
auto* callCount = reinterpret_cast<u_atomic_int32_t*>(fUnsafeCallCount);
umtx_storeRelease(*callCount, INT32_MIN);
delete fCompiled;
fCompiled = src.fCompiled;
// Reset the source object to leave it in a safe state.
auto* srcCallCount = reinterpret_cast<u_atomic_int32_t*>(src.fUnsafeCallCount);
@ -657,9 +666,9 @@ LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErro
void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const {
if (computeCompiled(status)) {
fCompiled->apply(results->quantity, results->string, status);
fCompiled->format(results->quantity, results->string, status);
} else {
NumberFormatterImpl::applyStatic(fMacros, results->quantity, results->string, status);
NumberFormatterImpl::formatStatic(fMacros, results->quantity, results->string, status);
}
}
@ -706,7 +715,11 @@ bool LocalizedNumberFormatter::computeCompiled(UErrorCode& status) const {
if (currentCount == fMacros.threshold && fMacros.threshold > 0) {
// Build the data structure and then use it (slow to fast path).
const NumberFormatterImpl* compiled = NumberFormatterImpl::fromMacros(fMacros, status);
const NumberFormatterImpl* compiled = new NumberFormatterImpl(fMacros, status);
if (compiled == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return false;
}
U_ASSERT(fCompiled == nullptr);
const_cast<LocalizedNumberFormatter*>(this)->fCompiled = compiled;
umtx_storeRelease(*callCount, INT32_MIN);

View File

@ -67,14 +67,18 @@ getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& sta
MicroPropsGenerator::~MicroPropsGenerator() = default;
NumberFormatterImpl* NumberFormatterImpl::fromMacros(const MacroProps& macros, UErrorCode& status) {
return new NumberFormatterImpl(macros, true, status);
NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status)
: NumberFormatterImpl(macros, true, status) {
}
void NumberFormatterImpl::applyStatic(const MacroProps& macros, DecimalQuantity& inValue,
NumberStringBuilder& outString, UErrorCode& status) {
int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue,
NumberStringBuilder& outString, UErrorCode& status) {
NumberFormatterImpl impl(macros, false, status);
impl.applyUnsafe(inValue, outString, status);
MicroProps& micros = impl.preProcessUnsafe(inValue, status);
if (U_FAILURE(status)) { return 0; }
int32_t length = writeNumber(micros, inValue, outString, 0, status);
length += writeAffixes(micros, outString, 0, length, status);
return length;
}
int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int8_t signum,
@ -89,22 +93,40 @@ int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int
// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
// See MicroProps::processQuantity() for details.
void NumberFormatterImpl::apply(DecimalQuantity& inValue, NumberStringBuilder& outString,
int32_t NumberFormatterImpl::format(DecimalQuantity& inValue, NumberStringBuilder& outString,
UErrorCode& status) const {
if (U_FAILURE(status)) { return; }
MicroProps micros;
if (!fMicroPropsGenerator) { return; }
fMicroPropsGenerator->processQuantity(inValue, micros, status);
if (U_FAILURE(status)) { return; }
microsToString(micros, inValue, outString, status);
preProcess(inValue, micros, status);
if (U_FAILURE(status)) { return 0; }
int32_t length = writeNumber(micros, inValue, outString, 0, status);
length += writeAffixes(micros, outString, 0, length, status);
return length;
}
void NumberFormatterImpl::applyUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString,
UErrorCode& status) {
void NumberFormatterImpl::preProcess(DecimalQuantity& inValue, MicroProps& microsOut,
UErrorCode& status) const {
if (U_FAILURE(status)) { return; }
if (fMicroPropsGenerator == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR;
return;
}
fMicroPropsGenerator->processQuantity(inValue, microsOut, status);
microsOut.rounder.apply(inValue, status);
microsOut.integerWidth.apply(inValue, status);
}
MicroProps& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity& inValue, UErrorCode& status) {
if (U_FAILURE(status)) {
return fMicros; // must always return a value
}
if (fMicroPropsGenerator == nullptr) {
status = U_INTERNAL_PROGRAM_ERROR;
return fMicros; // must always return a value
}
fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
if (U_FAILURE(status)) { return; }
microsToString(fMicros, inValue, outString, status);
fMicros.rounder.apply(inValue, status);
fMicros.integerWidth.apply(inValue, status);
return fMicros;
}
int32_t NumberFormatterImpl::getPrefixSuffix(int8_t signum, StandardPlural::Form plural,
@ -115,7 +137,7 @@ int32_t NumberFormatterImpl::getPrefixSuffix(int8_t signum, StandardPlural::Form
const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural);
modifier->apply(outString, 0, 0, status);
if (U_FAILURE(status)) { return 0; }
return modifier->getPrefixLength(status);
return modifier->getPrefixLength();
}
int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural,
@ -126,7 +148,7 @@ int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(int8_t signum, StandardPlural
fPatternModifier->setNumberProperties(signum, plural);
fPatternModifier->apply(outString, 0, 0, status);
if (U_FAILURE(status)) { return 0; }
return fPatternModifier->getPrefixLength(status);
return fPatternModifier->getPrefixLength();
}
NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
@ -344,25 +366,23 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
// Outer modifier (CLDR units and currency long names)
if (isCldrUnit) {
fLongNameHandler.adoptInstead(
new LongNameHandler(
LongNameHandler::forMeasureUnit(
macros.locale,
macros.unit,
macros.perUnit,
unitWidth,
resolvePluralRules(macros.rules, macros.locale, status),
chain,
status)));
LongNameHandler::forMeasureUnit(
macros.locale,
macros.unit,
macros.perUnit,
unitWidth,
resolvePluralRules(macros.rules, macros.locale, status),
chain,
status));
chain = fLongNameHandler.getAlias();
} else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
fLongNameHandler.adoptInstead(
new LongNameHandler(
LongNameHandler::forCurrencyLongNames(
macros.locale,
currency,
resolvePluralRules(macros.rules, macros.locale, status),
chain,
status)));
LongNameHandler::forCurrencyLongNames(
macros.locale,
currency,
resolvePluralRules(macros.rules, macros.locale, status),
chain,
status));
chain = fLongNameHandler.getAlias();
} else {
// No outer modifier required
@ -404,50 +424,46 @@ NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Local
return fRules.getAlias();
}
int32_t NumberFormatterImpl::microsToString(const MicroProps& micros, DecimalQuantity& quantity,
NumberStringBuilder& string, UErrorCode& status) {
micros.rounder.apply(quantity, status);
micros.integerWidth.apply(quantity, status);
int32_t length = writeNumber(micros, quantity, string, status);
// NOTE: When range formatting is added, these modifiers can bubble up.
// For now, apply them all here at once.
int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, NumberStringBuilder& string,
int32_t start, int32_t end, UErrorCode& status) {
// Always apply the inner modifier (which is "strong").
length += micros.modInner->apply(string, 0, length, status);
int32_t length = micros.modInner->apply(string, start, end, status);
if (micros.padding.isValid()) {
length += micros.padding
.padAndApply(*micros.modMiddle, *micros.modOuter, string, 0, length, status);
.padAndApply(*micros.modMiddle, *micros.modOuter, string, start, length + end, status);
} else {
length += micros.modMiddle->apply(string, 0, length, status);
length += micros.modOuter->apply(string, 0, length, status);
length += micros.modMiddle->apply(string, start, length + end, status);
length += micros.modOuter->apply(string, start, length + end, status);
}
return length;
}
int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
NumberStringBuilder& string, UErrorCode& status) {
NumberStringBuilder& string, int32_t index,
UErrorCode& status) {
int32_t length = 0;
if (quantity.isInfinite()) {
length += string.insert(
length,
length + index,
micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol),
UNUM_INTEGER_FIELD,
status);
} else if (quantity.isNaN()) {
length += string.insert(
length,
length + index,
micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol),
UNUM_INTEGER_FIELD,
status);
} else {
// Add the integer digits
length += writeIntegerDigits(micros, quantity, string, status);
length += writeIntegerDigits(micros, quantity, string, length + index, status);
// Add the decimal point
if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
length += string.insert(
length,
length + index,
micros.useCurrency ? micros.symbols->getSymbol(
DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
.symbols
@ -458,21 +474,22 @@ int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuanti
}
// Add the fraction digits
length += writeFractionDigits(micros, quantity, string, status);
length += writeFractionDigits(micros, quantity, string, length + index, status);
}
return length;
}
int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity,
NumberStringBuilder& string, UErrorCode& status) {
NumberStringBuilder& string, int32_t index,
UErrorCode& status) {
int length = 0;
int integerCount = quantity.getUpperDisplayMagnitude() + 1;
for (int i = 0; i < integerCount; i++) {
// Add grouping separator
if (micros.grouping.groupAtPosition(i, quantity)) {
length += string.insert(
0,
index,
micros.useCurrency ? micros.symbols->getSymbol(
DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol)
: micros.symbols->getSymbol(
@ -484,20 +501,21 @@ int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, Decima
// Get and append the next digit value
int8_t nextDigit = quantity.getDigit(i);
length += utils::insertDigitFromSymbols(
string, 0, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status);
string, index, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status);
}
return length;
}
int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity,
NumberStringBuilder& string, UErrorCode& status) {
NumberStringBuilder& string, int32_t index,
UErrorCode& status) {
int length = 0;
int fractionCount = -quantity.getLowerDisplayMagnitude();
for (int i = 0; i < fractionCount; i++) {
// Get and append the next digit value
int8_t nextDigit = quantity.getDigit(-i - 1);
length += utils::insertDigitFromSymbols(
string, string.length(), nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status);
string, length + index, nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status);
}
return length;
}

View File

@ -29,14 +29,14 @@ class NumberFormatterImpl : public UMemory {
* Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly.
* The caller owns the returned NumberFormatterImpl.
*/
static NumberFormatterImpl *fromMacros(const MacroProps &macros, UErrorCode &status);
NumberFormatterImpl(const MacroProps &macros, UErrorCode &status);
/**
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
*/
static void
applyStatic(const MacroProps &macros, DecimalQuantity &inValue, NumberStringBuilder &outString,
UErrorCode &status);
static int32_t
formatStatic(const MacroProps &macros, DecimalQuantity &inValue, NumberStringBuilder &outString,
UErrorCode &status);
/**
* Prints only the prefix and suffix; used for DecimalFormat getters.
@ -51,7 +51,12 @@ class NumberFormatterImpl : public UMemory {
/**
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
*/
void apply(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const;
int32_t format(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const;
/**
* Like format(), but saves the result into an output MicroProps without additional processing.
*/
void preProcess(DecimalQuantity& inValue, MicroProps& microsOut, UErrorCode& status) const;
/**
* Like getPrefixSuffixStatic() but uses the safe compiled object.
@ -59,6 +64,19 @@ class NumberFormatterImpl : public UMemory {
int32_t getPrefixSuffix(int8_t signum, StandardPlural::Form plural, NumberStringBuilder& outString,
UErrorCode& status) const;
/**
* Synthesizes the output string from a MicroProps and DecimalQuantity.
* This method formats only the main number, not affixes.
*/
static int32_t writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
NumberStringBuilder& string, int32_t index, UErrorCode& status);
/**
* Adds the affixes. Intended to be called immediately after formatNumber.
*/
static int32_t writeAffixes(const MicroProps& micros, NumberStringBuilder& string, int32_t start,
int32_t end, UErrorCode& status);
private:
// Head of the MicroPropsGenerator linked list:
const MicroPropsGenerator *fMicroPropsGenerator = nullptr;
@ -85,7 +103,7 @@ class NumberFormatterImpl : public UMemory {
NumberFormatterImpl(const MacroProps &macros, bool safe, UErrorCode &status);
void applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status);
MicroProps& preProcessUnsafe(DecimalQuantity &inValue, UErrorCode &status);
int32_t getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural,
NumberStringBuilder& outString, UErrorCode& status);
@ -113,31 +131,13 @@ class NumberFormatterImpl : public UMemory {
const MicroPropsGenerator *
macrosToMicroGenerator(const MacroProps &macros, bool safe, UErrorCode &status);
/**
* Synthesizes the output string from a MicroProps and DecimalQuantity.
*
* @param micros
* The MicroProps after the quantity has been consumed. Will not be mutated.
* @param quantity
* The DecimalQuantity to be rendered. May be mutated.
* @param string
* The output string. Will be mutated.
*/
static int32_t
microsToString(const MicroProps &micros, DecimalQuantity &quantity, NumberStringBuilder &string,
UErrorCode &status);
static int32_t
writeNumber(const MicroProps &micros, DecimalQuantity &quantity, NumberStringBuilder &string,
UErrorCode &status);
static int32_t
writeIntegerDigits(const MicroProps &micros, DecimalQuantity &quantity, NumberStringBuilder &string,
UErrorCode &status);
int32_t index, UErrorCode &status);
static int32_t
writeFractionDigits(const MicroProps &micros, DecimalQuantity &quantity, NumberStringBuilder &string,
UErrorCode &status);
int32_t index, UErrorCode &status);
};
} // namespace impl

View File

@ -39,7 +39,7 @@ static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) {
static UnicodeString getWithPlural(
const UnicodeString* strings,
int32_t plural,
StandardPlural::Form plural,
UErrorCode& status) {
UnicodeString result = strings[plural];
if (result.isBogus()) {
@ -156,7 +156,7 @@ UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &wid
} // namespace
LongNameHandler
LongNameHandler*
LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status) {
@ -173,20 +173,28 @@ LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, c
}
}
LongNameHandler result(rules, parent);
auto* result = new LongNameHandler(rules, parent);
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
UnicodeString simpleFormats[ARRAY_LENGTH];
getMeasureData(loc, unit, width, simpleFormats, status);
if (U_FAILURE(status)) { return result; }
// TODO: What field to use for units?
simpleFormatsToModifiers(simpleFormats, UNUM_FIELD_COUNT, result.fModifiers, status);
result->simpleFormatsToModifiers(simpleFormats, UNUM_FIELD_COUNT, status);
return result;
}
LongNameHandler
LongNameHandler*
LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status) {
LongNameHandler result(rules, parent);
auto* result = new LongNameHandler(rules, parent);
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
UnicodeString primaryData[ARRAY_LENGTH];
getMeasureData(loc, unit, width, primaryData, status);
if (U_FAILURE(status)) { return result; }
@ -213,46 +221,52 @@ LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, con
if (U_FAILURE(status)) { return result; }
}
// TODO: What field to use for units?
multiSimpleFormatsToModifiers(primaryData, perUnitFormat, UNUM_FIELD_COUNT, result.fModifiers, status);
result->multiSimpleFormatsToModifiers(primaryData, perUnitFormat, UNUM_FIELD_COUNT, status);
return result;
}
LongNameHandler LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit &currency,
LongNameHandler* LongNameHandler::forCurrencyLongNames(const Locale &loc, const CurrencyUnit &currency,
const PluralRules *rules,
const MicroPropsGenerator *parent,
UErrorCode &status) {
LongNameHandler result(rules, parent);
auto* result = new LongNameHandler(rules, parent);
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
UnicodeString simpleFormats[ARRAY_LENGTH];
getCurrencyLongNameData(loc, currency, simpleFormats, status);
if (U_FAILURE(status)) { return result; }
simpleFormatsToModifiers(simpleFormats, UNUM_CURRENCY_FIELD, result.fModifiers, status);
if (U_FAILURE(status)) { return nullptr; }
result->simpleFormatsToModifiers(simpleFormats, UNUM_CURRENCY_FIELD, status);
return result;
}
void LongNameHandler::simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
SimpleModifier *output, UErrorCode &status) {
UErrorCode &status) {
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
UnicodeString simpleFormat = getWithPlural(simpleFormats, i, status);
StandardPlural::Form plural = static_cast<StandardPlural::Form>(i);
UnicodeString simpleFormat = getWithPlural(simpleFormats, plural, status);
if (U_FAILURE(status)) { return; }
SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status);
if (U_FAILURE(status)) { return; }
output[i] = SimpleModifier(compiledFormatter, field, false);
fModifiers[i] = SimpleModifier(compiledFormatter, field, false, {this, 0, plural});
}
}
void LongNameHandler::multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
Field field, SimpleModifier *output, UErrorCode &status) {
Field field, UErrorCode &status) {
SimpleFormatter trailCompiled(trailFormat, 1, 1, status);
if (U_FAILURE(status)) { return; }
for (int32_t i = 0; i < StandardPlural::Form::COUNT; i++) {
UnicodeString leadFormat = getWithPlural(leadFormats, i, status);
StandardPlural::Form plural = static_cast<StandardPlural::Form>(i);
UnicodeString leadFormat = getWithPlural(leadFormats, plural, status);
if (U_FAILURE(status)) { return; }
UnicodeString compoundFormat;
trailCompiled.format(leadFormat, compoundFormat, status);
if (U_FAILURE(status)) { return; }
SimpleFormatter compoundCompiled(compoundFormat, 0, 1, status);
if (U_FAILURE(status)) { return; }
output[i] = SimpleModifier(compoundCompiled, field, false);
fModifiers[i] = SimpleModifier(compoundCompiled, field, false, {this, 0, plural});
}
}
@ -265,4 +279,8 @@ void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &mic
micros.modOuter = &fModifiers[utils::getStandardPlural(rules, copy)];
}
const Modifier* LongNameHandler::getModifier(int8_t /*signum*/, StandardPlural::Form plural) const {
return &fModifiers[plural];
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -14,13 +14,13 @@
U_NAMESPACE_BEGIN namespace number {
namespace impl {
class LongNameHandler : public MicroPropsGenerator, public UMemory {
class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public UMemory {
public:
static LongNameHandler
static LongNameHandler*
forCurrencyLongNames(const Locale &loc, const CurrencyUnit &currency, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status);
static LongNameHandler
static LongNameHandler*
forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status);
@ -28,6 +28,8 @@ class LongNameHandler : public MicroPropsGenerator, public UMemory {
void
processQuantity(DecimalQuantity &quantity, MicroProps &micros, UErrorCode &status) const U_OVERRIDE;
const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const U_OVERRIDE;
private:
SimpleModifier fModifiers[StandardPlural::Form::COUNT];
const PluralRules *rules;
@ -36,15 +38,14 @@ class LongNameHandler : public MicroPropsGenerator, public UMemory {
LongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent)
: rules(rules), parent(parent) {}
static LongNameHandler
static LongNameHandler*
forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, UErrorCode &status);
static void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field,
SimpleModifier *output, UErrorCode &status);
static void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
Field field, SimpleModifier *output, UErrorCode &status);
void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field, UErrorCode &status);
void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat,
Field field, UErrorCode &status);
};
} // namespace impl

View File

@ -225,8 +225,8 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
// TODO: Overriding here is a bit of a hack. Should this logic go earlier?
if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) {
// For the purposes of rounding, get the original min/max int/frac, since the local
// variables
// have been manipulated for display purposes.
// variables have been manipulated for display purposes.
int maxInt_ = properties.maximumIntegerDigits;
int minInt_ = properties.minimumIntegerDigits;
int minFrac_ = properties.minimumFractionDigits;
int maxFrac_ = properties.maximumFractionDigits;
@ -237,9 +237,15 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
// Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
macros.precision = Precision::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode);
} else {
// All other scientific patterns, which mean round to minInt+maxFrac
macros.precision = Precision::constructSignificant(
minInt_ + minFrac_, minInt_ + maxFrac_).withMode(roundingMode);
int maxSig_ = minInt_ + maxFrac_;
// Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
if (maxInt_ > minInt_ && minInt_ > 1) {
minInt_ = 1;
}
int minSig_ = minInt_ + minFrac_;
// To avoid regression, maxSig is not reset when minInt_ set to 1.
// TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
macros.precision = Precision::constructSignificant(minSig_, maxSig_).withMode(roundingMode);
}
}
}

View File

@ -53,6 +53,21 @@ void U_CALLCONV initDefaultCurrencySpacing(UErrorCode &status) {
Modifier::~Modifier() = default;
Modifier::Parameters::Parameters()
: obj(nullptr) {}
Modifier::Parameters::Parameters(
const ModifierStore* _obj, int8_t _signum, StandardPlural::Form _plural)
: obj(_obj), signum(_signum), plural(_plural) {}
ModifierStore::~ModifierStore() = default;
AdoptingModifierStore::~AdoptingModifierStore() {
for (const Modifier *mod : mods) {
delete mod;
}
}
int32_t ConstantAffixModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
UErrorCode &status) const {
@ -62,13 +77,11 @@ int32_t ConstantAffixModifier::apply(NumberStringBuilder &output, int leftIndex,
return length;
}
int32_t ConstantAffixModifier::getPrefixLength(UErrorCode &status) const {
(void)status;
int32_t ConstantAffixModifier::getPrefixLength() const {
return fPrefix.length();
}
int32_t ConstantAffixModifier::getCodePointCount(UErrorCode &status) const {
(void)status;
int32_t ConstantAffixModifier::getCodePointCount() const {
return fPrefix.countChar32() + fSuffix.countChar32();
}
@ -76,8 +89,38 @@ bool ConstantAffixModifier::isStrong() const {
return fStrong;
}
bool ConstantAffixModifier::containsField(UNumberFormatFields field) const {
(void)field;
// This method is not currently used.
U_ASSERT(false);
return false;
}
void ConstantAffixModifier::getParameters(Parameters& output) const {
(void)output;
// This method is not currently used.
U_ASSERT(false);
}
bool ConstantAffixModifier::semanticallyEquivalent(const Modifier& other) const {
auto* _other = dynamic_cast<const ConstantAffixModifier*>(&other);
if (_other == nullptr) {
return false;
}
return fPrefix == _other->fPrefix
&& fSuffix == _other->fSuffix
&& fField == _other->fField
&& fStrong == _other->fStrong;
}
SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong)
: fCompiledPattern(simpleFormatter.compiledPattern), fField(field), fStrong(strong) {
: SimpleModifier(simpleFormatter, field, strong, {}) {}
SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong,
const Modifier::Parameters parameters)
: fCompiledPattern(simpleFormatter.compiledPattern), fField(field), fStrong(strong),
fParameters(parameters) {
int32_t argLimit = SimpleFormatter::getArgumentLimit(
fCompiledPattern.getBuffer(), fCompiledPattern.length());
if (argLimit == 0) {
@ -90,15 +133,19 @@ SimpleModifier::SimpleModifier(const SimpleFormatter &simpleFormatter, Field fie
} else {
U_ASSERT(argLimit == 1);
if (fCompiledPattern.charAt(1) != 0) {
// Found prefix
fPrefixLength = fCompiledPattern.charAt(1) - ARG_NUM_LIMIT;
fSuffixOffset = 3 + fPrefixLength;
} else {
// No prefix
fPrefixLength = 0;
fSuffixOffset = 2;
}
if (3 + fPrefixLength < fCompiledPattern.length()) {
// Found suffix
fSuffixLength = fCompiledPattern.charAt(fSuffixOffset) - ARG_NUM_LIMIT;
} else {
// No suffix
fSuffixLength = 0;
}
}
@ -113,13 +160,11 @@ int32_t SimpleModifier::apply(NumberStringBuilder &output, int leftIndex, int ri
return formatAsPrefixSuffix(output, leftIndex, rightIndex, fField, status);
}
int32_t SimpleModifier::getPrefixLength(UErrorCode &status) const {
(void)status;
int32_t SimpleModifier::getPrefixLength() const {
return fPrefixLength;
}
int32_t SimpleModifier::getCodePointCount(UErrorCode &status) const {
(void)status;
int32_t SimpleModifier::getCodePointCount() const {
int32_t count = 0;
if (fPrefixLength > 0) {
count += fCompiledPattern.countChar32(2, fPrefixLength);
@ -134,10 +179,35 @@ bool SimpleModifier::isStrong() const {
return fStrong;
}
bool SimpleModifier::containsField(UNumberFormatFields field) const {
(void)field;
// This method is not currently used.
U_ASSERT(false);
return false;
}
void SimpleModifier::getParameters(Parameters& output) const {
output = fParameters;
}
bool SimpleModifier::semanticallyEquivalent(const Modifier& other) const {
auto* _other = dynamic_cast<const SimpleModifier*>(&other);
if (_other == nullptr) {
return false;
}
if (fParameters.obj != nullptr) {
return fParameters.obj == _other->fParameters.obj;
}
return fCompiledPattern == _other->fCompiledPattern
&& fField == _other->fField
&& fStrong == _other->fStrong;
}
int32_t
SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex,
Field field, UErrorCode &status) const {
if (fSuffixOffset == -1) {
if (fSuffixOffset == -1 && fPrefixLength + fSuffixLength > 0) {
// There is no argument for the inner number; overwrite the entire segment with our string.
return result.splice(startIndex, endIndex, fCompiledPattern, 2, 2 + fPrefixLength, field, status);
} else {
@ -157,6 +227,65 @@ SimpleModifier::formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startI
}
}
int32_t
SimpleModifier::formatTwoArgPattern(const SimpleFormatter& compiled, NumberStringBuilder& result,
int32_t index, int32_t* outPrefixLength, int32_t* outSuffixLength,
Field field, UErrorCode& status) {
const UnicodeString& compiledPattern = compiled.compiledPattern;
int32_t argLimit = SimpleFormatter::getArgumentLimit(
compiledPattern.getBuffer(), compiledPattern.length());
if (argLimit != 2) {
status = U_INTERNAL_PROGRAM_ERROR;
return 0;
}
int32_t offset = 1; // offset into compiledPattern
int32_t length = 0; // chars added to result
int32_t prefixLength = compiledPattern.charAt(offset);
offset++;
if (prefixLength < ARG_NUM_LIMIT) {
// No prefix
prefixLength = 0;
} else {
prefixLength -= ARG_NUM_LIMIT;
result.insert(index + length, compiledPattern, offset, offset + prefixLength, field, status);
offset += prefixLength;
length += prefixLength;
offset++;
}
int32_t infixLength = compiledPattern.charAt(offset);
offset++;
if (infixLength < ARG_NUM_LIMIT) {
// No infix
infixLength = 0;
} else {
infixLength -= ARG_NUM_LIMIT;
result.insert(index + length, compiledPattern, offset, offset + infixLength, field, status);
offset += infixLength;
length += infixLength;
offset++;
}
int32_t suffixLength;
if (offset == compiledPattern.length()) {
// No suffix
suffixLength = 0;
} else {
suffixLength = compiledPattern.charAt(offset) - ARG_NUM_LIMIT;
offset++;
result.insert(index + length, compiledPattern, offset, offset + suffixLength, field, status);
length += suffixLength;
}
*outPrefixLength = prefixLength;
*outSuffixLength = suffixLength;
return length;
}
int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex,
UErrorCode &status) const {
int32_t length = output.insert(leftIndex, fPrefix, status);
@ -171,13 +300,11 @@ int32_t ConstantMultiFieldModifier::apply(NumberStringBuilder &output, int leftI
return length;
}
int32_t ConstantMultiFieldModifier::getPrefixLength(UErrorCode &status) const {
(void)status;
int32_t ConstantMultiFieldModifier::getPrefixLength() const {
return fPrefix.length();
}
int32_t ConstantMultiFieldModifier::getCodePointCount(UErrorCode &status) const {
(void)status;
int32_t ConstantMultiFieldModifier::getCodePointCount() const {
return fPrefix.codePointCount() + fSuffix.codePointCount();
}
@ -185,6 +312,29 @@ bool ConstantMultiFieldModifier::isStrong() const {
return fStrong;
}
bool ConstantMultiFieldModifier::containsField(UNumberFormatFields field) const {
return fPrefix.containsField(field) || fSuffix.containsField(field);
}
void ConstantMultiFieldModifier::getParameters(Parameters& output) const {
output = fParameters;
}
bool ConstantMultiFieldModifier::semanticallyEquivalent(const Modifier& other) const {
auto* _other = dynamic_cast<const ConstantMultiFieldModifier*>(&other);
if (_other == nullptr) {
return false;
}
if (fParameters.obj != nullptr) {
return fParameters.obj == _other->fParameters.obj;
}
return fPrefix.contentEquals(_other->fPrefix)
&& fSuffix.contentEquals(_other->fSuffix)
&& fOverwrite == _other->fOverwrite
&& fStrong == _other->fStrong;
}
CurrencySpacingEnabledModifier::CurrencySpacingEnabledModifier(const NumberStringBuilder &prefix,
const NumberStringBuilder &suffix,
bool overwrite,

View File

@ -31,12 +31,18 @@ class U_I18N_API ConstantAffixModifier : public Modifier, public UObject {
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength() const U_OVERRIDE;
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
int32_t getCodePointCount() const U_OVERRIDE;
bool isStrong() const U_OVERRIDE;
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
void getParameters(Parameters& output) const U_OVERRIDE;
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
private:
UnicodeString fPrefix;
UnicodeString fSuffix;
@ -52,21 +58,30 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
public:
SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong);
SimpleModifier(const SimpleFormatter &simpleFormatter, Field field, bool strong,
const Modifier::Parameters parameters);
// Default constructor for LongNameHandler.h
SimpleModifier();
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength() const U_OVERRIDE;
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
int32_t getCodePointCount() const U_OVERRIDE;
bool isStrong() const U_OVERRIDE;
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
void getParameters(Parameters& output) const U_OVERRIDE;
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
/**
* TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not depend on it.
* NumberStringBuilder is an internal class and SimpleFormatterImpl feels like it should not depend on it.
*
* <p>
* Formats a value that is already stored inside the StringBuilder <code>result</code> between the indices
@ -85,16 +100,33 @@ class U_I18N_API SimpleModifier : public Modifier, public UMemory {
* @return The number of characters (UTF-16 code points) that were added to the StringBuilder.
*/
int32_t
formatAsPrefixSuffix(NumberStringBuilder &result, int32_t startIndex, int32_t endIndex, Field field,
UErrorCode &status) const;
formatAsPrefixSuffix(NumberStringBuilder& result, int32_t startIndex, int32_t endIndex, Field field,
UErrorCode& status) const;
/**
* TODO: Like above, this belongs with the rest of the SimpleFormatterImpl code.
* I put it here so that the SimpleFormatter uses in NumberStringBuilder are near each other.
*
* <p>
* Applies the compiled two-argument pattern to the NumberStringBuilder.
*
* <p>
* This method is optimized for the case where the prefix and suffix are often empty, such as
* in the range pattern like "{0}-{1}".
*/
static int32_t
formatTwoArgPattern(const SimpleFormatter& compiled, NumberStringBuilder& result,
int32_t index, int32_t* outPrefixLength, int32_t* outSuffixLength,
Field field, UErrorCode& status);
private:
UnicodeString fCompiledPattern;
Field fField;
bool fStrong;
int32_t fPrefixLength;
int32_t fSuffixOffset;
int32_t fSuffixLength;
bool fStrong = false;
int32_t fPrefixLength = 0;
int32_t fSuffixOffset = -1;
int32_t fSuffixLength = 0;
Modifier::Parameters fParameters;
};
/**
@ -103,6 +135,18 @@ 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 overwrite,
bool strong,
const Modifier::Parameters parameters)
: fPrefix(prefix),
fSuffix(suffix),
fOverwrite(overwrite),
fStrong(strong),
fParameters(parameters) {}
ConstantMultiFieldModifier(
const NumberStringBuilder &prefix,
const NumberStringBuilder &suffix,
@ -116,12 +160,18 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength() const U_OVERRIDE;
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
int32_t getCodePointCount() const U_OVERRIDE;
bool isStrong() const U_OVERRIDE;
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
void getParameters(Parameters& output) const U_OVERRIDE;
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
protected:
// NOTE: In Java, these are stored as array pointers. In C++, the NumberStringBuilder is stored by
// value and is treated internally as immutable.
@ -129,6 +179,7 @@ class U_I18N_API ConstantMultiFieldModifier : public Modifier, public UMemory {
NumberStringBuilder fSuffix;
bool fOverwrite;
bool fStrong;
Modifier::Parameters fParameters;
};
/** Identical to {@link ConstantMultiFieldModifier}, but supports currency spacing. */
@ -192,13 +243,11 @@ class U_I18N_API EmptyModifier : public Modifier, public UMemory {
return 0;
}
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE {
(void)status;
int32_t getPrefixLength() const U_OVERRIDE {
return 0;
}
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE {
(void)status;
int32_t getCodePointCount() const U_OVERRIDE {
return 0;
}
@ -206,55 +255,75 @@ class U_I18N_API EmptyModifier : public Modifier, public UMemory {
return fStrong;
}
bool containsField(UNumberFormatFields field) const U_OVERRIDE {
(void)field;
return false;
}
void getParameters(Parameters& output) const U_OVERRIDE {
output.obj = nullptr;
}
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE {
return other.getCodePointCount() == 0;
}
private:
bool fStrong;
};
/**
* A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two or more
* Modifiers and returns the modifier appropriate for the current situation.
* This implementation of ModifierStore adopts Modifer pointers.
*/
class U_I18N_API ParameterizedModifier : public UMemory {
class U_I18N_API AdoptingModifierStore : public ModifierStore, public UMemory {
public:
// NOTE: mods is zero-initialized (to nullptr)
ParameterizedModifier() : mods() {
}
virtual ~AdoptingModifierStore();
static constexpr StandardPlural::Form DEFAULT_STANDARD_PLURAL = StandardPlural::OTHER;
AdoptingModifierStore() = default;
// No copying!
ParameterizedModifier(const ParameterizedModifier &other) = delete;
AdoptingModifierStore(const AdoptingModifierStore &other) = delete;
~ParameterizedModifier() {
for (const Modifier *mod : mods) {
delete mod;
}
}
void adoptPositiveNegativeModifiers(
const Modifier *positive, const Modifier *zero, const Modifier *negative) {
mods[2] = positive;
mods[1] = zero;
mods[0] = negative;
}
/** The modifier is ADOPTED. */
void adoptSignPluralModifier(int8_t signum, StandardPlural::Form plural, const Modifier *mod) {
/**
* Sets the Modifier with the specified signum and plural form.
*/
void adoptModifier(int8_t signum, StandardPlural::Form plural, const Modifier *mod) {
U_ASSERT(mods[getModIndex(signum, plural)] == nullptr);
mods[getModIndex(signum, plural)] = mod;
}
/** Returns a reference to the modifier; no ownership change. */
const Modifier *getModifier(int8_t signum) const {
return mods[signum + 1];
/**
* Sets the Modifier with the specified signum.
* The modifier will apply to all plural forms.
*/
void adoptModifierWithoutPlural(int8_t signum, const Modifier *mod) {
U_ASSERT(mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)] == nullptr);
mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)] = mod;
}
/** Returns a reference to the modifier; no ownership change. */
const Modifier *getModifier(int8_t signum, StandardPlural::Form plural) const {
return mods[getModIndex(signum, plural)];
const Modifier *getModifier(int8_t signum, StandardPlural::Form plural) const U_OVERRIDE {
const Modifier* modifier = mods[getModIndex(signum, plural)];
if (modifier == nullptr && plural != DEFAULT_STANDARD_PLURAL) {
modifier = mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)];
}
return modifier;
}
/** Returns a reference to the modifier; no ownership change. */
const Modifier *getModifierWithoutPlural(int8_t signum) const {
return mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)];
}
private:
const Modifier *mods[3 * StandardPlural::COUNT];
// NOTE: mods is zero-initialized (to nullptr)
const Modifier *mods[3 * StandardPlural::COUNT] = {};
inline static int32_t getModIndex(int8_t signum, StandardPlural::Form plural) {
U_ASSERT(signum >= -1 && signum <= 1);
U_ASSERT(plural >= 0 && plural < StandardPlural::COUNT);
return static_cast<int32_t>(plural) * 3 + (signum + 1);
}
};

View File

@ -62,7 +62,7 @@ Padder Padder::forProperties(const DecimalFormatProperties& properties) {
int32_t Padder::padAndApply(const Modifier &mod1, const Modifier &mod2,
NumberStringBuilder &string, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const {
int32_t modLength = mod1.getCodePointCount(status) + mod2.getCodePointCount(status);
int32_t modLength = mod1.getCodePointCount() + mod2.getCodePointCount();
int32_t requiredPadding = fWidth - modLength - string.codePointCount();
U_ASSERT(leftIndex == 0 &&
rightIndex == string.length()); // fix the previous line to remove this assertion

View File

@ -69,7 +69,7 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* paren
StandardPlural::Form::MANY,
StandardPlural::Form::OTHER};
auto pm = new ParameterizedModifier();
auto pm = new AdoptingModifierStore();
if (pm == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
@ -79,11 +79,11 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* paren
// Slower path when we require the plural keyword.
for (StandardPlural::Form plural : STANDARD_PLURAL_VALUES) {
setNumberProperties(1, plural);
pm->adoptSignPluralModifier(1, plural, createConstantModifier(status));
pm->adoptModifier(1, plural, createConstantModifier(status));
setNumberProperties(0, plural);
pm->adoptSignPluralModifier(0, plural, createConstantModifier(status));
pm->adoptModifier(0, plural, createConstantModifier(status));
setNumberProperties(-1, plural);
pm->adoptSignPluralModifier(-1, plural, createConstantModifier(status));
pm->adoptModifier(-1, plural, createConstantModifier(status));
}
if (U_FAILURE(status)) {
delete pm;
@ -93,12 +93,11 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* paren
} else {
// Faster path when plural keyword is not needed.
setNumberProperties(1, StandardPlural::Form::COUNT);
Modifier* positive = createConstantModifier(status);
pm->adoptModifierWithoutPlural(1, createConstantModifier(status));
setNumberProperties(0, StandardPlural::Form::COUNT);
Modifier* zero = createConstantModifier(status);
pm->adoptModifierWithoutPlural(0, createConstantModifier(status));
setNumberProperties(-1, StandardPlural::Form::COUNT);
Modifier* negative = createConstantModifier(status);
pm->adoptPositiveNegativeModifiers(positive, zero, negative);
pm->adoptModifierWithoutPlural(-1, createConstantModifier(status));
if (U_FAILURE(status)) {
delete pm;
return nullptr;
@ -120,7 +119,7 @@ ConstantMultiFieldModifier* MutablePatternModifier::createConstantModifier(UErro
}
}
ImmutablePatternModifier::ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules,
ImmutablePatternModifier::ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules,
const MicroPropsGenerator* parent)
: pm(pm), rules(rules), parent(parent) {}
@ -132,7 +131,7 @@ void ImmutablePatternModifier::processQuantity(DecimalQuantity& quantity, MicroP
void ImmutablePatternModifier::applyToMicros(MicroProps& micros, DecimalQuantity& quantity) const {
if (rules == nullptr) {
micros.modMiddle = pm->getModifier(quantity.signum());
micros.modMiddle = pm->getModifierWithoutPlural(quantity.signum());
} else {
// TODO: Fix this. Avoid the copy.
DecimalQuantity copy(quantity);
@ -144,7 +143,7 @@ void ImmutablePatternModifier::applyToMicros(MicroProps& micros, DecimalQuantity
const Modifier* ImmutablePatternModifier::getModifier(int8_t signum, StandardPlural::Form plural) const {
if (rules == nullptr) {
return pm->getModifier(signum);
return pm->getModifierWithoutPlural(signum);
} else {
return pm->getModifier(signum, plural);
}
@ -204,23 +203,25 @@ int32_t MutablePatternModifier::apply(NumberStringBuilder& output, int32_t leftI
return prefixLen + overwriteLen + suffixLen;
}
int32_t MutablePatternModifier::getPrefixLength(UErrorCode& status) const {
int32_t MutablePatternModifier::getPrefixLength() const {
// The unsafe code path performs self-mutation, so we need a const_cast.
// This method needs to be const because it overrides a const method in the parent class.
auto nonConstThis = const_cast<MutablePatternModifier*>(this);
// Enter and exit CharSequence Mode to get the length.
UErrorCode status = U_ZERO_ERROR; // status fails only with an iilegal argument exception
nonConstThis->prepareAffix(true);
int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status); // prefix length
return result;
}
int32_t MutablePatternModifier::getCodePointCount(UErrorCode& status) const {
int32_t MutablePatternModifier::getCodePointCount() const {
// The unsafe code path performs self-mutation, so we need a const_cast.
// This method needs to be const because it overrides a const method in the parent class.
auto nonConstThis = const_cast<MutablePatternModifier*>(this);
// Render the affixes to get the length
UErrorCode status = U_ZERO_ERROR; // status fails only with an iilegal argument exception
nonConstThis->prepareAffix(true);
int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status); // prefix length
nonConstThis->prepareAffix(false);
@ -232,6 +233,26 @@ bool MutablePatternModifier::isStrong() const {
return fStrong;
}
bool MutablePatternModifier::containsField(UNumberFormatFields field) const {
(void)field;
// This method is not currently used.
U_ASSERT(false);
return false;
}
void MutablePatternModifier::getParameters(Parameters& output) const {
(void)output;
// This method is not currently used.
U_ASSERT(false);
}
bool MutablePatternModifier::semanticallyEquivalent(const Modifier& other) const {
(void)other;
// This method is not currently used.
U_ASSERT(false);
return false;
}
int32_t MutablePatternModifier::insertPrefix(NumberStringBuilder& sb, int position, UErrorCode& status) {
prepareAffix(true);
int length = AffixUtils::unescape(currentAffix, sb, position, *this, status);

View File

@ -18,13 +18,13 @@
U_NAMESPACE_BEGIN
// Export an explicit template instantiation of the LocalPointer that is used as a
// data member of ParameterizedModifier.
// data member of AdoptingModifierStore.
// (When building DLLs for Windows this is required.)
#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN
// Ignore warning 4661 as LocalPointerBase does not use operator== or operator!=
#pragma warning(suppress: 4661)
template class U_I18N_API LocalPointerBase<number::impl::ParameterizedModifier>;
template class U_I18N_API LocalPointer<number::impl::ParameterizedModifier>;
template class U_I18N_API LocalPointerBase<number::impl::AdoptingModifierStore>;
template class U_I18N_API LocalPointer<number::impl::AdoptingModifierStore>;
#endif
namespace number {
@ -45,10 +45,10 @@ class U_I18N_API ImmutablePatternModifier : public MicroPropsGenerator, public U
const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const;
private:
ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules,
ImmutablePatternModifier(AdoptingModifierStore* pm, const PluralRules* rules,
const MicroPropsGenerator* parent);
const LocalPointer<ParameterizedModifier> pm;
const LocalPointer<AdoptingModifierStore> pm;
const PluralRules* rules;
const MicroPropsGenerator* parent;
@ -178,12 +178,18 @@ class U_I18N_API MutablePatternModifier
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength() const U_OVERRIDE;
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
int32_t getCodePointCount() const U_OVERRIDE;
bool isStrong() const U_OVERRIDE;
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
void getParameters(Parameters& output) const U_OVERRIDE;
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
/**
* Returns the string that substitutes a given symbol type in a pattern.
*/

View File

@ -76,17 +76,16 @@ int32_t ScientificModifier::apply(NumberStringBuilder &output, int32_t /*leftInd
return i - rightIndex;
}
int32_t ScientificModifier::getPrefixLength(UErrorCode &status) const {
(void)status;
int32_t ScientificModifier::getPrefixLength() const {
// TODO: Localized exponent separator location.
return 0;
}
int32_t ScientificModifier::getCodePointCount(UErrorCode &status) const {
(void)status;
// This method is not used for strong modifiers.
U_ASSERT(false);
return 0;
int32_t ScientificModifier::getCodePointCount() const {
// NOTE: This method is only called one place, NumberRangeFormatterImpl.
// The call site only cares about != 0 and != 1.
// Return a very large value so that if this method is used elsewhere, we should notice.
return 999;
}
bool ScientificModifier::isStrong() const {
@ -94,6 +93,27 @@ bool ScientificModifier::isStrong() const {
return true;
}
bool ScientificModifier::containsField(UNumberFormatFields field) const {
(void)field;
// This method is not used for inner modifiers.
U_ASSERT(false);
return false;
}
void ScientificModifier::getParameters(Parameters& output) const {
// Not part of any plural sets
output.obj = nullptr;
}
bool ScientificModifier::semanticallyEquivalent(const Modifier& other) const {
auto* _other = dynamic_cast<const ScientificModifier*>(&other);
if (_other == nullptr) {
return false;
}
// TODO: Check for locale symbols and settings as well? Could be less efficient.
return fExponent == _other->fExponent;
}
// Note: Visual Studio does not compile this function without full name space. Why?
icu::number::impl::ScientificHandler::ScientificHandler(const Notation *notation, const DecimalFormatSymbols *symbols,
const MicroPropsGenerator *parent) :

View File

@ -24,12 +24,18 @@ class U_I18N_API ScientificModifier : public UMemory, public Modifier {
int32_t apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex,
UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength(UErrorCode &status) const U_OVERRIDE;
int32_t getPrefixLength() const U_OVERRIDE;
int32_t getCodePointCount(UErrorCode &status) const U_OVERRIDE;
int32_t getCodePointCount() const U_OVERRIDE;
bool isStrong() const U_OVERRIDE;
bool containsField(UNumberFormatFields field) const U_OVERRIDE;
void getParameters(Parameters& output) const U_OVERRIDE;
bool semanticallyEquivalent(const Modifier& other) const U_OVERRIDE;
private:
int32_t fExponent;
const ScientificHandler *fHandler;

View File

@ -241,6 +241,9 @@ NumberStringBuilder::insert(int32_t index, const NumberStringBuilder &other, UEr
}
int32_t NumberStringBuilder::prepareForInsert(int32_t index, int32_t count, UErrorCode &status) {
U_ASSERT(index >= 0);
U_ASSERT(index <= fLength);
U_ASSERT(count >= 0);
if (index == 0 && fZero - count >= 0) {
// Append to start
fZero -= count;
@ -485,4 +488,13 @@ void NumberStringBuilder::getAllFieldPositions(FieldPositionIteratorHandler& fpi
}
}
bool NumberStringBuilder::containsField(Field field) const {
for (int32_t i = 0; i < fLength; i++) {
if (field == fieldAt(i)) {
return true;
}
}
return false;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -106,6 +106,8 @@ class U_I18N_API NumberStringBuilder : public UMemory {
void getAllFieldPositions(FieldPositionIteratorHandler& fpih, UErrorCode& status) const;
bool containsField(Field field) const;
private:
bool fUsingHeap = false;
ValueOrHeapArray<char16_t> fChars;

View File

@ -16,6 +16,7 @@
#include "uassert.h"
#include "unicode/platform.h"
#include "unicode/uniset.h"
#include "standardplural.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
@ -45,6 +46,7 @@ class Modifier;
class MutablePatternModifier;
class DecimalQuantity;
class NumberStringBuilder;
class ModifierStore;
struct MicroProps;
@ -127,12 +129,13 @@ class U_I18N_API AffixPatternProvider {
virtual bool hasBody() const = 0;
};
/**
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied to the string
* builder. A Modifier usually contains a prefix and a suffix that are applied, but it could contain something else,
* like a {@link com.ibm.icu.text.SimpleFormatter} pattern.
*
* A Modifier is usually immutable, except in cases such as {@link MurkyModifier}, which are mutable for performance
* A Modifier is usually immutable, except in cases such as {@link MutablePatternModifier}, which are mutable for performance
* reasons.
*
* Exported as U_I18N_API because it is a base class for other exported types
@ -162,12 +165,12 @@ class U_I18N_API Modifier {
*
* @return The number of characters (UTF-16 code units) in the prefix.
*/
virtual int32_t getPrefixLength(UErrorCode& status) const = 0;
virtual int32_t getPrefixLength() const = 0;
/**
* Returns the number of code points in the modifier, prefix plus suffix.
*/
virtual int32_t getCodePointCount(UErrorCode& status) const = 0;
virtual int32_t getCodePointCount() const = 0;
/**
* Whether this modifier is strong. If a modifier is strong, it should always be applied immediately and not allowed
@ -177,8 +180,57 @@ class U_I18N_API Modifier {
* @return Whether the modifier is strong.
*/
virtual bool isStrong() const = 0;
/**
* Whether the modifier contains at least one occurrence of the given field.
*/
virtual bool containsField(UNumberFormatFields field) const = 0;
/**
* A fill-in for getParameters(). obj will always be set; if non-null, the other
* two fields are also safe to read.
*/
struct Parameters {
const ModifierStore* obj = nullptr;
int8_t signum;
StandardPlural::Form plural;
Parameters();
Parameters(const ModifierStore* _obj, int8_t _signum, StandardPlural::Form _plural);
};
/**
* Gets a set of "parameters" for this Modifier.
*
* TODO: Make this return a `const Parameters*` more like Java?
*/
virtual void getParameters(Parameters& output) const = 0;
/**
* Returns whether this Modifier is *semantically equivalent* to the other Modifier;
* in many cases, this is the same as equal, but parameters should be ignored.
*/
virtual bool semanticallyEquivalent(const Modifier& other) const = 0;
};
/**
* This is *not* a modifier; rather, it is an object that can return modifiers
* based on given parameters.
*
* Exported as U_I18N_API because it is a base class for other exported types.
*/
class U_I18N_API ModifierStore {
public:
virtual ~ModifierStore();
/**
* Returns a Modifier with the given parameters (best-effort).
*/
virtual const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const = 0;
};
/**
* This interface is used when all number formatting settings, including the locale, are known, except for the quantity
* itself. The {@link #processQuantity} method performs the final step in the number processing pipeline: it uses the

View File

@ -56,6 +56,11 @@ bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErr
return false;
}
// Only accept one exponent per string.
if (0 != (result.flags & FLAG_HAS_EXPONENT)) {
return false;
}
// First match the scientific separator, and then match another number after it.
// NOTE: This is guarded by the smoke test; no need to check fExponentSeparatorString length again.
int overlap1 = segment.getCommonPrefixLength(fExponentSeparatorString);

View File

@ -0,0 +1,438 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
// Allow implicit conversion from char16_t* to UnicodeString for this file:
// Helpful in toString methods and elsewhere.
#define UNISTR_FROM_STRING_EXPLICIT
#include "numrange_impl.h"
#include "util.h"
#include "number_utypes.h"
using namespace icu;
using namespace icu::number;
using namespace icu::number::impl;
// This function needs to be declared in this namespace so it can be friended.
// NOTE: In Java, this logic is handled in the resolve() function.
void icu::number::impl::touchRangeLocales(RangeMacroProps& macros) {
macros.formatter1.fMacros.locale = macros.locale;
macros.formatter2.fMacros.locale = macros.locale;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(const UnlocalizedNumberFormatter& formatter) const& {
Derived copy(*this);
copy.fMacros.formatter1 = formatter;
copy.fMacros.singleFormatter = true;
touchRangeLocales(copy.fMacros);
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(const UnlocalizedNumberFormatter& formatter) && {
Derived move(std::move(*this));
move.fMacros.formatter1 = formatter;
move.fMacros.singleFormatter = true;
touchRangeLocales(move.fMacros);
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(UnlocalizedNumberFormatter&& formatter) const& {
Derived copy(*this);
copy.fMacros.formatter1 = std::move(formatter);
copy.fMacros.singleFormatter = true;
touchRangeLocales(copy.fMacros);
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterBoth(UnlocalizedNumberFormatter&& formatter) && {
Derived move(std::move(*this));
move.fMacros.formatter1 = std::move(formatter);
move.fMacros.singleFormatter = true;
touchRangeLocales(move.fMacros);
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(const UnlocalizedNumberFormatter& formatter) const& {
Derived copy(*this);
copy.fMacros.formatter1 = formatter;
copy.fMacros.singleFormatter = false;
touchRangeLocales(copy.fMacros);
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(const UnlocalizedNumberFormatter& formatter) && {
Derived move(std::move(*this));
move.fMacros.formatter1 = formatter;
move.fMacros.singleFormatter = false;
touchRangeLocales(move.fMacros);
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(UnlocalizedNumberFormatter&& formatter) const& {
Derived copy(*this);
copy.fMacros.formatter1 = std::move(formatter);
copy.fMacros.singleFormatter = false;
touchRangeLocales(copy.fMacros);
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterFirst(UnlocalizedNumberFormatter&& formatter) && {
Derived move(std::move(*this));
move.fMacros.formatter1 = std::move(formatter);
move.fMacros.singleFormatter = false;
touchRangeLocales(move.fMacros);
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(const UnlocalizedNumberFormatter& formatter) const& {
Derived copy(*this);
copy.fMacros.formatter2 = formatter;
copy.fMacros.singleFormatter = false;
touchRangeLocales(copy.fMacros);
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(const UnlocalizedNumberFormatter& formatter) && {
Derived move(std::move(*this));
move.fMacros.formatter2 = formatter;
move.fMacros.singleFormatter = false;
touchRangeLocales(move.fMacros);
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(UnlocalizedNumberFormatter&& formatter) const& {
Derived copy(*this);
copy.fMacros.formatter2 = std::move(formatter);
copy.fMacros.singleFormatter = false;
touchRangeLocales(copy.fMacros);
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::numberFormatterSecond(UnlocalizedNumberFormatter&& formatter) && {
Derived move(std::move(*this));
move.fMacros.formatter2 = std::move(formatter);
move.fMacros.singleFormatter = false;
touchRangeLocales(move.fMacros);
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::collapse(UNumberRangeCollapse collapse) const& {
Derived copy(*this);
copy.fMacros.collapse = collapse;
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::collapse(UNumberRangeCollapse collapse) && {
Derived move(std::move(*this));
move.fMacros.collapse = collapse;
return move;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::identityFallback(UNumberRangeIdentityFallback identityFallback) const& {
Derived copy(*this);
copy.fMacros.identityFallback = identityFallback;
return copy;
}
template<typename Derived>
Derived NumberRangeFormatterSettings<Derived>::identityFallback(UNumberRangeIdentityFallback identityFallback) && {
Derived move(std::move(*this));
move.fMacros.identityFallback = identityFallback;
return move;
}
// Declare all classes that implement NumberRangeFormatterSettings
// See https://stackoverflow.com/a/495056/1407170
template
class icu::number::NumberRangeFormatterSettings<icu::number::UnlocalizedNumberRangeFormatter>;
template
class icu::number::NumberRangeFormatterSettings<icu::number::LocalizedNumberRangeFormatter>;
UnlocalizedNumberRangeFormatter NumberRangeFormatter::with() {
UnlocalizedNumberRangeFormatter result;
return result;
}
LocalizedNumberRangeFormatter NumberRangeFormatter::withLocale(const Locale& locale) {
return with().locale(locale);
}
template<typename T> using NFS = NumberRangeFormatterSettings<T>;
using LNF = LocalizedNumberRangeFormatter;
using UNF = UnlocalizedNumberRangeFormatter;
UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(const UNF& other)
: UNF(static_cast<const NFS<UNF>&>(other)) {}
UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(const NFS<UNF>& other)
: NFS<UNF>(other) {
// No additional fields to assign
}
// Make default copy constructor call the NumberRangeFormatterSettings copy constructor.
UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(UNF&& src) U_NOEXCEPT
: UNF(static_cast<NFS<UNF>&&>(src)) {}
UnlocalizedNumberRangeFormatter::UnlocalizedNumberRangeFormatter(NFS<UNF>&& src) U_NOEXCEPT
: NFS<UNF>(std::move(src)) {
// No additional fields to assign
}
UnlocalizedNumberRangeFormatter& UnlocalizedNumberRangeFormatter::operator=(const UNF& other) {
NFS<UNF>::operator=(static_cast<const NFS<UNF>&>(other));
// No additional fields to assign
return *this;
}
UnlocalizedNumberRangeFormatter& UnlocalizedNumberRangeFormatter::operator=(UNF&& src) U_NOEXCEPT {
NFS<UNF>::operator=(static_cast<NFS<UNF>&&>(src));
// No additional fields to assign
return *this;
}
// Make default copy constructor call the NumberRangeFormatterSettings copy constructor.
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(const LNF& other)
: LNF(static_cast<const NFS<LNF>&>(other)) {}
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(const NFS<LNF>& other)
: NFS<LNF>(other) {
// No additional fields to assign
}
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(LocalizedNumberRangeFormatter&& src) U_NOEXCEPT
: LNF(static_cast<NFS<LNF>&&>(src)) {}
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(NFS<LNF>&& src) U_NOEXCEPT
: NFS<LNF>(std::move(src)) {
// Steal the compiled formatter
LNF&& _src = static_cast<LNF&&>(src);
fImpl = _src.fImpl;
_src.fImpl = nullptr;
}
LocalizedNumberRangeFormatter& LocalizedNumberRangeFormatter::operator=(const LNF& other) {
NFS<LNF>::operator=(static_cast<const NFS<LNF>&>(other));
// Do not steal; just clear
delete fImpl;
fImpl = nullptr;
return *this;
}
LocalizedNumberRangeFormatter& LocalizedNumberRangeFormatter::operator=(LNF&& src) U_NOEXCEPT {
NFS<LNF>::operator=(static_cast<NFS<LNF>&&>(src));
// Steal the compiled formatter
delete fImpl;
fImpl = src.fImpl;
src.fImpl = nullptr;
return *this;
}
LocalizedNumberRangeFormatter::~LocalizedNumberRangeFormatter() {
delete fImpl;
}
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(const RangeMacroProps& macros, const Locale& locale) {
fMacros = macros;
fMacros.locale = locale;
touchRangeLocales(fMacros);
}
LocalizedNumberRangeFormatter::LocalizedNumberRangeFormatter(RangeMacroProps&& macros, const Locale& locale) {
fMacros = std::move(macros);
fMacros.locale = locale;
touchRangeLocales(fMacros);
}
LocalizedNumberRangeFormatter UnlocalizedNumberRangeFormatter::locale(const Locale& locale) const& {
return LocalizedNumberRangeFormatter(fMacros, locale);
}
LocalizedNumberRangeFormatter UnlocalizedNumberRangeFormatter::locale(const Locale& locale)&& {
return LocalizedNumberRangeFormatter(std::move(fMacros), locale);
}
FormattedNumberRange LocalizedNumberRangeFormatter::formatFormattableRange(
const Formattable& first, const Formattable& second, UErrorCode& status) const {
if (U_FAILURE(status)) {
return FormattedNumberRange(U_ILLEGAL_ARGUMENT_ERROR);
}
auto results = new UFormattedNumberRangeData();
if (results == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return FormattedNumberRange(status);
}
first.populateDecimalQuantity(results->quantity1, status);
if (U_FAILURE(status)) {
return FormattedNumberRange(status);
}
second.populateDecimalQuantity(results->quantity2, status);
if (U_FAILURE(status)) {
return FormattedNumberRange(status);
}
formatImpl(*results, first == second, status);
// Do not save the results object if we encountered a failure.
if (U_SUCCESS(status)) {
return FormattedNumberRange(results);
} else {
delete results;
return FormattedNumberRange(status);
}
}
void LocalizedNumberRangeFormatter::formatImpl(
UFormattedNumberRangeData& results, bool equalBeforeRounding, UErrorCode& status) const {
if (fImpl == nullptr) {
// TODO: Fix this once the atomic is ready!
auto* nonConstThis = const_cast<LocalizedNumberRangeFormatter*>(this);
nonConstThis->fImpl = new NumberRangeFormatterImpl(fMacros, status);
if (U_FAILURE(status)) {
return;
}
if (fImpl == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
}
fImpl->format(results, equalBeforeRounding, status);
}
FormattedNumberRange::FormattedNumberRange(FormattedNumberRange&& src) U_NOEXCEPT
: fResults(src.fResults), fErrorCode(src.fErrorCode) {
// Disown src.fResults to prevent double-deletion
src.fResults = nullptr;
src.fErrorCode = U_INVALID_STATE_ERROR;
}
FormattedNumberRange& FormattedNumberRange::operator=(FormattedNumberRange&& src) U_NOEXCEPT {
delete fResults;
fResults = src.fResults;
fErrorCode = src.fErrorCode;
// Disown src.fResults to prevent double-deletion
src.fResults = nullptr;
src.fErrorCode = U_INVALID_STATE_ERROR;
return *this;
}
UnicodeString FormattedNumberRange::toString(UErrorCode& status) const {
if (U_FAILURE(status)) {
return ICU_Utility::makeBogusString();
}
if (fResults == nullptr) {
status = fErrorCode;
return ICU_Utility::makeBogusString();
}
return fResults->string.toUnicodeString();
}
Appendable& FormattedNumberRange::appendTo(Appendable& appendable, UErrorCode& status) const {
if (U_FAILURE(status)) {
return appendable;
}
if (fResults == nullptr) {
status = fErrorCode;
return appendable;
}
appendable.appendString(fResults->string.chars(), fResults->string.length());
return appendable;
}
UBool FormattedNumberRange::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const {
if (U_FAILURE(status)) {
return FALSE;
}
if (fResults == nullptr) {
status = fErrorCode;
return FALSE;
}
// NOTE: MSVC sometimes complains when implicitly converting between bool and UBool
return fResults->string.nextFieldPosition(fieldPosition, status) ? TRUE : FALSE;
}
void FormattedNumberRange::getAllFieldPositions(FieldPositionIterator& iterator, UErrorCode& status) const {
FieldPositionIteratorHandler fpih(&iterator, status);
getAllFieldPositionsImpl(fpih, status);
}
void FormattedNumberRange::getAllFieldPositionsImpl(
FieldPositionIteratorHandler& fpih, UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
if (fResults == nullptr) {
status = fErrorCode;
return;
}
fResults->string.getAllFieldPositions(fpih, status);
}
UnicodeString FormattedNumberRange::getFirstDecimal(UErrorCode& status) const {
if (U_FAILURE(status)) {
return ICU_Utility::makeBogusString();
}
if (fResults == nullptr) {
status = fErrorCode;
return ICU_Utility::makeBogusString();
}
return fResults->quantity1.toScientificString();
}
UnicodeString FormattedNumberRange::getSecondDecimal(UErrorCode& status) const {
if (U_FAILURE(status)) {
return ICU_Utility::makeBogusString();
}
if (fResults == nullptr) {
status = fErrorCode;
return ICU_Utility::makeBogusString();
}
return fResults->quantity2.toScientificString();
}
UNumberRangeIdentityResult FormattedNumberRange::getIdentityResult(UErrorCode& status) const {
if (U_FAILURE(status)) {
return UNUM_IDENTITY_RESULT_NOT_EQUAL;
}
if (fResults == nullptr) {
status = fErrorCode;
return UNUM_IDENTITY_RESULT_NOT_EQUAL;
}
return fResults->identityResult;
}
FormattedNumberRange::~FormattedNumberRange() {
delete fResults;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -0,0 +1,483 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
// Allow implicit conversion from char16_t* to UnicodeString for this file:
// Helpful in toString methods and elsewhere.
#define UNISTR_FROM_STRING_EXPLICIT
#include "unicode/numberrangeformatter.h"
#include "numrange_impl.h"
#include "patternprops.h"
#include "uresimp.h"
#include "util.h"
using namespace icu;
using namespace icu::number;
using namespace icu::number::impl;
namespace {
// Helper function for 2-dimensional switch statement
constexpr int8_t identity2d(UNumberRangeIdentityFallback a, UNumberRangeIdentityResult b) {
return static_cast<int8_t>(a) | (static_cast<int8_t>(b) << 4);
}
struct NumberRangeData {
SimpleFormatter rangePattern;
SimpleFormatter approximatelyPattern;
};
class NumberRangeDataSink : public ResourceSink {
public:
NumberRangeDataSink(NumberRangeData& data) : fData(data) {}
void put(const char* key, ResourceValue& value, UBool /*noFallback*/, UErrorCode& status) U_OVERRIDE {
ResourceTable miscTable = value.getTable(status);
if (U_FAILURE(status)) { return; }
for (int i = 0; miscTable.getKeyAndValue(i, key, value); i++) {
if (uprv_strcmp(key, "range") == 0) {
if (fData.rangePattern.getArgumentLimit() != 0) {
continue; // have already seen this pattern
}
fData.rangePattern = {value.getUnicodeString(status), status};
} else if (uprv_strcmp(key, "approximately") == 0) {
if (fData.approximatelyPattern.getArgumentLimit() != 0) {
continue; // have already seen this pattern
}
fData.approximatelyPattern = {value.getUnicodeString(status), status};
}
}
}
private:
NumberRangeData& fData;
};
void getNumberRangeData(const char* localeName, const char* nsName, NumberRangeData& data, UErrorCode& status) {
if (U_FAILURE(status)) { return; }
LocalUResourceBundlePointer rb(ures_open(NULL, localeName, &status));
if (U_FAILURE(status)) { return; }
NumberRangeDataSink sink(data);
CharString dataPath;
dataPath.append("NumberElements/", -1, status);
dataPath.append(nsName, -1, status);
dataPath.append("/miscPatterns", -1, status);
ures_getAllItemsWithFallback(rb.getAlias(), dataPath.data(), sink, status);
if (U_FAILURE(status)) { return; }
// TODO: Is it necessary to manually fall back to latn, or does the data sink take care of that?
if (data.rangePattern.getArgumentLimit() == 0) {
// No data!
data.rangePattern = {u"{0}{1}", status};
}
if (data.approximatelyPattern.getArgumentLimit() == 0) {
// No data!
data.approximatelyPattern = {u"~{0}", status};
}
}
class PluralRangesDataSink : public ResourceSink {
public:
PluralRangesDataSink(StandardPluralRanges& output) : fOutput(output) {}
void put(const char* /*key*/, ResourceValue& value, UBool /*noFallback*/, UErrorCode& status) U_OVERRIDE {
ResourceArray entriesArray = value.getArray(status);
if (U_FAILURE(status)) { return; }
fOutput.setCapacity(entriesArray.getSize());
for (int i = 0; entriesArray.getValue(i, value); i++) {
ResourceArray pluralFormsArray = value.getArray(status);
if (U_FAILURE(status)) { return; }
pluralFormsArray.getValue(0, value);
StandardPlural::Form first = StandardPlural::fromString(value.getUnicodeString(status), status);
if (U_FAILURE(status)) { return; }
pluralFormsArray.getValue(1, value);
StandardPlural::Form second = StandardPlural::fromString(value.getUnicodeString(status), status);
if (U_FAILURE(status)) { return; }
pluralFormsArray.getValue(2, value);
StandardPlural::Form result = StandardPlural::fromString(value.getUnicodeString(status), status);
if (U_FAILURE(status)) { return; }
fOutput.addPluralRange(first, second, result);
}
}
private:
StandardPluralRanges& fOutput;
};
void getPluralRangesData(const Locale& locale, StandardPluralRanges& output, UErrorCode& status) {
if (U_FAILURE(status)) { return; }
LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "pluralRanges", &status));
if (U_FAILURE(status)) { return; }
CharString dataPath;
dataPath.append("locales/", -1, status);
dataPath.append(locale.getLanguage(), -1, status);
if (U_FAILURE(status)) { return; }
int32_t setLen;
// Not all languages are covered: fail gracefully
UErrorCode internalStatus = U_ZERO_ERROR;
const UChar* set = ures_getStringByKeyWithFallback(rb.getAlias(), dataPath.data(), &setLen, &internalStatus);
if (U_FAILURE(internalStatus)) { return; }
dataPath.clear();
dataPath.append("rules/", -1, status);
dataPath.appendInvariantChars(set, setLen, status);
if (U_FAILURE(status)) { return; }
PluralRangesDataSink sink(output);
ures_getAllItemsWithFallback(rb.getAlias(), dataPath.data(), sink, status);
if (U_FAILURE(status)) { return; }
}
} // namespace
void StandardPluralRanges::initialize(const Locale& locale, UErrorCode& status) {
getPluralRangesData(locale, *this, status);
}
void StandardPluralRanges::addPluralRange(
StandardPlural::Form first,
StandardPlural::Form second,
StandardPlural::Form result) {
U_ASSERT(fTriplesLen < fTriples.getCapacity());
fTriples[fTriplesLen] = {first, second, result};
fTriplesLen++;
}
void StandardPluralRanges::setCapacity(int32_t length) {
if (length > fTriples.getCapacity()) {
fTriples.resize(length, 0);
}
}
StandardPlural::Form
StandardPluralRanges::resolve(StandardPlural::Form first, StandardPlural::Form second) const {
for (int32_t i=0; i<fTriplesLen; i++) {
const auto& triple = fTriples[i];
if (triple.first == first && triple.second == second) {
return triple.result;
}
}
// Default fallback
return StandardPlural::OTHER;
}
NumberRangeFormatterImpl::NumberRangeFormatterImpl(const RangeMacroProps& macros, UErrorCode& status)
: formatterImpl1(macros.formatter1.fMacros, status),
formatterImpl2(macros.formatter2.fMacros, status),
fSameFormatters(macros.singleFormatter),
fCollapse(macros.collapse),
fIdentityFallback(macros.identityFallback) {
// TODO: As of this writing (ICU 63), there is no locale that has different number miscPatterns
// based on numbering system. Therefore, data is loaded only from latn. If this changes,
// this part of the code should be updated to load from the local numbering system.
// The numbering system could come from the one specified in the NumberFormatter passed to
// numberFormatterBoth() or similar.
// See ICU-20144
NumberRangeData data;
getNumberRangeData(macros.locale.getName(), "latn", data, status);
if (U_FAILURE(status)) { return; }
fRangeFormatter = data.rangePattern;
fApproximatelyModifier = {data.approximatelyPattern, UNUM_FIELD_COUNT, false};
// TODO: Get locale from PluralRules instead?
fPluralRanges.initialize(macros.locale, status);
if (U_FAILURE(status)) { return; }
}
void NumberRangeFormatterImpl::format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
MicroProps micros1;
MicroProps micros2;
formatterImpl1.preProcess(data.quantity1, micros1, status);
if (fSameFormatters) {
formatterImpl1.preProcess(data.quantity2, micros2, status);
} else {
formatterImpl2.preProcess(data.quantity2, micros2, status);
}
if (U_FAILURE(status)) {
return;
}
// If any of the affixes are different, an identity is not possible
// and we must use formatRange().
// TODO: Write this as MicroProps operator==() ?
// TODO: Avoid the redundancy of these equality operations with the
// ones in formatRange?
if (!micros1.modInner->semanticallyEquivalent(*micros2.modInner)
|| !micros1.modMiddle->semanticallyEquivalent(*micros2.modMiddle)
|| !micros1.modOuter->semanticallyEquivalent(*micros2.modOuter)) {
formatRange(data, micros1, micros2, status);
data.identityResult = UNUM_IDENTITY_RESULT_NOT_EQUAL;
return;
}
// Check for identity
if (equalBeforeRounding) {
data.identityResult = UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING;
} else if (data.quantity1 == data.quantity2) {
data.identityResult = UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING;
} else {
data.identityResult = UNUM_IDENTITY_RESULT_NOT_EQUAL;
}
switch (identity2d(fIdentityFallback, data.identityResult)) {
case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
UNUM_IDENTITY_RESULT_NOT_EQUAL):
case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
case identity2d(UNUM_IDENTITY_FALLBACK_RANGE,
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
UNUM_IDENTITY_RESULT_NOT_EQUAL):
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
UNUM_IDENTITY_RESULT_NOT_EQUAL):
case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
UNUM_IDENTITY_RESULT_NOT_EQUAL):
formatRange(data, micros1, micros2, status);
break;
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
formatApproximately(data, micros1, micros2, status);
break;
case identity2d(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING):
case identity2d(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING):
formatSingleValue(data, micros1, micros2, status);
break;
default:
U_ASSERT(false);
break;
}
}
void NumberRangeFormatterImpl::formatSingleValue(UFormattedNumberRangeData& data,
MicroProps& micros1, MicroProps& micros2,
UErrorCode& status) const {
if (U_FAILURE(status)) { return; }
if (fSameFormatters) {
int32_t length = NumberFormatterImpl::writeNumber(micros1, data.quantity1, data.string, 0, status);
NumberFormatterImpl::writeAffixes(micros1, data.string, 0, length, status);
} else {
formatRange(data, micros1, micros2, status);
}
}
void NumberRangeFormatterImpl::formatApproximately (UFormattedNumberRangeData& data,
MicroProps& micros1, MicroProps& micros2,
UErrorCode& status) const {
if (U_FAILURE(status)) { return; }
if (fSameFormatters) {
int32_t length = NumberFormatterImpl::writeNumber(micros1, data.quantity1, data.string, 0, status);
length += NumberFormatterImpl::writeAffixes(micros1, data.string, 0, length, status);
fApproximatelyModifier.apply(data.string, 0, length, status);
} else {
formatRange(data, micros1, micros2, status);
}
}
void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data,
MicroProps& micros1, MicroProps& micros2,
UErrorCode& status) const {
if (U_FAILURE(status)) { return; }
// modInner is always notation (scientific); collapsable in ALL.
// modOuter is always units; collapsable in ALL, AUTO, and UNIT.
// modMiddle could be either; collapsable in ALL and sometimes AUTO and UNIT.
// Never collapse an outer mod but not an inner mod.
bool collapseOuter, collapseMiddle, collapseInner;
switch (fCollapse) {
case UNUM_RANGE_COLLAPSE_ALL:
case UNUM_RANGE_COLLAPSE_AUTO:
case UNUM_RANGE_COLLAPSE_UNIT:
{
// OUTER MODIFIER
collapseOuter = micros1.modOuter->semanticallyEquivalent(*micros2.modOuter);
if (!collapseOuter) {
// Never collapse inner mods if outer mods are not collapsable
collapseMiddle = false;
collapseInner = false;
break;
}
// MIDDLE MODIFIER
collapseMiddle = micros1.modMiddle->semanticallyEquivalent(*micros2.modMiddle);
if (!collapseMiddle) {
// Never collapse inner mods if outer mods are not collapsable
collapseInner = false;
break;
}
// MIDDLE MODIFIER HEURISTICS
// (could disable collapsing of the middle modifier)
// The modifiers are equal by this point, so we can look at just one of them.
const Modifier* mm = micros1.modMiddle;
if (fCollapse == UNUM_RANGE_COLLAPSE_UNIT) {
// Only collapse if the modifier is a unit.
// TODO: Make a better way to check for a unit?
// TODO: Handle case where the modifier has both notation and unit (compact currency)?
if (!mm->containsField(UNUM_CURRENCY_FIELD) && !mm->containsField(UNUM_PERCENT_FIELD)) {
collapseMiddle = false;
}
} else if (fCollapse == UNUM_RANGE_COLLAPSE_AUTO) {
// Heuristic as of ICU 63: collapse only if the modifier is more than one code point.
if (mm->getCodePointCount() <= 1) {
collapseMiddle = false;
}
}
if (!collapseMiddle || fCollapse != UNUM_RANGE_COLLAPSE_ALL) {
collapseInner = false;
break;
}
// INNER MODIFIER
collapseInner = micros1.modInner->semanticallyEquivalent(*micros2.modInner);
// All done checking for collapsability.
break;
}
default:
collapseOuter = false;
collapseMiddle = false;
collapseInner = false;
break;
}
NumberStringBuilder& string = data.string;
int32_t lengthPrefix = 0;
int32_t length1 = 0;
int32_t lengthInfix = 0;
int32_t length2 = 0;
int32_t lengthSuffix = 0;
// Use #define so that these are evaluated at the call site.
#define UPRV_INDEX_0 (lengthPrefix)
#define UPRV_INDEX_1 (lengthPrefix + length1)
#define UPRV_INDEX_2 (lengthPrefix + length1 + lengthInfix)
#define UPRV_INDEX_3 (lengthPrefix + length1 + lengthInfix + length2)
int32_t lengthRange = SimpleModifier::formatTwoArgPattern(
fRangeFormatter,
string,
0,
&lengthPrefix,
&lengthSuffix,
UNUM_FIELD_COUNT,
status);
if (U_FAILURE(status)) { return; }
lengthInfix = lengthRange - lengthPrefix - lengthSuffix;
U_ASSERT(lengthInfix > 0);
// SPACING HEURISTIC
// Add spacing unless all modifiers are collapsed.
// TODO: add API to control this?
// TODO: Use a data-driven heuristic like currency spacing?
// TODO: Use Unicode [:whitespace:] instead of PatternProps whitespace? (consider speed implications)
{
bool repeatInner = !collapseInner && micros1.modInner->getCodePointCount() > 0;
bool repeatMiddle = !collapseMiddle && micros1.modMiddle->getCodePointCount() > 0;
bool repeatOuter = !collapseOuter && micros1.modOuter->getCodePointCount() > 0;
if (repeatInner || repeatMiddle || repeatOuter) {
// Add spacing if there is not already spacing
if (!PatternProps::isWhiteSpace(string.charAt(UPRV_INDEX_1))) {
lengthInfix += string.insertCodePoint(UPRV_INDEX_1, u'\u0020', UNUM_FIELD_COUNT, status);
}
if (!PatternProps::isWhiteSpace(string.charAt(UPRV_INDEX_2 - 1))) {
lengthInfix += string.insertCodePoint(UPRV_INDEX_2, u'\u0020', UNUM_FIELD_COUNT, status);
}
}
}
length1 += NumberFormatterImpl::writeNumber(micros1, data.quantity1, string, UPRV_INDEX_0, status);
length2 += NumberFormatterImpl::writeNumber(micros2, data.quantity2, string, UPRV_INDEX_2, status);
// TODO: Support padding?
if (collapseInner) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
const Modifier& mod = resolveModifierPlurals(*micros1.modInner, *micros2.modInner);
lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
} else {
length1 += micros1.modInner->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
length2 += micros2.modInner->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
}
if (collapseMiddle) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
const Modifier& mod = resolveModifierPlurals(*micros1.modMiddle, *micros2.modMiddle);
lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
} else {
length1 += micros1.modMiddle->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
length2 += micros2.modMiddle->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
}
if (collapseOuter) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
const Modifier& mod = resolveModifierPlurals(*micros1.modOuter, *micros2.modOuter);
lengthInfix += mod.apply(string, UPRV_INDEX_0, UPRV_INDEX_3, status);
} else {
length1 += micros1.modOuter->apply(string, UPRV_INDEX_0, UPRV_INDEX_1, status);
length2 += micros2.modOuter->apply(string, UPRV_INDEX_2, UPRV_INDEX_3, status);
}
}
const Modifier&
NumberRangeFormatterImpl::resolveModifierPlurals(const Modifier& first, const Modifier& second) const {
Modifier::Parameters parameters;
first.getParameters(parameters);
if (parameters.obj == nullptr) {
// No plural form; return a fallback (e.g., the first)
return first;
}
StandardPlural::Form firstPlural = parameters.plural;
second.getParameters(parameters);
if (parameters.obj == nullptr) {
// No plural form; return a fallback (e.g., the first)
return first;
}
StandardPlural::Form secondPlural = parameters.plural;
// Get the required plural form from data
StandardPlural::Form resultPlural = fPluralRanges.resolve(firstPlural, secondPlural);
// Get and return the new Modifier
const Modifier* mod = parameters.obj->getModifier(parameters.signum, resultPlural);
U_ASSERT(mod != nullptr);
return *mod;
}
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -0,0 +1,114 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#ifndef __SOURCE_NUMRANGE_TYPES_H__
#define __SOURCE_NUMRANGE_TYPES_H__
#include "unicode/numberformatter.h"
#include "unicode/numberrangeformatter.h"
#include "unicode/simpleformatter.h"
#include "number_types.h"
#include "number_decimalquantity.h"
#include "number_formatimpl.h"
#include "number_stringbuilder.h"
U_NAMESPACE_BEGIN namespace number {
namespace impl {
/**
* Class similar to UFormattedNumberData.
*
* Has incomplete magic number logic that will need to be finished
* if this is to be exposed as C API in the future.
*/
struct UFormattedNumberRangeData : public UMemory {
// The magic number to identify incoming objects.
// Reads in ASCII as "FDR" (FormatteDnumberRange with room at the end)
static constexpr int32_t kMagic = 0x46445200;
// Data members:
int32_t fMagic = kMagic;
DecimalQuantity quantity1;
DecimalQuantity quantity2;
NumberStringBuilder string;
UNumberRangeIdentityResult identityResult = UNUM_IDENTITY_RESULT_COUNT;
// No C conversion methods (no C API yet)
};
class StandardPluralRanges : public UMemory {
public:
void initialize(const Locale& locale, UErrorCode& status);
StandardPlural::Form resolve(StandardPlural::Form first, StandardPlural::Form second) const;
/** Used for data loading. */
void addPluralRange(
StandardPlural::Form first,
StandardPlural::Form second,
StandardPlural::Form result);
/** Used for data loading. */
void setCapacity(int32_t length);
private:
struct StandardPluralRangeTriple {
StandardPlural::Form first;
StandardPlural::Form second;
StandardPlural::Form result;
};
// TODO: An array is simple here, but it results in linear lookup time.
// Certain locales have 20-30 entries in this list.
// Consider changing to a smarter data structure.
typedef MaybeStackArray<StandardPluralRangeTriple, 3> PluralRangeTriples;
PluralRangeTriples fTriples;
int32_t fTriplesLen = 0;
};
class NumberRangeFormatterImpl : public UMemory {
public:
NumberRangeFormatterImpl(const RangeMacroProps& macros, UErrorCode& status);
void format(UFormattedNumberRangeData& data, bool equalBeforeRounding, UErrorCode& status) const;
private:
NumberFormatterImpl formatterImpl1;
NumberFormatterImpl formatterImpl2;
bool fSameFormatters;
UNumberRangeCollapse fCollapse;
UNumberRangeIdentityFallback fIdentityFallback;
SimpleFormatter fRangeFormatter;
SimpleModifier fApproximatelyModifier;
StandardPluralRanges fPluralRanges;
void formatSingleValue(UFormattedNumberRangeData& data,
MicroProps& micros1, MicroProps& micros2,
UErrorCode& status) const;
void formatApproximately(UFormattedNumberRangeData& data,
MicroProps& micros1, MicroProps& micros2,
UErrorCode& status) const;
void formatRange(UFormattedNumberRangeData& data,
MicroProps& micros1, MicroProps& micros2,
UErrorCode& status) const;
const Modifier& resolveModifierPlurals(const Modifier& first, const Modifier& second) const;
};
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__SOURCE_NUMRANGE_TYPES_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -130,7 +130,7 @@ class BasicTimeZone;
*
* **Note:** for some non-Gregorian calendars, different
* fields may be necessary for complete disambiguation. For example, a full
* specification of the historial Arabic astronomical calendar requires year,
* specification of the historical Arabic astronomical calendar requires year,
* month, day-of-month *and* day-of-week in some cases.
*
* **Note:** There are certain possible ambiguities in
@ -886,7 +886,7 @@ public:
/**
* Sets the behavior for handling wall time repeating multiple times
* at negative time zone offset transitions. For example, 1:30 AM on
* November 6, 2011 in US Eastern time (Ameirca/New_York) occurs twice;
* November 6, 2011 in US Eastern time (America/New_York) occurs twice;
* 1:30 AM EDT, then 1:30 AM EST one hour later. When <code>UCAL_WALLTIME_FIRST</code>
* is used, the wall time 1:30AM in this example will be interpreted as 1:30 AM EDT
* (first occurrence). When <code>UCAL_WALLTIME_LAST</code> is used, it will be
@ -2152,7 +2152,7 @@ private:
TimeZone* fZone;
/**
* Option for rpeated wall time
* Option for repeated wall time
* @see #setRepeatedWallTimeOption
*/
UCalendarWallTimeOption fRepeatedWallTime;
@ -2437,7 +2437,7 @@ private:
BasicTimeZone* getBasicTimeZone() const;
/**
* Find the previous zone transtion near the given time.
* Find the previous zone transition near the given time.
* @param base The base time, inclusive
* @param transitionTime Receives the result time
* @param status The error status

View File

@ -144,6 +144,9 @@ class MultiplierFormatHandler;
class CurrencySymbols;
class GeneratorHelpers;
class DecNum;
class NumberRangeFormatterImpl;
struct RangeMacroProps;
void touchRangeLocales(impl::RangeMacroProps& macros);
} // namespace impl
@ -1423,7 +1426,8 @@ struct U_I18N_API MacroProps : public UMemory {
/**
* An abstract base class for specifying settings related to number formatting. This class is implemented by
* {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}.
* {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}. This class is not intended for
* public subclassing.
*/
template<typename Derived>
class U_I18N_API NumberFormatterSettings {
@ -2108,6 +2112,10 @@ class U_I18N_API NumberFormatterSettings {
friend class LocalizedNumberFormatter;
friend class UnlocalizedNumberFormatter;
// Give NumberRangeFormatter access to the MacroProps
friend void impl::touchRangeLocales(impl::RangeMacroProps& macros);
friend class impl::NumberRangeFormatterImpl;
};
/**
@ -2124,13 +2132,6 @@ class U_I18N_API UnlocalizedNumberFormatter
* Associate the given locale with the number formatter. The locale is used for picking the appropriate symbols,
* formats, and other data for number display.
*
* <p>
* To use the Java default locale, call Locale::getDefault():
*
* <pre>
* NumberFormatter::with(). ... .locale(Locale::getDefault())
* </pre>
*
* @param locale
* The locale to use when loading data for number formatting.
* @return The fluent chain.
@ -2156,7 +2157,6 @@ class U_I18N_API UnlocalizedNumberFormatter
*/
UnlocalizedNumberFormatter() = default;
// Make default copy constructor call the NumberFormatterSettings copy constructor.
/**
* Returns a copy of this UnlocalizedNumberFormatter.
* @draft ICU 60
@ -2295,7 +2295,6 @@ class U_I18N_API LocalizedNumberFormatter
*/
LocalizedNumberFormatter() = default;
// Make default copy constructor call the NumberFormatterSettings copy constructor.
/**
* Returns a copy of this LocalizedNumberFormatter.
* @draft ICU 60
@ -2359,6 +2358,8 @@ class U_I18N_API LocalizedNumberFormatter
LocalizedNumberFormatter(impl::MacroProps &&macros, const Locale &locale);
void clear();
void lnfMoveHelper(LocalizedNumberFormatter&& src);
/**
@ -2457,9 +2458,9 @@ class U_I18N_API FormattedNumber : public UMemory {
#endif /* U_HIDE_DEPRECATED_API */
/**
* Determines the start and end indices of the next occurrence of the given <em>field</em> in the
* output string. This allows you to determine the locations of, for example, the integer part,
* fraction part, or symbols.
* Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
* <em>field</em> in the output string. This allows you to determine the locations of, for example,
* the integer part, fraction part, or symbols.
*
* If a field occurs just once, calling this method will find that occurrence and return it. If a
* field occurs multiple times, this method may be called repeatedly with the following pattern:
@ -2478,7 +2479,7 @@ class U_I18N_API FormattedNumber : public UMemory {
* Input+output variable. On input, the "field" property determines which field to look
* up, and the "beginIndex" and "endIndex" properties determine where to begin the search.
* On output, the "beginIndex" is set to the beginning of the first occurrence of the
* field with either begin or end indices after the input indices, "endIndex" is set to
* field with either begin or end indices after the input indices; "endIndex" is set to
* the end of that occurrence of the field (exclusive index). If a field position is not
* found, the method returns FALSE and the FieldPosition may or may not be changed.
* @param status

View File

@ -0,0 +1,851 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBERRANGEFORMATTER_H__
#define __NUMBERRANGEFORMATTER_H__
#include "unicode/appendable.h"
#include "unicode/fieldpos.h"
#include "unicode/fpositer.h"
#include "unicode/numberformatter.h"
#ifndef U_HIDE_DRAFT_API
/**
* \file
* \brief C++ API: Library for localized formatting of number, currency, and unit ranges.
*
* The main entrypoint to the formatting of ranges of numbers, including currencies and other units of measurement.
* <p>
* Usage example:
* <p>
* <pre>
* NumberRangeFormatter::with()
* .identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE)
* .numberFormatterFirst(NumberFormatter::with().adoptUnit(MeasureUnit::createMeter()))
* .numberFormatterSecond(NumberFormatter::with().adoptUnit(MeasureUnit::createKilometer()))
* .locale("en-GB")
* .formatRange(750, 1.2, status)
* .toString(status);
* // => "750 m - 1.2 km"
* </pre>
* <p>
* Like NumberFormatter, NumberRangeFormatter instances are immutable and thread-safe. This API is based on the
* <em>fluent</em> design pattern popularized by libraries such as Google's Guava.
*
* @author Shane Carr
*/
/**
* Defines how to merge fields that are identical across the range sign.
*
* @draft ICU 63
*/
typedef enum UNumberRangeCollapse {
/**
* Use locale data and heuristics to determine how much of the string to collapse. Could end up collapsing none,
* some, or all repeated pieces in a locale-sensitive way.
*
* The heuristics used for this option are subject to change over time.
*
* @draft ICU 63
*/
UNUM_RANGE_COLLAPSE_AUTO,
/**
* Do not collapse any part of the number. Example: "3.2 thousand kilograms 5.3 thousand kilograms"
*
* @draft ICU 63
*/
UNUM_RANGE_COLLAPSE_NONE,
/**
* Collapse the unit part of the number, but not the notation, if present. Example: "3.2 thousand 5.3 thousand
* kilograms"
*
* @draft ICU 63
*/
UNUM_RANGE_COLLAPSE_UNIT,
/**
* Collapse any field that is equal across the range sign. May introduce ambiguity on the magnitude of the
* number. Example: "3.2 5.3 thousand kilograms"
*
* @draft ICU 63
*/
UNUM_RANGE_COLLAPSE_ALL
} UNumberRangeCollapse;
/**
* Defines the behavior when the two numbers in the range are identical after rounding. To programmatically detect
* when the identity fallback is used, compare the lower and upper BigDecimals via FormattedNumber.
*
* @draft ICU 63
* @see NumberRangeFormatter
*/
typedef enum UNumberRangeIdentityFallback {
/**
* Show the number as a single value rather than a range. Example: "$5"
*
* @draft ICU 63
*/
UNUM_IDENTITY_FALLBACK_SINGLE_VALUE,
/**
* Show the number using a locale-sensitive approximation pattern. If the numbers were the same before rounding,
* show the single value. Example: "~$5" or "$5"
*
* @draft ICU 63
*/
UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE,
/**
* Show the number using a locale-sensitive approximation pattern. Use the range pattern always, even if the
* inputs are the same. Example: "~$5"
*
* @draft ICU 63
*/
UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
/**
* Show the number as the range of two equal values. Use the range pattern always, even if the inputs are the
* same. Example (with RangeCollapse.NONE): "$5 $5"
*
* @draft ICU 63
*/
UNUM_IDENTITY_FALLBACK_RANGE
} UNumberRangeIdentityFallback;
/**
* Used in the result class FormattedNumberRange to indicate to the user whether the numbers formatted in the range
* were equal or not, and whether or not the identity fallback was applied.
*
* @draft ICU 63
* @see NumberRangeFormatter
*/
typedef enum UNumberRangeIdentityResult {
/**
* Used to indicate that the two numbers in the range were equal, even before any rounding rules were applied.
*
* @draft ICU 63
* @see NumberRangeFormatter
*/
UNUM_IDENTITY_RESULT_EQUAL_BEFORE_ROUNDING,
/**
* Used to indicate that the two numbers in the range were equal, but only after rounding rules were applied.
*
* @draft ICU 63
* @see NumberRangeFormatter
*/
UNUM_IDENTITY_RESULT_EQUAL_AFTER_ROUNDING,
/**
* Used to indicate that the two numbers in the range were not equal, even after rounding rules were applied.
*
* @draft ICU 63
* @see NumberRangeFormatter
*/
UNUM_IDENTITY_RESULT_NOT_EQUAL,
#ifndef U_HIDE_INTERNAL_API
/**
* The number of entries in this enum.
* @internal
*/
UNUM_IDENTITY_RESULT_COUNT
#endif
} UNumberRangeIdentityResult;
U_NAMESPACE_BEGIN
namespace number { // icu::number
// Forward declarations:
class UnlocalizedNumberRangeFormatter;
class LocalizedNumberRangeFormatter;
class FormattedNumberRange;
namespace impl {
// Forward declarations:
struct RangeMacroProps;
class DecimalQuantity;
struct UFormattedNumberRangeData;
class NumberRangeFormatterImpl;
} // namespace impl
// Other helper classes would go here, but there are none.
namespace impl { // icu::number::impl
// Do not enclose entire MacroProps with #ifndef U_HIDE_INTERNAL_API, needed for a protected field
/** @internal */
struct U_I18N_API RangeMacroProps : public UMemory {
/** @internal */
UnlocalizedNumberFormatter formatter1; // = NumberFormatter::with();
/** @internal */
UnlocalizedNumberFormatter formatter2; // = NumberFormatter::with();
/** @internal */
bool singleFormatter = true;
/** @internal */
UNumberRangeCollapse collapse = UNUM_RANGE_COLLAPSE_AUTO;
/** @internal */
UNumberRangeIdentityFallback identityFallback = UNUM_IDENTITY_FALLBACK_APPROXIMATELY;
/** @internal */
Locale locale;
// NOTE: Uses default copy and move constructors.
/**
* Check all members for errors.
* @internal
*/
bool copyErrorTo(UErrorCode &status) const {
return formatter1.copyErrorTo(status) || formatter2.copyErrorTo(status);
}
};
} // namespace impl
/**
* An abstract base class for specifying settings related to number formatting. This class is implemented by
* {@link UnlocalizedNumberRangeFormatter} and {@link LocalizedNumberRangeFormatter}. This class is not intended for
* public subclassing.
*/
template<typename Derived>
class U_I18N_API NumberRangeFormatterSettings {
public:
/**
* Sets the NumberFormatter instance to use for the numbers in the range. The same formatter is applied to both
* sides of the range.
* <p>
* The NumberFormatter instances must not have a locale applied yet; the locale specified on the
* NumberRangeFormatter will be used.
*
* @param formatter
* The formatter to use for both numbers in the range.
* @return The fluent chain.
* @draft ICU 63
*/
Derived numberFormatterBoth(const UnlocalizedNumberFormatter &formatter) const &;
/**
* Overload of numberFormatterBoth() for use on an rvalue reference.
*
* @param formatter
* The formatter to use for both numbers in the range.
* @return The fluent chain.
* @see #numberFormatterBoth
* @draft ICU 63
*/
Derived numberFormatterBoth(const UnlocalizedNumberFormatter &formatter) &&;
/**
* Overload of numberFormatterBoth() for use on an rvalue reference.
*
* @param formatter
* The formatter to use for both numbers in the range.
* @return The fluent chain.
* @see #numberFormatterBoth
* @draft ICU 63
*/
Derived numberFormatterBoth(UnlocalizedNumberFormatter &&formatter) const &;
/**
* Overload of numberFormatterBoth() for use on an rvalue reference.
*
* @param formatter
* The formatter to use for both numbers in the range.
* @return The fluent chain.
* @see #numberFormatterBoth
* @draft ICU 63
*/
Derived numberFormatterBoth(UnlocalizedNumberFormatter &&formatter) &&;
/**
* Sets the NumberFormatter instance to use for the first number in the range.
* <p>
* The NumberFormatter instances must not have a locale applied yet; the locale specified on the
* NumberRangeFormatter will be used.
*
* @param formatterFirst
* The formatter to use for the first number in the range.
* @return The fluent chain.
* @draft ICU 63
*/
Derived numberFormatterFirst(const UnlocalizedNumberFormatter &formatterFirst) const &;
/**
* Overload of numberFormatterFirst() for use on an rvalue reference.
*
* @param formatterFirst
* The formatter to use for the first number in the range.
* @return The fluent chain.
* @see #numberFormatterFirst
* @draft ICU 63
*/
Derived numberFormatterFirst(const UnlocalizedNumberFormatter &formatterFirst) &&;
/**
* Overload of numberFormatterFirst() for use on an rvalue reference.
*
* @param formatterFirst
* The formatter to use for the first number in the range.
* @return The fluent chain.
* @see #numberFormatterFirst
* @draft ICU 63
*/
Derived numberFormatterFirst(UnlocalizedNumberFormatter &&formatterFirst) const &;
/**
* Overload of numberFormatterFirst() for use on an rvalue reference.
*
* @param formatterFirst
* The formatter to use for the first number in the range.
* @return The fluent chain.
* @see #numberFormatterFirst
* @draft ICU 63
*/
Derived numberFormatterFirst(UnlocalizedNumberFormatter &&formatterFirst) &&;
/**
* Sets the NumberFormatter instance to use for the second number in the range.
* <p>
* The NumberFormatter instances must not have a locale applied yet; the locale specified on the
* NumberRangeFormatter will be used.
*
* @param formatterSecond
* The formatter to use for the second number in the range.
* @return The fluent chain.
* @draft ICU 63
*/
Derived numberFormatterSecond(const UnlocalizedNumberFormatter &formatterSecond) const &;
/**
* Overload of numberFormatterSecond() for use on an rvalue reference.
*
* @param formatterSecond
* The formatter to use for the second number in the range.
* @return The fluent chain.
* @see #numberFormatterSecond
* @draft ICU 63
*/
Derived numberFormatterSecond(const UnlocalizedNumberFormatter &formatterSecond) &&;
/**
* Overload of numberFormatterSecond() for use on an rvalue reference.
*
* @param formatterSecond
* The formatter to use for the second number in the range.
* @return The fluent chain.
* @see #numberFormatterSecond
* @draft ICU 63
*/
Derived numberFormatterSecond(UnlocalizedNumberFormatter &&formatterSecond) const &;
/**
* Overload of numberFormatterSecond() for use on an rvalue reference.
*
* @param formatterSecond
* The formatter to use for the second number in the range.
* @return The fluent chain.
* @see #numberFormatterSecond
* @draft ICU 63
*/
Derived numberFormatterSecond(UnlocalizedNumberFormatter &&formatterSecond) &&;
/**
* Sets the aggressiveness of "collapsing" fields across the range separator. Possible values:
* <p>
* <ul>
* <li>ALL: "3-5K miles"</li>
* <li>UNIT: "3K - 5K miles"</li>
* <li>NONE: "3K miles - 5K miles"</li>
* <li>AUTO: usually UNIT or NONE, depending on the locale and formatter settings</li>
* </ul>
* <p>
* The default value is AUTO.
*
* @param collapse
* The collapsing strategy to use for this range.
* @return The fluent chain.
* @draft ICU 63
*/
Derived collapse(UNumberRangeCollapse collapse) const &;
/**
* Overload of collapse() for use on an rvalue reference.
*
* @param collapse
* The collapsing strategy to use for this range.
* @return The fluent chain.
* @see #collapse
* @draft ICU 63
*/
Derived collapse(UNumberRangeCollapse collapse) &&;
/**
* Sets the behavior when the two sides of the range are the same. This could happen if the same two numbers are
* passed to the formatRange function, or if different numbers are passed to the function but they become the same
* after rounding rules are applied. Possible values:
* <p>
* <ul>
* <li>SINGLE_VALUE: "5 miles"</li>
* <li>APPROXIMATELY_OR_SINGLE_VALUE: "~5 miles" or "5 miles", depending on whether the number was the same before
* rounding was applied</li>
* <li>APPROXIMATELY: "~5 miles"</li>
* <li>RANGE: "5-5 miles" (with collapse=UNIT)</li>
* </ul>
* <p>
* The default value is APPROXIMATELY.
*
* @param identityFallback
* The strategy to use when formatting two numbers that end up being the same.
* @return The fluent chain.
* @draft ICU 63
*/
Derived identityFallback(UNumberRangeIdentityFallback identityFallback) const &;
/**
* Overload of identityFallback() for use on an rvalue reference.
*
* @param identityFallback
* The strategy to use when formatting two numbers that end up being the same.
* @return The fluent chain.
* @see #identityFallback
* @draft ICU 63
*/
Derived identityFallback(UNumberRangeIdentityFallback identityFallback) &&;
/**
* Sets the UErrorCode if an error occurred in the fluent chain.
* Preserves older error codes in the outErrorCode.
* @return TRUE if U_FAILURE(outErrorCode)
* @draft ICU 63
*/
UBool copyErrorTo(UErrorCode &outErrorCode) const {
if (U_FAILURE(outErrorCode)) {
// Do not overwrite the older error code
return TRUE;
}
fMacros.copyErrorTo(outErrorCode);
return U_FAILURE(outErrorCode);
};
// NOTE: Uses default copy and move constructors.
private:
impl::RangeMacroProps fMacros;
// Don't construct me directly! Use (Un)LocalizedNumberFormatter.
NumberRangeFormatterSettings() = default;
friend class LocalizedNumberRangeFormatter;
friend class UnlocalizedNumberRangeFormatter;
};
/**
* A NumberRangeFormatter that does not yet have a locale. In order to format, a locale must be specified.
*
* @see NumberRangeFormatter
* @draft ICU 63
*/
class U_I18N_API UnlocalizedNumberRangeFormatter
: public NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>, public UMemory {
public:
/**
* Associate the given locale with the number range formatter. The locale is used for picking the
* appropriate symbols, formats, and other data for number display.
*
* @param locale
* The locale to use when loading data for number formatting.
* @return The fluent chain.
* @draft ICU 63
*/
LocalizedNumberRangeFormatter locale(const icu::Locale &locale) const &;
/**
* Overload of locale() for use on an rvalue reference.
*
* @param locale
* The locale to use when loading data for number formatting.
* @return The fluent chain.
* @see #locale
* @draft ICU 63
*/
LocalizedNumberRangeFormatter locale(const icu::Locale &locale) &&;
/**
* Default constructor: puts the formatter into a valid but undefined state.
*
* @draft ICU 63
*/
UnlocalizedNumberRangeFormatter() = default;
/**
* Returns a copy of this UnlocalizedNumberRangeFormatter.
* @draft ICU 63
*/
UnlocalizedNumberRangeFormatter(const UnlocalizedNumberRangeFormatter &other);
/**
* Move constructor:
* The source UnlocalizedNumberRangeFormatter will be left in a valid but undefined state.
* @draft ICU 63
*/
UnlocalizedNumberRangeFormatter(UnlocalizedNumberRangeFormatter&& src) U_NOEXCEPT;
/**
* Copy assignment operator.
* @draft ICU 63
*/
UnlocalizedNumberRangeFormatter& operator=(const UnlocalizedNumberRangeFormatter& other);
/**
* Move assignment operator:
* The source UnlocalizedNumberRangeFormatter will be left in a valid but undefined state.
* @draft ICU 63
*/
UnlocalizedNumberRangeFormatter& operator=(UnlocalizedNumberRangeFormatter&& src) U_NOEXCEPT;
private:
explicit UnlocalizedNumberRangeFormatter(
const NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>& other);
explicit UnlocalizedNumberRangeFormatter(
NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>&& src) U_NOEXCEPT;
// To give the fluent setters access to this class's constructor:
friend class NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>;
// To give NumberRangeFormatter::with() access to this class's constructor:
friend class NumberRangeFormatter;
};
/**
* A NumberRangeFormatter that has a locale associated with it; this means .formatRange() methods are available.
*
* @see NumberFormatter
* @draft ICU 63
*/
class U_I18N_API LocalizedNumberRangeFormatter
: public NumberRangeFormatterSettings<LocalizedNumberRangeFormatter>, public UMemory {
public:
/**
* Format the given Formattables to a string using the settings specified in the NumberRangeFormatter fluent setting
* chain.
*
* @param first
* The first number in the range, usually to the left in LTR locales.
* @param second
* The second number in the range, usually to the right in LTR locales.
* @param status
* Set if an error occurs while formatting.
* @return A FormattedNumberRange object; call .toString() to get the string.
* @draft ICU 63
*/
FormattedNumberRange formatFormattableRange(
const Formattable& first, const Formattable& second, UErrorCode& status) const;
/**
* Default constructor: puts the formatter into a valid but undefined state.
*
* @draft ICU 63
*/
LocalizedNumberRangeFormatter() = default;
/**
* Returns a copy of this LocalizedNumberRangeFormatter.
* @draft ICU 63
*/
LocalizedNumberRangeFormatter(const LocalizedNumberRangeFormatter &other);
/**
* Move constructor:
* The source LocalizedNumberRangeFormatter will be left in a valid but undefined state.
* @draft ICU 63
*/
LocalizedNumberRangeFormatter(LocalizedNumberRangeFormatter&& src) U_NOEXCEPT;
/**
* Copy assignment operator.
* @draft ICU 63
*/
LocalizedNumberRangeFormatter& operator=(const LocalizedNumberRangeFormatter& other);
/**
* Move assignment operator:
* The source LocalizedNumberRangeFormatter will be left in a valid but undefined state.
* @draft ICU 63
*/
LocalizedNumberRangeFormatter& operator=(LocalizedNumberRangeFormatter&& src) U_NOEXCEPT;
#ifndef U_HIDE_INTERNAL_API
/**
* @param results
* The results object. This method will mutate it to save the results.
* @param status
* Set if an error occurs while formatting.
* @internal
*/
void formatImpl(impl::UFormattedNumberRangeData& results, bool equalBeforeRounding,
UErrorCode& status) const;
#endif
/**
* Destruct this LocalizedNumberRangeFormatter, cleaning up any memory it might own.
* @draft ICU 63
*/
~LocalizedNumberRangeFormatter();
private:
// TODO: This is not thread-safe! Do NOT check this in without an atomic here.
impl::NumberRangeFormatterImpl* fImpl = nullptr;
explicit LocalizedNumberRangeFormatter(
const NumberRangeFormatterSettings<LocalizedNumberRangeFormatter>& other);
explicit LocalizedNumberRangeFormatter(
NumberRangeFormatterSettings<LocalizedNumberRangeFormatter>&& src) U_NOEXCEPT;
LocalizedNumberRangeFormatter(const impl::RangeMacroProps &macros, const Locale &locale);
LocalizedNumberRangeFormatter(impl::RangeMacroProps &&macros, const Locale &locale);
void clear();
// To give the fluent setters access to this class's constructor:
friend class NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter>;
friend class NumberRangeFormatterSettings<LocalizedNumberRangeFormatter>;
// To give UnlocalizedNumberRangeFormatter::locale() access to this class's constructor:
friend class UnlocalizedNumberRangeFormatter;
};
/**
* The result of a number range formatting operation. This class allows the result to be exported in several data types,
* including a UnicodeString and a FieldPositionIterator.
*
* @draft ICU 63
*/
class U_I18N_API FormattedNumberRange : public UMemory {
public:
/**
* Returns a UnicodeString representation of the formatted number range.
*
* @param status
* Set if an error occurs while formatting the number to the UnicodeString.
* @return a UnicodeString containing the localized number range.
* @draft ICU 63
*/
UnicodeString toString(UErrorCode& status) const;
/**
* Appends the formatted number range to an Appendable.
*
* @param appendable
* The Appendable to which to append the formatted number range string.
* @param status
* Set if an error occurs while formatting the number range to the Appendable.
* @return The same Appendable, for chaining.
* @draft ICU 63
* @see Appendable
*/
Appendable &appendTo(Appendable &appendable, UErrorCode& status) const;
/**
* Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
* <em>field</em> in the output string. This allows you to determine the locations of, for example,
* the integer part, fraction part, or symbols.
*
* If both sides of the range have the same field, the field will occur twice, once before the
* range separator and once after the range separator, if applicable.
*
* If a field occurs just once, calling this method will find that occurrence and return it. If a
* field occurs multiple times, this method may be called repeatedly with the following pattern:
*
* <pre>
* FieldPosition fpos(UNUM_INTEGER_FIELD);
* while (formattedNumberRange.nextFieldPosition(fpos, status)) {
* // do something with fpos.
* }
* </pre>
*
* This method is useful if you know which field to query. If you want all available field position
* information, use #getAllFieldPositions().
*
* @param fieldPosition
* Input+output variable. See {@link FormattedNumber#nextFieldPosition}.
* @param status
* Set if an error occurs while populating the FieldPosition.
* @return TRUE if a new occurrence of the field was found; FALSE otherwise.
* @draft ICU 63
* @see UNumberFormatFields
*/
UBool nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const;
/**
* Export the formatted number range to a FieldPositionIterator. This allows you to determine which characters in
* the output string correspond to which <em>fields</em>, such as the integer part, fraction part, and sign.
*
* If information on only one field is needed, use #nextFieldPosition() instead.
*
* @param iterator
* The FieldPositionIterator to populate with all of the fields present in the formatted number.
* @param status
* Set if an error occurs while populating the FieldPositionIterator.
* @draft ICU 63
* @see UNumberFormatFields
*/
void getAllFieldPositions(FieldPositionIterator &iterator, UErrorCode &status) const;
/**
* Export the first formatted number as a decimal number. This endpoint
* is useful for obtaining the exact number being printed after scaling
* and rounding have been applied by the number range formatting pipeline.
*
* The syntax of the unformatted number is a "numeric string"
* as defined in the Decimal Arithmetic Specification, available at
* http://speleotrove.com/decimal
*
* @return A decimal representation of the first formatted number.
* @draft ICU 63
* @see NumberRangeFormatter
* @see #getSecondDecimal
*/
UnicodeString getFirstDecimal(UErrorCode& status) const;
/**
* Export the second formatted number as a decimal number. This endpoint
* is useful for obtaining the exact number being printed after scaling
* and rounding have been applied by the number range formatting pipeline.
*
* The syntax of the unformatted number is a "numeric string"
* as defined in the Decimal Arithmetic Specification, available at
* http://speleotrove.com/decimal
*
* @return A decimal representation of the second formatted number.
* @draft ICU 63
* @see NumberRangeFormatter
* @see #getFirstDecimal
*/
UnicodeString getSecondDecimal(UErrorCode& status) const;
/**
* Returns whether the pair of numbers was successfully formatted as a range or whether an identity fallback was
* used. For example, if the first and second number were the same either before or after rounding occurred, an
* identity fallback was used.
*
* @return An indication the resulting identity situation in the formatted number range.
* @draft ICU 63
* @see UNumberRangeIdentityFallback
*/
UNumberRangeIdentityResult getIdentityResult(UErrorCode& status) const;
/**
* Copying not supported; use move constructor instead.
*/
FormattedNumberRange(const FormattedNumberRange&) = delete;
/**
* Copying not supported; use move assignment instead.
*/
FormattedNumberRange& operator=(const FormattedNumberRange&) = delete;
/**
* Move constructor:
* Leaves the source FormattedNumberRange in an undefined state.
* @draft ICU 63
*/
FormattedNumberRange(FormattedNumberRange&& src) U_NOEXCEPT;
/**
* Move assignment:
* Leaves the source FormattedNumberRange in an undefined state.
* @draft ICU 63
*/
FormattedNumberRange& operator=(FormattedNumberRange&& src) U_NOEXCEPT;
/**
* Destruct an instance of FormattedNumberRange, cleaning up any memory it might own.
* @draft ICU 63
*/
~FormattedNumberRange();
private:
// Can't use LocalPointer because UFormattedNumberRangeData is forward-declared
const impl::UFormattedNumberRangeData *fResults;
// Error code for the terminal methods
UErrorCode fErrorCode;
/**
* Internal constructor from data type. Adopts the data pointer.
* @internal
*/
explicit FormattedNumberRange(impl::UFormattedNumberRangeData *results)
: fResults(results), fErrorCode(U_ZERO_ERROR) {};
explicit FormattedNumberRange(UErrorCode errorCode)
: fResults(nullptr), fErrorCode(errorCode) {};
void getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih, UErrorCode& status) const;
// To give LocalizedNumberRangeFormatter format methods access to this class's constructor:
friend class LocalizedNumberRangeFormatter;
};
/**
* See the main description in numberrangeformatter.h for documentation and examples.
*
* @draft ICU 63
*/
class U_I18N_API NumberRangeFormatter final {
public:
/**
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is not currently
* known at the call site.
*
* @return An {@link UnlocalizedNumberRangeFormatter}, to be used for chaining.
* @draft ICU 63
*/
static UnlocalizedNumberRangeFormatter with();
/**
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is known at the call
* site.
*
* @param locale
* The locale from which to load formats and symbols for number range formatting.
* @return A {@link LocalizedNumberRangeFormatter}, to be used for chaining.
* @draft ICU 63
*/
static LocalizedNumberRangeFormatter withLocale(const Locale &locale);
/**
* Use factory methods instead of the constructor to create a NumberFormatter.
*/
NumberRangeFormatter() = delete;
};
} // namespace number
U_NAMESPACE_END
#endif // U_HIDE_DRAFT_API
#endif // __NUMBERRANGEFORMATTER_H__
#endif /* #if !UCONFIG_NO_FORMATTING */

View File

@ -48,8 +48,8 @@ int main(int argc, char **argv)
/* Protos */
static void usage(void);
static void version(void);
static void date(UDate when, const UChar *tz, UDateFormatStyle style, const char *format, UErrorCode *status);
static UDate getWhen(const char *millis, const char *seconds, const char *format, UDateFormatStyle style, const char *parse, const UChar *tz, UErrorCode *status);
static void date(UDate when, const UChar *tz, UDateFormatStyle style, const char *format, const char *locale, UErrorCode *status);
static UDate getWhen(const char *millis, const char *seconds, const char *format, const char *locale, UDateFormatStyle style, const char *parse, const UChar *tz, UErrorCode *status);
UConverter *cnv = NULL;
@ -74,6 +74,7 @@ main(int argc,
UDateFormatStyle style = UDAT_DEFAULT;
UErrorCode status = U_ZERO_ERROR;
const char *format = NULL;
const char *locale = NULL;
char *parse = NULL;
char *seconds = NULL;
char *millis = NULL;
@ -132,6 +133,12 @@ main(int argc,
parse = argv[optInd];
}
}
else if (strcmp(arg, "-L") == 0) {
if (optInd + 1 < argc) {
optInd++;
locale = argv[optInd];
}
}
/* POSIX.1 says all arguments after -- are not options */
else if(strcmp(arg, "--") == 0) {
/* skip the -- */
@ -162,13 +169,13 @@ main(int argc,
}
/* get the 'when' (or now) */
when = getWhen(millis, seconds, format, style, parse, tz, &status);
when = getWhen(millis, seconds, format, locale, style, parse, tz, &status);
if(parse != NULL) {
format = FORMAT_MILLIS; /* output in millis */
}
/* print the date */
date(when, tz, style, format, &status);
date(when, tz, style, format, locale, &status);
ucnv_close(cnv);
@ -194,6 +201,7 @@ usage()
puts(" -r <seconds> Use <seconds> as the time (Epoch 1970) rather than now.");
puts(" -R <millis> Use <millis> as the time (Epoch 1970) rather than now.");
puts(" -P <string> Parse <string> as the time, output in millis format.");
puts(" -L <string> Use the locale <string> instead of the default ICU locale.");
}
/* Version information */
@ -245,6 +253,7 @@ date(UDate when,
const UChar *tz,
UDateFormatStyle style,
const char *format,
const char *locale,
UErrorCode *status )
{
UChar *s = 0;
@ -264,7 +273,7 @@ date(UDate when,
}
}
fmt = udat_open(style, style, 0, tz, -1,NULL,0, status);
fmt = udat_open(style, style, locale, tz, -1,NULL,0, status);
if ( format != NULL ) {
charsToUCharsDefault(uFormat,sizeof(uFormat)/sizeof(uFormat[0]),format,-1,status);
udat_applyPattern(fmt,FALSE,uFormat,-1);
@ -292,7 +301,7 @@ date(UDate when,
free(s);
}
static UDate getWhen(const char *millis, const char *seconds, const char *format,
static UDate getWhen(const char *millis, const char *seconds, const char *format, const char *locale,
UDateFormatStyle style, const char *parse, const UChar *tz, UErrorCode *status) {
UDateFormat *fmt = NULL;
UChar uFormat[100];
@ -319,7 +328,7 @@ static UDate getWhen(const char *millis, const char *seconds, const char *format
}
}
fmt = udat_open(style, style, 0, tz, -1,NULL,0, status);
fmt = udat_open(style, style, locale, tz, -1,NULL,0, status);
if ( format != NULL ) {
charsToUCharsDefault(uFormat,sizeof(uFormat)/sizeof(uFormat[0]), format,-1,status);
udat_applyPattern(fmt,FALSE,uFormat,-1);

View File

@ -251,6 +251,7 @@ void addLocaleTest(TestNode** root)
TESTCASE(TestLikelySubtags);
TESTCASE(TestToLanguageTag);
TESTCASE(TestForLanguageTag);
TESTCASE(TestInvalidLanguageTag);
TESTCASE(TestTrailingNull);
TESTCASE(TestUnicodeDefines);
TESTCASE(TestEnglishExemplarCharacters);
@ -6030,6 +6031,9 @@ static const struct {
{"ja-u-ijkl-efgh-abcd-ca-japanese-xx-yyy-zzz-kn", "ja@attribute=abcd-efgh-ijkl;calendar=japanese;colnumeric=yes;xx=yyy-zzz", FULL_LENGTH},
{"de-u-xc-xphonebk-co-phonebk-ca-buddhist-mo-very-lo-extensi-xd-that-de-should-vc-probably-xz-killthebuffer",
"de@calendar=buddhist;collation=phonebook;de=should;lo=extensi;mo=very;vc=probably;xc=xphonebk;xd=that;xz=yes", 91},
{"de-1901-1901", "de__1901", 7},
{"de-DE-1901-1901", "de_DE_1901", 10},
{"en-a-bbb-a-ccc", "en@a=bbb", 8},
/* #12761 */
{"en-a-bar-u-baz", "en@a=bar;attribute=baz", FULL_LENGTH},
{"en-a-bar-u-baz-x-u-foo", "en@a=bar;attribute=baz;x=u-foo", FULL_LENGTH},
@ -6047,6 +6051,11 @@ static const struct {
{"zh-cmn-TW", "cmn_TW", FULL_LENGTH},
{"zh-x_t-ab", "zh", 2},
{"zh-hans-cn-u-ca-x_t-u", "zh_Hans_CN@calendar=yes", 15},
/* #20140 dupe keys in U-extension */
{"zh-u-ca-chinese-ca-gregory", "zh@calendar=chinese", FULL_LENGTH},
{"zh-u-ca-gregory-co-pinyin-ca-chinese", "zh@calendar=gregorian;collation=pinyin", FULL_LENGTH},
{"de-latn-DE-1901-u-co-phonebk-co-pinyin-ca-gregory", "de_Latn_DE_1901@calendar=gregorian;collation=phonebook", FULL_LENGTH},
{"th-u-kf-nu-thai-kf-false", "th@colcasefirst=yes;numbers=thai", FULL_LENGTH},
{NULL, NULL, 0}
};
@ -6081,6 +6090,35 @@ static void TestForLanguageTag(void) {
}
}
/* See https://unicode-org.atlassian.net/browse/ICU-20149 .
* Depending on the resolution of that bug, this test may have
* to be revised.
*/
static void TestInvalidLanguageTag(void) {
static const char* invalid_lang_tags[] = {
"zh-u-foo-foo-co-pinyin", /* duplicate attribute in U extension */
"zh-cmn-hans-u-foo-foo-co-pinyin", /* duplicate attribute in U extension */
#if 0
/*
* These do not lead to an error. Instead, parsing stops at the 1st
* invalid subtag.
*/
"de-DE-1901-1901", /* duplicate variant */
"en-a-bbb-a-ccc", /* duplicate extension */
#endif
NULL
};
char locale[256];
for (const char** tag = invalid_lang_tags; *tag != NULL; tag++) {
UErrorCode status = U_ZERO_ERROR;
uloc_forLanguageTag(*tag, locale, sizeof(locale), NULL, &status);
if (status != U_ILLEGAL_ARGUMENT_ERROR) {
log_err("Error returned by uloc_forLanguageTag for input language tag [%s] : %s - expected error: %s\n",
*tag, u_errorName(status), u_errorName(U_ILLEGAL_ARGUMENT_ERROR));
}
}
}
static void TestToUnicodeLocaleKey(void)
{
/* $IN specifies the result should be the input pointer itself */

View File

@ -123,6 +123,7 @@ static void TestLikelySubtags(void);
* lanuage tag
*/
static void TestForLanguageTag(void);
static void TestInvalidLanguageTag(void);
static void TestToLanguageTag(void);
static void TestToUnicodeLocaleKey(void);

View File

@ -164,7 +164,6 @@ library: common
ubidi ushape ubiditransform
listformatter
resourcebundle service_registration resbund_cnv ures_cnv icudataver ucat
loclikely
currency
locale_display_names2
conversion converter_selector ucnv_set ucnvdisp
@ -383,7 +382,7 @@ group: cstr
group: uscript
uscript.o # uscript_getCode() accepts a locale ID and loads its script code data
deps
propname loclikely
propname resourcebundle
group: uscript_props # script metadata properties
uscript_props.o
@ -583,7 +582,7 @@ group: locale_display_names2
group: currency
ucurr.o
deps
loclikely resourcebundle ulist ustring_case_locale
resourcebundle ulist ustring_case_locale
stdlib_qsort # for ucurr.o (which does not use ICU's uarrsort.o)
static_unicode_sets usetiter
@ -592,11 +591,6 @@ group: icudataver # u_getDataVersion()
deps
resourcebundle
group: loclikely
loclikely.o
deps
resourcebundle uscript_props propname
group: locresdata
# This was intended to collect locale functions that load resource bundle data.
# See the resourcebundle group about what else loads data.
@ -631,9 +625,12 @@ group: resourcebundle
locid.o locmap.o wintz.o
# Do we need class LocaleBased? http://bugs.icu-project.org/trac/ticket/8608
locbased.o
loclikely.o
deps
udata ucol_swp
sort stringenumeration uhash uvector
uscript_props propname
bytesinkutil
group: udata
udata.o ucmndata.o udatamem.o
@ -832,7 +829,6 @@ group: localedata
deps
uniset_props resourcebundle
uset_props # TODO: change to using C++ UnicodeSet, remove this dependency
loclikely
group: genderinfo
gender.o

View File

@ -66,7 +66,7 @@ numbertest_affixutils.o numbertest_api.o numbertest_decimalquantity.o \
numbertest_modifiers.o numbertest_patternmodifier.o numbertest_patternstring.o \
numbertest_stringbuilder.o numbertest_stringsegment.o \
numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \
static_unisets_test.o numfmtdatadriventest.o erarulestest.o
static_unisets_test.o numfmtdatadriventest.o numbertest_range.o erarulestest.o
DEPS = $(OBJECTS:.o=.d)

View File

@ -256,6 +256,7 @@
<ClCompile Include="numbertest_parse.cpp" />
<ClCompile Include="numbertest_doubleconversion.cpp" />
<ClCompile Include="numbertest_skeletons.cpp" />
<ClCompile Include="numbertest_range.cpp" />
<ClCompile Include="numfmtst.cpp" />
<ClCompile Include="numfmtdatadriventest.cpp" />
<ClCompile Include="numrgts.cpp" />

View File

@ -289,6 +289,9 @@
<ClCompile Include="numbertest_skeletons.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numbertest_range.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="numfmtst.cpp">
<Filter>formatting</Filter>
</ClCompile>

View File

@ -6,7 +6,12 @@
* others. All Rights Reserved.
********************************************************************/
#include <iterator>
#include <set>
#include <utility>
#include "loctest.h"
#include "unicode/localpointer.h"
#include "unicode/decimfmt.h"
#include "unicode/ucurr.h"
#include "unicode/smpdtfmt.h"
@ -220,9 +225,18 @@ void LocaleTest::runIndexedTest( int32_t index, UBool exec, const char* &name, c
#endif
TESTCASE_AUTO(TestSetIsBogus);
TESTCASE_AUTO(TestParallelAPIValues);
TESTCASE_AUTO(TestAddLikelySubtags);
TESTCASE_AUTO(TestMinimizeSubtags);
TESTCASE_AUTO(TestKeywordVariants);
TESTCASE_AUTO(TestCreateUnicodeKeywords);
TESTCASE_AUTO(TestKeywordVariantParsing);
TESTCASE_AUTO(TestCreateKeywordSet);
TESTCASE_AUTO(TestCreateUnicodeKeywordSet);
TESTCASE_AUTO(TestGetKeywordValueStdString);
TESTCASE_AUTO(TestGetUnicodeKeywordValueStdString);
TESTCASE_AUTO(TestSetKeywordValue);
TESTCASE_AUTO(TestSetKeywordValueStringPiece);
TESTCASE_AUTO(TestSetUnicodeKeywordValueStringPiece);
TESTCASE_AUTO(TestGetBaseName);
#if !UCONFIG_NO_FILE_IO
TESTCASE_AUTO(TestGetLocale);
@ -236,6 +250,8 @@ void LocaleTest::runIndexedTest( int32_t index, UBool exec, const char* &name, c
TESTCASE_AUTO(TestBug13554);
TESTCASE_AUTO(TestForLanguageTag);
TESTCASE_AUTO(TestToLanguageTag);
TESTCASE_AUTO(TestMoveAssign);
TESTCASE_AUTO(TestMoveCtor);
TESTCASE_AUTO_END;
}
@ -1601,6 +1617,34 @@ LocaleTest::TestSetIsBogus() {
}
void
LocaleTest::TestAddLikelySubtags() {
IcuTestErrorCode status(*this, "TestAddLikelySubtags()");
static const Locale min("sv");
static const Locale max("sv_Latn_SE");
Locale result(min);
result.addLikelySubtags(status);
status.errIfFailureAndReset("\"%s\"", min.getName());
assertEquals("addLikelySubtags", max.getName(), result.getName());
}
void
LocaleTest::TestMinimizeSubtags() {
IcuTestErrorCode status(*this, "TestMinimizeSubtags()");
static const Locale max("zh_Hant_TW");
static const Locale min("zh_TW");
Locale result(max);
result.minimizeSubtags(status);
status.errIfFailureAndReset("\"%s\"", max.getName());
assertEquals("minimizeSubtags", min.getName(), result.getName());
}
void
LocaleTest::TestKeywordVariants(void) {
static const struct {
@ -1708,6 +1752,55 @@ LocaleTest::TestKeywordVariants(void) {
}
void
LocaleTest::TestCreateUnicodeKeywords(void) {
IcuTestErrorCode status(*this, "TestCreateUnicodeKeywords()");
static const Locale l("de@calendar=buddhist;collation=phonebook");
LocalPointer<StringEnumeration> keys(l.createUnicodeKeywords(status));
status.errIfFailureAndReset("\"%s\"", l.getName());
const char* key;
int32_t resultLength;
key = keys->next(&resultLength, status);
status.errIfFailureAndReset("key #1");
assertEquals("resultLength", 2, resultLength);
assertTrue("key != nullptr", key != nullptr);
assertEquals("calendar", "ca", key);
key = keys->next(&resultLength, status);
status.errIfFailureAndReset("key #2");
assertEquals("resultLength", 2, resultLength);
assertTrue("key != nullptr", key != nullptr);
assertEquals("collation", "co", key);
key = keys->next(&resultLength, status);
status.errIfFailureAndReset("end of keys");
assertEquals("resultLength", 0, resultLength);
assertTrue("key == nullptr", key == nullptr);
const UnicodeString* skey;
keys->reset(status); // KeywordEnumeration::reset() never touches status.
skey = keys->snext(status);
status.errIfFailureAndReset("skey #1");
assertTrue("skey != nullptr", skey != nullptr);
assertEquals("calendar", "ca", *skey);
skey = keys->snext(status);
status.errIfFailureAndReset("skey #2");
assertTrue("skey != nullptr", skey != nullptr);
assertEquals("collation", "co", *skey);
skey = keys->snext(status);
status.errIfFailureAndReset("end of keys");
assertTrue("skey == nullptr", skey == nullptr);
}
void
LocaleTest::TestKeywordVariantParsing(void) {
static const struct {
@ -1739,6 +1832,74 @@ LocaleTest::TestKeywordVariantParsing(void) {
}
}
void
LocaleTest::TestCreateKeywordSet(void) {
IcuTestErrorCode status(*this, "TestCreateKeywordSet()");
static const Locale l("de@calendar=buddhist;collation=phonebook");
std::set<std::string> result;
l.getKeywords<std::string>(
std::insert_iterator<decltype(result)>(result, result.begin()),
status);
status.errIfFailureAndReset("\"%s\"", l.getName());
assertEquals("set::size()", 2, result.size());
assertTrue("set::find(\"calendar\")",
result.find("calendar") != result.end());
assertTrue("set::find(\"collation\")",
result.find("collation") != result.end());
}
void
LocaleTest::TestCreateUnicodeKeywordSet(void) {
IcuTestErrorCode status(*this, "TestCreateUnicodeKeywordSet()");
static const Locale l("de@calendar=buddhist;collation=phonebook");
std::set<std::string> result;
l.getUnicodeKeywords<std::string>(
std::insert_iterator<decltype(result)>(result, result.begin()),
status);
status.errIfFailureAndReset("\"%s\"", l.getName());
assertEquals("set::size()", 2, result.size());
assertTrue("set::find(\"ca\")",
result.find("ca") != result.end());
assertTrue("set::find(\"co\")",
result.find("co") != result.end());
}
void
LocaleTest::TestGetKeywordValueStdString(void) {
IcuTestErrorCode status(*this, "TestGetKeywordValueStdString()");
static const char tag[] = "fa-u-nu-latn";
static const char keyword[] = "numbers";
static const char expected[] = "latn";
Locale l = Locale::forLanguageTag(tag, status);
status.errIfFailureAndReset("\"%s\"", tag);
std::string result = l.getKeywordValue<std::string>(keyword, status);
status.errIfFailureAndReset("\"%s\"", keyword);
assertEquals(keyword, expected, result.c_str());
}
void
LocaleTest::TestGetUnicodeKeywordValueStdString(void) {
IcuTestErrorCode status(*this, "TestGetUnicodeKeywordValueStdString()");
static const char keyword[] = "co";
static const char expected[] = "phonebk";
static const Locale l("de@calendar=buddhist;collation=phonebook");
std::string result = l.getUnicodeKeywordValue<std::string>(keyword, status);
status.errIfFailureAndReset("\"%s\"", keyword);
assertEquals(keyword, expected, result.c_str());
}
void
LocaleTest::TestSetKeywordValue(void) {
static const struct {
@ -1774,6 +1935,33 @@ LocaleTest::TestSetKeywordValue(void) {
}
}
void
LocaleTest::TestSetKeywordValueStringPiece(void) {
IcuTestErrorCode status(*this, "TestSetKeywordValueStringPiece()");
Locale l(Locale::getGerman());
l.setKeywordValue(StringPiece("collation"), StringPiece("phonebook"), status);
l.setKeywordValue(StringPiece("calendarxxx", 8), StringPiece("buddhistxxx", 8), status);
static const char expected[] = "de@calendar=buddhist;collation=phonebook";
assertEquals("", expected, l.getName());
}
void
LocaleTest::TestSetUnicodeKeywordValueStringPiece(void) {
IcuTestErrorCode status(*this, "TestSetUnicodeKeywordValueStringPiece()");
Locale l(Locale::getGerman());
l.setUnicodeKeywordValue(StringPiece("co"), StringPiece("phonebk"), status);
status.errIfFailureAndReset();
l.setUnicodeKeywordValue(StringPiece("caxxx", 2), StringPiece("buddhistxxx", 8), status);
status.errIfFailureAndReset();
static const char expected[] = "de@calendar=buddhist;collation=phonebook";
assertEquals("", expected, l.getName());
}
void
LocaleTest::TestGetBaseName(void) {
static const struct {
@ -2845,3 +3033,95 @@ void LocaleTest::TestToLanguageTag() {
assertEquals("bogus", U_ILLEGAL_ARGUMENT_ERROR, status.reset());
assertTrue(result_bogus.c_str(), result_bogus.empty());
}
void LocaleTest::TestMoveAssign() {
// ULOC_FULLNAME_CAPACITY == 157 (uloc.h)
Locale l1("de@collation=phonebook;x="
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
"aaaaabbbbbzz");
Locale l2;
{
Locale l3(l1);
assertTrue("l1 == l3", l1 == l3);
l2 = std::move(l3);
assertTrue("l1 == l2", l1 == l2);
assertTrue("l2 != l3", l2.getName() != l3.getName());
}
// This should remain true also after l3 has been destructed.
assertTrue("l1 == l2, again", l1 == l2);
Locale l4("de@collation=phonebook");
Locale l5;
{
Locale l6(l4);
assertTrue("l4 == l6", l4 == l6);
l5 = std::move(l6);
assertTrue("l4 == l5", l4 == l5);
assertTrue("l5 != l6", l5.getName() != l6.getName());
}
// This should remain true also after l6 has been destructed.
assertTrue("l4 == l5, again", l4 == l5);
Locale l7("vo_Cyrl_AQ_EURO");
Locale l8;
{
Locale l9(l7);
assertTrue("l7 == l9", l7 == l9);
l8 = std::move(l9);
assertTrue("l7 == l8", l7 == l8);
assertTrue("l8 != l9", l8.getName() != l9.getName());
}
// This should remain true also after l9 has been destructed.
assertTrue("l7 == l8, again", l7 == l8);
assertEquals("language", l7.getLanguage(), l8.getLanguage());
assertEquals("script", l7.getScript(), l8.getScript());
assertEquals("country", l7.getCountry(), l8.getCountry());
assertEquals("variant", l7.getVariant(), l8.getVariant());
assertEquals("bogus", l7.isBogus(), l8.isBogus());
}
void LocaleTest::TestMoveCtor() {
// ULOC_FULLNAME_CAPACITY == 157 (uloc.h)
Locale l1("de@collation=phonebook;x="
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
"aaaaabbbbbcccccdddddeeeeefffffggggghhhhh"
"aaaaabbbbbzz");
Locale l3(l1);
assertTrue("l1 == l3", l1 == l3);
Locale l2(std::move(l3));
assertTrue("l1 == l2", l1 == l2);
assertTrue("l2 != l3", l2.getName() != l3.getName());
Locale l4("de@collation=phonebook");
Locale l6(l4);
assertTrue("l4 == l6", l4 == l6);
Locale l5(std::move(l6));
assertTrue("l4 == l5", l4 == l5);
assertTrue("l5 != l6", l5.getName() != l6.getName());
Locale l7("vo_Cyrl_AQ_EURO");
Locale l9(l7);
assertTrue("l7 == l9", l7 == l9);
Locale l8(std::move(l9));
assertTrue("l7 == l8", l7 == l8);
assertTrue("l8 != l9", l8.getName() != l9.getName());
assertEquals("language", l7.getLanguage(), l8.getLanguage());
assertEquals("script", l7.getScript(), l8.getScript());
assertEquals("country", l7.getCountry(), l8.getCountry());
assertEquals("variant", l7.getVariant(), l8.getVariant());
assertEquals("bogus", l7.isBogus(), l8.isBogus());
}

View File

@ -72,14 +72,21 @@ public:
void TestVariantParsing(void);
/* Test getting keyword enumeratin */
/* Test getting keyword enumeration */
void TestKeywordVariants(void);
void TestCreateUnicodeKeywords(void);
/* Test getting keyword values */
void TestKeywordVariantParsing(void);
void TestCreateKeywordSet(void);
void TestCreateUnicodeKeywordSet(void);
void TestGetKeywordValueStdString(void);
void TestGetUnicodeKeywordValueStdString(void);
/* Test setting keyword values */
void TestSetKeywordValue(void);
void TestSetKeywordValueStringPiece(void);
void TestSetUnicodeKeywordValueStringPiece(void);
/* Test getting the locale base name */
void TestGetBaseName(void);
@ -108,9 +115,15 @@ public:
void TestBug13277();
void TestBug13554();
void TestAddLikelySubtags();
void TestMinimizeSubtags();
void TestForLanguageTag();
void TestToLanguageTag();
void TestMoveAssign();
void TestMoveCtor();
private:
void _checklocs(const char* label,
const char* req,

View File

@ -11,6 +11,8 @@
#include "number_affixutils.h"
#include "numparse_stringsegment.h"
#include "unicode/locid.h"
#include "unicode/numberformatter.h"
#include "unicode/numberrangeformatter.h"
using namespace icu::number;
using namespace icu::number::impl;
@ -244,6 +246,54 @@ class NumberSkeletonTest : public IntlTest {
void expectedErrorSkeleton(const char16_t** cases, int32_t casesLen);
};
class NumberRangeFormatterTest : public IntlTest {
public:
NumberRangeFormatterTest();
NumberRangeFormatterTest(UErrorCode &status);
void testSanity();
void testBasic();
void testCollapse();
void testIdentity();
void testDifferentFormatters();
void testPlurals();
void testCopyMove();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
private:
CurrencyUnit USD;
CurrencyUnit GBP;
CurrencyUnit PTE;
MeasureUnit METER;
MeasureUnit KILOMETER;
MeasureUnit FAHRENHEIT;
MeasureUnit KELVIN;
void assertFormatRange(
const char16_t* message,
const UnlocalizedNumberRangeFormatter& f,
Locale locale,
const char16_t* expected_10_50,
const char16_t* expected_49_51,
const char16_t* expected_50_50,
const char16_t* expected_00_30,
const char16_t* expected_00_00,
const char16_t* expected_30_3K,
const char16_t* expected_30K_50K,
const char16_t* expected_49K_51K,
const char16_t* expected_50K_50K,
const char16_t* expected_50K_50M);
void assertFormattedRangeEquals(
const char16_t* message,
const LocalizedNumberRangeFormatter& l,
double first,
double second,
const char16_t* expected);
};
// NOTE: This macro is identical to the one in itformat.cpp
#define TESTCLASS(id, TestClass) \
@ -276,6 +326,7 @@ class NumberTest : public IntlTest {
TESTCLASS(8, StringSegmentTest);
TESTCLASS(9, NumberParserTest);
TESTCLASS(10, NumberSkeletonTest);
TESTCLASS(11, NumberRangeFormatterTest);
default: name = ""; break; // needed to end loop
}
}

View File

@ -17,6 +17,7 @@
#include "unicode/utypes.h"
// Horrible workaround for the lack of a status code in the constructor...
// (Also affects numbertest_range.cpp)
UErrorCode globalNumberFormatterApiTestStatus = U_ZERO_ERROR;
NumberFormatterApiTest::NumberFormatterApiTest()
@ -2371,8 +2372,15 @@ void NumberFormatterApiTest::copyMove() {
assertTrue("[constructor] Source should be reset after move", l1.getCompiled() == nullptr);
// Reset l1 and l2 to check for macro-props copying for behavior testing
// Make the test more interesting: also warm them up with a compiled formatter.
l1 = NumberFormatter::withLocale("en");
l1.formatInt(1, status);
l1.formatInt(1, status);
l1.formatInt(1, status);
l2 = NumberFormatter::withLocale("en");
l2.formatInt(1, status);
l2.formatInt(1, status);
l2.formatInt(1, status);
// Copy assignment
l1 = l3;

View File

@ -162,12 +162,12 @@ void ModifiersTest::assertModifierEquals(const Modifier &mod, NumberStringBuilde
UErrorCode &status) {
int32_t oldCount = sb.codePointCount();
mod.apply(sb, 0, sb.length(), status);
assertEquals("Prefix length", expectedPrefixLength, mod.getPrefixLength(status));
assertEquals("Prefix length", expectedPrefixLength, mod.getPrefixLength());
assertEquals("Strong", expectedStrong, mod.isStrong());
if (dynamic_cast<const CurrencySpacingEnabledModifier*>(&mod) == nullptr) {
// i.e., if mod is not a CurrencySpacingEnabledModifier
assertEquals("Code point count equals actual code point count",
sb.codePointCount() - oldCount, mod.getCodePointCount(status));
sb.codePointCount() - oldCount, mod.getCodePointCount());
}
UnicodeString debugString;

View File

@ -170,14 +170,14 @@ void PatternModifierTest::testMutableEqualsImmutable() {
UnicodeString PatternModifierTest::getPrefix(const MutablePatternModifier &mod, UErrorCode &status) {
NumberStringBuilder nsb;
mod.apply(nsb, 0, 0, status);
int32_t prefixLength = mod.getPrefixLength(status);
int32_t prefixLength = mod.getPrefixLength();
return UnicodeString(nsb.toUnicodeString(), 0, prefixLength);
}
UnicodeString PatternModifierTest::getSuffix(const MutablePatternModifier &mod, UErrorCode &status) {
NumberStringBuilder nsb;
mod.apply(nsb, 0, 0, status);
int32_t prefixLength = mod.getPrefixLength(status);
int32_t prefixLength = mod.getPrefixLength();
return UnicodeString(nsb.toUnicodeString(), prefixLength, nsb.length() - prefixLength);
}

View File

@ -0,0 +1,793 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
#include "numbertest.h"
#include "unicode/numberrangeformatter.h"
#include <cmath>
#include <numparse_affixes.h>
// Horrible workaround for the lack of a status code in the constructor...
// (Also affects numbertest_api.cpp)
UErrorCode globalNumberRangeFormatterTestStatus = U_ZERO_ERROR;
NumberRangeFormatterTest::NumberRangeFormatterTest()
: NumberRangeFormatterTest(globalNumberRangeFormatterTestStatus) {
}
NumberRangeFormatterTest::NumberRangeFormatterTest(UErrorCode& status)
: USD(u"USD", status),
GBP(u"GBP", status),
PTE(u"PTE", status) {
// Check for error on the first MeasureUnit in case there is no data
LocalPointer<MeasureUnit> unit(MeasureUnit::createMeter(status));
if (U_FAILURE(status)) {
dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status));
return;
}
METER = *unit;
KILOMETER = *LocalPointer<MeasureUnit>(MeasureUnit::createKilometer(status));
FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status));
KELVIN = *LocalPointer<MeasureUnit>(MeasureUnit::createKelvin(status));
}
void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
if (exec) {
logln("TestSuite NumberRangeFormatterTest: ");
}
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testSanity);
TESTCASE_AUTO(testBasic);
TESTCASE_AUTO(testCollapse);
TESTCASE_AUTO(testIdentity);
TESTCASE_AUTO(testDifferentFormatters);
TESTCASE_AUTO(testPlurals);
TESTCASE_AUTO(testCopyMove);
TESTCASE_AUTO_END;
}
void NumberRangeFormatterTest::testSanity() {
IcuTestErrorCode status(*this, "testSanity");
LocalizedNumberRangeFormatter lnrf1 = NumberRangeFormatter::withLocale("en-us");
LocalizedNumberRangeFormatter lnrf2 = NumberRangeFormatter::with().locale("en-us");
assertEquals("Formatters should have same behavior 1",
lnrf1.formatFormattableRange(4, 6, status).toString(status),
lnrf2.formatFormattableRange(4, 6, status).toString(status));
}
void NumberRangeFormatterTest::testBasic() {
assertFormatRange(
u"Basic",
NumberRangeFormatter::with(),
Locale("en-us"),
u"15",
u"~5",
u"~5",
u"03",
u"~0",
u"33,000",
u"3,0005,000",
u"4,9995,001",
u"~5,000",
u"5,0005,000,000");
assertFormatRange(
u"Basic with units",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with().unit(METER)),
Locale("en-us"),
u"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33,000 m",
u"3,0005,000 m",
u"4,9995,001 m",
u"~5,000 m",
u"5,0005,000,000 m");
assertFormatRange(
u"Basic with different units",
NumberRangeFormatter::with()
.numberFormatterFirst(NumberFormatter::with().unit(METER))
.numberFormatterSecond(NumberFormatter::with().unit(KILOMETER)),
Locale("en-us"),
u"1 m 5 km",
u"5 m 5 km",
u"5 m 5 km",
u"0 m 3 km",
u"0 m 0 km",
u"3 m 3,000 km",
u"3,000 m 5,000 km",
u"4,999 m 5,001 km",
u"5,000 m 5,000 km",
u"5,000 m 5,000,000 km");
assertFormatRange(
u"Basic long unit",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
Locale("en-us"),
u"15 meters",
u"~5 meters",
u"~5 meters",
u"03 meters",
u"~0 meters",
u"33,000 meters",
u"3,0005,000 meters",
u"4,9995,001 meters",
u"~5,000 meters",
u"5,0005,000,000 meters");
assertFormatRange(
u"Non-English locale and unit",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
Locale("fr-FR"),
u"15 degrés Fahrenheit",
u"~5 degrés Fahrenheit",
u"~5 degrés Fahrenheit",
u"03 degrés Fahrenheit",
u"~0 degré Fahrenheit",
u"33 000 degrés Fahrenheit",
u"3 0005 000 degrés Fahrenheit",
u"4 9995 001 degrés Fahrenheit",
u"~5 000 degrés Fahrenheit",
u"5 0005 000 000 degrés Fahrenheit");
assertFormatRange(
u"Locale with custom range separator",
NumberRangeFormatter::with(),
Locale("ja"),
u"15",
u"~5",
u"~5",
u"03",
u"~0",
u"33,000",
u"3,0005,000",
u"4,9995,001",
u"~5,000",
u"5,0005,000,000");
assertFormatRange(
u"Locale that already has spaces around range separator",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_NONE)
.numberFormatterBoth(NumberFormatter::with().unit(KELVIN)),
Locale("hr"),
u"1 K 5 K",
u"~5 K",
u"~5 K",
u"0 K 3 K",
u"~0 K",
u"3 K 3.000 K",
u"3.000 K 5.000 K",
u"4.999 K 5.001 K",
u"~5.000 K",
u"5.000 K 5.000.000 K");
assertFormatRange(
u"Locale with custom numbering system and no plural ranges data",
NumberRangeFormatter::with(),
Locale("shn@numbers=beng"),
// 012459 = ০১৩৪৫৯
u"১–৫",
u"~৫",
u"~৫",
u"০–৩",
u"~",
u"৩–৩,",
u"৩,০০০–৫,",
u",৯৯৯–৫,০০১",
u"~৫,",
u"৫,০০০–৫,,");
assertFormatRange(
u"Portuguese currency",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with().unit(PTE)),
Locale("pt-PT"),
u"1$00 - 5$00 \u200B",
u"~5$00 \u200B",
u"~5$00 \u200B",
u"0$00 - 3$00 \u200B",
u"~0$00 \u200B",
u"3$00 - 3000$00 \u200B",
u"3000$00 - 5000$00 \u200B",
u"4999$00 - 5001$00 \u200B",
u"~5000$00 \u200B",
u"5000$00 - 5,000,000$00 \u200B");
}
void NumberRangeFormatterTest::testCollapse() {
assertFormatRange(
u"Default collapse on currency (default rounding)",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with().unit(USD)),
Locale("en-us"),
u"$1.00 $5.00",
u"~$5.00",
u"~$5.00",
u"$0.00 $3.00",
u"~$0.00",
u"$3.00 $3,000.00",
u"$3,000.00 $5,000.00",
u"$4,999.00 $5,001.00",
u"~$5,000.00",
u"$5,000.00 $5,000,000.00");
assertFormatRange(
u"Default collapse on currency",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
Locale("en-us"),
u"$1 $5",
u"~$5",
u"~$5",
u"$0 $3",
u"~$0",
u"$3 $3,000",
u"$3,000 $5,000",
u"$4,999 $5,001",
u"~$5,000",
u"$5,000 $5,000,000");
assertFormatRange(
u"No collapse on currency",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_NONE)
.numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
Locale("en-us"),
u"$1 $5",
u"~$5",
u"~$5",
u"$0 $3",
u"~$0",
u"$3 $3,000",
u"$3,000 $5,000",
u"$4,999 $5,001",
u"~$5,000",
u"$5,000 $5,000,000");
assertFormatRange(
u"Unit collapse on currency",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
.numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
Locale("en-us"),
u"$15",
u"~$5",
u"~$5",
u"$03",
u"~$0",
u"$33,000",
u"$3,0005,000",
u"$4,9995,001",
u"~$5,000",
u"$5,0005,000,000");
assertFormatRange(
u"All collapse on currency",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_ALL)
.numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
Locale("en-us"),
u"$15",
u"~$5",
u"~$5",
u"$03",
u"~$0",
u"$33,000",
u"$3,0005,000",
u"$4,9995,001",
u"~$5,000",
u"$5,0005,000,000");
assertFormatRange(
u"Default collapse on currency ISO code",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with()
.unit(GBP)
.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
.precision(Precision::integer())),
Locale("en-us"),
u"GBP 15",
u"~GBP 5", // TODO: Fix this at some point
u"~GBP 5",
u"GBP 03",
u"~GBP 0",
u"GBP 33,000",
u"GBP 3,0005,000",
u"GBP 4,9995,001",
u"~GBP 5,000",
u"GBP 5,0005,000,000");
assertFormatRange(
u"No collapse on currency ISO code",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_NONE)
.numberFormatterBoth(NumberFormatter::with()
.unit(GBP)
.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
.precision(Precision::integer())),
Locale("en-us"),
u"GBP 1 GBP 5",
u"~GBP 5", // TODO: Fix this at some point
u"~GBP 5",
u"GBP 0 GBP 3",
u"~GBP 0",
u"GBP 3 GBP 3,000",
u"GBP 3,000 GBP 5,000",
u"GBP 4,999 GBP 5,001",
u"~GBP 5,000",
u"GBP 5,000 GBP 5,000,000");
assertFormatRange(
u"Unit collapse on currency ISO code",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
.numberFormatterBoth(NumberFormatter::with()
.unit(GBP)
.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
.precision(Precision::integer())),
Locale("en-us"),
u"GBP 15",
u"~GBP 5", // TODO: Fix this at some point
u"~GBP 5",
u"GBP 03",
u"~GBP 0",
u"GBP 33,000",
u"GBP 3,0005,000",
u"GBP 4,9995,001",
u"~GBP 5,000",
u"GBP 5,0005,000,000");
assertFormatRange(
u"All collapse on currency ISO code",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_ALL)
.numberFormatterBoth(NumberFormatter::with()
.unit(GBP)
.unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
.precision(Precision::integer())),
Locale("en-us"),
u"GBP 15",
u"~GBP 5", // TODO: Fix this at some point
u"~GBP 5",
u"GBP 03",
u"~GBP 0",
u"GBP 33,000",
u"GBP 3,0005,000",
u"GBP 4,9995,001",
u"~GBP 5,000",
u"GBP 5,0005,000,000");
// Default collapse on measurement unit is in testBasic()
assertFormatRange(
u"No collapse on measurement unit",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_NONE)
.numberFormatterBoth(NumberFormatter::with().unit(METER)),
Locale("en-us"),
u"1 m 5 m",
u"~5 m",
u"~5 m",
u"0 m 3 m",
u"~0 m",
u"3 m 3,000 m",
u"3,000 m 5,000 m",
u"4,999 m 5,001 m",
u"~5,000 m",
u"5,000 m 5,000,000 m");
assertFormatRange(
u"Unit collapse on measurement unit",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
.numberFormatterBoth(NumberFormatter::with().unit(METER)),
Locale("en-us"),
u"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33,000 m",
u"3,0005,000 m",
u"4,9995,001 m",
u"~5,000 m",
u"5,0005,000,000 m");
assertFormatRange(
u"All collapse on measurement unit",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_ALL)
.numberFormatterBoth(NumberFormatter::with().unit(METER)),
Locale("en-us"),
u"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33,000 m",
u"3,0005,000 m",
u"4,9995,001 m",
u"~5,000 m",
u"5,0005,000,000 m");
assertFormatRange(
u"Default collapse, long-form compact notation",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
Locale("de-CH"),
u"15",
u"~5",
u"~5",
u"03",
u"~0",
u"33 Tausend",
u"35 Tausend",
u"~5 Tausend",
u"~5 Tausend",
u"5 Tausend 5 Millionen");
assertFormatRange(
u"Unit collapse, long-form compact notation",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
Locale("de-CH"),
u"15",
u"~5",
u"~5",
u"03",
u"~0",
u"33 Tausend",
u"3 Tausend 5 Tausend",
u"~5 Tausend",
u"~5 Tausend",
u"5 Tausend 5 Millionen");
assertFormatRange(
u"Default collapse on measurement unit with compact-short notation",
NumberRangeFormatter::with()
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
Locale("en-us"),
u"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33K m",
u"3K 5K m",
u"~5K m",
u"~5K m",
u"5K 5M m");
assertFormatRange(
u"No collapse on measurement unit with compact-short notation",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_NONE)
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
Locale("en-us"),
u"1 m 5 m",
u"~5 m",
u"~5 m",
u"0 m 3 m",
u"~0 m",
u"3 m 3K m",
u"3K m 5K m",
u"~5K m",
u"~5K m",
u"5K m 5M m");
assertFormatRange(
u"Unit collapse on measurement unit with compact-short notation",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_UNIT)
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
Locale("en-us"),
u"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33K m",
u"3K 5K m",
u"~5K m",
u"~5K m",
u"5K 5M m");
assertFormatRange(
u"All collapse on measurement unit with compact-short notation",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_ALL)
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
Locale("en-us"),
u"15 m",
u"~5 m",
u"~5 m",
u"03 m",
u"~0 m",
u"33K m",
u"35K m", // this one is the key use case for ALL
u"~5K m",
u"~5K m",
u"5K 5M m");
assertFormatRange(
u"No collapse on scientific notation",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_NONE)
.numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
Locale("en-us"),
u"1E0 5E0",
u"~5E0",
u"~5E0",
u"0E0 3E0",
u"~0E0",
u"3E0 3E3",
u"3E3 5E3",
u"4.999E3 5.001E3",
u"~5E3",
u"5E3 5E6");
assertFormatRange(
u"All collapse on scientific notation",
NumberRangeFormatter::with()
.collapse(UNUM_RANGE_COLLAPSE_ALL)
.numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
Locale("en-us"),
u"15E0",
u"~5E0",
u"~5E0",
u"03E0",
u"~0E0",
u"3E0 3E3",
u"35E3",
u"4.9995.001E3",
u"~5E3",
u"5E3 5E6");
// TODO: Test compact currency?
// The code is not smart enough to differentiate the notation from the unit.
}
void NumberRangeFormatterTest::testIdentity() {
assertFormatRange(
u"Identity fallback Range",
NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_RANGE),
Locale("en-us"),
u"15",
u"55",
u"55",
u"03",
u"00",
u"33,000",
u"3,0005,000",
u"4,9995,001",
u"5,0005,000",
u"5,0005,000,000");
assertFormatRange(
u"Identity fallback Approximately or Single Value",
NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE),
Locale("en-us"),
u"15",
u"~5",
u"5",
u"03",
u"0",
u"33,000",
u"3,0005,000",
u"4,9995,001",
u"5,000",
u"5,0005,000,000");
assertFormatRange(
u"Identity fallback Single Value",
NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE),
Locale("en-us"),
u"15",
u"5",
u"5",
u"03",
u"0",
u"33,000",
u"3,0005,000",
u"4,9995,001",
u"5,000",
u"5,0005,000,000");
assertFormatRange(
u"Identity fallback Approximately or Single Value with compact notation",
NumberRangeFormatter::with()
.identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE)
.numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort())),
Locale("en-us"),
u"15",
u"~5",
u"5",
u"03",
u"0",
u"33K",
u"3K 5K",
u"~5K",
u"5K",
u"5K 5M");
}
void NumberRangeFormatterTest::testDifferentFormatters() {
assertFormatRange(
u"Different rounding rules",
NumberRangeFormatter::with()
.numberFormatterFirst(NumberFormatter::with().precision(Precision::integer()))
.numberFormatterSecond(NumberFormatter::with().precision(Precision::fixedDigits(2))),
Locale("en-us"),
u"15.0",
u"55.0",
u"55.0",
u"03.0",
u"00.0",
u"33,000",
u"3,0005,000",
u"4,9995,000",
u"5,0005,000", // TODO: Should this one be ~5,000?
u"5,0005,000,000");
}
void NumberRangeFormatterTest::testPlurals() {
IcuTestErrorCode status(*this, "testPlurals");
// Locale sl has interesting plural forms:
// GBP{
// one{"britanski funt"}
// two{"britanska funta"}
// few{"britanski funti"}
// other{"britanskih funtov"}
// }
Locale locale("sl");
UnlocalizedNumberFormatter unf = NumberFormatter::with()
.unit(GBP)
.unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
.precision(Precision::integer());
LocalizedNumberFormatter lnf = unf.locale(locale);
// For comparison, run the non-range version of the formatter
assertEquals(Int64ToUnicodeString(1), u"1 britanski funt", lnf.formatDouble(1, status).toString(status));
assertEquals(Int64ToUnicodeString(2), u"2 britanska funta", lnf.formatDouble(2, status).toString(status));
assertEquals(Int64ToUnicodeString(3), u"3 britanski funti", lnf.formatDouble(3, status).toString(status));
assertEquals(Int64ToUnicodeString(5), u"5 britanskih funtov", lnf.formatDouble(5, status).toString(status));
if (status.errIfFailureAndReset()) { return; }
LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::with()
.numberFormatterBoth(unf)
.identityFallback(UNUM_IDENTITY_FALLBACK_RANGE)
.locale(locale);
struct TestCase {
double first;
double second;
const char16_t* expected;
} cases[] = {
{1, 1, u"11 britanski funti"}, // one + one -> few
{1, 2, u"12 britanska funta"}, // one + two -> two
{1, 3, u"13 britanski funti"}, // one + few -> few
{1, 5, u"15 britanskih funtov"}, // one + other -> other
{2, 1, u"21 britanski funti"}, // two + one -> few
{2, 2, u"22 britanska funta"}, // two + two -> two
{2, 3, u"23 britanski funti"}, // two + few -> few
{2, 5, u"25 britanskih funtov"}, // two + other -> other
{3, 1, u"31 britanski funti"}, // few + one -> few
{3, 2, u"32 britanska funta"}, // few + two -> two
{3, 3, u"33 britanski funti"}, // few + few -> few
{3, 5, u"35 britanskih funtov"}, // few + other -> other
{5, 1, u"51 britanski funti"}, // other + one -> few
{5, 2, u"52 britanska funta"}, // other + two -> two
{5, 3, u"53 britanski funti"}, // other + few -> few
{5, 5, u"55 britanskih funtov"}, // other + other -> other
};
for (auto& cas : cases) {
UnicodeString message = Int64ToUnicodeString(cas.first);
message += u" ";
message += Int64ToUnicodeString(cas.second);
status.setScope(message);
UnicodeString actual = lnrf.formatFormattableRange(cas.first, cas.second, status).toString(status);
assertEquals(message, cas.expected, actual);
status.errIfFailureAndReset();
}
}
void NumberRangeFormatterTest::testCopyMove() {
IcuTestErrorCode status(*this, "testCopyMove");
// Default constructors
LocalizedNumberRangeFormatter l1;
assertEquals("Initial behavior", u"15", l1.formatFormattableRange(1, 5, status).toString(status));
if (status.errDataIfFailureAndReset()) { return; }
// Setup
l1 = NumberRangeFormatter::withLocale("fr-FR")
.numberFormatterBoth(NumberFormatter::with().unit(USD));
assertEquals("Currency behavior", u"1,005,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
// Copy constructor
LocalizedNumberRangeFormatter l2 = l1;
assertEquals("Copy constructor", u"1,005,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
// Move constructor
LocalizedNumberRangeFormatter l3 = std::move(l1);
assertEquals("Move constructor", u"1,005,00 $US", l3.formatFormattableRange(1, 5, status).toString(status));
// Reset objects for assignment tests
l1 = NumberRangeFormatter::withLocale("en-us");
l2 = NumberRangeFormatter::withLocale("en-us");
assertEquals("Rest behavior, l1", u"15", l1.formatFormattableRange(1, 5, status).toString(status));
assertEquals("Rest behavior, l2", u"15", l2.formatFormattableRange(1, 5, status).toString(status));
// Copy assignment
l1 = l3;
assertEquals("Copy constructor", u"1,005,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
// Move assignment
l2 = std::move(l3);
assertEquals("Copy constructor", u"1,005,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
// FormattedNumberRange
FormattedNumberRange result = l1.formatFormattableRange(1, 5, status);
assertEquals("FormattedNumberRange move constructor", u"1,005,00 $US", result.toString(status));
result = l1.formatFormattableRange(3, 6, status);
assertEquals("FormattedNumberRange move assignment", u"3,006,00 $US", result.toString(status));
}
void NumberRangeFormatterTest::assertFormatRange(
const char16_t* message,
const UnlocalizedNumberRangeFormatter& f,
Locale locale,
const char16_t* expected_10_50,
const char16_t* expected_49_51,
const char16_t* expected_50_50,
const char16_t* expected_00_30,
const char16_t* expected_00_00,
const char16_t* expected_30_3K,
const char16_t* expected_30K_50K,
const char16_t* expected_49K_51K,
const char16_t* expected_50K_50K,
const char16_t* expected_50K_50M) {
LocalizedNumberRangeFormatter l = f.locale(locale);
assertFormattedRangeEquals(message, l, 1, 5, expected_10_50);
assertFormattedRangeEquals(message, l, 4.9999999, 5.0000001, expected_49_51);
assertFormattedRangeEquals(message, l, 5, 5, expected_50_50);
assertFormattedRangeEquals(message, l, 0, 3, expected_00_30);
assertFormattedRangeEquals(message, l, 0, 0, expected_00_00);
assertFormattedRangeEquals(message, l, 3, 3000, expected_30_3K);
assertFormattedRangeEquals(message, l, 3000, 5000, expected_30K_50K);
assertFormattedRangeEquals(message, l, 4999, 5001, expected_49K_51K);
assertFormattedRangeEquals(message, l, 5000, 5000, expected_50K_50K);
assertFormattedRangeEquals(message, l, 5e3, 5e6, expected_50K_50M);
}
void NumberRangeFormatterTest::assertFormattedRangeEquals(
const char16_t* message,
const LocalizedNumberRangeFormatter& l,
double first,
double second,
const char16_t* expected) {
IcuTestErrorCode status(*this, "assertFormattedRangeEquals");
UnicodeString fullMessage = UnicodeString(message) + u": " + DoubleToUnicodeString(first) + u", " + DoubleToUnicodeString(second);
status.setScope(fullMessage);
UnicodeString actual = l.formatFormattableRange(first, second, status).toString(status);
assertEquals(fullMessage, expected, actual);
}
#endif

View File

@ -215,6 +215,7 @@ void NumberFormatTest::runIndexedTest( int32_t index, UBool exec, const char* &n
TESTCASE_AUTO(Test13763_FieldPositionIteratorOffset);
TESTCASE_AUTO(Test13777_ParseLongNameNonCurrencyMode);
TESTCASE_AUTO(Test13804_EmptyStringsWhenParsing);
TESTCASE_AUTO(Test20037_ScientificIntegerOverflow);
TESTCASE_AUTO(Test13840_ParseLongStringCrash);
TESTCASE_AUTO(Test13850_EmptyStringCurrency);
TESTCASE_AUTO_END;
@ -7367,6 +7368,35 @@ void NumberFormatTest::TestSignificantDigits(void) {
}
result.remove();
}
// Test for ICU-20063
{
DecimalFormat df({"en-us", status}, status);
df.setSignificantDigitsUsed(TRUE);
expect(df, 9.87654321, u"9.87654");
df.setMaximumSignificantDigits(3);
expect(df, 9.87654321, u"9.88");
// setSignificantDigitsUsed with maxSig only
df.setSignificantDigitsUsed(TRUE);
expect(df, 9.87654321, u"9.88");
df.setMinimumSignificantDigits(2);
expect(df, 9, u"9.0");
// setSignificantDigitsUsed with both minSig and maxSig
df.setSignificantDigitsUsed(TRUE);
expect(df, 9, u"9.0");
// setSignificantDigitsUsed to false: should revert to fraction rounding
df.setSignificantDigitsUsed(FALSE);
expect(df, 9.87654321, u"9.876543");
expect(df, 9, u"9");
df.setSignificantDigitsUsed(TRUE);
df.setMinimumSignificantDigits(2);
expect(df, 9.87654321, u"9.87654");
expect(df, 9, u"9.0");
// setSignificantDigitsUsed with minSig only
df.setSignificantDigitsUsed(TRUE);
expect(df, 9.87654321, u"9.87654");
expect(df, 9, u"9.0");
}
}
void NumberFormatTest::TestShowZero() {
@ -9156,6 +9186,28 @@ void NumberFormatTest::Test13804_EmptyStringsWhenParsing() {
}
}
void NumberFormatTest::Test20037_ScientificIntegerOverflow() {
IcuTestErrorCode status(*this, "Test20037_ScientificIntegerOverflow");
LocalPointer<NumberFormat> nf(NumberFormat::createInstance(status));
Formattable result;
// Test overflow of exponent
nf->parse(u"1E-2147483648", result, status);
StringPiece sp = result.getDecimalNumber(status);
assertEquals(u"Should snap to zero",
u"0",
{sp.data(), sp.length(), US_INV});
// Test edge case overflow of exponent
result = Formattable();
nf->parse(u"1E-2147483647E-1", result, status);
sp = result.getDecimalNumber(status);
assertEquals(u"Should not overflow and should parse only the first exponent",
u"1E-2147483647",
{sp.data(), sp.length(), US_INV});
}
void NumberFormatTest::Test13840_ParseLongStringCrash() {
IcuTestErrorCode status(*this, "Test13840_ParseLongStringCrash");

View File

@ -279,6 +279,7 @@ class NumberFormatTest: public CalendarTimeZoneTest {
void Test13763_FieldPositionIteratorOffset();
void Test13777_ParseLongNameNonCurrencyMode();
void Test13804_EmptyStringsWhenParsing();
void Test20037_ScientificIntegerOverflow();
void Test13840_ParseLongStringCrash();
void Test13850_EmptyStringCurrency();

View File

@ -13,6 +13,7 @@
#include "umutex.h"
#include "cmemory.h"
#include "cstring.h"
#include "indiancal.h"
#include "uparse.h"
#include "unicode/localpointer.h"
#include "unicode/resbund.h"
@ -82,6 +83,7 @@ void MultithreadTest::runIndexedTest( int32_t index, UBool exec,
TESTCASE_AUTO(TestBreakTranslit);
TESTCASE_AUTO(TestIncDec);
#endif /* #if !UCONFIG_NO_TRANSLITERATION */
TESTCASE_AUTO(Test20104);
TESTCASE_AUTO_END
}
@ -1549,4 +1551,35 @@ void MultithreadTest::TestIncDec()
}
static Calendar *gSharedCalendar = {};
class Test20104Thread : public SimpleThread {
public:
Test20104Thread() { };
virtual void run();
};
void Test20104Thread::run() {
gSharedCalendar->defaultCenturyStartYear();
}
void MultithreadTest::Test20104() {
UErrorCode status = U_ZERO_ERROR;
Locale loc("hi_IN");
gSharedCalendar = new IndianCalendar(loc, status);
assertSuccess("Test20104", status);
static constexpr int NUM_THREADS = 4;
Test20104Thread threads[NUM_THREADS];
for (auto &thread:threads) {
thread.start();
}
for (auto &thread:threads) {
thread.join();
}
delete gSharedCalendar;
// Note: failure is reported by Thread Sanitizer. Test itself succeeds.
}
#endif /* !UCONFIG_NO_TRANSLITERATION */

View File

@ -53,6 +53,7 @@ public:
void TestUnifiedCache();
void TestBreakTranslit();
void TestIncDec();
void Test20104();
};
#endif

View File

@ -353,6 +353,15 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre
// JDK fails here because it tries to use 9 + 6 = 15 sig digits.
2 9 1 6 29.979246E7 K
test ticket 20058
set locale en
begin
pattern format output breaks
#00.0##E0 0 0.0E0 K
#00.0##E0 1.2 1.2E0 K
#00.0E0 0 0.0E0 K
#00.0E0 1.2 1.2E0 K
test significant digits scientific
set locale en
set pattern #E0

View File

@ -0,0 +1,104 @@
#! /usr/bin/python -B
# Copyright (C) 2016 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html
# Copyright (C) 2009-2011, International Business Machines Corporation, Google and Others.
# All rights reserved.
#
# Script to check that ICU source files contain only valid UTF-8 encoded text,
# and that all files except '.txt' files do not contain a Byte Order Mark (BOM).
#
# THIS SCRIPT DOES NOT WORK ON WINDOWS
# It only works correctly on platforms where the native line ending is a plain \n
#
# usage:
# icu-file-utf8-check.py [options]
#
# options:
# -h | --help Print a usage line and exit.
#
# The tool operates recursively on the directory from which it is run.
# Only files from the ICU github repository are checked.
# No changes are made to the repository; only the working copy will be altered.
import sys
import os
import os.path
import re
import getopt
def runCommand(cmd):
output_file = os.popen(cmd);
output_text = output_file.read();
exit_status = output_file.close();
if exit_status:
print >>sys.stderr, '"', cmd, '" failed. Exiting.'
sys.exit(exit_status)
return output_text
def usage():
print "usage: " + sys.argv[0] + " [-h | --help]"
#
# File check. Check source code files for UTF-8 and all except text files for not containing a BOM
# file_name: name of a text file.
# is_source: Flag, set to True if file is a source code file (.c, .cpp, .h, .java).
#
def check_file(file_name, is_source):
f = open(file_name, 'r')
bytes = f.read()
f.close()
if is_source:
try:
bytes.decode("UTF-8")
except UnicodeDecodeError:
print "Error: %s is a source code file but contains non-utf-8 bytes." % file_name
if ord(bytes[0]) == 0xef:
if not (file_name.endswith(".txt") or file_name.endswith(".sln")
or file_name.endswith(".targets")
or ".vcxproj" in file_name):
print "Warning: file %s contains a UTF-8 BOM: " % file_name
return
def main(argv):
try:
opts, args = getopt.getopt(argv, "h", ("help"))
except getopt.GetoptError:
print "unrecognized option: " + argv[0]
usage()
sys.exit(2)
for opt, arg in opts:
if opt in ("-h", "--help"):
usage()
sys.exit()
if args:
print "unexpected command line argument"
usage()
sys.exit()
output = runCommand("git ls-files ");
file_list = output.splitlines()
source_file_re = re.compile(".*((?:\\.c$)|(?:\\.cpp$)|(?:\\.h$)|(?:\\.java$))")
for f in file_list:
if os.path.isdir(f):
print "Skipping dir " + f
continue
if not os.path.isfile(f):
print "Repository file not in working copy: " + f
continue;
source_file = source_file_re.match(f)
check_file(f, source_file)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -1,189 +0,0 @@
#! /usr/bin/python -B
# Copyright (C) 2016 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html
# Copyright (C) 2009-2011, International Business Machines Corporation, Google and Others.
# All rights reserved.
#
# Script to check and fix svn property settings for ICU source files.
# Also check for the correct line endings on files with svn:eol-style = native
#
# THIS SCRIPT DOES NOT WORK ON WINDOWS
# It only works correctly on platforms where the native line ending is a plain \n
#
# usage:
# icu-svnprops-check.py [options]
#
# options:
# -f | --fix Fix any problems that are found
# -h | --help Print a usage line and exit.
#
# The tool operates recursively on the directory from which it is run.
# Only files from the svn repository are checked.
# No changes are made to the repository; only the working copy will be altered.
import sys
import os
import os.path
import re
import getopt
# file_types: The parsed form of the svn auto-props specification.
# A list of file types - .cc, .cpp, .txt, etc.
# each element is a [type, proplist]
# "type" is a regular expression string that will match a file name
# prop list is another list, one element per property.
# Each property item is a two element list, [prop name, prop value]
file_types = list()
def parse_auto_props():
aprops = svn_auto_props.splitlines()
for propline in aprops:
if re.match("\s*(#.*)?$", propline): # Match comment and blank lines
continue
if re.match("\s*\[auto-props\]", propline): # Match the [auto-props] line.
continue
if not re.match("\s*[^\s]+\s*=", propline): # minimal syntax check for <file-type> =
print "Bad line from autoprops definitions: " + propline
continue
file_type, string_proplist = propline.split("=", 1)
#transform the file type expression from autoprops into a normal regular expression.
# e.g. "*.cpp" ==> ".*\.cpp$"
file_type = file_type.strip()
file_type = file_type.replace(".", "\.")
file_type = file_type.replace("*", ".*")
file_type = file_type + "$"
# example string_proplist at this point: " svn:eol-style=native;svn:executable"
# split on ';' into a list of properties. The negative lookahead and lookbehind
# in the split regexp are to prevent matching on ';;', which is an escaped ';'
# within a property value.
string_proplist = re.split("(?<!;);(?!;)", string_proplist)
proplist = list()
for prop in string_proplist:
if prop.find("=") >= 0:
prop_name, prop_val = prop.split("=", 1)
else:
# properties with no explicit value, e.g. svn:executable
prop_name, prop_val = prop, ""
prop_name = prop_name.strip()
prop_val = prop_val.strip()
# unescape any ";;" in a property value, e.g. the mime-type from
# *.java = svn:eol-style=native;svn:mime-type=text/plain;;charset=utf-8
prop_val = prop_val.replace(";;", ";");
# If the prop value "is quoted", remove the quotes.
# See svn:keywords for an example of a quoted prop value.
match = re.match('^"(.+)"$', prop_val)
if match:
prop_val = match.group(1)
proplist.append((prop_name, prop_val))
file_types.append((file_type, proplist))
# print file_types
def runCommand(cmd):
output_file = os.popen(cmd);
output_text = output_file.read();
exit_status = output_file.close();
if exit_status:
print >>sys.stderr, '"', cmd, '" failed. Exiting.'
sys.exit(exit_status)
return output_text
svn_auto_props = runCommand("svn propget svn:auto-props http://source.icu-project.org/repos/icu")
def usage():
print "usage: " + sys.argv[0] + " [-f | --fix] [-h | --help]"
#
# UTF-8 file check. For text files with svn:mime-type=text/anything, check the specified charset
# file_name: name of a text file.
# base_mime_type: svn:mime-type property from the auto-props settings for this file type.
# actual_mime_type: existing svn:mime-type property value for the file.
# return: The correct svn:mime-type property value,
# either the original, if it looks OK, otherwise the value from auto-props
#
def check_utf8(file_name, base_mime_type, actual_mime_type):
f = open(file_name, 'r')
bytes = f.read()
f.close()
file_is_utf8 = True
try:
bytes.decode("UTF-8")
except UnicodeDecodeError:
file_is_utf8 = False
if not file_is_utf8 and actual_mime_type.find("utf-8") >= 0:
print "Error: %s is not valid utf-8, but has a utf-8 mime type." % file_name
return actual_mime_type
if file_is_utf8 and actual_mime_type.find("charset") >=0 and actual_mime_type.find("utf-8") < 0:
print "Warning: %s is valid utf-8, but has a mime-type of %s." % (file_name, actual_mime_type)
if ord(bytes[0]) == 0xef:
if not file_name.endswith(".txt"):
print "Warning: file %s contains a UTF-8 BOM: " % file_name
# If the file already has a charset in its mime-type, don't make any change.
if actual_mime_type.find("charset=") >= 0:
return actual_mime_type;
return base_mime_type
def main(argv):
fix_problems = False;
try:
opts, args = getopt.getopt(argv, "fh", ("fix", "help"))
except getopt.GetoptError:
print "unrecognized option: " + argv[0]
usage()
sys.exit(2)
for opt, arg in opts:
if opt in ("-h", "--help"):
usage()
sys.exit()
if opt in ("-f", "--fix"):
fix_problems = True
if args:
print "unexpected command line argument"
usage()
sys.exit()
parse_auto_props()
output = runCommand("svn ls -R ");
file_list = output.splitlines()
for f in file_list:
if os.path.isdir(f):
# print "Skipping dir " + f
continue
if not os.path.isfile(f):
print "Repository file not in working copy: " + f
continue;
for file_pattern, props in file_types:
if re.match(file_pattern, f):
# print "doing " + f
for propname, propval in props:
actual_propval = runCommand("svn propget --strict " + propname + " " + f)
#print propname + ": " + actual_propval
if propname == "svn:mime-type" and propval.find("text/") == 0:
# check for UTF-8 text files, should have svn:mime-type=text/something; charset=utf8
propval = check_utf8(f, propval, actual_propval)
if not (propval == actual_propval or (propval == "" and actual_propval == "*")):
print "svn propset %s '%s' %s" % (propname, propval, f)
if fix_problems:
os.system("svn propset %s '%s' %s" % (propname, propval, f))
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -2,8 +2,11 @@ eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
@ -13,6 +16,7 @@ org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
@ -62,12 +66,14 @@ org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
@ -84,12 +90,16 @@ org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=info
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
@ -97,6 +107,7 @@ org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning

View File

@ -37,11 +37,11 @@ public class Relation<K, V> implements Freezable<Relation<K,V>> { // TODO: add ,
Object[] setComparatorParam;
public static <K, V> Relation<K, V> of(Map<K, Set<V>> map, Class<?> setCreator) {
return new Relation<K, V>(map, setCreator);
return new Relation<>(map, setCreator);
}
public static <K,V> Relation<K, V> of(Map<K, Set<V>> map, Class<?> setCreator, Comparator<V> setComparator) {
return new Relation<K, V>(map, setCreator, setComparator);
return new Relation<>(map, setCreator, setComparator);
}
public Relation(Map<K, Set<V>> map, Class<?> setCreator) {
@ -91,10 +91,10 @@ public class Relation<K, V> implements Freezable<Relation<K,V>> { // TODO: add ,
}
public Set<Entry<K, V>> keyValueSet() {
Set<Entry<K, V>> result = new LinkedHashSet<Entry<K, V>>();
Set<Entry<K, V>> result = new LinkedHashSet<>();
for (K key : data.keySet()) {
for (V value : data.get(key)) {
result.add(new SimpleEntry<K, V>(key, value));
result.add(new SimpleEntry<>(key, value));
}
}
return result;
@ -320,7 +320,9 @@ public class Relation<K, V> implements Freezable<Relation<K,V>> { // TODO: add ,
return result;
}
public Set<V> removeAll(K... keys) {
@SafeVarargs
@SuppressWarnings("varargs") // Not supported by Eclipse, but we need this for javac
public final Set<V> removeAll(K... keys) {
return removeAll(Arrays.asList(keys));
}
@ -333,7 +335,7 @@ public class Relation<K, V> implements Freezable<Relation<K,V>> { // TODO: add ,
}
public Set<V> removeAll(Collection<K> toBeRemoved) {
Set<V> result = new LinkedHashSet<V>();
Set<V> result = new LinkedHashSet<>();
for (K key : toBeRemoved) {
try {
final Set<V> removals = data.remove(key);

View File

@ -44,7 +44,9 @@ public class XCldrStub {
? setClass
: HashSet.class);
}
public Multimap<K, V> putAll(K key, V... values) {
@SafeVarargs
@SuppressWarnings("varargs") // Not supported by Eclipse, but we need this for javac
public final Multimap<K, V> putAll(K key, V... values) {
if (values.length != 0) {
createSetIfMissing(key).addAll(Arrays.asList(values));
}
@ -107,7 +109,7 @@ public class XCldrStub {
return map.size();
}
public Iterable<Entry<K, V>> entries() {
return new MultimapIterator<K, V>(map);
return new MultimapIterator<>(map);
}
@Override
public boolean equals(Object obj) {
@ -147,7 +149,7 @@ public class XCldrStub {
private static class MultimapIterator<K,V> implements Iterator<Entry<K,V>>, Iterable<Entry<K,V>> {
private final Iterator<Entry<K, Set<V>>> it1;
private Iterator<V> it2 = null;
private final ReusableEntry<K,V> entry = new ReusableEntry<K,V>();
private final ReusableEntry<K,V> entry = new ReusableEntry<>();
private MultimapIterator(Map<K,Set<V>> map) {
it1 = map.entrySet().iterator();
@ -199,7 +201,7 @@ public class XCldrStub {
super(new HashMap<K, Set<V>>(), HashSet.class);
}
public static <K, V> HashMultimap<K, V> create() {
return new HashMultimap<K, V>();
return new HashMultimap<>();
}
}
@ -208,7 +210,7 @@ public class XCldrStub {
super(new TreeMap<K, Set<V>>(), TreeSet.class);
}
public static <K, V> TreeMultimap<K, V> create() {
return new TreeMultimap<K, V>();
return new TreeMultimap<>();
}
}
@ -217,7 +219,7 @@ public class XCldrStub {
super(new LinkedHashMap<K, Set<V>>(), LinkedHashSet.class);
}
public static <K, V> LinkedHashMultimap<K, V> create() {
return new LinkedHashMultimap<K, V>();
return new LinkedHashMultimap<>();
}
}
@ -315,24 +317,24 @@ public class XCldrStub {
public static class ImmutableSet {
public static <T> Set<T> copyOf(Set<T> values) {
return Collections.unmodifiableSet(new LinkedHashSet<T>(values)); // copy set for safety, preserve order
return Collections.unmodifiableSet(new LinkedHashSet<>(values)); // copy set for safety, preserve order
}
}
public static class ImmutableMap {
public static <K,V> Map<K,V> copyOf(Map<K,V> values) {
return Collections.unmodifiableMap(new LinkedHashMap<K,V>(values)); // copy set for safety, preserve order
return Collections.unmodifiableMap(new LinkedHashMap<>(values)); // copy set for safety, preserve order
}
}
public static class ImmutableMultimap {
public static <K,V> Multimap<K,V> copyOf(Multimap<K,V> values) {
LinkedHashMap<K, Set<V>> temp = new LinkedHashMap<K,Set<V>>(); // semi-deep copy, preserve order
LinkedHashMap<K, Set<V>> temp = new LinkedHashMap<>(); // semi-deep copy, preserve order
for (Entry<K, Set<V>> entry : values.asMap().entrySet()) {
Set<V> value = entry.getValue();
temp.put(entry.getKey(), value.size() == 1
? Collections.singleton(value.iterator().next())
: Collections.unmodifiableSet(new LinkedHashSet<V>(value)));
: Collections.unmodifiableSet(new LinkedHashSet<>(value)));
}
return new Multimap<K,V>(Collections.unmodifiableMap(temp), null);
return new Multimap<>(Collections.unmodifiableMap(temp), null);
}
}

View File

@ -5,10 +5,11 @@ package com.ibm.icu.impl.number;
import com.ibm.icu.impl.StandardPlural;
/**
* A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two
* or more Modifiers and returns the modifier appropriate for the current situation.
* This implementation of ModifierStore adopts references to Modifiers.
*
* (This is named "adopting" because in C++, this class takes ownership of the Modifiers.)
*/
public class ParameterizedModifier {
public class AdoptingModifierStore implements ModifierStore {
private final Modifier positive;
private final Modifier zero;
private final Modifier negative;
@ -21,7 +22,7 @@ public class ParameterizedModifier {
* <p>
* If this constructor is used, a plural form CANNOT be passed to {@link #getModifier}.
*/
public ParameterizedModifier(Modifier positive, Modifier zero, Modifier negative) {
public AdoptingModifierStore(Modifier positive, Modifier zero, Modifier negative) {
this.positive = positive;
this.zero = zero;
this.negative = negative;
@ -36,7 +37,7 @@ public class ParameterizedModifier {
* <p>
* If this constructor is used, a plural form MUST be passed to {@link #getModifier}.
*/
public ParameterizedModifier() {
public AdoptingModifierStore() {
this.positive = null;
this.zero = null;
this.negative = null;
@ -53,7 +54,7 @@ public class ParameterizedModifier {
frozen = true;
}
public Modifier getModifier(int signum) {
public Modifier getModifierWithoutPlural(int signum) {
assert frozen;
assert mods == null;
return signum == 0 ? zero : signum < 0 ? negative : positive;
@ -66,6 +67,8 @@ public class ParameterizedModifier {
}
private static int getModIndex(int signum, StandardPlural plural) {
assert signum >= -1 && signum <= 1;
assert plural != null;
return plural.ordinal() * 3 + (signum + 1);
}
}

View File

@ -74,6 +74,28 @@ public class ConstantAffixModifier implements Modifier {
return strong;
}
@Override
public boolean containsField(Field field) {
// This method is not currently used.
assert false;
return false;
}
@Override
public Parameters getParameters() {
return null;
}
@Override
public boolean semanticallyEquivalent(Modifier other) {
if (!(other instanceof ConstantAffixModifier)) {
return false;
}
ConstantAffixModifier _other = (ConstantAffixModifier) other;
return prefix.equals(_other.prefix) && suffix.equals(_other.suffix) && field == _other.field
&& strong == _other.strong;
}
@Override
public String toString() {
return String.format("<ConstantAffixModifier prefix:'%s' suffix:'%s'>", prefix, suffix);

View File

@ -2,6 +2,8 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.util.Arrays;
import com.ibm.icu.text.NumberFormat.Field;
/**
@ -20,17 +22,30 @@ public class ConstantMultiFieldModifier implements Modifier {
private final boolean overwrite;
private final boolean strong;
// Parameters: used for number range formatting
private final Parameters parameters;
public ConstantMultiFieldModifier(
NumberStringBuilder prefix,
NumberStringBuilder suffix,
boolean overwrite,
boolean strong) {
this(prefix, suffix, overwrite, strong, null);
}
public ConstantMultiFieldModifier(
NumberStringBuilder prefix,
NumberStringBuilder suffix,
boolean overwrite,
boolean strong,
Parameters parameters) {
prefixChars = prefix.toCharArray();
suffixChars = suffix.toCharArray();
prefixFields = prefix.toFieldArray();
suffixFields = suffix.toFieldArray();
this.overwrite = overwrite;
this.strong = strong;
this.parameters = parameters;
}
@Override
@ -59,6 +74,40 @@ public class ConstantMultiFieldModifier implements Modifier {
return strong;
}
@Override
public boolean containsField(Field field) {
for (int i = 0; i < prefixFields.length; i++) {
if (prefixFields[i] == field) {
return true;
}
}
for (int i = 0; i < suffixFields.length; i++) {
if (suffixFields[i] == field) {
return true;
}
}
return false;
}
@Override
public Parameters getParameters() {
return parameters;
}
@Override
public boolean semanticallyEquivalent(Modifier other) {
if (!(other instanceof ConstantMultiFieldModifier)) {
return false;
}
ConstantMultiFieldModifier _other = (ConstantMultiFieldModifier) other;
if (parameters != null && _other.parameters != null && parameters.obj == _other.parameters.obj) {
return true;
}
return Arrays.equals(prefixChars, _other.prefixChars) && Arrays.equals(prefixFields, _other.prefixFields)
&& Arrays.equals(suffixChars, _other.suffixChars) && Arrays.equals(suffixFields, _other.suffixFields)
&& overwrite == _other.overwrite && strong == _other.strong;
}
@Override
public String toString() {
NumberStringBuilder temp = new NumberStringBuilder();

View File

@ -1014,6 +1014,46 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
}
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
if (!(other instanceof DecimalQuantity_AbstractBCD)) {
return false;
}
DecimalQuantity_AbstractBCD _other = (DecimalQuantity_AbstractBCD) other;
boolean basicEquals =
scale == _other.scale
&& precision == _other.precision
&& flags == _other.flags
&& lOptPos == _other.lOptPos
&& lReqPos == _other.lReqPos
&& rReqPos == _other.rReqPos
&& rOptPos == _other.rOptPos
&& isApproximate == _other.isApproximate;
if (!basicEquals) {
return false;
}
if (precision == 0) {
return true;
} else if (isApproximate) {
return origDouble == _other.origDouble && origDelta == _other.origDelta;
} else {
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
if (getDigit(m) != _other.getDigit(m)) {
return false;
}
}
return true;
}
}
/**
* Returns a single digit from the BCD list. No internal state is changed by calling this method.
*

View File

@ -21,7 +21,7 @@ import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
public class LongNameHandler implements MicroPropsGenerator {
public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
private static final int DNAM_INDEX = StandardPlural.COUNT;
private static final int PER_INDEX = StandardPlural.COUNT + 1;
@ -175,10 +175,11 @@ public class LongNameHandler implements MicroPropsGenerator {
String[] simpleFormats = new String[ARRAY_LENGTH];
getCurrencyLongNameData(locale, currency, simpleFormats);
// TODO(ICU4J): Reduce the number of object creations here?
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
StandardPlural.class);
simpleFormatsToModifiers(simpleFormats, null, modifiers);
return new LongNameHandler(modifiers, rules, parent);
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
result.simpleFormatsToModifiers(simpleFormats, null);
return result;
}
public static LongNameHandler forMeasureUnit(
@ -203,10 +204,11 @@ public class LongNameHandler implements MicroPropsGenerator {
getMeasureData(locale, unit, width, simpleFormats);
// TODO: What field to use for units?
// TODO(ICU4J): Reduce the number of object creations here?
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
StandardPlural.class);
simpleFormatsToModifiers(simpleFormats, null, modifiers);
return new LongNameHandler(modifiers, rules, parent);
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
result.simpleFormatsToModifiers(simpleFormats, null);
return result;
}
private static LongNameHandler forCompoundUnit(
@ -238,29 +240,32 @@ public class LongNameHandler implements MicroPropsGenerator {
perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
}
// TODO: What field to use for units?
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<StandardPlural, SimpleModifier>(
Map<StandardPlural, SimpleModifier> modifiers = new EnumMap<>(
StandardPlural.class);
multiSimpleFormatsToModifiers(primaryData, perUnitFormat, null, modifiers);
return new LongNameHandler(modifiers, rules, parent);
LongNameHandler result = new LongNameHandler(modifiers, rules, parent);
result.multiSimpleFormatsToModifiers(primaryData, perUnitFormat, null);
return result;
}
private static void simpleFormatsToModifiers(
private void simpleFormatsToModifiers(
String[] simpleFormats,
NumberFormat.Field field,
Map<StandardPlural, SimpleModifier> output) {
NumberFormat.Field field) {
StringBuilder sb = new StringBuilder();
for (StandardPlural plural : StandardPlural.VALUES) {
String simpleFormat = getWithPlural(simpleFormats, plural);
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 0, 1);
output.put(plural, new SimpleModifier(compiled, field, false));
Modifier.Parameters parameters = new Modifier.Parameters();
parameters.obj = this;
parameters.signum = 0;
parameters.plural = plural;
modifiers.put(plural, new SimpleModifier(compiled, field, false, parameters));
}
}
private static void multiSimpleFormatsToModifiers(
private void multiSimpleFormatsToModifiers(
String[] leadFormats,
String trailFormat,
NumberFormat.Field field,
Map<StandardPlural, SimpleModifier> output) {
NumberFormat.Field field) {
StringBuilder sb = new StringBuilder();
String trailCompiled = SimpleFormatterImpl.compileToStringMinMaxArguments(trailFormat, sb, 1, 1);
for (StandardPlural plural : StandardPlural.VALUES) {
@ -268,7 +273,11 @@ public class LongNameHandler implements MicroPropsGenerator {
String compoundFormat = SimpleFormatterImpl.formatCompiledPattern(trailCompiled, leadFormat);
String compoundCompiled = SimpleFormatterImpl
.compileToStringMinMaxArguments(compoundFormat, sb, 0, 1);
output.put(plural, new SimpleModifier(compoundCompiled, field, false));
Modifier.Parameters parameters = new Modifier.Parameters();
parameters.obj = this;
parameters.signum = 0;
parameters.plural = plural;
modifiers.put(plural, new SimpleModifier(compoundCompiled, field, false, parameters));
}
}
@ -281,4 +290,9 @@ public class LongNameHandler implements MicroPropsGenerator {
micros.modOuter = modifiers.get(copy.getStandardPlural(rules));
return micros;
}
@Override
public Modifier getModifier(int signum, StandardPlural plural) {
return modifiers.get(plural);
}
}

View File

@ -2,6 +2,9 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.text.NumberFormat.Field;
/**
* A Modifier is an object that can be passed through the formatting pipeline until it is finally applied
* to the string builder. A Modifier usually contains a prefix and a suffix that are applied, but it
@ -48,4 +51,30 @@ public interface Modifier {
* @return Whether the modifier is strong.
*/
public boolean isStrong();
/**
* Whether the modifier contains at least one occurrence of the given field.
*/
public boolean containsField(Field currency);
/**
* A fill-in for getParameters(). obj will always be set; if non-null, the other
* two fields are also safe to read.
*/
public static class Parameters {
public ModifierStore obj;
public int signum;
public StandardPlural plural;
}
/**
* Gets a set of "parameters" for this Modifier.
*/
public Parameters getParameters();
/**
* Returns whether this Modifier is *semantically equivalent* to the other Modifier;
* in many cases, this is the same as equal, but parameters should be ignored.
*/
public boolean semanticallyEquivalent(Modifier other);
}

View File

@ -0,0 +1,18 @@
// © 2018 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.StandardPlural;
/**
* This is *not* a modifier; rather, it is an object that can return modifiers
* based on given parameters.
*
* @author sffc
*/
public interface ModifierStore {
/**
* Returns a Modifier with the given parameters (best-effort).
*/
Modifier getModifier(int signum, StandardPlural plural);
}

View File

@ -7,6 +7,7 @@ import com.ibm.icu.impl.number.AffixUtils.SymbolProvider;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat.Field;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.Currency;
@ -165,7 +166,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
NumberStringBuilder b = new NumberStringBuilder();
if (needsPlurals()) {
// Slower path when we require the plural keyword.
ParameterizedModifier pm = new ParameterizedModifier();
AdoptingModifierStore pm = new AdoptingModifierStore();
for (StandardPlural plural : StandardPlural.VALUES) {
setNumberProperties(1, plural);
pm.setModifier(1, plural, createConstantModifier(a, b));
@ -184,7 +185,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
Modifier zero = createConstantModifier(a, b);
setNumberProperties(-1, null);
Modifier negative = createConstantModifier(a, b);
ParameterizedModifier pm = new ParameterizedModifier(positive, zero, negative);
AdoptingModifierStore pm = new AdoptingModifierStore(positive, zero, negative);
return new ImmutablePatternModifier(pm, null, parent);
}
}
@ -213,12 +214,12 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
}
public static class ImmutablePatternModifier implements MicroPropsGenerator {
final ParameterizedModifier pm;
final AdoptingModifierStore pm;
final PluralRules rules;
final MicroPropsGenerator parent;
ImmutablePatternModifier(
ParameterizedModifier pm,
AdoptingModifierStore pm,
PluralRules rules,
MicroPropsGenerator parent) {
this.pm = pm;
@ -235,7 +236,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
public void applyToMicros(MicroProps micros, DecimalQuantity quantity) {
if (rules == null) {
micros.modMiddle = pm.getModifier(quantity.signum());
micros.modMiddle = pm.getModifierWithoutPlural(quantity.signum());
} else {
// TODO: Fix this. Avoid the copy.
DecimalQuantity copy = quantity.createCopy();
@ -319,6 +320,27 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
return isStrong;
}
@Override
public boolean containsField(Field field) {
// This method is not currently used. (unsafe path not used in range formatting)
assert false;
return false;
}
@Override
public Parameters getParameters() {
// This method is not currently used.
assert false;
return null;
}
@Override
public boolean semanticallyEquivalent(Modifier other) {
// This method is not currently used. (unsafe path not used in range formatting)
assert false;
return false;
}
private int insertPrefix(NumberStringBuilder sb, int position) {
prepareAffix(true);
int length = AffixUtils.unescape(currentAffix, sb, position, this);

View File

@ -3,7 +3,9 @@
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
import com.ibm.icu.text.NumberFormat.Field;
import com.ibm.icu.util.ICUException;
/**
* The second primary implementation of {@link Modifier}, this one consuming a
@ -17,15 +19,24 @@ public class SimpleModifier implements Modifier {
private final int suffixOffset;
private final int suffixLength;
// Parameters: used for number range formatting
private final Parameters parameters;
/** TODO: This is copied from SimpleFormatterImpl. */
private static final int ARG_NUM_LIMIT = 0x100;
/** Creates a modifier that uses the SimpleFormatter string formats. */
public SimpleModifier(String compiledPattern, Field field, boolean strong) {
this(compiledPattern, field, strong, null);
}
/** Creates a modifier that uses the SimpleFormatter string formats. */
public SimpleModifier(String compiledPattern, Field field, boolean strong, Parameters parameters) {
assert compiledPattern != null;
this.compiledPattern = compiledPattern;
this.field = field;
this.strong = strong;
this.parameters = parameters;
int argLimit = SimpleFormatterImpl.getArgumentLimit(compiledPattern);
if (argLimit == 0) {
@ -80,6 +91,30 @@ public class SimpleModifier implements Modifier {
return strong;
}
@Override
public boolean containsField(Field field) {
// This method is not currently used.
assert false;
return false;
}
@Override
public Parameters getParameters() {
return parameters;
}
@Override
public boolean semanticallyEquivalent(Modifier other) {
if (!(other instanceof SimpleModifier)) {
return false;
}
SimpleModifier _other = (SimpleModifier) other;
if (parameters != null && _other.parameters != null && parameters.obj == _other.parameters.obj) {
return true;
}
return compiledPattern.equals(_other.compiledPattern) && field == _other.field && strong == _other.strong;
}
/**
* TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is because
* DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it should not
@ -123,4 +158,66 @@ public class SimpleModifier implements Modifier {
return prefixLength + suffixLength;
}
}
/**
* TODO: Like above, this belongs with the rest of the SimpleFormatterImpl code.
* I put it here so that the SimpleFormatter uses in NumberStringBuilder are near each other.
*
* <p>
* Applies the compiled two-argument pattern to the NumberStringBuilder.
*
* <p>
* This method is optimized for the case where the prefix and suffix are often empty, such as
* in the range pattern like "{0}-{1}".
*/
public static void formatTwoArgPattern(String compiledPattern, NumberStringBuilder result, int index, PrefixInfixSuffixLengthHelper h,
Field field) {
int argLimit = SimpleFormatterImpl.getArgumentLimit(compiledPattern);
if (argLimit != 2) {
throw new ICUException();
}
int offset = 1; // offset into compiledPattern
int length = 0; // chars added to result
int prefixLength = compiledPattern.charAt(offset);
offset++;
if (prefixLength < ARG_NUM_LIMIT) {
// No prefix
prefixLength = 0;
} else {
prefixLength -= ARG_NUM_LIMIT;
result.insert(index + length, compiledPattern, offset, offset + prefixLength, field);
offset += prefixLength;
length += prefixLength;
offset++;
}
int infixLength = compiledPattern.charAt(offset);
offset++;
if (infixLength < ARG_NUM_LIMIT) {
// No infix
infixLength = 0;
} else {
infixLength -= ARG_NUM_LIMIT;
result.insert(index + length, compiledPattern, offset, offset + infixLength, field);
offset += infixLength;
length += infixLength;
offset++;
}
int suffixLength;
if (offset == compiledPattern.length()) {
// No suffix
suffixLength = 0;
} else {
suffixLength = compiledPattern.charAt(offset) - ARG_NUM_LIMIT;
offset++;
result.insert(index + length, compiledPattern, offset, offset + suffixLength, field);
length += suffixLength;
}
h.lengthPrefix = prefixLength;
h.lengthInfix = infixLength;
h.lengthSuffix = suffixLength;
}
}

View File

@ -54,6 +54,11 @@ public class ScientificMatcher implements NumberParseMatcher {
return false;
}
// Only accept one exponent per string.
if (0 != (result.flags & ParsedNumber.FLAG_HAS_EXPONENT)) {
return false;
}
// First match the scientific separator, and then match another number after it.
// NOTE: This is guarded by the smoke test; no need to check exponentSeparatorString length again.
int overlap1 = segment.getCommonPrefixLength(exponentSeparatorString);

View File

@ -0,0 +1,30 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number.range;
/**
* A small, mutable internal helper class for keeping track of offsets on range patterns.
*/
public class PrefixInfixSuffixLengthHelper {
public int lengthPrefix = 0;
public int length1 = 0;
public int lengthInfix = 0;
public int length2 = 0;
public int lengthSuffix = 0;
public int index0() {
return lengthPrefix;
}
public int index1() {
return lengthPrefix + length1;
}
public int index2() {
return lengthPrefix + length1 + lengthInfix;
}
public int index3() {
return lengthPrefix + length1 + lengthInfix + length2;
}
}

View File

@ -0,0 +1,48 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number.range;
import java.util.Objects;
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
import com.ibm.icu.number.UnlocalizedNumberFormatter;
import com.ibm.icu.util.ULocale;
/**
* @author sffc
*
*/
public class RangeMacroProps {
public UnlocalizedNumberFormatter formatter1;
public UnlocalizedNumberFormatter formatter2;
public int sameFormatters = -1; // -1 for unset, 0 for false, 1 for true
public RangeCollapse collapse;
public RangeIdentityFallback identityFallback;
public ULocale loc;
@Override
public int hashCode() {
return Objects.hash(formatter1,
formatter2,
collapse,
identityFallback,
loc);
}
@Override
public boolean equals(Object _other) {
if (_other == null)
return false;
if (this == _other)
return true;
if (!(_other instanceof RangeMacroProps))
return false;
RangeMacroProps other = (RangeMacroProps) _other;
return Objects.equals(formatter1, other.formatter1)
&& Objects.equals(formatter2, other.formatter2)
&& Objects.equals(collapse, other.collapse)
&& Objects.equals(identityFallback, other.identityFallback)
&& Objects.equals(loc, other.loc);
}
}

View File

@ -0,0 +1,104 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number.range;
import java.util.MissingResourceException;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
/**
* @author sffc
*
*/
public class StandardPluralRanges {
StandardPlural[] flatTriples;
int numTriples = 0;
////////////////////
private static final class PluralRangesDataSink extends UResource.Sink {
StandardPluralRanges output;
PluralRangesDataSink(StandardPluralRanges output) {
this.output = output;
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Array entriesArray = value.getArray();
output.setCapacity(entriesArray.getSize());
for (int i = 0; entriesArray.getValue(i, value); ++i) {
UResource.Array pluralFormsArray = value.getArray();
pluralFormsArray.getValue(0, value);
StandardPlural first = StandardPlural.fromString(value.getString());
pluralFormsArray.getValue(1, value);
StandardPlural second = StandardPlural.fromString(value.getString());
pluralFormsArray.getValue(2, value);
StandardPlural result = StandardPlural.fromString(value.getString());
output.addPluralRange(first, second, result);
}
}
}
private static void getPluralRangesData(
ULocale locale,
StandardPluralRanges out) {
StringBuilder sb = new StringBuilder();
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "pluralRanges");
sb.append("locales/");
sb.append(locale.getLanguage());
String key = sb.toString();
String set;
try {
set = resource.getStringWithFallback(key);
} catch (MissingResourceException e) {
// Not all languages are covered: fail gracefully
return;
}
sb.setLength(0);
sb.append("rules/");
sb.append(set);
key = sb.toString();
PluralRangesDataSink sink = new PluralRangesDataSink(out);
resource.getAllItemsWithFallback(key, sink);
}
////////////////////
public StandardPluralRanges(ULocale locale) {
getPluralRangesData(locale, this);
}
/** Used for data loading. */
private void addPluralRange(StandardPlural first, StandardPlural second, StandardPlural result) {
flatTriples[3 * numTriples] = first;
flatTriples[3 * numTriples + 1] = second;
flatTriples[3 * numTriples + 2] = result;
numTriples++;
}
/** Used for data loading. */
private void setCapacity(int length) {
flatTriples = new StandardPlural[length*3];
}
public StandardPlural resolve(StandardPlural first, StandardPlural second) {
for (int i = 0; i < numTriples; i++) {
if (first == flatTriples[3 * i] && second == flatTriples[3 * i + 1]) {
return flatTriples[3 * i + 2];
}
}
// Default fallback
return StandardPlural.OTHER;
}
}

View File

@ -70,9 +70,9 @@ public class FormattedNumber {
}
/**
* Determine the start and end indices of the first occurrence of the given <em>field</em> in the
* output string. This allows you to determine the locations of the integer part, fraction part, and
* sign.
* Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
* <em>field</em> in the output string. This allows you to determine the locations of, for example,
* the integer part, fraction part, or symbols.
*
* <p>
* If multiple different field attributes are needed, this method can be called repeatedly, or if

View File

@ -0,0 +1,209 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.number;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.util.Arrays;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.NumberStringBuilder;
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityResult;
import com.ibm.icu.util.ICUUncheckedIOException;
/**
* The result of a number range formatting operation. This class allows the result to be exported in several data types,
* including a String, an AttributedCharacterIterator, and a BigDecimal.
*
* @author sffc
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public class FormattedNumberRange {
final NumberStringBuilder string;
final DecimalQuantity quantity1;
final DecimalQuantity quantity2;
final RangeIdentityResult identityResult;
FormattedNumberRange(NumberStringBuilder string, DecimalQuantity quantity1, DecimalQuantity quantity2,
RangeIdentityResult identityResult) {
this.string = string;
this.quantity1 = quantity1;
this.quantity2 = quantity2;
this.identityResult = identityResult;
}
/**
* Creates a String representation of the the formatted number range.
*
* @return a String containing the localized number range.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
@Override
public String toString() {
return string.toString();
}
/**
* Append the formatted number range to an Appendable, such as a StringBuilder. This may be slightly more efficient
* than creating a String.
*
* <p>
* If an IOException occurs when appending to the Appendable, an unchecked {@link ICUUncheckedIOException} is thrown
* instead.
*
* @param appendable
* The Appendable to which to append the formatted number range string.
* @return The same Appendable, for chaining.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see Appendable
* @see NumberRangeFormatter
*/
public <A extends Appendable> A appendTo(A appendable) {
try {
appendable.append(string);
} catch (IOException e) {
// Throw as an unchecked exception to avoid users needing try/catch
throw new ICUUncheckedIOException(e);
}
return appendable;
}
/**
* Determines the start (inclusive) and end (exclusive) indices of the next occurrence of the given
* <em>field</em> in the output string. This allows you to determine the locations of, for example,
* the integer part, fraction part, or symbols.
* <p>
* If both sides of the range have the same field, the field will occur twice, once before the range separator and
* once after the range separator, if applicable.
* <p>
* If a field occurs just once, calling this method will find that occurrence and return it. If a field occurs
* multiple times, this method may be called repeatedly with the following pattern:
*
* <pre>
* FieldPosition fpos = new FieldPosition(NumberFormat.Field.INTEGER);
* while (formattedNumberRange.nextFieldPosition(fpos, status)) {
* // do something with fpos.
* }
* </pre>
* <p>
* This method is useful if you know which field to query. If you want all available field position information, use
* {@link #toCharacterIterator()}.
*
* @param fieldPosition
* Input+output variable. See {@link FormattedNumber#nextFieldPosition(FieldPosition)}.
* @return true if a new occurrence of the field was found; false otherwise.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see com.ibm.icu.text.NumberFormat.Field
* @see NumberRangeFormatter
*/
public boolean nextFieldPosition(FieldPosition fieldPosition) {
return string.nextFieldPosition(fieldPosition);
}
/**
* Export the formatted number range as an AttributedCharacterIterator. This allows you to determine which
* characters in the output string correspond to which <em>fields</em>, such as the integer part, fraction part, and
* sign.
* <p>
* If information on only one field is needed, use {@link #nextFieldPosition(FieldPosition)} instead.
*
* @return An AttributedCharacterIterator, containing information on the field attributes of the number range
* string.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see com.ibm.icu.text.NumberFormat.Field
* @see AttributedCharacterIterator
* @see NumberRangeFormatter
*/
public AttributedCharacterIterator toCharacterIterator() {
return string.toCharacterIterator();
}
/**
* Export the first formatted number as a BigDecimal. This endpoint is useful for obtaining the exact number being
* printed after scaling and rounding have been applied by the number range formatting pipeline.
*
* @return A BigDecimal representation of the first formatted number.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
* @see #getSecondBigDecimal
*/
public BigDecimal getFirstBigDecimal() {
return quantity1.toBigDecimal();
}
/**
* Export the second formatted number as a BigDecimal. This endpoint is useful for obtaining the exact number being
* printed after scaling and rounding have been applied by the number range formatting pipeline.
*
* @return A BigDecimal representation of the second formatted number.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
* @see #getFirstBigDecimal
*/
public BigDecimal getSecondBigDecimal() {
return quantity2.toBigDecimal();
}
/**
* Returns whether the pair of numbers was successfully formatted as a range or whether an identity fallback was
* used. For example, if the first and second number were the same either before or after rounding occurred, an
* identity fallback was used.
*
* @return A RangeIdentityType indicating the resulting identity situation in the formatted number range.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
* @see NumberRangeFormatter.RangeIdentityFallback
*/
public RangeIdentityResult getIdentityResult() {
return identityResult;
}
/**
* {@inheritDoc}
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
*/
@Override
public int hashCode() {
// NumberStringBuilder and BigDecimal are mutable, so we can't call
// #equals() or #hashCode() on them directly.
return Arrays.hashCode(string.toCharArray()) ^ Arrays.hashCode(string.toFieldArray())
^ quantity1.toBigDecimal().hashCode() ^ quantity2.toBigDecimal().hashCode();
}
/**
* {@inheritDoc}
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
*/
@Override
public boolean equals(Object other) {
if (this == other)
return true;
if (other == null)
return false;
if (!(other instanceof FormattedNumberRange))
return false;
// NumberStringBuilder and BigDecimal are mutable, so we can't call
// #equals() or #hashCode() on them directly.
FormattedNumberRange _other = (FormattedNumberRange) other;
return Arrays.equals(string.toCharArray(), _other.string.toCharArray())
&& Arrays.equals(string.toFieldArray(), _other.string.toFieldArray())
&& quantity1.toBigDecimal().equals(_other.quantity1.toBigDecimal())
&& quantity2.toBigDecimal().equals(_other.quantity2.toBigDecimal());
}
}

View File

@ -152,9 +152,9 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
public FormattedNumber format(DecimalQuantity fq) {
NumberStringBuilder string = new NumberStringBuilder();
if (computeCompiled()) {
compiled.apply(fq, string);
compiled.format(fq, string);
} else {
NumberFormatterImpl.applyStatic(resolve(), fq, string);
NumberFormatterImpl.formatStatic(resolve(), fq, string);
}
return new FormattedNumber(string, fq);
}
@ -190,7 +190,7 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedN
// Further benchmarking is required.
long currentCount = callCount.incrementAndGet(this);
if (currentCount == macros.threshold.longValue()) {
compiled = NumberFormatterImpl.fromMacros(macros);
compiled = new NumberFormatterImpl(macros);
return true;
} else if (compiled != null) {
return true;

View File

@ -0,0 +1,98 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.number;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
/**
* A NumberRangeFormatter that has a locale associated with it; this means .formatRange() methods are available.
*
* @author sffc
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public class LocalizedNumberRangeFormatter extends NumberRangeFormatterSettings<LocalizedNumberRangeFormatter> {
private volatile NumberRangeFormatterImpl fImpl;
LocalizedNumberRangeFormatter(NumberRangeFormatterSettings<?> parent, int key, Object value) {
super(parent, key, value);
}
/**
* Format the given integers to a string using the settings specified in the NumberRangeFormatter fluent setting
* chain.
*
* @param first
* The first number in the range, usually to the left in LTR locales.
* @param second
* The second number in the range, usually to the right in LTR locales.
* @return A FormattedNumberRange object; call .toString() to get the string.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public FormattedNumberRange formatRange(int first, int second) {
DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first);
DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second);
return formatImpl(dq1, dq2, first == second);
}
/**
* Format the given doubles to a string using the settings specified in the NumberRangeFormatter fluent setting
* chain.
*
* @param first
* The first number in the range, usually to the left in LTR locales.
* @param second
* The second number in the range, usually to the right in LTR locales.
* @return A FormattedNumberRange object; call .toString() to get the string.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public FormattedNumberRange formatRange(double first, double second) {
DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first);
DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second);
// Note: double equality could be changed to epsilon equality later if there is demand.
// The epsilon should be set via an API method.
return formatImpl(dq1, dq2, first == second);
}
/**
* Format the given Numbers to a string using the settings specified in the NumberRangeFormatter fluent setting
* chain.
*
* @param first
* The first number in the range, usually to the left in LTR locales.
* @param second
* The second number in the range, usually to the right in LTR locales.
* @return A FormattedNumberRange object; call .toString() to get the string.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public FormattedNumberRange formatRange(Number first, Number second) {
if (first == null || second == null) {
throw new IllegalArgumentException("Cannot format null values in range");
}
DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first);
DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second);
return formatImpl(dq1, dq2, first.equals(second));
}
FormattedNumberRange formatImpl(DecimalQuantity first, DecimalQuantity second, boolean equalBeforeRounding) {
if (fImpl == null) {
fImpl = new NumberRangeFormatterImpl(resolve());
}
return fImpl.format(first, second, equalBeforeRounding);
}
@Override
LocalizedNumberRangeFormatter create(int key, Object value) {
return new LocalizedNumberRangeFormatter(this, key, value);
}
}

View File

@ -42,21 +42,21 @@ import com.ibm.icu.util.MeasureUnit;
class NumberFormatterImpl {
/** Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly. */
public static NumberFormatterImpl fromMacros(MacroProps macros) {
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, true);
return new NumberFormatterImpl(microPropsGenerator);
public NumberFormatterImpl(MacroProps macros) {
this(macrosToMicroGenerator(macros, true));
}
/**
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
*/
public static void applyStatic(
public static int formatStatic(
MacroProps macros,
DecimalQuantity inValue,
NumberStringBuilder outString) {
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
MicroProps micros = microPropsGenerator.processQuantity(inValue);
microsToString(micros, inValue, outString);
MicroProps micros = preProcessUnsafe(macros, inValue);
int length = writeNumber(micros, inValue, outString, 0);
length += writeAffixes(micros, outString, 0, length);
return length;
}
/**
@ -82,9 +82,40 @@ class NumberFormatterImpl {
this.microPropsGenerator = microPropsGenerator;
}
public void apply(DecimalQuantity inValue, NumberStringBuilder outString) {
/**
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
*/
public int format(DecimalQuantity inValue, NumberStringBuilder outString) {
MicroProps micros = preProcess(inValue);
int length = writeNumber(micros, inValue, outString, 0);
length += writeAffixes(micros, outString, 0, length);
return length;
}
/**
* Like format(), but saves the result into an output MicroProps without additional processing.
*/
public MicroProps preProcess(DecimalQuantity inValue) {
MicroProps micros = microPropsGenerator.processQuantity(inValue);
microsToString(micros, inValue, outString);
micros.rounder.apply(inValue);
if (micros.integerWidth.maxInt == -1) {
inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
} else {
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
}
return micros;
}
private static MicroProps preProcessUnsafe(MacroProps macros, DecimalQuantity inValue) {
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
MicroProps micros = microPropsGenerator.processQuantity(inValue);
micros.rounder.apply(inValue);
if (micros.integerWidth.maxInt == -1) {
inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
} else {
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
}
return micros;
}
public int getPrefixSuffix(byte signum, StandardPlural plural, NumberStringBuilder output) {
@ -350,64 +381,55 @@ class NumberFormatterImpl {
//////////
/**
* Synthesizes the output string from a MicroProps and DecimalQuantity.
*
* @param micros
* The MicroProps after the quantity has been consumed. Will not be mutated.
* @param quantity
* The DecimalQuantity to be rendered. May be mutated.
* @param string
* The output string. Will be mutated.
* Adds the affixes. Intended to be called immediately after formatNumber.
*/
private static void microsToString(
public static int writeAffixes(
MicroProps micros,
DecimalQuantity quantity,
NumberStringBuilder string) {
micros.rounder.apply(quantity);
if (micros.integerWidth.maxInt == -1) {
quantity.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
} else {
quantity.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
}
int length = writeNumber(micros, quantity, string);
// NOTE: When range formatting is added, these modifiers can bubble up.
// For now, apply them all here at once.
NumberStringBuilder string,
int start,
int end) {
// Always apply the inner modifier (which is "strong").
length += micros.modInner.apply(string, 0, length);
int length = micros.modInner.apply(string, start, end);
if (micros.padding.isValid()) {
micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, 0, length);
micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, start, end + length);
} else {
length += micros.modMiddle.apply(string, 0, length);
length += micros.modOuter.apply(string, 0, length);
length += micros.modMiddle.apply(string, start, end + length);
length += micros.modOuter.apply(string, start, end + length);
}
return length;
}
private static int writeNumber(
/**
* Synthesizes the output string from a MicroProps and DecimalQuantity.
* This method formats only the main number, not affixes.
*/
public static int writeNumber(
MicroProps micros,
DecimalQuantity quantity,
NumberStringBuilder string) {
NumberStringBuilder string,
int index) {
int length = 0;
if (quantity.isInfinite()) {
length += string.insert(length, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
length += string.insert(length + index, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
} else if (quantity.isNaN()) {
length += string.insert(length, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
length += string.insert(length + index, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
} else {
// Add the integer digits
length += writeIntegerDigits(micros, quantity, string);
length += writeIntegerDigits(micros, quantity, string, length + index);
// Add the decimal point
if (quantity.getLowerDisplayMagnitude() < 0
|| micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
length += string.insert(length,
length += string.insert(length + index,
micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
: micros.symbols.getDecimalSeparatorString(),
NumberFormat.Field.DECIMAL_SEPARATOR);
}
// Add the fraction digits
length += writeFractionDigits(micros, quantity, string);
length += writeFractionDigits(micros, quantity, string, length + index);
}
return length;
@ -416,13 +438,14 @@ class NumberFormatterImpl {
private static int writeIntegerDigits(
MicroProps micros,
DecimalQuantity quantity,
NumberStringBuilder string) {
NumberStringBuilder string,
int index) {
int length = 0;
int integerCount = quantity.getUpperDisplayMagnitude() + 1;
for (int i = 0; i < integerCount; i++) {
// Add grouping separator
if (micros.grouping.groupAtPosition(i, quantity)) {
length += string.insert(0,
length += string.insert(index,
micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
: micros.symbols.getGroupingSeparatorString(),
NumberFormat.Field.GROUPING_SEPARATOR);
@ -431,11 +454,11 @@ class NumberFormatterImpl {
// Get and append the next digit value
byte nextDigit = quantity.getDigit(i);
if (micros.symbols.getCodePointZero() != -1) {
length += string.insertCodePoint(0,
length += string.insertCodePoint(index,
micros.symbols.getCodePointZero() + nextDigit,
NumberFormat.Field.INTEGER);
} else {
length += string.insert(0,
length += string.insert(index,
micros.symbols.getDigitStringsLocal()[nextDigit],
NumberFormat.Field.INTEGER);
}
@ -446,17 +469,18 @@ class NumberFormatterImpl {
private static int writeFractionDigits(
MicroProps micros,
DecimalQuantity quantity,
NumberStringBuilder string) {
NumberStringBuilder string,
int index) {
int length = 0;
int fractionCount = -quantity.getLowerDisplayMagnitude();
for (int i = 0; i < fractionCount; i++) {
// Get and append the next digit value
byte nextDigit = quantity.getDigit(-i - 1);
if (micros.symbols.getCodePointZero() != -1) {
length += string.appendCodePoint(micros.symbols.getCodePointZero() + nextDigit,
length += string.insertCodePoint(length + index, micros.symbols.getCodePointZero() + nextDigit,
NumberFormat.Field.FRACTION);
} else {
length += string.append(micros.symbols.getDigitStringsLocal()[nextDigit],
length += string.insert(length + index, micros.symbols.getDigitStringsLocal()[nextDigit],
NumberFormat.Field.FRACTION);
}
}

View File

@ -47,10 +47,10 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
static final int KEY_PER_UNIT = 15;
static final int KEY_MAX = 16;
final NumberFormatterSettings<?> parent;
final int key;
final Object value;
volatile MacroProps resolvedMacros;
private final NumberFormatterSettings<?> parent;
private final int key;
private final Object value;
private volatile MacroProps resolvedMacros;
NumberFormatterSettings(NumberFormatterSettings<?> parent, int key, Object value) {
this.parent = parent;

View File

@ -263,8 +263,8 @@ final class NumberPropertyMapper {
// TODO: Overriding here is a bit of a hack. Should this logic go earlier?
if (macros.precision instanceof FractionPrecision) {
// For the purposes of rounding, get the original min/max int/frac, since the local
// variables
// have been manipulated for display purposes.
// variables have been manipulated for display purposes.
int maxInt_ = properties.getMaximumIntegerDigits();
int minInt_ = properties.getMinimumIntegerDigits();
int minFrac_ = properties.getMinimumFractionDigits();
int maxFrac_ = properties.getMaximumFractionDigits();
@ -275,8 +275,15 @@ final class NumberPropertyMapper {
// Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
macros.precision = Precision.constructSignificant(1, maxFrac_ + 1).withMode(mathContext);
} else {
// All other scientific patterns, which mean round to minInt+maxFrac
macros.precision = Precision.constructSignificant(minInt_ + minFrac_, minInt_ + maxFrac_)
int maxSig_ = minInt_ + maxFrac_;
// Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
if (maxInt_ > minInt_ && minInt_ > 1) {
minInt_ = 1;
}
int minSig_ = minInt_ + minFrac_;
// To avoid regression, maxSig is not reset when minInt_ set to 1.
// TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
macros.precision = Precision.constructSignificant(minSig_, maxSig_)
.withMode(mathContext);
}
}

View File

@ -0,0 +1,209 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.number;
import java.util.Locale;
import com.ibm.icu.util.ULocale;
/**
* The main entrypoint to the formatting of ranges of numbers, including currencies and other units of measurement.
* <p>
* Usage example:
* <pre>
* NumberRangeFormatter.with()
* .identityFallback(RangeIdentityFallback.APPROXIMATELY_OR_SINGLE_VALUE)
* .numberFormatterFirst(NumberFormatter.with().unit(MeasureUnit.METER))
* .numberFormatterSecond(NumberFormatter.with().unit(MeasureUnit.KILOMETER))
* .locale(ULocale.UK)
* .formatRange(750, 1.2)
* .toString();
* // => "750 m - 1.2 km"
* </pre>
* <p>
* Like NumberFormatter, NumberRangeFormatter instances are immutable and thread-safe. This API is based on the
* <em>fluent</em> design pattern popularized by libraries such as Google's Guava.
*
* @author sffc
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
*/
public abstract class NumberRangeFormatter {
/**
* Defines how to merge fields that are identical across the range sign.
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public enum RangeCollapse {
/**
* Use locale data and heuristics to determine how much of the string to collapse. Could end up collapsing none,
* some, or all repeated pieces in a locale-sensitive way.
* <p>
* The heuristics used for this option are subject to change over time.
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
AUTO,
/**
* Do not collapse any part of the number. Example: "3.2 thousand kilograms 5.3 thousand kilograms"
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
NONE,
/**
* Collapse the unit part of the number, but not the notation, if present. Example: "3.2 thousand 5.3 thousand
* kilograms"
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
UNIT,
/**
* Collapse any field that is equal across the range sign. May introduce ambiguity on the magnitude of the
* number. Example: "3.2 5.3 thousand kilograms"
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
ALL
}
/**
* Defines the behavior when the two numbers in the range are identical after rounding. To programmatically detect
* when the identity fallback is used, compare the lower and upper BigDecimals via FormattedNumber.
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public static enum RangeIdentityFallback {
/**
* Show the number as a single value rather than a range. Example: "$5"
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
SINGLE_VALUE,
/**
* Show the number using a locale-sensitive approximation pattern. If the numbers were the same before rounding,
* show the single value. Example: "~$5" or "$5"
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
APPROXIMATELY_OR_SINGLE_VALUE,
/**
* Show the number using a locale-sensitive approximation pattern. Use the range pattern always, even if the
* inputs are the same. Example: "~$5"
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
APPROXIMATELY,
/**
* Show the number as the range of two equal values. Use the range pattern always, even if the inputs are the
* same. Example (with RangeCollapse.NONE): "$5 $5"
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
RANGE
}
/**
* Used in the result class FormattedNumberRange to indicate to the user whether the numbers formatted in the range
* were equal or not, and whether or not the identity fallback was applied.
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public static enum RangeIdentityResult {
/**
* Used to indicate that the two numbers in the range were equal, even before any rounding rules were applied.
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
EQUAL_BEFORE_ROUNDING,
/**
* Used to indicate that the two numbers in the range were equal, but only after rounding rules were applied.
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
EQUAL_AFTER_ROUNDING,
/**
* Used to indicate that the two numbers in the range were not equal, even after rounding rules were applied.
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
NOT_EQUAL
}
private static final UnlocalizedNumberRangeFormatter BASE = new UnlocalizedNumberRangeFormatter();
/**
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is not currently
* known at the call site.
*
* @return An {@link UnlocalizedNumberRangeFormatter}, to be used for chaining.
* @draft ICU 63
*/
public static UnlocalizedNumberRangeFormatter with() {
return BASE;
}
/**
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is known at the call
* site.
*
* @param locale
* The locale from which to load formats and symbols for number range formatting.
* @return A {@link LocalizedNumberRangeFormatter}, to be used for chaining.
* @draft ICU 63
*/
public static LocalizedNumberRangeFormatter withLocale(Locale locale) {
return BASE.locale(locale);
}
/**
* Call this method at the beginning of a NumberRangeFormatter fluent chain in which the locale is known at the call
* site.
*
* @param locale
* The locale from which to load formats and symbols for number range formatting.
* @return A {@link LocalizedNumberRangeFormatter}, to be used for chaining.
* @draft ICU 63
*/
public static LocalizedNumberRangeFormatter withLocale(ULocale locale) {
return BASE.locale(locale);
}
}

View File

@ -0,0 +1,370 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.number;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.MicroProps;
import com.ibm.icu.impl.number.Modifier;
import com.ibm.icu.impl.number.NumberStringBuilder;
import com.ibm.icu.impl.number.SimpleModifier;
import com.ibm.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
import com.ibm.icu.impl.number.range.RangeMacroProps;
import com.ibm.icu.impl.number.range.StandardPluralRanges;
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityResult;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
/**
* Business logic behind NumberRangeFormatter.
*/
class NumberRangeFormatterImpl {
final NumberFormatterImpl formatterImpl1;
final NumberFormatterImpl formatterImpl2;
final boolean fSameFormatters;
final NumberRangeFormatter.RangeCollapse fCollapse;
final NumberRangeFormatter.RangeIdentityFallback fIdentityFallback;
// Should be final, but they are set in a helper function, not the constructor proper.
// TODO: Clean up to make these fields actually final.
/* final */ String fRangePattern;
/* final */ SimpleModifier fApproximatelyModifier;
final StandardPluralRanges fPluralRanges;
////////////////////
// Helper function for 2-dimensional switch statement
int identity2d(RangeIdentityFallback a, RangeIdentityResult b) {
return a.ordinal() | (b.ordinal() << 4);
}
private static final class NumberRangeDataSink extends UResource.Sink {
String rangePattern;
String approximatelyPattern;
// For use with SimpleFormatterImpl
StringBuilder sb;
NumberRangeDataSink(StringBuilder sb) {
this.sb = sb;
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table miscTable = value.getTable();
for (int i = 0; miscTable.getKeyAndValue(i, key, value); ++i) {
if (key.contentEquals("range") && rangePattern == null) {
String pattern = value.getString();
rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
}
if (key.contentEquals("approximately") && approximatelyPattern == null) {
String pattern = value.getString();
approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
}
}
}
}
private static void getNumberRangeData(
ULocale locale,
String nsName,
NumberRangeFormatterImpl out) {
StringBuilder sb = new StringBuilder();
NumberRangeDataSink sink = new NumberRangeDataSink(sb);
ICUResourceBundle resource;
resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
sb.append("NumberElements/");
sb.append(nsName);
sb.append("/miscPatterns");
String key = sb.toString();
resource.getAllItemsWithFallback(key, sink);
// TODO: Is it necessary to manually fall back to latn, or does the data sink take care of that?
if (sink.rangePattern == null) {
sink.rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments("{0}{1}", sb, 2, 2);
}
if (sink.approximatelyPattern == null) {
sink.approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments("~{0}", sb, 1, 1);
}
out.fRangePattern = sink.rangePattern;
out.fApproximatelyModifier = new SimpleModifier(sink.approximatelyPattern, null, false);
}
////////////////////
public NumberRangeFormatterImpl(RangeMacroProps macros) {
formatterImpl1 = new NumberFormatterImpl(macros.formatter1 != null ? macros.formatter1.resolve()
: NumberFormatter.withLocale(macros.loc).resolve());
formatterImpl2 = new NumberFormatterImpl(macros.formatter2 != null ? macros.formatter2.resolve()
: NumberFormatter.withLocale(macros.loc).resolve());
fSameFormatters = macros.sameFormatters != 0;
fCollapse = macros.collapse != null ? macros.collapse : NumberRangeFormatter.RangeCollapse.AUTO;
fIdentityFallback = macros.identityFallback != null ? macros.identityFallback
: NumberRangeFormatter.RangeIdentityFallback.APPROXIMATELY;
// TODO: As of this writing (ICU 63), there is no locale that has different number miscPatterns
// based on numbering system. Therefore, data is loaded only from latn. If this changes,
// this part of the code should be updated to load from the local numbering system.
// The numbering system could come from the one specified in the NumberFormatter passed to
// numberFormatterBoth() or similar.
// See ICU-20144
getNumberRangeData(macros.loc, "latn", this);
// TODO: Get locale from PluralRules instead?
fPluralRanges = new StandardPluralRanges(macros.loc);
}
public FormattedNumberRange format(DecimalQuantity quantity1, DecimalQuantity quantity2, boolean equalBeforeRounding) {
NumberStringBuilder string = new NumberStringBuilder();
MicroProps micros1 = formatterImpl1.preProcess(quantity1);
MicroProps micros2;
if (fSameFormatters) {
micros2 = formatterImpl1.preProcess(quantity2);
} else {
micros2 = formatterImpl2.preProcess(quantity2);
}
// If any of the affixes are different, an identity is not possible
// and we must use formatRange().
// TODO: Write this as MicroProps operator==() ?
// TODO: Avoid the redundancy of these equality operations with the
// ones in formatRange?
if (!micros1.modInner.semanticallyEquivalent(micros2.modInner)
|| !micros1.modMiddle.semanticallyEquivalent(micros2.modMiddle)
|| !micros1.modOuter.semanticallyEquivalent(micros2.modOuter)) {
formatRange(quantity1, quantity2, string, micros1, micros2);
return new FormattedNumberRange(string, quantity1, quantity2, RangeIdentityResult.NOT_EQUAL);
}
// Check for identity
RangeIdentityResult identityResult;
if (equalBeforeRounding) {
identityResult = RangeIdentityResult.EQUAL_BEFORE_ROUNDING;
} else if (quantity1.equals(quantity2)) {
identityResult = RangeIdentityResult.EQUAL_AFTER_ROUNDING;
} else {
identityResult = RangeIdentityResult.NOT_EQUAL;
}
// Java does not let us use a constexpr like C++;
// we need to expand identity2d calls.
switch (identity2d(fIdentityFallback, identityResult)) {
case (3 | (2 << 4)): // RANGE, NOT_EQUAL
case (3 | (1 << 4)): // RANGE, EQUAL_AFTER_ROUNDING
case (3 | (0 << 4)): // RANGE, EQUAL_BEFORE_ROUNDING
case (2 | (2 << 4)): // APPROXIMATELY, NOT_EQUAL
case (1 | (2 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, NOT_EQUAL
case (0 | (2 << 4)): // SINGLE_VALUE, NOT_EQUAL
formatRange(quantity1, quantity2, string, micros1, micros2);
break;
case (2 | (1 << 4)): // APPROXIMATELY, EQUAL_AFTER_ROUNDING
case (2 | (0 << 4)): // APPROXIMATELY, EQUAL_BEFORE_ROUNDING
case (1 | (1 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, EQUAL_AFTER_ROUNDING
formatApproximately(quantity1, quantity2, string, micros1, micros2);
break;
case (1 | (0 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, EQUAL_BEFORE_ROUNDING
case (0 | (1 << 4)): // SINGLE_VALUE, EQUAL_AFTER_ROUNDING
case (0 | (0 << 4)): // SINGLE_VALUE, EQUAL_BEFORE_ROUNDING
formatSingleValue(quantity1, quantity2, string, micros1, micros2);
break;
default:
assert false;
break;
}
return new FormattedNumberRange(string, quantity1, quantity2, identityResult);
}
private void formatSingleValue(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
MicroProps micros1, MicroProps micros2) {
if (fSameFormatters) {
int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
NumberFormatterImpl.writeAffixes(micros1, string, 0, length);
} else {
formatRange(quantity1, quantity2, string, micros1, micros2);
}
}
private void formatApproximately(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
MicroProps micros1, MicroProps micros2) {
if (fSameFormatters) {
int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
length += NumberFormatterImpl.writeAffixes(micros1, string, 0, length);
fApproximatelyModifier.apply(string, 0, length);
} else {
formatRange(quantity1, quantity2, string, micros1, micros2);
}
}
private void formatRange(DecimalQuantity quantity1, DecimalQuantity quantity2, NumberStringBuilder string,
MicroProps micros1, MicroProps micros2) {
// modInner is always notation (scientific); collapsable in ALL.
// modOuter is always units; collapsable in ALL, AUTO, and UNIT.
// modMiddle could be either; collapsable in ALL and sometimes AUTO and UNIT.
// Never collapse an outer mod but not an inner mod.
boolean collapseOuter, collapseMiddle, collapseInner;
switch (fCollapse) {
case ALL:
case AUTO:
case UNIT:
{
// OUTER MODIFIER
collapseOuter = micros1.modOuter.semanticallyEquivalent(micros2.modOuter);
if (!collapseOuter) {
// Never collapse inner mods if outer mods are not collapsable
collapseMiddle = false;
collapseInner = false;
break;
}
// MIDDLE MODIFIER
collapseMiddle = micros1.modMiddle.semanticallyEquivalent(micros2.modMiddle);
if (!collapseMiddle) {
// Never collapse inner mods if outer mods are not collapsable
collapseInner = false;
break;
}
// MIDDLE MODIFIER HEURISTICS
// (could disable collapsing of the middle modifier)
// The modifiers are equal by this point, so we can look at just one of them.
Modifier mm = micros1.modMiddle;
if (fCollapse == RangeCollapse.UNIT) {
// Only collapse if the modifier is a unit.
// TODO: Make a better way to check for a unit?
// TODO: Handle case where the modifier has both notation and unit (compact currency)?
if (!mm.containsField(NumberFormat.Field.CURRENCY) && !mm.containsField(NumberFormat.Field.PERCENT)) {
collapseMiddle = false;
}
} else if (fCollapse == RangeCollapse.AUTO) {
// Heuristic as of ICU 63: collapse only if the modifier is more than one code point.
if (mm.getCodePointCount() <= 1) {
collapseMiddle = false;
}
}
if (!collapseMiddle || fCollapse != RangeCollapse.ALL) {
collapseInner = false;
break;
}
// INNER MODIFIER
collapseInner = micros1.modInner.semanticallyEquivalent(micros2.modInner);
// All done checking for collapsability.
break;
}
default:
collapseOuter = false;
collapseMiddle = false;
collapseInner = false;
break;
}
// Java doesn't have macros, constexprs, or stack objects.
// Use a helper object instead.
PrefixInfixSuffixLengthHelper h = new PrefixInfixSuffixLengthHelper();
SimpleModifier.formatTwoArgPattern(fRangePattern, string, 0, h, null);
assert h.lengthInfix > 0;
// SPACING HEURISTIC
// Add spacing unless all modifiers are collapsed.
// TODO: add API to control this?
// TODO: Use a data-driven heuristic like currency spacing?
// TODO: Use Unicode [:whitespace:] instead of PatternProps whitespace? (consider speed implications)
{
boolean repeatInner = !collapseInner && micros1.modInner.getCodePointCount() > 0;
boolean repeatMiddle = !collapseMiddle && micros1.modMiddle.getCodePointCount() > 0;
boolean repeatOuter = !collapseOuter && micros1.modOuter.getCodePointCount() > 0;
if (repeatInner || repeatMiddle || repeatOuter) {
// Add spacing if there is not already spacing
if (!PatternProps.isWhiteSpace(string.charAt(h.index1()))) {
h.lengthInfix += string.insertCodePoint(h.index1(), '\u0020', null);
}
if (!PatternProps.isWhiteSpace(string.charAt(h.index2() - 1))) {
h.lengthInfix += string.insertCodePoint(h.index2(), '\u0020', null);
}
}
}
h.length1 += NumberFormatterImpl.writeNumber(micros1, quantity1, string, h.index0());
h.length2 += NumberFormatterImpl.writeNumber(micros2, quantity2, string, h.index2());
// TODO: Support padding?
if (collapseInner) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modInner, micros2.modInner);
h.lengthInfix += mod.apply(string, h.index0(), h.index3());
} else {
h.length1 += micros1.modInner.apply(string, h.index0(), h.index1());
h.length2 += micros2.modInner.apply(string, h.index2(), h.index3());
}
if (collapseMiddle) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modMiddle, micros2.modMiddle);
h.lengthInfix += mod.apply(string, h.index0(), h.index3());
} else {
h.length1 += micros1.modMiddle.apply(string, h.index0(), h.index1());
h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index3());
}
if (collapseOuter) {
// Note: this is actually a mix of prefix and suffix, but adding to infix length works
Modifier mod = resolveModifierPlurals(micros1.modOuter, micros2.modOuter);
h.lengthInfix += mod.apply(string, h.index0(), h.index3());
} else {
h.length1 += micros1.modOuter.apply(string, h.index0(), h.index1());
h.length2 += micros2.modOuter.apply(string, h.index2(), h.index3());
}
}
Modifier resolveModifierPlurals(Modifier first, Modifier second) {
Modifier.Parameters firstParameters = first.getParameters();
if (firstParameters == null) {
// No plural form; return a fallback (e.g., the first)
return first;
}
Modifier.Parameters secondParameters = second.getParameters();
if (secondParameters == null) {
// No plural form; return a fallback (e.g., the first)
return first;
}
// Get the required plural form from data
StandardPlural resultPlural = fPluralRanges.resolve(firstParameters.plural, secondParameters.plural);
// Get and return the new Modifier
assert firstParameters.obj == secondParameters.obj;
assert firstParameters.signum == secondParameters.signum;
Modifier mod = firstParameters.obj.getModifier(firstParameters.signum, resultPlural);
assert mod != null;
return mod;
}
}

View File

@ -0,0 +1,240 @@
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.number;
import com.ibm.icu.impl.number.range.RangeMacroProps;
import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
import com.ibm.icu.util.ULocale;
/**
* An abstract base class for specifying settings related to number formatting. This class is implemented by
* {@link UnlocalizedNumberRangeFormatter} and {@link LocalizedNumberRangeFormatter}. This class is not intended for
* public subclassing.
*
* @author sffc
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatterSettings<?>> {
static final int KEY_MACROS = 0; // not used
static final int KEY_LOCALE = 1;
static final int KEY_FORMATTER_1 = 2;
static final int KEY_FORMATTER_2 = 3;
static final int KEY_SAME_FORMATTERS = 4;
static final int KEY_COLLAPSE = 5;
static final int KEY_IDENTITY_FALLBACK = 6;
static final int KEY_MAX = 7;
private final NumberRangeFormatterSettings<?> parent;
private final int key;
private final Object value;
private volatile RangeMacroProps resolvedMacros;
NumberRangeFormatterSettings(NumberRangeFormatterSettings<?> parent, int key, Object value) {
this.parent = parent;
this.key = key;
this.value = value;
}
/**
* Sets the NumberFormatter instance to use for the numbers in the range. The same formatter is applied to both
* sides of the range.
* <p>
* The NumberFormatter instances must not have a locale applied yet; the locale specified on the
* NumberRangeFormatter will be used.
*
* @param formatter
* The formatter to use for both numbers in the range.
* @return The fluent chain.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
* @see NumberRangeFormatter
*/
@SuppressWarnings("unchecked")
public T numberFormatterBoth(UnlocalizedNumberFormatter formatter) {
return (T) create(KEY_SAME_FORMATTERS, true).create(KEY_FORMATTER_1, formatter);
}
/**
* Sets the NumberFormatter instance to use for the first number in the range.
* <p>
* The NumberFormatter instance must not have a locale applied yet; the locale specified on the
* NumberRangeFormatter will be used.
*
* @param formatterFirst
* The formatter to use for the first number in the range.
* @return The fluent chain.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
* @see NumberRangeFormatter
*/
@SuppressWarnings("unchecked")
public T numberFormatterFirst(UnlocalizedNumberFormatter formatterFirst) {
return (T) create(KEY_SAME_FORMATTERS, false).create(KEY_FORMATTER_1, formatterFirst);
}
/**
* Sets the NumberFormatter instances to use for the second number in the range.
* <p>
* The NumberFormatter instance must not have a locale applied yet; the locale specified on the
* NumberRangeFormatter will be used.
*
* @param formatterSecond
* The formatter to use for the second number in the range.
* @return The fluent chain.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
* @see NumberRangeFormatter
*/
@SuppressWarnings("unchecked")
public T numberFormatterSecond(UnlocalizedNumberFormatter formatterSecond) {
return (T) create(KEY_SAME_FORMATTERS, false).create(KEY_FORMATTER_2, formatterSecond);
}
/**
* Sets the aggressiveness of "collapsing" fields across the range separator. Possible values:
* <ul>
* <li>ALL: "3-5K miles"</li>
* <li>UNIT: "3K - 5K miles"</li>
* <li>NONE: "3K miles - 5K miles"</li>
* <li>AUTO: usually UNIT or NONE, depending on the locale and formatter settings</li>
* </ul>
* <p>
* The default value is AUTO.
*
* @param collapse
* The collapsing strategy to use for this range.
* @return The fluent chain.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public T collapse(RangeCollapse collapse) {
return create(KEY_COLLAPSE, collapse);
}
/**
* Sets the behavior when the two sides of the range are the same. This could happen if the same two numbers are
* passed to the formatRange function, or if different numbers are passed to the function but they become the same
* after rounding rules are applied. Possible values:
* <ul>
* <li>SINGLE_VALUE: "5 miles"</li>
* <li>APPROXIMATELY_OR_SINGLE_VALUE: "~5 miles" or "5 miles", depending on whether the number was the same before
* rounding was applied</li>
* <li>APPROXIMATELY: "~5 miles"</li>
* <li>RANGE: "5-5 miles" (with collapse=UNIT)</li>
* </ul>
* <p>
* The default value is APPROXIMATELY.
*
* @param identityFallback
* The strategy to use when formatting two numbers that end up being the same.
* @return The fluent chain.
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
* @see NumberRangeFormatter
*/
public T identityFallback(RangeIdentityFallback identityFallback) {
return create(KEY_IDENTITY_FALLBACK, identityFallback);
}
/* package-protected */ abstract T create(int key, Object value);
RangeMacroProps resolve() {
if (resolvedMacros != null) {
return resolvedMacros;
}
// Although the linked-list fluent storage approach requires this method,
// my benchmarks show that linked-list is still faster than a full clone
// of a MacroProps object at each step.
// TODO: Remove the reference to the parent after the macros are resolved?
RangeMacroProps macros = new RangeMacroProps();
NumberRangeFormatterSettings<?> current = this;
while (current != null) {
switch (current.key) {
case KEY_MACROS:
// ignored for now
break;
case KEY_LOCALE:
if (macros.loc == null) {
macros.loc = (ULocale) current.value;
}
break;
case KEY_FORMATTER_1:
if (macros.formatter1 == null) {
macros.formatter1 = (UnlocalizedNumberFormatter) current.value;
}
break;
case KEY_FORMATTER_2:
if (macros.formatter2 == null) {
macros.formatter2 = (UnlocalizedNumberFormatter) current.value;
}
break;
case KEY_SAME_FORMATTERS:
if (macros.sameFormatters == -1) {
macros.sameFormatters = (boolean) current.value ? 1 : 0;
}
break;
case KEY_COLLAPSE:
if (macros.collapse == null) {
macros.collapse = (RangeCollapse) current.value;
}
break;
case KEY_IDENTITY_FALLBACK:
if (macros.identityFallback == null) {
macros.identityFallback = (RangeIdentityFallback) current.value;
}
break;
default:
throw new AssertionError("Unknown key: " + current.key);
}
current = current.parent;
}
// Copy the locale into the children (see touchRangeLocales in C++)
if (macros.formatter1 != null) {
macros.formatter1.resolve().loc = macros.loc;
}
if (macros.formatter2 != null) {
macros.formatter2.resolve().loc = macros.loc;
}
resolvedMacros = macros;
return macros;
}
/**
* {@inheritDoc}
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
*/
@Override
public int hashCode() {
return resolve().hashCode();
}
/**
* {@inheritDoc}
*
* @draft ICU 63
* @provisional This API might change or be removed in a future release.
*/
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null) {
return false;
}
if (!(other instanceof NumberRangeFormatterSettings)) {
return false;
}
return resolve().equals(((NumberRangeFormatterSettings<?>) other).resolve());
}
}

View File

@ -13,6 +13,7 @@ import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.Precision.SignificantRounderImpl;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.NumberFormat.Field;
/**
* A class that defines the scientific notation style to be used when formatting numbers in
@ -221,8 +222,10 @@ public class ScientificNotation extends Notation implements Cloneable {
@Override
public int getCodePointCount() {
// This method is not used for strong modifiers.
throw new AssertionError();
// NOTE: This method is only called one place, NumberRangeFormatterImpl.
// The call site only cares about != 0 and != 1.
// Return a very large value so that if this method is used elsewhere, we should notice.
return 999;
}
@Override
@ -231,6 +234,27 @@ public class ScientificNotation extends Notation implements Cloneable {
return true;
}
@Override
public boolean containsField(Field field) {
// This method is not currently used. (unsafe path not used in range formatting)
assert false;
return false;
}
@Override
public Parameters getParameters() {
// This method is not currently used.
assert false;
return null;
}
@Override
public boolean semanticallyEquivalent(Modifier other) {
// This method is not currently used. (unsafe path not used in range formatting)
assert false;
return false;
}
@Override
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
return doApply(exponent, output, rightIndex);
@ -279,8 +303,10 @@ public class ScientificNotation extends Notation implements Cloneable {
@Override
public int getCodePointCount() {
// This method is not used for strong modifiers.
throw new AssertionError();
// NOTE: This method is only called one place, NumberRangeFormatterImpl.
// The call site only cares about != 0 and != 1.
// Return a very large value so that if this method is used elsewhere, we should notice.
return 999;
}
@Override
@ -288,5 +314,27 @@ public class ScientificNotation extends Notation implements Cloneable {
// Scientific is always strong
return true;
}
@Override
public boolean containsField(Field field) {
// This method is not used for inner modifiers.
assert false;
return false;
}
@Override
public Parameters getParameters() {
return null;
}
@Override
public boolean semanticallyEquivalent(Modifier other) {
if (!(other instanceof ScientificModifier)) {
return false;
}
ScientificModifier _other = (ScientificModifier) other;
// TODO: Check for locale symbols and settings as well? Could be less efficient.
return exponent == _other.exponent;
}
}
}

Some files were not shown because too many files have changed in this diff Show More