ICU-21087 Merge maint/maint-67 to master
diff --git a/.ci-builds/data-filter.json b/.ci-builds/data-filter.json
index d2dd74d..ffde995 100644
--- a/.ci-builds/data-filter.json
+++ b/.ci-builds/data-filter.json
@@ -1,6 +1,6 @@
{
"localeFilter": {
- "filterType": "language",
+ "filterType": "locale",
"whitelist": [
"en",
"de",
diff --git a/docs/userguide/icu_data/buildtool.md b/docs/userguide/icu_data/buildtool.md
index aa34b8e..7c3eadc 100644
--- a/docs/userguide/icu_data/buildtool.md
+++ b/docs/userguide/icu_data/buildtool.md
@@ -202,7 +202,7 @@
| Region Display <br/> Names | `"region_tree"` | region/\*.txt | **1.1 MiB** |
| Rule-Based <br/> Number Formatting <br/> (Spellout, Ordinals) | `"rbnf_tree"` | rbnf/\*.txt | 538 KiB |
| StringPrep | `"stringprep"` | sprep/\*.txt | 193 KiB |
-| Time Zones | `"misc"` <br/> `"zone_tree"` | misc/metaZones.txt <br/> misc/timezoneTypes.txt <br/> misc/windowsZones.txt <br/> misc/zoneinfo64.txt <br/> zone/\*.txt | 41 KiB <br/> 20 KiB <br/> 22 KiB <br/> 151 KiB <br/> **2.7 MiB** |
+| Time Zones | `"misc"` <br/> `"zone_tree"` <br/> `"zone_supplemental"` | misc/metaZones.txt <br/> misc/timezoneTypes.txt <br/> misc/windowsZones.txt <br/> misc/zoneinfo64.txt <br/> zone/\*.txt <br/> zone/tzdbNames.txt | 41 KiB <br/> 20 KiB <br/> 22 KiB <br/> 151 KiB <br/> **2.7 MiB** <br/> 4.8 KiB |
| Transliteration | `"translit"` | translit/\*.txt | 685 KiB |
| Unicode Character <br/> Names | `"unames"` | in/unames.icu | 269 KiB |
| Unicode Text Layout | `"ulayout"` | in/ulayout.icu | 14 KiB |
diff --git a/icu4c/source/common/localematcher.cpp b/icu4c/source/common/localematcher.cpp
index 7f0dceb..85db8c8 100644
--- a/icu4c/source/common/localematcher.cpp
+++ b/icu4c/source/common/localematcher.cpp
@@ -466,6 +466,7 @@
thresholdDistance(src.thresholdDistance),
demotionPerDesiredLocale(src.demotionPerDesiredLocale),
favorSubtag(src.favorSubtag),
+ direction(src.direction),
supportedLocales(src.supportedLocales), lsrs(src.lsrs),
supportedLocalesLength(src.supportedLocalesLength),
supportedLsrToIndex(src.supportedLsrToIndex),
@@ -502,6 +503,7 @@
thresholdDistance = src.thresholdDistance;
demotionPerDesiredLocale = src.demotionPerDesiredLocale;
favorSubtag = src.favorSubtag;
+ direction = src.direction;
supportedLocales = src.supportedLocales;
lsrs = src.lsrs;
supportedLocalesLength = src.supportedLocalesLength;
diff --git a/icu4c/source/common/unicode/umutablecptrie.h b/icu4c/source/common/unicode/umutablecptrie.h
index 13e71ef..f2af364 100644
--- a/icu4c/source/common/unicode/umutablecptrie.h
+++ b/icu4c/source/common/unicode/umutablecptrie.h
@@ -83,25 +83,6 @@
U_CAPI void U_EXPORT2
umutablecptrie_close(UMutableCPTrie *trie);
-#if U_SHOW_CPLUSPLUS_API
-
-U_NAMESPACE_BEGIN
-
-/**
- * \class LocalUMutableCPTriePointer
- * "Smart pointer" class, closes a UMutableCPTrie via umutablecptrie_close().
- * For most methods see the LocalPointerBase base class.
- *
- * @see LocalPointerBase
- * @see LocalPointer
- * @stable ICU 63
- */
-U_DEFINE_LOCAL_OPEN_POINTER(LocalUMutableCPTriePointer, UMutableCPTrie, umutablecptrie_close);
-
-U_NAMESPACE_END
-
-#endif
-
/**
* Creates a mutable trie with the same contents as the UCPMap.
* You must umutablecptrie_close() the mutable trie once you are done using it.
@@ -235,4 +216,23 @@
U_CDECL_END
+#if U_SHOW_CPLUSPLUS_API
+
+U_NAMESPACE_BEGIN
+
+/**
+ * \class LocalUMutableCPTriePointer
+ * "Smart pointer" class, closes a UMutableCPTrie via umutablecptrie_close().
+ * For most methods see the LocalPointerBase base class.
+ *
+ * @see LocalPointerBase
+ * @see LocalPointer
+ * @stable ICU 63
+ */
+U_DEFINE_LOCAL_OPEN_POINTER(LocalUMutableCPTriePointer, UMutableCPTrie, umutablecptrie_close);
+
+U_NAMESPACE_END
+
+#endif
+
#endif
diff --git a/icu4c/source/common/unicode/utext.h b/icu4c/source/common/unicode/utext.h
index 196056b..37d71a3 100644
--- a/icu4c/source/common/unicode/utext.h
+++ b/icu4c/source/common/unicode/utext.h
@@ -183,25 +183,6 @@
U_STABLE UText * U_EXPORT2
utext_close(UText *ut);
-#if U_SHOW_CPLUSPLUS_API
-
-U_NAMESPACE_BEGIN
-
-/**
- * \class LocalUTextPointer
- * "Smart pointer" class, closes a UText via utext_close().
- * For most methods see the LocalPointerBase base class.
- *
- * @see LocalPointerBase
- * @see LocalPointer
- * @stable ICU 4.4
- */
-U_DEFINE_LOCAL_OPEN_POINTER(LocalUTextPointer, UText, utext_close);
-
-U_NAMESPACE_END
-
-#endif
-
/**
* Open a read-only UText implementation for UTF-8 strings.
*
@@ -1599,5 +1580,24 @@
U_CDECL_END
+#if U_SHOW_CPLUSPLUS_API
+
+U_NAMESPACE_BEGIN
+
+/**
+ * \class LocalUTextPointer
+ * "Smart pointer" class, closes a UText via utext_close().
+ * For most methods see the LocalPointerBase base class.
+ *
+ * @see LocalPointerBase
+ * @see LocalPointer
+ * @stable ICU 4.4
+ */
+U_DEFINE_LOCAL_OPEN_POINTER(LocalUTextPointer, UText, utext_close);
+
+U_NAMESPACE_END
+
+#endif
+
#endif
diff --git a/icu4c/source/data/BUILDRULES.py b/icu4c/source/data/BUILDRULES.py
index 2338afd..e6ddea9 100644
--- a/icu4c/source/data/BUILDRULES.py
+++ b/icu4c/source/data/BUILDRULES.py
@@ -33,6 +33,7 @@
requests += generate_unames(config, io, common_vars)
requests += generate_misc(config, io, common_vars)
requests += generate_curr_supplemental(config, io, common_vars)
+ requests += generate_zone_supplemental(config, io, common_vars)
requests += generate_translit(config, io, common_vars)
# Res Tree Files
@@ -399,6 +400,29 @@
]
+def generate_zone_supplemental(config, io, common_vars):
+ # tzdbNames Res File
+ input_file = InFile("zone/tzdbNames.txt")
+ input_basename = "tzdbNames.txt"
+ output_file = OutFile("zone/tzdbNames.res")
+ return [
+ SingleExecutionRequest(
+ name = "zone_supplemental_res",
+ category = "zone_supplemental",
+ dep_targets = [],
+ input_files = [input_file],
+ output_files = [output_file],
+ tool = IcuTool("genrb"),
+ args = "-s {IN_DIR}/zone -d {OUT_DIR}/zone -i {OUT_DIR} "
+ "-k "
+ "{INPUT_BASENAME}",
+ format_with = {
+ "INPUT_BASENAME": input_basename
+ }
+ )
+ ]
+
+
def generate_translit(config, io, common_vars):
input_files = [
InFile("translit/root.txt"),
@@ -444,10 +468,11 @@
requests = []
category = "%s_tree" % sub_dir
out_prefix = "%s/" % out_sub_dir if out_sub_dir else ""
- # TODO: Clean this up for curr
input_files = [InFile(filename) for filename in io.glob("%s/*.txt" % sub_dir)]
if sub_dir == "curr":
input_files.remove(InFile("curr/supplementalData.txt"))
+ if sub_dir == "zone":
+ input_files.remove(InFile("zone/tzdbNames.txt"))
input_basenames = [v.filename[len(sub_dir)+1:] for v in input_files]
output_files = [
OutFile("%s%s.res" % (out_prefix, v[:-4]))
diff --git a/icu4c/source/i18n/double-conversion-utils.h b/icu4c/source/i18n/double-conversion-utils.h
index 10d8fdd..8c6a0e1 100644
--- a/icu4c/source/i18n/double-conversion-utils.h
+++ b/icu4c/source/i18n/double-conversion-utils.h
@@ -66,15 +66,23 @@
#endif
#endif
+// Not all compilers support __has_attribute and combining a check for both
+// ifdef and __has_attribute on the same preprocessor line isn't portable.
+#ifdef __has_attribute
+# define DOUBLE_CONVERSION_HAS_ATTRIBUTE(x) __has_attribute(x)
+#else
+# define DOUBLE_CONVERSION_HAS_ATTRIBUTE(x) 0
+#endif
+
#ifndef DOUBLE_CONVERSION_UNUSED
-#ifdef __GNUC__
+#if DOUBLE_CONVERSION_HAS_ATTRIBUTE(unused)
#define DOUBLE_CONVERSION_UNUSED __attribute__((unused))
#else
#define DOUBLE_CONVERSION_UNUSED
#endif
#endif
-#if defined(__clang__) && __has_attribute(uninitialized)
+#if DOUBLE_CONVERSION_HAS_ATTRIBUTE(uninitialized)
#define DOUBLE_CONVERSION_STACK_UNINITIALIZED __attribute__((uninitialized))
#else
#define DOUBLE_CONVERSION_STACK_UNINITIALIZED
diff --git a/icu4c/source/i18n/listformatter.cpp b/icu4c/source/i18n/listformatter.cpp
index b9065e8..da99c92 100644
--- a/icu4c/source/i18n/listformatter.cpp
+++ b/icu4c/source/i18n/listformatter.cpp
@@ -348,6 +348,7 @@
return result;
}
+#if !UCONFIG_NO_FORMATTING
static const char* typeWidthToStyleString(UListFormatterType type, UListFormatterWidth width) {
switch (type) {
case ULISTFMT_TYPE_AND:
@@ -391,6 +392,7 @@
return nullptr;
}
+#endif
static const UChar solidus = 0x2F;
static const UChar aliasPrefix[] = { 0x6C,0x69,0x73,0x74,0x50,0x61,0x74,0x74,0x65,0x72,0x6E,0x2F }; // "listPattern/"
@@ -511,9 +513,14 @@
}
ListFormatter* ListFormatter::createInstance(const Locale& locale, UErrorCode& errorCode) {
+#if !UCONFIG_NO_FORMATTING
return createInstance(locale, ULISTFMT_TYPE_AND, ULISTFMT_WIDTH_WIDE, errorCode);
+#else
+ return createInstance(locale, "standard", errorCode);
+#endif
}
+#if !UCONFIG_NO_FORMATTING
ListFormatter* ListFormatter::createInstance(
const Locale& locale, UListFormatterType type, UListFormatterWidth width, UErrorCode& errorCode) {
const char* style = typeWidthToStyleString(type, width);
@@ -523,6 +530,7 @@
}
return createInstance(locale, style, errorCode);
}
+#endif
ListFormatter* ListFormatter::createInstance(const Locale& locale, const char *style, UErrorCode& errorCode) {
const ListFormatInternal* listFormatInternal = getListFormatInternal(locale, style, errorCode);
diff --git a/icu4c/source/i18n/measunit.cpp b/icu4c/source/i18n/measunit.cpp
index 344ba45..4edf130 100644
--- a/icu4c/source/i18n/measunit.cpp
+++ b/icu4c/source/i18n/measunit.cpp
@@ -537,9 +537,9 @@
"solar-mass",
"stone",
"ton",
- "one",
- "percent",
- "permille",
+ "", // TODO(ICU-21076): manual edit of what should have been generated by Java.
+ "percent", // TODO(ICU-21076): regenerate, deal with duplication.
+ "permille", // TODO(ICU-21076): regenerate, deal with duplication.
"gigawatt",
"horsepower",
"kilowatt",
diff --git a/icu4c/source/i18n/measunit_extra.cpp b/icu4c/source/i18n/measunit_extra.cpp
index 8af9e41..aeb60017 100644
--- a/icu4c/source/i18n/measunit_extra.cpp
+++ b/icu4c/source/i18n/measunit_extra.cpp
@@ -12,6 +12,7 @@
// Helpful in toString methods and elsewhere.
#define UNISTR_FROM_STRING_EXPLICIT
+#include <cstdlib>
#include "cstring.h"
#include "measunit_impl.h"
#include "uarrsort.h"
@@ -34,17 +35,32 @@
// TODO: Propose a new error code for this?
constexpr UErrorCode kUnitIdentifierSyntaxError = U_ILLEGAL_ARGUMENT_ERROR;
-// This is to ensure we only insert positive integers into the trie
+// Trie value offset for SI Prefixes. This is big enough to ensure we only
+// insert positive integers into the trie.
constexpr int32_t kSIPrefixOffset = 64;
+// Trie value offset for compound parts, e.g. "-per-", "-", "-and-".
constexpr int32_t kCompoundPartOffset = 128;
enum CompoundPart {
+ // Represents "-per-"
COMPOUND_PART_PER = kCompoundPartOffset,
+ // Represents "-"
COMPOUND_PART_TIMES,
- COMPOUND_PART_PLUS,
+ // Represents "-and-"
+ COMPOUND_PART_AND,
};
+// Trie value offset for "per-".
+constexpr int32_t kInitialCompoundPartOffset = 192;
+
+enum InitialCompoundPart {
+ // Represents "per-", the only compound part that can appear at the start of
+ // an identifier.
+ INITIAL_COMPOUND_PART_PER = kInitialCompoundPartOffset,
+};
+
+// Trie value offset for powers like "square-", "cubic-", "p2-" etc.
constexpr int32_t kPowerPartOffset = 256;
enum PowerPart {
@@ -64,6 +80,8 @@
POWER_PART_P15,
};
+// Trie value offset for simple units, e.g. "gram", "nautical-mile",
+// "fluid-ounce-imperial".
constexpr int32_t kSimpleUnitOffset = 512;
const struct SIPrefixStrings {
@@ -94,7 +112,6 @@
// TODO(ICU-21059): Get this list from data
const char16_t* const gSimpleUnits[] = {
- u"one", // note: expected to be index 0
u"candela",
u"carat",
u"gram",
@@ -226,7 +243,8 @@
// Add syntax parts (compound, power prefixes)
b.add(u"-per-", COMPOUND_PART_PER, status);
b.add(u"-", COMPOUND_PART_TIMES, status);
- b.add(u"-and-", COMPOUND_PART_PLUS, status);
+ b.add(u"-and-", COMPOUND_PART_AND, status);
+ b.add(u"per-", INITIAL_COMPOUND_PART_PER, status);
b.add(u"square-", POWER_PART_P2, status);
b.add(u"cubic-", POWER_PART_P3, status);
b.add(u"p2-", POWER_PART_P2, status);
@@ -270,28 +288,30 @@
enum Type {
TYPE_UNDEFINED,
TYPE_SI_PREFIX,
+ // Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART,
+ // Token type for "per-".
+ TYPE_INITIAL_COMPOUND_PART,
TYPE_POWER_PART,
- TYPE_ONE,
TYPE_SIMPLE_UNIT,
};
+ // Calling getType() is invalid, resulting in an assertion failure, if Token
+ // value isn't positive.
Type getType() const {
- if (fMatch <= 0) {
- UPRV_UNREACHABLE;
- }
+ U_ASSERT(fMatch > 0);
if (fMatch < kCompoundPartOffset) {
return TYPE_SI_PREFIX;
}
- if (fMatch < kPowerPartOffset) {
+ if (fMatch < kInitialCompoundPartOffset) {
return TYPE_COMPOUND_PART;
}
+ if (fMatch < kPowerPartOffset) {
+ return TYPE_INITIAL_COMPOUND_PART;
+ }
if (fMatch < kSimpleUnitOffset) {
return TYPE_POWER_PART;
}
- if (fMatch == kSimpleUnitOffset) {
- return TYPE_ONE;
- }
return TYPE_SIMPLE_UNIT;
}
@@ -300,11 +320,22 @@
return static_cast<UMeasureSIPrefix>(fMatch - kSIPrefixOffset);
}
+ // Valid only for tokens with type TYPE_COMPOUND_PART.
int32_t getMatch() const {
U_ASSERT(getType() == TYPE_COMPOUND_PART);
return fMatch;
}
+ int32_t getInitialCompoundPart() const {
+ // Even if there is only one InitialCompoundPart value, we have this
+ // function for the simplicity of code consistency.
+ U_ASSERT(getType() == TYPE_INITIAL_COMPOUND_PART);
+ // Defensive: if this assert fails, code using this function also needs
+ // to change.
+ U_ASSERT(fMatch == INITIAL_COMPOUND_PART_PER);
+ return fMatch;
+ }
+
int8_t getPower() const {
U_ASSERT(getType() == TYPE_POWER_PART);
return static_cast<int8_t>(fMatch - kPowerPartOffset);
@@ -321,6 +352,14 @@
class Parser {
public:
+ /**
+ * Factory function for parsing the given identifier.
+ *
+ * @param source The identifier to parse. This function does not make a copy
+ * of source: the underlying string that source points at, must outlive the
+ * parser.
+ * @param status ICU error code.
+ */
static Parser from(StringPiece source, UErrorCode& status) {
if (U_FAILURE(status)) {
return Parser();
@@ -339,10 +378,18 @@
}
private:
+ // Tracks parser progress: the offset into fSource.
int32_t fIndex = 0;
+
+ // Since we're not owning this memory, whatever is passed to the constructor
+ // should live longer than this Parser - and the parser shouldn't return any
+ // references to that string.
StringPiece fSource;
UCharsTrie fTrie;
+ // Set to true when we've seen a "-per-" or a "per-", after which all units
+ // are in the denominator. Until we find an "-and-", at which point the
+ // identifier is invalid pending TODO(CLDR-13700).
bool fAfterPer = false;
Parser() : fSource(""), fTrie(u"") {}
@@ -354,11 +401,17 @@
return fIndex < fSource.length();
}
+ // Returns the next Token parsed from fSource, advancing fIndex to the end
+ // of that token in fSource. In case of U_FAILURE(status), the token
+ // returned will cause an abort if getType() is called on it.
Token nextToken(UErrorCode& status) {
fTrie.reset();
int32_t match = -1;
+ // Saves the position in the fSource string for the end of the most
+ // recent matching token.
int32_t previ = -1;
- do {
+ // Find the longest token that matches a value in the trie:
+ while (fIndex < fSource.length()) {
auto result = fTrie.next(fSource.data()[fIndex++]);
if (result == USTRINGTRIE_NO_MATCH) {
break;
@@ -373,7 +426,7 @@
}
U_ASSERT(result == USTRINGTRIE_INTERMEDIATE_VALUE);
// continue;
- } while (fIndex < fSource.length());
+ }
if (match < 0) {
status = kUnitIdentifierSyntaxError;
@@ -383,62 +436,88 @@
return Token(match);
}
- void nextSingleUnit(SingleUnitImpl& result, bool& sawPlus, UErrorCode& status) {
- sawPlus = false;
+ /**
+ * Returns the next "single unit" via result.
+ *
+ * If a "-per-" was parsed, the result will have appropriate negative
+ * dimensionality.
+ *
+ * Returns an error if we parse both compound units and "-and-", since mixed
+ * compound units are not yet supported - TODO(CLDR-13700).
+ *
+ * @param result Will be overwritten by the result, if status shows success.
+ * @param sawAnd If an "-and-" was parsed prior to finding the "single
+ * unit", sawAnd is set to true. If not, it is left as is.
+ * @param status ICU error code.
+ */
+ void nextSingleUnit(SingleUnitImpl& result, bool& sawAnd, UErrorCode& status) {
if (U_FAILURE(status)) {
return;
}
- if (!hasNext()) {
- // probably "one"
- return;
- }
-
// state:
// 0 = no tokens seen yet (will accept power, SI prefix, or simple unit)
// 1 = power token seen (will not accept another power token)
// 2 = SI prefix token seen (will not accept a power or SI prefix token)
int32_t state = 0;
- int32_t previ = fIndex;
- // Maybe read a compound part
- if (fIndex != 0) {
- Token token = nextToken(status);
- if (U_FAILURE(status)) {
- return;
+ bool atStart = fIndex == 0;
+ Token token = nextToken(status);
+ if (U_FAILURE(status)) { return; }
+
+ if (atStart) {
+ // Identifiers optionally start with "per-".
+ if (token.getType() == Token::TYPE_INITIAL_COMPOUND_PART) {
+ U_ASSERT(token.getInitialCompoundPart() == INITIAL_COMPOUND_PART_PER);
+ fAfterPer = true;
+ result.dimensionality = -1;
+
+ token = nextToken(status);
+ if (U_FAILURE(status)) { return; }
}
+ } else {
+ // All other SingleUnit's are separated from previous SingleUnit's
+ // via a compound part:
if (token.getType() != Token::TYPE_COMPOUND_PART) {
status = kUnitIdentifierSyntaxError;
return;
}
+
switch (token.getMatch()) {
- case COMPOUND_PART_PER:
- if (fAfterPer) {
- status = kUnitIdentifierSyntaxError;
- return;
- }
- fAfterPer = true;
+ case COMPOUND_PART_PER:
+ if (sawAnd) {
+ // Mixed compound units not yet supported,
+ // TODO(CLDR-13700).
+ status = kUnitIdentifierSyntaxError;
+ return;
+ }
+ fAfterPer = true;
+ result.dimensionality = -1;
+ break;
+
+ case COMPOUND_PART_TIMES:
+ if (fAfterPer) {
result.dimensionality = -1;
- break;
+ }
+ break;
- case COMPOUND_PART_TIMES:
- break;
-
- case COMPOUND_PART_PLUS:
- sawPlus = true;
- fAfterPer = false;
- break;
+ case COMPOUND_PART_AND:
+ if (fAfterPer) {
+ // Can't start with "-and-", and mixed compound units
+ // not yet supported, TODO(CLDR-13700).
+ status = kUnitIdentifierSyntaxError;
+ return;
+ }
+ sawAnd = true;
+ break;
}
- previ = fIndex;
+
+ token = nextToken(status);
+ if (U_FAILURE(status)) { return; }
}
- // Read a unit
- while (hasNext()) {
- Token token = nextToken(status);
- if (U_FAILURE(status)) {
- return;
- }
-
+ // Read tokens until we have a complete SingleUnit or we reach the end.
+ while (true) {
switch (token.getType()) {
case Token::TYPE_POWER_PART:
if (state > 0) {
@@ -446,7 +525,6 @@
return;
}
result.dimensionality *= token.getPower();
- previ = fIndex;
state = 1;
break;
@@ -456,54 +534,62 @@
return;
}
result.siPrefix = token.getSIPrefix();
- previ = fIndex;
state = 2;
break;
- case Token::TYPE_ONE:
- // Skip "one" and go to the next unit
- return nextSingleUnit(result, sawPlus, status);
-
case Token::TYPE_SIMPLE_UNIT:
result.index = token.getSimpleUnitIndex();
- result.identifier = fSource.substr(previ, fIndex - previ);
return;
default:
status = kUnitIdentifierSyntaxError;
return;
}
- }
- // We ran out of tokens before finding a complete single unit.
- status = kUnitIdentifierSyntaxError;
+ if (!hasNext()) {
+ // We ran out of tokens before finding a complete single unit.
+ status = kUnitIdentifierSyntaxError;
+ return;
+ }
+ token = nextToken(status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+ }
}
+ /// @param result is modified, not overridden. Caller must pass in a
+ /// default-constructed (empty) MeasureUnitImpl instance.
void parseImpl(MeasureUnitImpl& result, UErrorCode& status) {
if (U_FAILURE(status)) {
return;
}
+ if (fSource.empty()) {
+ // The dimenionless unit: nothing to parse. leave result as is.
+ return;
+ }
int32_t unitNum = 0;
while (hasNext()) {
- bool sawPlus;
+ bool sawAnd = false;
SingleUnitImpl singleUnit;
- nextSingleUnit(singleUnit, sawPlus, status);
+ nextSingleUnit(singleUnit, sawAnd, status);
if (U_FAILURE(status)) {
return;
}
- if (singleUnit.index == 0) {
- continue;
- }
+ U_ASSERT(!singleUnit.isDimensionless());
bool added = result.append(singleUnit, status);
- if (sawPlus && !added) {
+ if (sawAnd && !added) {
// Two similar units are not allowed in a mixed unit
status = kUnitIdentifierSyntaxError;
return;
}
if ((++unitNum) >= 2) {
- UMeasureUnitComplexity complexity = sawPlus
- ? UMEASURE_UNIT_MIXED
- : UMEASURE_UNIT_COMPOUND;
+ // nextSingleUnit fails appropriately for "per" and "and" in the
+ // same identifier. It doesn't fail for other compound units
+ // (COMPOUND_PART_TIMES). Consequently we take care of that
+ // here.
+ UMeasureUnitComplexity complexity =
+ sawAnd ? UMEASURE_UNIT_MIXED : UMEASURE_UNIT_COMPOUND;
if (unitNum == 2) {
U_ASSERT(result.complexity == UMEASURE_UNIT_SINGLE);
result.complexity = complexity;
@@ -526,15 +612,22 @@
/**
* Generate the identifier string for a single unit in place.
+ *
+ * Does not support the dimensionless SingleUnitImpl: calling serializeSingle
+ * with the dimensionless unit results in an U_INTERNAL_PROGRAM_ERROR.
+ *
+ * @param first If singleUnit is part of a compound unit, and not its first
+ * single unit, set this to false. Otherwise: set to true.
*/
void serializeSingle(const SingleUnitImpl& singleUnit, bool first, CharString& output, UErrorCode& status) {
if (first && singleUnit.dimensionality < 0) {
- output.append("one-per-", status);
+ // Essentially the "unary per". For compound units with a numerator, the
+ // caller takes care of the "binary per".
+ output.append("per-", status);
}
- if (singleUnit.index == 0) {
- // Don't propagate SI prefixes and powers on one
- output.append("one", status);
+ if (singleUnit.isDimensionless()) {
+ status = U_INTERNAL_PROGRAM_ERROR;
return;
}
int8_t posPower = std::abs(singleUnit.dimensionality);
@@ -573,7 +666,7 @@
return;
}
- output.append(singleUnit.identifier, status);
+ output.appendInvariantChars(gSimpleUnits[singleUnit.index], status);
}
/**
@@ -585,7 +678,8 @@
}
U_ASSERT(impl.identifier.isEmpty());
if (impl.units.length() == 0) {
- impl.identifier.append("one", status);
+ // Dimensionless, constructed by the default constructor: no appending
+ // to impl.identifier, we wish it to contain the zero-length string.
return;
}
if (impl.complexity == UMEASURE_UNIT_COMPOUND) {
@@ -624,8 +718,17 @@
}
-/** @return true if a new item was added */
+/**
+ * Appends a SingleUnitImpl to a MeasureUnitImpl.
+ *
+ * @return true if a new item was added. If unit is the dimensionless unit, it
+ * is never added: the return value will always be false.
+ */
bool appendImpl(MeasureUnitImpl& impl, const SingleUnitImpl& unit, UErrorCode& status) {
+ if (unit.isDimensionless()) {
+ // We don't append dimensionless units.
+ return false;
+ }
// Find a similar unit that already exists, to attempt to coalesce
SingleUnitImpl* oldUnit = nullptr;
for (int32_t i = 0; i < impl.units.length(); i++) {
@@ -635,6 +738,8 @@
}
}
if (oldUnit) {
+ // Both dimensionalities will be positive, or both will be negative, by
+ // virtue of isCompatibleWith().
oldUnit->dimensionality += unit.dimensionality;
} else {
SingleUnitImpl* destination = impl.units.emplaceBack();
@@ -734,7 +839,12 @@
}
int32_t MeasureUnit::getDimensionality(UErrorCode& status) const {
- return SingleUnitImpl::forMeasureUnit(*this, status).dimensionality;
+ SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
+ if (U_FAILURE(status)) { return 0; }
+ if (singleUnit.isDimensionless()) {
+ return 0;
+ }
+ return singleUnit.dimensionality;
}
MeasureUnit MeasureUnit::withDimensionality(int32_t dimensionality, UErrorCode& status) const {
diff --git a/icu4c/source/i18n/measunit_impl.h b/icu4c/source/i18n/measunit_impl.h
index cf0ea63..c69d243 100644
--- a/icu4c/source/i18n/measunit_impl.h
+++ b/icu4c/source/i18n/measunit_impl.h
@@ -25,14 +25,25 @@
struct SingleUnitImpl : public UMemory {
/**
* Gets a single unit from the MeasureUnit. If there are multiple single units, sets an error
- * code and return the base dimensionless unit. Parses if necessary.
+ * code and returns the base dimensionless unit. Parses if necessary.
*/
static SingleUnitImpl forMeasureUnit(const MeasureUnit& measureUnit, UErrorCode& status);
/** Transform this SingleUnitImpl into a MeasureUnit, simplifying if possible. */
MeasureUnit build(UErrorCode& status) const;
- /** Compare this SingleUnitImpl to another SingleUnitImpl. */
+ /**
+ * Compare this SingleUnitImpl to another SingleUnitImpl for the sake of
+ * sorting and coalescing.
+ *
+ * Takes the sign of dimensionality into account, but not the absolute
+ * value: per-meter is not considered the same as meter, but meter is
+ * considered the same as square-meter.
+ *
+ * The dimensionless unit generally does not get compared, but if it did, it
+ * would sort before other units by virtue of index being < 0 and
+ * dimensionality not being negative.
+ */
int32_t compareTo(const SingleUnitImpl& other) const {
if (dimensionality < 0 && other.dimensionality > 0) {
// Positive dimensions first
@@ -66,16 +77,36 @@
return (compareTo(other) == 0);
}
- /** Simple unit index, unique for every simple unit. */
- int32_t index = 0;
+ /**
+ * Returns true if this unit is the "dimensionless base unit", as produced
+ * by the MeasureUnit() default constructor. (This does not include the
+ * likes of concentrations or angles.)
+ */
+ bool isDimensionless() const {
+ return index == -1;
+ }
- /** Simple unit identifier; memory not owned by the SimpleUnit. */
- StringPiece identifier;
+ /**
+ * Simple unit index, unique for every simple unit, -1 for the dimensionless
+ * unit. This is an index into a string list in measunit_extra.cpp.
+ *
+ * The default value is -1, meaning the dimensionless unit:
+ * isDimensionless() will return true, until index is changed.
+ */
+ int32_t index = -1;
- /** SI prefix. **/
+ /**
+ * SI prefix.
+ *
+ * This is ignored for the dimensionless unit.
+ */
UMeasureSIPrefix siPrefix = UMEASURE_SI_PREFIX_ONE;
-
- /** Dimensionality. **/
+
+ /**
+ * Dimensionality.
+ *
+ * This is meaningless for the dimensionless unit.
+ */
int32_t dimensionality = 1;
};
@@ -95,7 +126,8 @@
*
* @param identifier The unit identifier string.
* @param status Set if the identifier string is not valid.
- * @return A newly parsed value object.
+ * @return A newly parsed value object. Behaviour of this unit is
+ * unspecified if an error is returned via status.
*/
static MeasureUnitImpl forIdentifier(StringPiece identifier, UErrorCode& status);
@@ -148,15 +180,23 @@
/** Mutates this MeasureUnitImpl to take the reciprocal. */
void takeReciprocal(UErrorCode& status);
- /** Mutates this MeasureUnitImpl to append a single unit. */
+ /**
+ * Mutates this MeasureUnitImpl to append a single unit.
+ *
+ * @return true if a new item was added. If unit is the dimensionless unit,
+ * it is never added: the return value will always be false.
+ */
bool append(const SingleUnitImpl& singleUnit, UErrorCode& status);
/** The complexity, either SINGLE, COMPOUND, or MIXED. */
UMeasureUnitComplexity complexity = UMEASURE_UNIT_SINGLE;
/**
- * The list of simple units. These may be summed or multiplied, based on the value of the
- * complexity field.
+ * The list of simple units. These may be summed or multiplied, based on the
+ * value of the complexity field.
+ *
+ * The "dimensionless" unit (SingleUnitImpl default constructor) must not be
+ * added to this list.
*/
MaybeStackVector<SingleUnitImpl> units;
diff --git a/icu4c/source/i18n/nounit.cpp b/icu4c/source/i18n/nounit.cpp
index b993cb5..1d4aa05 100644
--- a/icu4c/source/i18n/nounit.cpp
+++ b/icu4c/source/i18n/nounit.cpp
@@ -11,7 +11,7 @@
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(NoUnit)
NoUnit U_EXPORT2 NoUnit::base() {
- return NoUnit("one");
+ return NoUnit("");
}
NoUnit U_EXPORT2 NoUnit::percent() {
diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp
index 8042979..5bba09c 100644
--- a/icu4c/source/i18n/number_formatimpl.cpp
+++ b/icu4c/source/i18n/number_formatimpl.cpp
@@ -203,6 +203,9 @@
patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
}
pattern = utils::getPatternForStyle(macros.locale, nsName, patternStyle, status);
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
}
auto patternInfo = new ParsedPatternInfo();
if (patternInfo == nullptr) {
@@ -211,6 +214,9 @@
}
fPatternInfo.adoptInstead(patternInfo);
PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
/////////////////////////////////////////////////////////////////////////////////////
/// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
@@ -241,6 +247,9 @@
roundingMode = precision.fRoundingMode;
}
fMicros.rounder = {precision, roundingMode, currency, status};
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
// Grouping strategy
if (!macros.grouper.isBogus()) {
@@ -323,6 +332,9 @@
if (safe) {
fImmutablePatternModifier.adoptInstead(patternModifier->createImmutable(status));
}
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
// Outer modifier (CLDR units and currency long names)
if (isCldrUnit) {
@@ -349,6 +361,9 @@
// No outer modifier required
fMicros.modOuter = &fMicros.helpers.emptyWeakModifier;
}
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
// Compact notation
if (macros.notation.fType == Notation::NTN_COMPACT) {
@@ -371,6 +386,9 @@
fCompactHandler.adoptInstead(newCompactHandler);
chain = fCompactHandler.getAlias();
}
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
// Always add the pattern modifier as the last element of the chain.
if (safe) {
diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp
index 74ee0ef..bb32d03 100644
--- a/icu4c/source/i18n/number_longnames.cpp
+++ b/icu4c/source/i18n/number_longnames.cpp
@@ -246,7 +246,8 @@
if (U_FAILURE(status)) { return result; }
UnicodeString secondaryFormat = getWithPlural(secondaryData, StandardPlural::Form::ONE, status);
if (U_FAILURE(status)) { return result; }
- SimpleFormatter secondaryCompiled(secondaryFormat, 1, 1, status);
+ // Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
+ SimpleFormatter secondaryCompiled(secondaryFormat, 0, 1, status);
if (U_FAILURE(status)) { return result; }
UnicodeString secondaryString = secondaryCompiled.getTextWithNoArguments().trim();
// TODO: Why does UnicodeString need to be explicit in the following line?
diff --git a/icu4c/source/i18n/unicode/dtptngen.h b/icu4c/source/i18n/unicode/dtptngen.h
index 35736a0..dd99d58 100644
--- a/icu4c/source/i18n/unicode/dtptngen.h
+++ b/icu4c/source/i18n/unicode/dtptngen.h
@@ -483,6 +483,8 @@
*/
const UnicodeString& getDecimal() const;
+#if !UCONFIG_NO_FORMATTING
+
#ifndef U_HIDE_DRAFT_API
/**
* Get the default hour cycle for a locale. Uses the locale that the
@@ -499,6 +501,8 @@
UDateFormatHourCycle getDefaultHourCycle(UErrorCode& status) const;
#endif /* U_HIDE_DRAFT_API */
+#endif /* #if !UCONFIG_NO_FORMATTING */
+
/**
* ICU "poor man's RTTI", returns a UClassID for the actual class.
*
diff --git a/icu4c/source/i18n/unicode/listformatter.h b/icu4c/source/i18n/unicode/listformatter.h
index 26b42c2..211055d 100644
--- a/icu4c/source/i18n/unicode/listformatter.h
+++ b/icu4c/source/i18n/unicode/listformatter.h
@@ -186,6 +186,7 @@
static ListFormatter* createInstance(const Locale& locale, UErrorCode& errorCode);
#ifndef U_HIDE_DRAFT_API
+#if !UCONFIG_NO_FORMATTING
/**
* Creates a ListFormatter for the given locale, list type, and style.
*
@@ -198,8 +199,9 @@
*/
static ListFormatter* createInstance(
const Locale& locale, UListFormatterType type, UListFormatterWidth width, UErrorCode& errorCode);
+#endif /* !UCONFIG_NO_FORMATTING */
#endif /* U_HIDE_DRAFT_API */
-
+
#ifndef U_HIDE_INTERNAL_API
/**
* Creates a ListFormatter appropriate for a locale and style.
diff --git a/icu4c/source/i18n/unicode/measunit.h b/icu4c/source/i18n/unicode/measunit.h
index d221fd8..e240092 100644
--- a/icu4c/source/i18n/unicode/measunit.h
+++ b/icu4c/source/i18n/unicode/measunit.h
@@ -37,7 +37,7 @@
* Enumeration for unit complexity. There are three levels:
*
* - SINGLE: A single unit, optionally with a power and/or SI prefix. Examples: hectare,
- * square-kilometer, kilojoule, one-per-second.
+ * square-kilometer, kilojoule, per-second.
* - COMPOUND: A unit composed of the product of multiple single units. Examples:
* meter-per-second, kilowatt-hour, kilogram-meter-per-square-second.
* - MIXED: A unit composed of the sum of multiple single units. Examples: foot+inch,
@@ -387,6 +387,8 @@
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
+ * For the base dimensionless unit, withDimensionality does nothing.
+ *
* @param dimensionality The dimensionality (power).
* @param status Set if this is not a SINGLE unit or if another error occurs.
* @return A new SINGLE unit.
@@ -401,6 +403,8 @@
* NOTE: Only works on SINGLE units. If this is a COMPOUND or MIXED unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
+ * For the base dimensionless unit, getDimensionality returns 0.
+ *
* @param status Set if this is not a SINGLE unit or if another error occurs.
* @return The dimensionality (power) of this simple unit.
* @draft ICU 67
@@ -447,7 +451,7 @@
*
* Examples:
* - Given "meter-kilogram-per-second", three units will be returned: "meter",
- * "kilogram", and "one-per-second".
+ * "kilogram", and "per-second".
* - Given "hour+minute+second", three units will be returned: "hour", "minute",
* and "second".
*
@@ -3375,11 +3379,15 @@
private:
- // If non-null, fImpl is owned by the MeasureUnit.
+ // Used by new draft APIs in ICU 67. If non-null, fImpl is owned by the
+ // MeasureUnit.
MeasureUnitImpl* fImpl;
- // These two ints are indices into static string lists in measunit.cpp
+ // An index into a static string list in measunit.cpp. If set to -1, fImpl
+ // is in use instead of fTypeId and fSubTypeId.
int16_t fSubTypeId;
+ // An index into a static string list in measunit.cpp. If set to -1, fImpl
+ // is in use instead of fTypeId and fSubTypeId.
int8_t fTypeId;
MeasureUnit(int32_t typeId, int32_t subTypeId);
@@ -3389,7 +3397,11 @@
static MeasureUnit *create(int typeId, int subTypeId, UErrorCode &status);
/**
- * @return Whether subType is known to ICU.
+ * Sets output's typeId and subTypeId according to subType, if subType is a
+ * valid/known identifier.
+ *
+ * @return Whether subType is known to ICU. If false, output was not
+ * modified.
*/
static bool findBySubType(StringPiece subType, MeasureUnit* output);
diff --git a/icu4c/source/i18n/unicode/udatpg.h b/icu4c/source/i18n/unicode/udatpg.h
index 74c812f..5abe147 100644
--- a/icu4c/source/i18n/unicode/udatpg.h
+++ b/icu4c/source/i18n/unicode/udatpg.h
@@ -652,6 +652,8 @@
const UChar *skeleton, int32_t skeletonLength,
int32_t *pLength);
+#if !UCONFIG_NO_FORMATTING
+
#ifndef U_HIDE_DRAFT_API
/**
* Return the default hour cycle for a locale. Uses the locale that the
@@ -670,4 +672,6 @@
udatpg_getDefaultHourCycle(const UDateTimePatternGenerator *dtpg, UErrorCode* pErrorCode);
#endif /* U_HIDE_DRAFT_API */
+#endif /* #if !UCONFIG_NO_FORMATTING */
+
#endif
diff --git a/icu4c/source/python/icutools/databuilder/filtration.py b/icu4c/source/python/icutools/databuilder/filtration.py
index acdba0e..554013a 100644
--- a/icu4c/source/python/icutools/databuilder/filtration.py
+++ b/icu4c/source/python/icutools/databuilder/filtration.py
@@ -217,7 +217,7 @@
return "root"
i = locale.rfind("_")
if i < 0:
- assert locale == "root"
+ assert locale == "root", "Invalid locale: %s/%s" % (tree, locale)
return None
return locale[:i]
diff --git a/icu4c/source/test/cintltst/unumberformattertst.c b/icu4c/source/test/cintltst/unumberformattertst.c
index 2e296f0..8919c78 100644
--- a/icu4c/source/test/cintltst/unumberformattertst.c
+++ b/icu4c/source/test/cintltst/unumberformattertst.c
@@ -9,9 +9,11 @@
// Helpful in toString methods and elsewhere.
#define UNISTR_FROM_STRING_EXPLICIT
+#include <stdio.h>
#include "unicode/unumberformatter.h"
#include "unicode/umisc.h"
#include "unicode/unum.h"
+#include "unicode/ustring.h"
#include "cformtst.h"
#include "cintltst.h"
#include "cmemory.h"
@@ -26,6 +28,8 @@
static void TestSkeletonParseError(void);
+static void TestPerUnitInArabic(void);
+
void addUNumberFormatterTest(TestNode** root);
#define TESTCASE(x) addTest(root, &x, "tsformat/unumberformatter/" #x)
@@ -36,6 +40,7 @@
TESTCASE(TestExampleCode);
TESTCASE(TestFormattedValue);
TESTCASE(TestSkeletonParseError);
+ TESTCASE(TestPerUnitInArabic);
}
@@ -254,5 +259,88 @@
unumf_close(uformatter);
}
-
+static void TestPerUnitInArabic() {
+ const char* simpleMeasureUnits[] = {
+ "area-acre",
+ "digital-bit",
+ "digital-byte",
+ "temperature-celsius",
+ "length-centimeter",
+ "duration-day",
+ "angle-degree",
+ "temperature-fahrenheit",
+ "volume-fluid-ounce",
+ "length-foot",
+ "volume-gallon",
+ "digital-gigabit",
+ "digital-gigabyte",
+ "mass-gram",
+ "area-hectare",
+ "duration-hour",
+ "length-inch",
+ "digital-kilobit",
+ "digital-kilobyte",
+ "mass-kilogram",
+ "length-kilometer",
+ "volume-liter",
+ "digital-megabit",
+ "digital-megabyte",
+ "length-meter",
+ "length-mile",
+ "length-mile-scandinavian",
+ "volume-milliliter",
+ "length-millimeter",
+ "duration-millisecond",
+ "duration-minute",
+ "duration-month",
+ "mass-ounce",
+ "concentr-percent",
+ "digital-petabyte",
+ "mass-pound",
+ "duration-second",
+ "mass-stone",
+ "digital-terabit",
+ "digital-terabyte",
+ "duration-week",
+ "length-yard",
+ "duration-year"
+ };
+#define BUFFER_LEN 256
+ char buffer[BUFFER_LEN];
+ UChar ubuffer[BUFFER_LEN];
+ const char* locale = "ar";
+ UErrorCode status = U_ZERO_ERROR;
+ UFormattedNumber* formatted = unumf_openResult(&status);
+ if (U_FAILURE(status)) {
+ log_err("FAIL: unumf_openResult failed");
+ return;
+ }
+ for(int32_t i=0; i < UPRV_LENGTHOF(simpleMeasureUnits); ++i) {
+ for(int32_t j=0; j < UPRV_LENGTHOF(simpleMeasureUnits); ++j) {
+ status = U_ZERO_ERROR;
+ sprintf(buffer, "measure-unit/%s per-measure-unit/%s",
+ simpleMeasureUnits[i], simpleMeasureUnits[j]);
+ int32_t outputlen = 0;
+ u_strFromUTF8(ubuffer, BUFFER_LEN, &outputlen, buffer, strlen(buffer), &status);
+ if (U_FAILURE(status)) {
+ log_err("FAIL u_strFromUTF8: %s = %s ( %s )\n", locale, buffer,
+ u_errorName(status));
+ }
+ UNumberFormatter* nf = unumf_openForSkeletonAndLocale(
+ ubuffer, outputlen, locale, &status);
+ if (U_FAILURE(status)) {
+ log_err("FAIL unumf_openForSkeletonAndLocale: %s = %s ( %s )\n",
+ locale, buffer, u_errorName(status));
+ } else {
+ unumf_formatDouble(nf, 1, formatted, &status);
+ if (U_FAILURE(status)) {
+ log_err("FAIL unumf_formatDouble: %s = %s ( %s )\n",
+ locale, buffer, u_errorName(status));
+ }
+ }
+ unumf_close(nf);
+ }
+ }
+ unumf_closeResult(formatted);
+}
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/intltest/dtifmtts.cpp b/icu4c/source/test/intltest/dtifmtts.cpp
index 2f59c19..9713dee 100644
--- a/icu4c/source/test/intltest/dtifmtts.cpp
+++ b/icu4c/source/test/intltest/dtifmtts.cpp
@@ -1943,6 +1943,9 @@
int32_t j = 0;
for (const UnicodeString skeleton : {u"hh", u"HH", u"kk", u"KK", u"jj", u"JJs", u"CC"}) {
LocalPointer<DateIntervalFormat> dtifmt(DateIntervalFormat::createInstance(skeleton, locale, status));
+ if (status.errDataIfFailureAndReset()) {
+ continue;
+ }
FieldPosition fposition;
UnicodeString result;
LocalPointer<Calendar> calendar(Calendar::createInstance(TimeZone::createTimeZone(timeZone), status));
diff --git a/icu4c/source/test/intltest/localematchertest.cpp b/icu4c/source/test/intltest/localematchertest.cpp
index 6d7f48d..683466b 100644
--- a/icu4c/source/test/intltest/localematchertest.cpp
+++ b/icu4c/source/test/intltest/localematchertest.cpp
@@ -6,6 +6,7 @@
#include <string>
#include <vector>
+#include <utility>
#include "unicode/utypes.h"
#include "unicode/localematcher.h"
@@ -333,7 +334,9 @@
{
// arz is a close one-way match to ar, and the region matches.
// (Egyptian Arabic vs. Arabic)
- LocaleMatcher withOneWay = builder.build(errorCode);
+ // Also explicitly exercise the move copy constructor.
+ LocaleMatcher built = builder.build(errorCode);
+ LocaleMatcher withOneWay(std::move(built));
Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
assertEquals("with one-way", "ar",
locString(withOneWay.getBestMatch(desiredIter, errorCode)));
@@ -341,8 +344,11 @@
{
// nb is a less close two-way match to nn, and the regions differ.
// (Norwegian Bokmal vs. Nynorsk)
- LocaleMatcher onlyTwoWay =
+ // Also explicitly exercise the move assignment operator.
+ LocaleMatcher onlyTwoWay = builder.build(errorCode);
+ LocaleMatcher built =
builder.setDirection(ULOCMATCH_DIRECTION_ONLY_TWO_WAY).build(errorCode);
+ onlyTwoWay = std::move(built);
Locale::RangeIterator<Locale *> desiredIter(ARRAY_RANGE(desired));
assertEquals("only two-way", "nn",
locString(onlyTwoWay.getBestMatch(desiredIter, errorCode)));
diff --git a/icu4c/source/test/intltest/measfmttest.cpp b/icu4c/source/test/intltest/measfmttest.cpp
index 6f6c14c..51a85fe 100644
--- a/icu4c/source/test/intltest/measfmttest.cpp
+++ b/icu4c/source/test/intltest/measfmttest.cpp
@@ -79,9 +79,11 @@
void Test20332_PersonUnits();
void TestNumericTime();
void TestNumericTimeSomeSpecialFormats();
+ void TestIdentifiers();
void TestInvalidIdentifiers();
void TestCompoundUnitOperations();
- void TestIdentifiers();
+ void TestDimensionlessBehaviour();
+ void Test21060_AddressSanitizerProblem();
void verifyFormat(
const char *description,
@@ -201,9 +203,11 @@
TESTCASE_AUTO(Test20332_PersonUnits);
TESTCASE_AUTO(TestNumericTime);
TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats);
+ TESTCASE_AUTO(TestIdentifiers);
TESTCASE_AUTO(TestInvalidIdentifiers);
TESTCASE_AUTO(TestCompoundUnitOperations);
- TESTCASE_AUTO(TestIdentifiers);
+ TESTCASE_AUTO(TestDimensionlessBehaviour);
+ TESTCASE_AUTO(Test21060_AddressSanitizerProblem);
TESTCASE_AUTO_END;
}
@@ -3237,10 +3241,43 @@
verifyFormat("Danish fhoursFminutes", fmtDa, fhoursFminutes, 2, "2.03,877");
}
+void MeasureFormatTest::TestIdentifiers() {
+ IcuTestErrorCode status(*this, "TestIdentifiers");
+ struct TestCase {
+ const char* id;
+ const char* normalized;
+ } cases[] = {
+ // Correctly normalized identifiers should not change
+ {"", ""},
+ {"square-meter-per-square-meter", "square-meter-per-square-meter"},
+ {"kilogram-meter-per-square-meter-square-second",
+ "kilogram-meter-per-square-meter-square-second"},
+ {"square-mile-and-square-foot", "square-mile-and-square-foot"},
+ {"square-foot-and-square-mile", "square-foot-and-square-mile"},
+ {"per-cubic-centimeter", "per-cubic-centimeter"},
+ {"per-kilometer", "per-kilometer"},
+
+ // Normalization of power and per
+ {"p2-foot-and-p2-mile", "square-foot-and-square-mile"},
+ {"gram-square-gram-per-dekagram", "cubic-gram-per-dekagram"},
+ {"kilogram-per-meter-per-second", "kilogram-per-meter-second"},
+
+ // TODO(ICU-20920): Add more test cases once the proper ranking is available.
+ };
+ for (const auto &cas : cases) {
+ status.setScope(cas.id);
+ MeasureUnit unit = MeasureUnit::forIdentifier(cas.id, status);
+ status.errIfFailureAndReset();
+ const char* actual = unit.getIdentifier();
+ assertEquals(cas.id, cas.normalized, actual);
+ status.errIfFailureAndReset();
+ }
+}
+
void MeasureFormatTest::TestInvalidIdentifiers() {
IcuTestErrorCode status(*this, "TestInvalidIdentifiers");
- const char* const inputs[] = {
+ const char *const inputs[] = {
"kilo",
"kilokilo",
"onekilo",
@@ -3256,7 +3293,23 @@
"-p2-meter",
"+p2-meter",
"+",
- "-"
+ "-",
+ "-mile",
+ "-and-mile",
+ "-per-mile",
+ "one",
+ "one-one",
+ "one-per-mile",
+ "one-per-cubic-centimeter",
+ "square--per-meter",
+ "metersecond", // Must have compound part in between single units
+
+ // Negative powers not supported in mixed units yet. TODO(CLDR-13701).
+ "per-hour-and-hertz",
+ "hertz-and-per-hour",
+
+ // Compound units not supported in mixed units yet. TODO(CLDR-13700).
+ "kilonewton-meter-and-newton-meter",
};
for (const auto& input : inputs) {
@@ -3293,9 +3346,9 @@
MeasureUnit overQuarticKilometer1 = kilometer.withDimensionality(-4, status);
verifySingleUnit(squareMeter, UMEASURE_SI_PREFIX_ONE, 2, "square-meter");
- verifySingleUnit(overCubicCentimeter, UMEASURE_SI_PREFIX_CENTI, -3, "one-per-cubic-centimeter");
+ verifySingleUnit(overCubicCentimeter, UMEASURE_SI_PREFIX_CENTI, -3, "per-cubic-centimeter");
verifySingleUnit(quarticKilometer, UMEASURE_SI_PREFIX_KILO, 4, "p4-kilometer");
- verifySingleUnit(overQuarticKilometer1, UMEASURE_SI_PREFIX_KILO, -4, "one-per-p4-kilometer");
+ verifySingleUnit(overQuarticKilometer1, UMEASURE_SI_PREFIX_KILO, -4, "per-p4-kilometer");
assertTrue("power inequality", quarticKilometer != overQuarticKilometer1);
@@ -3308,9 +3361,9 @@
.reciprocal(status)
.withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
- verifySingleUnit(overQuarticKilometer2, UMEASURE_SI_PREFIX_KILO, -4, "one-per-p4-kilometer");
- verifySingleUnit(overQuarticKilometer3, UMEASURE_SI_PREFIX_KILO, -4, "one-per-p4-kilometer");
- verifySingleUnit(overQuarticKilometer4, UMEASURE_SI_PREFIX_KILO, -4, "one-per-p4-kilometer");
+ verifySingleUnit(overQuarticKilometer2, UMEASURE_SI_PREFIX_KILO, -4, "per-p4-kilometer");
+ verifySingleUnit(overQuarticKilometer3, UMEASURE_SI_PREFIX_KILO, -4, "per-p4-kilometer");
+ verifySingleUnit(overQuarticKilometer4, UMEASURE_SI_PREFIX_KILO, -4, "per-p4-kilometer");
assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer2);
assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer3);
@@ -3341,7 +3394,7 @@
const char* secondCentimeterSub[] = {"centimeter", "square-kilosecond"};
verifyCompoundUnit(secondCentimeter, "centimeter-square-kilosecond",
secondCentimeterSub, UPRV_LENGTHOF(secondCentimeterSub));
- const char* secondCentimeterPerKilometerSub[] = {"centimeter", "square-kilosecond", "one-per-kilometer"};
+ const char* secondCentimeterPerKilometerSub[] = {"centimeter", "square-kilosecond", "per-kilometer"};
verifyCompoundUnit(secondCentimeterPerKilometer, "centimeter-square-kilosecond-per-kilometer",
secondCentimeterPerKilometerSub, UPRV_LENGTHOF(secondCentimeterPerKilometerSub));
@@ -3376,31 +3429,16 @@
assertTrue("order matters inequality", footInch != inchFoot);
- MeasureUnit one1;
- MeasureUnit one2 = MeasureUnit::forIdentifier("one", status);
- MeasureUnit one3 = MeasureUnit::forIdentifier("", status);
- MeasureUnit squareOne = one2.withDimensionality(2, status);
- MeasureUnit onePerOne = one2.reciprocal(status);
- MeasureUnit squareKiloOne = squareOne.withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
- MeasureUnit onePerSquareKiloOne = squareKiloOne.reciprocal(status);
- MeasureUnit oneOne = MeasureUnit::forIdentifier("one-one", status);
- MeasureUnit onePlusOne = MeasureUnit::forIdentifier("one-and-one", status);
- MeasureUnit kilometer2 = one2.product(kilometer, status);
+ MeasureUnit dimensionless;
+ MeasureUnit dimensionless2 = MeasureUnit::forIdentifier("", status);
+ status.errIfFailureAndReset("Dimensionless MeasureUnit.");
+ assertTrue("dimensionless equality", dimensionless == dimensionless2);
- verifySingleUnit(one1, UMEASURE_SI_PREFIX_ONE, 1, "one");
- verifySingleUnit(one2, UMEASURE_SI_PREFIX_ONE, 1, "one");
- verifySingleUnit(one3, UMEASURE_SI_PREFIX_ONE, 1, "one");
- verifySingleUnit(squareOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
- verifySingleUnit(onePerOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
- verifySingleUnit(squareKiloOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
- verifySingleUnit(onePerSquareKiloOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
- verifySingleUnit(oneOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
- verifySingleUnit(onePlusOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
+ // We support starting from an "identity" MeasureUnit and then combining it
+ // with others via product:
+ MeasureUnit kilometer2 = dimensionless.product(kilometer, status);
+ status.errIfFailureAndReset("dimensionless.product(kilometer, status)");
verifySingleUnit(kilometer2, UMEASURE_SI_PREFIX_KILO, 1, "kilometer");
-
- assertTrue("one equality", one1 == one2);
- assertTrue("one equality", one2 == one3);
- assertTrue("one-per-one equality", onePerOne == onePerSquareKiloOne);
assertTrue("kilometer equality", kilometer == kilometer2);
// Test out-of-range powers
@@ -3411,36 +3449,102 @@
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
MeasureUnit power16b = power15.product(kilometer, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
- MeasureUnit powerN15 = MeasureUnit::forIdentifier("one-per-p15-kilometer", status);
- verifySingleUnit(powerN15, UMEASURE_SI_PREFIX_KILO, -15, "one-per-p15-kilometer");
+ MeasureUnit powerN15 = MeasureUnit::forIdentifier("per-p15-kilometer", status);
+ verifySingleUnit(powerN15, UMEASURE_SI_PREFIX_KILO, -15, "per-p15-kilometer");
status.errIfFailureAndReset();
- MeasureUnit powerN16a = MeasureUnit::forIdentifier("one-per-p16-kilometer", status);
+ MeasureUnit powerN16a = MeasureUnit::forIdentifier("per-p16-kilometer", status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
MeasureUnit powerN16b = powerN15.product(overQuarticKilometer1, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
}
-void MeasureFormatTest::TestIdentifiers() {
- IcuTestErrorCode status(*this, "TestIdentifiers");
- struct TestCase {
- bool valid;
- const char* id;
- const char* normalized;
- } cases[] = {
- { true, "square-meter-per-square-meter", "square-meter-per-square-meter" },
- // TODO(ICU-20920): Add more test cases once the proper ranking is available.
- };
- for (const auto& cas : cases) {
- status.setScope(cas.id);
- MeasureUnit unit = MeasureUnit::forIdentifier(cas.id, status);
- if (!cas.valid) {
- status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
- continue;
- }
- const char* actual = unit.getIdentifier();
- assertEquals(cas.id, cas.normalized, actual);
- status.errIfFailureAndReset();
- }
+void MeasureFormatTest::TestDimensionlessBehaviour() {
+ IcuTestErrorCode status(*this, "TestDimensionlessBehaviour");
+ MeasureUnit dimensionless;
+ MeasureUnit modified;
+
+ // At the time of writing, each of the seven groups below caused
+ // Parser::from("") to be called:
+
+ // splitToSingleUnits
+ int32_t count;
+ LocalArray<MeasureUnit> singles = dimensionless.splitToSingleUnits(count, status);
+ status.errIfFailureAndReset("dimensionless.splitToSingleUnits(...)");
+ assertEquals("no singles in dimensionless", 0, count);
+
+ // product(dimensionless)
+ MeasureUnit mile = MeasureUnit::getMile();
+ mile = mile.product(dimensionless, status);
+ status.errIfFailureAndReset("mile.product(dimensionless, ...)");
+ verifySingleUnit(mile, UMEASURE_SI_PREFIX_ONE, 1, "mile");
+
+ // dimensionless.getSIPrefix()
+ UMeasureSIPrefix siPrefix = dimensionless.getSIPrefix(status);
+ status.errIfFailureAndReset("dimensionless.getSIPrefix(...)");
+ assertEquals("dimensionless SIPrefix", UMEASURE_SI_PREFIX_ONE, siPrefix);
+
+ // dimensionless.withSIPrefix()
+ modified = dimensionless.withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
+ status.errIfFailureAndReset("dimensionless.withSIPrefix(...)");
+ singles = modified.splitToSingleUnits(count, status);
+ assertEquals("no singles in modified", 0, count);
+ siPrefix = modified.getSIPrefix(status);
+ status.errIfFailureAndReset("modified.getSIPrefix(...)");
+ assertEquals("modified SIPrefix", UMEASURE_SI_PREFIX_ONE, siPrefix);
+
+ // dimensionless.getComplexity()
+ UMeasureUnitComplexity complexity = dimensionless.getComplexity(status);
+ status.errIfFailureAndReset("dimensionless.getComplexity(...)");
+ assertEquals("dimensionless complexity", UMEASURE_UNIT_SINGLE, complexity);
+
+ // Dimensionality is mostly meaningless for dimensionless units, but it's
+ // still considered a SINGLE unit, so this code doesn't throw errors:
+
+ // dimensionless.getDimensionality()
+ int32_t dimensionality = dimensionless.getDimensionality(status);
+ status.errIfFailureAndReset("dimensionless.getDimensionality(...)");
+ assertEquals("dimensionless dimensionality", 0, dimensionality);
+
+ // dimensionless.withDimensionality()
+ dimensionless.withDimensionality(-1, status);
+ status.errIfFailureAndReset("dimensionless.withDimensionality(...)");
+ dimensionality = dimensionless.getDimensionality(status);
+ status.errIfFailureAndReset("dimensionless.getDimensionality(...)");
+ assertEquals("dimensionless dimensionality", 0, dimensionality);
+}
+
+// ICU-21060
+void MeasureFormatTest::Test21060_AddressSanitizerProblem() {
+ IcuTestErrorCode status(*this, "Test21060_AddressSanitizerProblem");
+
+ MeasureUnit first = MeasureUnit::forIdentifier("", status);
+ status.errIfFailureAndReset();
+
+ // Experimentally, a compound unit like "kilogram-meter" failed. A single
+ // unit like "kilogram" or "meter" did not fail, did not trigger the
+ // problem.
+ MeasureUnit crux = MeasureUnit::forIdentifier("per-meter", status);
+
+ // Heap allocation of a new CharString for first.identifier happens here:
+ first = first.product(crux, status);
+
+ // Constructing second from first's identifier resulted in a failure later,
+ // as second held a reference to a substring of first's identifier:
+ MeasureUnit second = MeasureUnit::forIdentifier(first.getIdentifier(), status);
+
+ // Heap is freed here, as an old first.identifier CharString is deallocated
+ // and a new CharString is allocated:
+ first = first.product(crux, status);
+
+ // Proving we've had no failure yet:
+ status.errIfFailureAndReset();
+
+ // heap-use-after-free failure happened here, since a SingleUnitImpl had
+ // held onto a StringPiece pointing at a substring of an identifier that was
+ // freed above:
+ second = second.product(crux, status);
+
+ status.errIfFailureAndReset();
}
diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h
index 2597aa8..cb0be28 100644
--- a/icu4c/source/test/intltest/numbertest.h
+++ b/icu4c/source/test/intltest/numbertest.h
@@ -258,6 +258,7 @@
void defaultTokens();
void flexibleSeparators();
void wildcardCharacters();
+ void perUnitInArabic();
void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp
index 3aad601..d5e4bcf 100644
--- a/icu4c/source/test/intltest/numbertest_skeletons.cpp
+++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp
@@ -30,6 +30,7 @@
TESTCASE_AUTO(defaultTokens);
TESTCASE_AUTO(flexibleSeparators);
TESTCASE_AUTO(wildcardCharacters);
+ TESTCASE_AUTO(perUnitInArabic);
TESTCASE_AUTO_END;
}
@@ -362,5 +363,77 @@
}
}
+void NumberSkeletonTest::perUnitInArabic() {
+ IcuTestErrorCode status(*this, "perUnitInArabic");
+
+ struct TestCase {
+ const char16_t* type;
+ const char16_t* subtype;
+ } cases[] = {
+ {u"area", u"acre"},
+ {u"digital", u"bit"},
+ {u"digital", u"byte"},
+ {u"temperature", u"celsius"},
+ {u"length", u"centimeter"},
+ {u"duration", u"day"},
+ {u"angle", u"degree"},
+ {u"temperature", u"fahrenheit"},
+ {u"volume", u"fluid-ounce"},
+ {u"length", u"foot"},
+ {u"volume", u"gallon"},
+ {u"digital", u"gigabit"},
+ {u"digital", u"gigabyte"},
+ {u"mass", u"gram"},
+ {u"area", u"hectare"},
+ {u"duration", u"hour"},
+ {u"length", u"inch"},
+ {u"digital", u"kilobit"},
+ {u"digital", u"kilobyte"},
+ {u"mass", u"kilogram"},
+ {u"length", u"kilometer"},
+ {u"volume", u"liter"},
+ {u"digital", u"megabit"},
+ {u"digital", u"megabyte"},
+ {u"length", u"meter"},
+ {u"length", u"mile"},
+ {u"length", u"mile-scandinavian"},
+ {u"volume", u"milliliter"},
+ {u"length", u"millimeter"},
+ {u"duration", u"millisecond"},
+ {u"duration", u"minute"},
+ {u"duration", u"month"},
+ {u"mass", u"ounce"},
+ {u"concentr", u"percent"},
+ {u"digital", u"petabyte"},
+ {u"mass", u"pound"},
+ {u"duration", u"second"},
+ {u"mass", u"stone"},
+ {u"digital", u"terabit"},
+ {u"digital", u"terabyte"},
+ {u"duration", u"week"},
+ {u"length", u"yard"},
+ {u"duration", u"year"},
+ };
+
+ for (const auto& cas1 : cases) {
+ for (const auto& cas2 : cases) {
+ UnicodeString skeleton(u"measure-unit/");
+ skeleton += cas1.type;
+ skeleton += u"-";
+ skeleton += cas1.subtype;
+ skeleton += u" ";
+ skeleton += u"per-measure-unit/";
+ skeleton += cas2.type;
+ skeleton += u"-";
+ skeleton += cas2.subtype;
+
+ status.setScope(skeleton);
+ UnicodeString actual = NumberFormatter::forSkeleton(skeleton, status).locale("ar")
+ .formatDouble(5142.3, status)
+ .toString(status);
+ status.errIfFailureAndReset();
+ }
+ }
+}
#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp
index 62e161f..5a26a7a 100644
--- a/icu4c/source/test/intltest/numfmtst.cpp
+++ b/icu4c/source/test/intltest/numfmtst.cpp
@@ -9679,6 +9679,9 @@
IcuTestErrorCode status(*this, "Test20956_MonetarySymbolGetters");
LocalPointer<DecimalFormat> decimalFormat(static_cast<DecimalFormat*>(
NumberFormat::createCurrencyInstance("et", status)));
+ if (status.errDataIfFailureAndReset()) {
+ return;
+ }
decimalFormat->setCurrency(u"EEK");
@@ -9823,6 +9826,9 @@
{
LocalPointer<DecimalFormat> decimalFormat(static_cast<DecimalFormat*>(
NumberFormat::createInstance("en-US", UNUM_CURRENCY_PLURAL, status)));
+ if (status.errDataIfFailureAndReset()) {
+ return;
+ }
UnicodeString result;
decimalFormat->toPattern(result);
assertEquals("Currency pattern", u"#,##0.00 ¤¤¤", result);
diff --git a/icu4c/source/test/intltest/restsnew.cpp b/icu4c/source/test/intltest/restsnew.cpp
index b2d72d9..482e241 100644
--- a/icu4c/source/test/intltest/restsnew.cpp
+++ b/icu4c/source/test/intltest/restsnew.cpp
@@ -1395,6 +1395,14 @@
}
}
+/*
+ * The following test for ICU-20706 has infinite loops on certain inputs for
+ * locales and calendars. In order to unblock the build (ICU-21055), those
+ * specific values are temporarily removed.
+ * The issue of the infinite loops and its blocking dependencies were captured
+ * in ICU-21080.
+ */
+
void NewResourceBundleTest::TestIntervalAliasFallbacks() {
const char* locales[] = {
// Thee will not cause infinity loop
@@ -1402,6 +1410,7 @@
"ja",
// These will cause infinity loop
+#if 0
"fr_CA",
"en_150",
"es_419",
@@ -1413,6 +1422,7 @@
"zh_Hant",
"zh_Hant_TW",
"zh_TW",
+#endif
};
const char* calendars[] = {
// These won't cause infinity loop
@@ -1420,6 +1430,7 @@
"chinese",
// These will cause infinity loop
+#if 0
"islamic",
"islamic-civil",
"islamic-tbla",
@@ -1428,6 +1439,7 @@
"islamic-rgsa",
"japanese",
"roc",
+#endif
};
for (int lidx = 0; lidx < UPRV_LENGTHOF(locales); lidx++) {
diff --git a/icu4c/source/test/intltest/restsnew.h b/icu4c/source/test/intltest/restsnew.h
index d3b2d9c..45cc930 100644
--- a/icu4c/source/test/intltest/restsnew.h
+++ b/icu4c/source/test/intltest/restsnew.h
@@ -39,6 +39,7 @@
void TestGetByFallback(void);
void TestFilter(void);
+
void TestIntervalAliasFallbacks(void);
#if U_ENABLE_TRACING
diff --git a/icu4c/source/test/intltest/transtst.cpp b/icu4c/source/test/intltest/transtst.cpp
index fd7f733..8e7bcb0 100644
--- a/icu4c/source/test/intltest/transtst.cpp
+++ b/icu4c/source/test/intltest/transtst.cpp
@@ -1562,6 +1562,7 @@
BASIC_TRANSLITERATOR_ID[i], UTRANS_FORWARD, parseError, status));
if (translit.get() == nullptr || !U_SUCCESS(status)) {
dataerrln("FAIL: createInstance %s failed", BASIC_TRANSLITERATOR_ID[i]);
+ continue;
}
UnicodeString data(TEST_DATA);
UnicodeString expected(EXPECTED_RESULTS[i]);
@@ -1570,6 +1571,7 @@
dataerrln(UnicodeString("FAIL: expected translit(") +
BASIC_TRANSLITERATOR_ID[i] + ") = '" +
EXPECTED_RESULTS[i] + "' but got '" + data);
+ continue;
}
}
for (int32_t i=0; BASIC_TRANSLITERATOR_RULES[i]; i++) {
@@ -1580,6 +1582,7 @@
BASIC_TRANSLITERATOR_RULES[i], UTRANS_FORWARD, parseError, status));
if (translit.get() == nullptr || !U_SUCCESS(status)) {
dataerrln("FAIL: createFromRules %s failed", BASIC_TRANSLITERATOR_RULES[i]);
+ continue;
}
}
}
diff --git a/icu4j/build.properties b/icu4j/build.properties
index 61b6d0b..10f87c9 100644
--- a/icu4j/build.properties
+++ b/icu4j/build.properties
@@ -6,7 +6,7 @@
#*******************************************************************************
api.report.version = 67
api.report.prev.version = 66
-release.file.ver = 67rc
-api.doc.version = 67 Release Candidate
-maven.pom.ver = 67.1-SNAPSHOT
+release.file.ver = 67_1
+api.doc.version = 67.1
+maven.pom.ver = 67.1
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
index 459e514..4f58c66 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
@@ -241,8 +241,10 @@
String compiled = SimpleFormatterImpl
.compileToStringMinMaxArguments(rawPerUnitFormat, sb, 2, 2);
String secondaryFormat = getWithPlural(secondaryData, StandardPlural.ONE);
+
+ // Some "one" pattern may not contain "{0}". For example in "ar" or "ne" locale.
String secondaryCompiled = SimpleFormatterImpl
- .compileToStringMinMaxArguments(secondaryFormat, sb, 1, 1);
+ .compileToStringMinMaxArguments(secondaryFormat, sb, 0, 1);
String secondaryString = SimpleFormatterImpl.getTextWithNoArguments(secondaryCompiled)
.trim();
perUnitFormat = SimpleFormatterImpl.formatCompiledPattern(compiled, "{0}", secondaryString);
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
index 03caedc..db6ee5e 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
@@ -349,4 +349,65 @@
assertEquals(mode.toString(), modeString, skeleton.substring(14));
}
}
+
+ @Test
+ public void perUnitInArabic() {
+ String[][] cases = {
+ {"area", "acre"},
+ {"digital", "bit"},
+ {"digital", "byte"},
+ {"temperature", "celsius"},
+ {"length", "centimeter"},
+ {"duration", "day"},
+ {"angle", "degree"},
+ {"temperature", "fahrenheit"},
+ {"volume", "fluid-ounce"},
+ {"length", "foot"},
+ {"volume", "gallon"},
+ {"digital", "gigabit"},
+ {"digital", "gigabyte"},
+ {"mass", "gram"},
+ {"area", "hectare"},
+ {"duration", "hour"},
+ {"length", "inch"},
+ {"digital", "kilobit"},
+ {"digital", "kilobyte"},
+ {"mass", "kilogram"},
+ {"length", "kilometer"},
+ {"volume", "liter"},
+ {"digital", "megabit"},
+ {"digital", "megabyte"},
+ {"length", "meter"},
+ {"length", "mile"},
+ {"length", "mile-scandinavian"},
+ {"volume", "milliliter"},
+ {"length", "millimeter"},
+ {"duration", "millisecond"},
+ {"duration", "minute"},
+ {"duration", "month"},
+ {"mass", "ounce"},
+ {"concentr", "percent"},
+ {"digital", "petabyte"},
+ {"mass", "pound"},
+ {"duration", "second"},
+ {"mass", "stone"},
+ {"digital", "terabit"},
+ {"digital", "terabyte"},
+ {"duration", "week"},
+ {"length", "yard"},
+ {"duration", "year"},
+ };
+
+ ULocale arabic = new ULocale("ar");
+ for (String[] cas1 : cases) {
+ for (String[] cas2 : cases) {
+ String skeleton = "measure-unit/";
+ skeleton += cas1[0] + "-" + cas1[1] + " per-measure-unit/" + cas2[0] + "-" + cas2[1];
+
+ String actual = NumberFormatter.forSkeleton(skeleton).locale(arabic).format(5142.3)
+ .toString();
+ // Just make sure it won't throw exception
+ }
+ }
+ }
}
diff --git a/vendor/double-conversion/upstream/double-conversion/utils.h b/vendor/double-conversion/upstream/double-conversion/utils.h
index 471c3da..438d055 100644
--- a/vendor/double-conversion/upstream/double-conversion/utils.h
+++ b/vendor/double-conversion/upstream/double-conversion/utils.h
@@ -56,15 +56,23 @@
#endif
#endif
+// Not all compilers support __has_attribute and combining a check for both
+// ifdef and __has_attribute on the same preprocessor line isn't portable.
+#ifdef __has_attribute
+# define DOUBLE_CONVERSION_HAS_ATTRIBUTE(x) __has_attribute(x)
+#else
+# define DOUBLE_CONVERSION_HAS_ATTRIBUTE(x) 0
+#endif
+
#ifndef DOUBLE_CONVERSION_UNUSED
-#ifdef __GNUC__
+#if DOUBLE_CONVERSION_HAS_ATTRIBUTE(unused)
#define DOUBLE_CONVERSION_UNUSED __attribute__((unused))
#else
#define DOUBLE_CONVERSION_UNUSED
#endif
#endif
-#if defined(__clang__) && __has_attribute(uninitialized)
+#if DOUBLE_CONVERSION_HAS_ATTRIBUTE(uninitialized)
#define DOUBLE_CONVERSION_STACK_UNINITIALIZED __attribute__((uninitialized))
#else
#define DOUBLE_CONVERSION_STACK_UNINITIALIZED
diff --git a/vendor/double-conversion/upstream/msvc/double-conversion.vcxproj b/vendor/double-conversion/upstream/msvc/double-conversion.vcxproj
index 5616c8a..e2d2ef8 100644
--- a/vendor/double-conversion/upstream/msvc/double-conversion.vcxproj
+++ b/vendor/double-conversion/upstream/msvc/double-conversion.vcxproj
@@ -147,24 +147,25 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
- <ClCompile Include="..\double-conversion\bignum-dtoa.cc" />
<ClCompile Include="..\double-conversion\bignum.cc" />
+ <ClCompile Include="..\double-conversion\bignum-dtoa.cc" />
<ClCompile Include="..\double-conversion\cached-powers.cc" />
- <ClCompile Include="..\double-conversion\diy-fp.cc" />
- <ClCompile Include="..\double-conversion\double-conversion.cc" />
+ <ClCompile Include="..\double-conversion\double-to-string.cc" />
<ClCompile Include="..\double-conversion\fast-dtoa.cc" />
<ClCompile Include="..\double-conversion\fixed-dtoa.cc" />
+ <ClCompile Include="..\double-conversion\string-to-double.cc" />
<ClCompile Include="..\double-conversion\strtod.cc" />
</ItemGroup>
<ItemGroup>
- <ClInclude Include="..\double-conversion\bignum-dtoa.h" />
<ClInclude Include="..\double-conversion\bignum.h" />
<ClInclude Include="..\double-conversion\cached-powers.h" />
<ClInclude Include="..\double-conversion\diy-fp.h" />
<ClInclude Include="..\double-conversion\double-conversion.h" />
+ <ClInclude Include="..\double-conversion\double-to-string.h" />
<ClInclude Include="..\double-conversion\fast-dtoa.h" />
<ClInclude Include="..\double-conversion\fixed-dtoa.h" />
<ClInclude Include="..\double-conversion\ieee.h" />
+ <ClInclude Include="..\double-conversion\string-to-double.h" />
<ClInclude Include="..\double-conversion\strtod.h" />
<ClInclude Include="..\double-conversion\utils.h" />
</ItemGroup>
diff --git a/vendor/double-conversion/upstream/msvc/double-conversion.vcxproj.filters b/vendor/double-conversion/upstream/msvc/double-conversion.vcxproj.filters
index 664a27f..cebae94 100644
--- a/vendor/double-conversion/upstream/msvc/double-conversion.vcxproj.filters
+++ b/vendor/double-conversion/upstream/msvc/double-conversion.vcxproj.filters
@@ -24,12 +24,6 @@
<ClCompile Include="..\double-conversion\cached-powers.cc">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\double-conversion\diy-fp.cc">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\double-conversion\double-conversion.cc">
- <Filter>Source Files</Filter>
- </ClCompile>
<ClCompile Include="..\double-conversion\fast-dtoa.cc">
<Filter>Source Files</Filter>
</ClCompile>
@@ -39,14 +33,17 @@
<ClCompile Include="..\double-conversion\strtod.cc">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\double-conversion\double-to-string.cc">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\double-conversion\string-to-double.cc">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\double-conversion\bignum.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="..\double-conversion\bignum-dtoa.h">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="..\double-conversion\cached-powers.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -71,5 +68,11 @@
<ClInclude Include="..\double-conversion\utils.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\double-conversion\double-to-string.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\double-conversion\string-to-double.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
</Project>
\ No newline at end of file
diff --git a/vendor/double-conversion/upstream/msvc/run_tests/run_tests.vcxproj b/vendor/double-conversion/upstream/msvc/run_tests/run_tests.vcxproj
index 05d2873..1cb0d36 100644
--- a/vendor/double-conversion/upstream/msvc/run_tests/run_tests.vcxproj
+++ b/vendor/double-conversion/upstream/msvc/run_tests/run_tests.vcxproj
@@ -109,6 +109,7 @@
<PreprocessorDefinitions>_SCL_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>false</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)..</AdditionalIncludeDirectories>
+ <AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>