summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/tmutfmt.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /intl/icu/source/i18n/tmutfmt.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'intl/icu/source/i18n/tmutfmt.cpp')
-rw-r--r--intl/icu/source/i18n/tmutfmt.cpp806
1 files changed, 806 insertions, 0 deletions
diff --git a/intl/icu/source/i18n/tmutfmt.cpp b/intl/icu/source/i18n/tmutfmt.cpp
new file mode 100644
index 0000000000..87b509ea35
--- /dev/null
+++ b/intl/icu/source/i18n/tmutfmt.cpp
@@ -0,0 +1,806 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+ *******************************************************************************
+ * Copyright (C) 2008-2015, Google, International Business Machines Corporation
+ * and others. All Rights Reserved.
+ *******************************************************************************
+ */
+
+#include "unicode/tmutfmt.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+#include "unicode/decimfmt.h"
+#include "unicode/localpointer.h"
+#include "plurrule_impl.h"
+#include "uvector.h"
+#include "charstr.h"
+#include "cmemory.h"
+#include "cstring.h"
+#include "hash.h"
+#include "uresimp.h"
+#include "ureslocs.h"
+#include "unicode/msgfmt.h"
+#include "uassert.h"
+
+#define LEFT_CURLY_BRACKET ((char16_t)0x007B)
+#define RIGHT_CURLY_BRACKET ((char16_t)0x007D)
+#define SPACE ((char16_t)0x0020)
+#define DIGIT_ZERO ((char16_t)0x0030)
+#define LOW_S ((char16_t)0x0073)
+#define LOW_M ((char16_t)0x006D)
+#define LOW_I ((char16_t)0x0069)
+#define LOW_N ((char16_t)0x006E)
+#define LOW_H ((char16_t)0x0068)
+#define LOW_W ((char16_t)0x0077)
+#define LOW_D ((char16_t)0x0064)
+#define LOW_Y ((char16_t)0x0079)
+#define LOW_Z ((char16_t)0x007A)
+#define LOW_E ((char16_t)0x0065)
+#define LOW_R ((char16_t)0x0072)
+#define LOW_O ((char16_t)0x006F)
+#define LOW_N ((char16_t)0x006E)
+#define LOW_T ((char16_t)0x0074)
+
+
+//TODO: define in compile time
+//#define TMUTFMT_DEBUG 1
+
+#ifdef TMUTFMT_DEBUG
+#include <iostream>
+#endif
+
+U_NAMESPACE_BEGIN
+
+
+
+UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeUnitFormat)
+
+static const char gUnitsTag[] = "units";
+static const char gShortUnitsTag[] = "unitsShort";
+static const char gTimeUnitYear[] = "year";
+static const char gTimeUnitMonth[] = "month";
+static const char gTimeUnitDay[] = "day";
+static const char gTimeUnitWeek[] = "week";
+static const char gTimeUnitHour[] = "hour";
+static const char gTimeUnitMinute[] = "minute";
+static const char gTimeUnitSecond[] = "second";
+static const char gPluralCountOther[] = "other";
+
+static const char16_t DEFAULT_PATTERN_FOR_SECOND[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_S, 0};
+static const char16_t DEFAULT_PATTERN_FOR_MINUTE[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, LOW_I, LOW_N, 0};
+static const char16_t DEFAULT_PATTERN_FOR_HOUR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_H, 0};
+static const char16_t DEFAULT_PATTERN_FOR_WEEK[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_W, 0};
+static const char16_t DEFAULT_PATTERN_FOR_DAY[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_D, 0};
+static const char16_t DEFAULT_PATTERN_FOR_MONTH[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, 0};
+static const char16_t DEFAULT_PATTERN_FOR_YEAR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_Y, 0};
+
+static const char16_t PLURAL_COUNT_ZERO[] = {LOW_Z, LOW_E, LOW_R, LOW_O, 0};
+static const char16_t PLURAL_COUNT_ONE[] = {LOW_O, LOW_N, LOW_E, 0};
+static const char16_t PLURAL_COUNT_TWO[] = {LOW_T, LOW_W, LOW_O, 0};
+
+TimeUnitFormat::TimeUnitFormat(UErrorCode& status) {
+ initMeasureFormat(Locale::getDefault(), UMEASFMT_WIDTH_WIDE, nullptr, status);
+ create(UTMUTFMT_FULL_STYLE, status);
+}
+
+
+TimeUnitFormat::TimeUnitFormat(const Locale& locale, UErrorCode& status) {
+ initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
+ create(UTMUTFMT_FULL_STYLE, status);
+}
+
+
+TimeUnitFormat::TimeUnitFormat(const Locale& locale, UTimeUnitFormatStyle style, UErrorCode& status) {
+ switch (style) {
+ case UTMUTFMT_FULL_STYLE:
+ initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
+ break;
+ case UTMUTFMT_ABBREVIATED_STYLE:
+ initMeasureFormat(locale, UMEASFMT_WIDTH_SHORT, nullptr, status);
+ break;
+ default:
+ initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, nullptr, status);
+ break;
+ }
+ create(style, status);
+}
+
+TimeUnitFormat::TimeUnitFormat(const TimeUnitFormat& other)
+: MeasureFormat(other),
+ fStyle(other.fStyle)
+{
+ for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
+ i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ i = (TimeUnit::UTimeUnitFields)(i+1)) {
+ UErrorCode status = U_ZERO_ERROR;
+ fTimeUnitToCountToPatterns[i] = initHash(status);
+ if (U_SUCCESS(status)) {
+ copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status);
+ } else {
+ delete fTimeUnitToCountToPatterns[i];
+ fTimeUnitToCountToPatterns[i] = nullptr;
+ }
+ }
+}
+
+
+TimeUnitFormat::~TimeUnitFormat() {
+ for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
+ i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ i = (TimeUnit::UTimeUnitFields)(i+1)) {
+ deleteHash(fTimeUnitToCountToPatterns[i]);
+ fTimeUnitToCountToPatterns[i] = nullptr;
+ }
+}
+
+
+TimeUnitFormat*
+TimeUnitFormat::clone() const {
+ return new TimeUnitFormat(*this);
+}
+
+
+TimeUnitFormat&
+TimeUnitFormat::operator=(const TimeUnitFormat& other) {
+ if (this == &other) {
+ return *this;
+ }
+ MeasureFormat::operator=(other);
+ for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
+ i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ i = (TimeUnit::UTimeUnitFields)(i+1)) {
+ deleteHash(fTimeUnitToCountToPatterns[i]);
+ fTimeUnitToCountToPatterns[i] = nullptr;
+ }
+ for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
+ i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ i = (TimeUnit::UTimeUnitFields)(i+1)) {
+ UErrorCode status = U_ZERO_ERROR;
+ fTimeUnitToCountToPatterns[i] = initHash(status);
+ if (U_SUCCESS(status)) {
+ copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status);
+ } else {
+ delete fTimeUnitToCountToPatterns[i];
+ fTimeUnitToCountToPatterns[i] = nullptr;
+ }
+ }
+ fStyle = other.fStyle;
+ return *this;
+}
+
+void
+TimeUnitFormat::parseObject(const UnicodeString& source,
+ Formattable& result,
+ ParsePosition& pos) const {
+ Formattable resultNumber(0.0);
+ UBool withNumberFormat = false;
+ TimeUnit::UTimeUnitFields resultTimeUnit = TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ int32_t oldPos = pos.getIndex();
+ int32_t newPos = -1;
+ int32_t longestParseDistance = 0;
+ UnicodeString* countOfLongestMatch = nullptr;
+#ifdef TMUTFMT_DEBUG
+ char res[1000];
+ source.extract(0, source.length(), res, "UTF-8");
+ std::cout << "parse source: " << res << "\n";
+#endif
+ // parse by iterating through all available patterns
+ // and looking for the longest match.
+ for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
+ i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ i = (TimeUnit::UTimeUnitFields)(i+1)) {
+ Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i];
+ int32_t elemPos = UHASH_FIRST;
+ const UHashElement* elem = nullptr;
+ while ((elem = countToPatterns->nextElement(elemPos)) != nullptr){
+ const UHashTok keyTok = elem->key;
+ UnicodeString* count = (UnicodeString*)keyTok.pointer;
+#ifdef TMUTFMT_DEBUG
+ count->extract(0, count->length(), res, "UTF-8");
+ std::cout << "parse plural count: " << res << "\n";
+#endif
+ const UHashTok valueTok = elem->value;
+ // the value is a pair of MessageFormat*
+ MessageFormat** patterns = (MessageFormat**)valueTok.pointer;
+ for (UTimeUnitFormatStyle style = UTMUTFMT_FULL_STYLE; style < UTMUTFMT_FORMAT_STYLE_COUNT;
+ style = (UTimeUnitFormatStyle)(style + 1)) {
+ MessageFormat* pattern = patterns[style];
+ pos.setErrorIndex(-1);
+ pos.setIndex(oldPos);
+ // see if we can parse
+ Formattable parsed;
+ pattern->parseObject(source, parsed, pos);
+ if (pos.getErrorIndex() != -1 || pos.getIndex() == oldPos) {
+ continue;
+ }
+ #ifdef TMUTFMT_DEBUG
+ std::cout << "parsed.getType: " << parsed.getType() << "\n";
+ #endif
+ Formattable tmpNumber(0.0);
+ if (pattern->getArgTypeCount() != 0) {
+ Formattable& temp = parsed[0];
+ if (temp.getType() == Formattable::kString) {
+ UnicodeString tmpString;
+ UErrorCode pStatus = U_ZERO_ERROR;
+ getNumberFormatInternal().parse(temp.getString(tmpString), tmpNumber, pStatus);
+ if (U_FAILURE(pStatus)) {
+ continue;
+ }
+ } else if (temp.isNumeric()) {
+ tmpNumber = temp;
+ } else {
+ continue;
+ }
+ }
+ int32_t parseDistance = pos.getIndex() - oldPos;
+ if (parseDistance > longestParseDistance) {
+ if (pattern->getArgTypeCount() != 0) {
+ resultNumber = tmpNumber;
+ withNumberFormat = true;
+ } else {
+ withNumberFormat = false;
+ }
+ resultTimeUnit = i;
+ newPos = pos.getIndex();
+ longestParseDistance = parseDistance;
+ countOfLongestMatch = count;
+ }
+ }
+ }
+ }
+ /* After find the longest match, parse the number.
+ * Result number could be null for the pattern without number pattern.
+ * such as unit pattern in Arabic.
+ * When result number is null, use plural rule to set the number.
+ */
+ if (withNumberFormat == false && longestParseDistance != 0) {
+ // set the number using plurrual count
+ if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ZERO, 4)) {
+ resultNumber = Formattable(0.0);
+ } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ONE, 3)) {
+ resultNumber = Formattable(1.0);
+ } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_TWO, 3)) {
+ resultNumber = Formattable(2.0);
+ } else {
+ // should not happen.
+ // TODO: how to handle?
+ resultNumber = Formattable(3.0);
+ }
+ }
+ if (longestParseDistance == 0) {
+ pos.setIndex(oldPos);
+ pos.setErrorIndex(0);
+ } else {
+ UErrorCode status = U_ZERO_ERROR;
+ LocalPointer<TimeUnitAmount> tmutamt(new TimeUnitAmount(resultNumber, resultTimeUnit, status), status);
+ if (U_SUCCESS(status)) {
+ result.adoptObject(tmutamt.orphan());
+ pos.setIndex(newPos);
+ pos.setErrorIndex(-1);
+ } else {
+ pos.setIndex(oldPos);
+ pos.setErrorIndex(0);
+ }
+ }
+}
+
+void
+TimeUnitFormat::create(UTimeUnitFormatStyle style, UErrorCode& status) {
+ // fTimeUnitToCountToPatterns[] must have its elements initialized to nullptr first
+ // before checking for failure status.
+ for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
+ i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ i = (TimeUnit::UTimeUnitFields)(i+1)) {
+ fTimeUnitToCountToPatterns[i] = nullptr;
+ }
+
+ if (U_FAILURE(status)) {
+ return;
+ }
+ if (style < UTMUTFMT_FULL_STYLE || style >= UTMUTFMT_FORMAT_STYLE_COUNT) {
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
+ fStyle = style;
+
+ //TODO: format() and parseObj() are const member functions,
+ //so, can not do lazy initialization in C++.
+ //setup has to be done in constructors.
+ //and here, the behavior is not consistent with Java.
+ //In Java, create an empty instance does not setup locale as
+ //default locale. If it followed by setNumberFormat(),
+ //in format(), the locale will set up as the locale in fNumberFormat.
+ //But in C++, this sets the locale as the default locale.
+ setup(status);
+}
+
+void
+TimeUnitFormat::setup(UErrorCode& err) {
+ initDataMembers(err);
+
+ UVector pluralCounts(nullptr, uhash_compareUnicodeString, 6, err);
+ LocalPointer<StringEnumeration> keywords(getPluralRules().getKeywords(err), err);
+ if (U_FAILURE(err)) {
+ return;
+ }
+ UnicodeString* pluralCount;
+ while ((pluralCount = const_cast<UnicodeString*>(keywords->snext(err))) != nullptr) {
+ pluralCounts.addElement(pluralCount, err);
+ }
+ readFromCurrentLocale(UTMUTFMT_FULL_STYLE, gUnitsTag, pluralCounts, err);
+ checkConsistency(UTMUTFMT_FULL_STYLE, gUnitsTag, err);
+ readFromCurrentLocale(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, pluralCounts, err);
+ checkConsistency(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, err);
+}
+
+
+void
+TimeUnitFormat::initDataMembers(UErrorCode& err){
+ if (U_FAILURE(err)) {
+ return;
+ }
+ for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
+ i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ i = (TimeUnit::UTimeUnitFields)(i+1)) {
+ deleteHash(fTimeUnitToCountToPatterns[i]);
+ fTimeUnitToCountToPatterns[i] = nullptr;
+ }
+}
+
+struct TimeUnitFormatReadSink : public ResourceSink {
+ TimeUnitFormat *timeUnitFormatObj;
+ const UVector &pluralCounts;
+ UTimeUnitFormatStyle style;
+ UBool beenHere;
+
+ TimeUnitFormatReadSink(TimeUnitFormat *timeUnitFormatObj,
+ const UVector &pluralCounts, UTimeUnitFormatStyle style) :
+ timeUnitFormatObj(timeUnitFormatObj), pluralCounts(pluralCounts),
+ style(style), beenHere(false){}
+
+ virtual ~TimeUnitFormatReadSink();
+
+ virtual void put(const char *key, ResourceValue &value, UBool, UErrorCode &errorCode) override {
+ // Skip all put() calls except the first one -- discard all fallback data.
+ if (beenHere) {
+ return;
+ } else {
+ beenHere = true;
+ }
+
+ ResourceTable units = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+
+ for (int32_t i = 0; units.getKeyAndValue(i, key, value); ++i) {
+ const char* timeUnitName = key;
+ if (timeUnitName == nullptr) {
+ continue;
+ }
+
+ TimeUnit::UTimeUnitFields timeUnitField = TimeUnit::UTIMEUNIT_FIELD_COUNT;
+ if ( uprv_strcmp(timeUnitName, gTimeUnitYear) == 0 ) {
+ timeUnitField = TimeUnit::UTIMEUNIT_YEAR;
+ } else if ( uprv_strcmp(timeUnitName, gTimeUnitMonth) == 0 ) {
+ timeUnitField = TimeUnit::UTIMEUNIT_MONTH;
+ } else if ( uprv_strcmp(timeUnitName, gTimeUnitDay) == 0 ) {
+ timeUnitField = TimeUnit::UTIMEUNIT_DAY;
+ } else if ( uprv_strcmp(timeUnitName, gTimeUnitHour) == 0 ) {
+ timeUnitField = TimeUnit::UTIMEUNIT_HOUR;
+ } else if ( uprv_strcmp(timeUnitName, gTimeUnitMinute) == 0 ) {
+ timeUnitField = TimeUnit::UTIMEUNIT_MINUTE;
+ } else if ( uprv_strcmp(timeUnitName, gTimeUnitSecond) == 0 ) {
+ timeUnitField = TimeUnit::UTIMEUNIT_SECOND;
+ } else if ( uprv_strcmp(timeUnitName, gTimeUnitWeek) == 0 ) {
+ timeUnitField = TimeUnit::UTIMEUNIT_WEEK;
+ } else {
+ continue;
+ }
+ LocalPointer<Hashtable> localCountToPatterns;
+ Hashtable *countToPatterns =
+ timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField];
+ if (countToPatterns == nullptr) {
+ localCountToPatterns.adoptInsteadAndCheckErrorCode(
+ timeUnitFormatObj->initHash(errorCode), errorCode);
+ countToPatterns = localCountToPatterns.getAlias();
+ if (U_FAILURE(errorCode)) {
+ return;
+ }
+ }
+
+ ResourceTable countsToPatternTable = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) {
+ continue;
+ }
+ for (int32_t j = 0; countsToPatternTable.getKeyAndValue(j, key, value); ++j) {
+ errorCode = U_ZERO_ERROR;
+ UnicodeString pattern = value.getUnicodeString(errorCode);
+ if (U_FAILURE(errorCode)) {
+ continue;
+ }
+ UnicodeString pluralCountUniStr(key, -1, US_INV);
+ if (!pluralCounts.contains(&pluralCountUniStr)) {
+ continue;
+ }
+ LocalPointer<MessageFormat> messageFormat(new MessageFormat(
+ pattern, timeUnitFormatObj->getLocale(errorCode), errorCode), errorCode);
+ if (U_FAILURE(errorCode)) {
+ return;
+ }
+ MessageFormat** formatters =
+ (MessageFormat**)countToPatterns->get(pluralCountUniStr);
+ if (formatters == nullptr) {
+ LocalMemory<MessageFormat *> localFormatters(
+ (MessageFormat **)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
+ if (localFormatters.isNull()) {
+ errorCode = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ localFormatters[UTMUTFMT_FULL_STYLE] = nullptr;
+ localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
+ countToPatterns->put(pluralCountUniStr, localFormatters.getAlias(), errorCode);
+ if (U_FAILURE(errorCode)) {
+ return;
+ }
+ formatters = localFormatters.orphan();
+ }
+ formatters[style] = messageFormat.orphan();
+ }
+
+ if (timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] == nullptr) {
+ timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] = localCountToPatterns.orphan();
+ }
+ }
+ }
+
+};
+
+TimeUnitFormatReadSink::~TimeUnitFormatReadSink() {}
+
+void
+TimeUnitFormat::readFromCurrentLocale(UTimeUnitFormatStyle style, const char* key,
+ const UVector& pluralCounts, UErrorCode& err) {
+ if (U_FAILURE(err)) {
+ return;
+ }
+ // fill timeUnitToCountToPatterns from resource file
+ // err is used to indicate wrong status except missing resource.
+ // status is an error code used in resource lookup.
+ // status does not affect "err".
+ UErrorCode status = U_ZERO_ERROR;
+ LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, getLocaleID(status), &status));
+
+ LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, nullptr, &status));
+ ures_getByKey(unitsRes.getAlias(), "duration", unitsRes.getAlias(), &status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+
+ TimeUnitFormatReadSink sink(this, pluralCounts, style);
+ ures_getAllItemsWithFallback(unitsRes.getAlias(), "", sink, status);
+}
+
+void
+TimeUnitFormat::checkConsistency(UTimeUnitFormatStyle style, const char* key, UErrorCode& err) {
+ if (U_FAILURE(err)) {
+ return;
+ }
+ // there should be patterns for each plural rule in each time unit.
+ // For each time unit,
+ // for each plural rule, following is unit pattern fall-back rule:
+ // ( for example: "one" hour )
+ // look for its unit pattern in its locale tree.
+ // if pattern is not found in its own locale, such as de_DE,
+ // look for the pattern in its parent, such as de,
+ // keep looking till found or till root.
+ // if the pattern is not found in root either,
+ // fallback to plural count "other",
+ // look for the pattern of "other" in the locale tree:
+ // "de_DE" to "de" to "root".
+ // If not found, fall back to value of
+ // static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h".
+ //
+ // Following is consistency check to create pattern for each
+ // plural rule in each time unit using above fall-back rule.
+ //
+ LocalPointer<StringEnumeration> keywords(
+ getPluralRules().getKeywords(err), err);
+ const UnicodeString* pluralCount;
+ while (U_SUCCESS(err) && (pluralCount = keywords->snext(err)) != nullptr) {
+ for (int32_t i = 0; i < TimeUnit::UTIMEUNIT_FIELD_COUNT; ++i) {
+ // for each time unit,
+ // get all the patterns for each plural rule in this locale.
+ Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i];
+ if ( countToPatterns == nullptr ) {
+ fTimeUnitToCountToPatterns[i] = countToPatterns = initHash(err);
+ if (U_FAILURE(err)) {
+ return;
+ }
+ }
+ MessageFormat** formatters = (MessageFormat**)countToPatterns->get(*pluralCount);
+ if( formatters == nullptr || formatters[style] == nullptr ) {
+ // look through parents
+ const char* localeName = getLocaleID(err);
+ CharString pluralCountChars;
+ pluralCountChars.appendInvariantChars(*pluralCount, err);
+ searchInLocaleChain(style, key, localeName,
+ (TimeUnit::UTimeUnitFields)i,
+ *pluralCount, pluralCountChars.data(),
+ countToPatterns, err);
+ }
+ // TODO: what to do with U_FAILURE(err) at this point.
+ // As is, the outer loop continues to run, but does nothing.
+ }
+ }
+}
+
+
+
+// srcPluralCount is the original plural count on which the pattern is
+// searched for.
+// searchPluralCount is the fallback plural count.
+// For example, to search for pattern for ""one" hour",
+// "one" is the srcPluralCount,
+// if the pattern is not found even in root, fallback to
+// using patterns of plural count "other",
+// then, "other" is the searchPluralCount.
+void
+TimeUnitFormat::searchInLocaleChain(UTimeUnitFormatStyle style, const char* key, const char* localeName,
+ TimeUnit::UTimeUnitFields srcTimeUnitField,
+ const UnicodeString& srcPluralCount,
+ const char* searchPluralCount,
+ Hashtable* countToPatterns,
+ UErrorCode& err) {
+ if (U_FAILURE(err)) {
+ return;
+ }
+ UErrorCode status = U_ZERO_ERROR;
+ char parentLocale[ULOC_FULLNAME_CAPACITY];
+ uprv_strcpy(parentLocale, localeName);
+ int32_t locNameLen;
+ U_ASSERT(countToPatterns != nullptr);
+ while ((locNameLen = uloc_getParent(parentLocale, parentLocale,
+ ULOC_FULLNAME_CAPACITY, &status)) >= 0){
+ // look for pattern for srcPluralCount in locale tree
+ LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, parentLocale, &status));
+ LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, nullptr, &status));
+ const char* timeUnitName = getTimeUnitName(srcTimeUnitField, status);
+ LocalUResourceBundlePointer countsToPatternRB(ures_getByKey(unitsRes.getAlias(), timeUnitName, nullptr, &status));
+ const char16_t* pattern;
+ int32_t ptLength;
+ pattern = ures_getStringByKeyWithFallback(countsToPatternRB.getAlias(), searchPluralCount, &ptLength, &status);
+ if (U_SUCCESS(status)) {
+ //found
+ LocalPointer<MessageFormat> messageFormat(
+ new MessageFormat(UnicodeString(true, pattern, ptLength), getLocale(err), err), err);
+ if (U_FAILURE(err)) {
+ return;
+ }
+ MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
+ if (formatters == nullptr) {
+ LocalMemory<MessageFormat *> localFormatters(
+ (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
+ formatters = localFormatters.getAlias();
+ localFormatters[UTMUTFMT_FULL_STYLE] = nullptr;
+ localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
+ countToPatterns->put(srcPluralCount, localFormatters.orphan(), err);
+ if (U_FAILURE(err)) {
+ return;
+ }
+ }
+ //delete formatters[style];
+ formatters[style] = messageFormat.orphan();
+ return;
+ }
+ status = U_ZERO_ERROR;
+ if (locNameLen == 0) {
+ break;
+ }
+ }
+
+ // if no unitsShort resource was found even after fallback to root locale
+ // then search the units resource fallback from the current level to root
+ if ( locNameLen == 0 && uprv_strcmp(key, gShortUnitsTag) == 0) {
+#ifdef TMUTFMT_DEBUG
+ std::cout << "loop into searchInLocaleChain since Short-Long-Alternative \n";
+#endif
+ CharString pLocale(localeName, -1, err);
+ // Add an underscore at the tail of locale name,
+ // so that searchInLocaleChain will check the current locale before falling back
+ pLocale.append('_', err);
+ searchInLocaleChain(style, gUnitsTag, pLocale.data(), srcTimeUnitField, srcPluralCount,
+ searchPluralCount, countToPatterns, err);
+ if (U_FAILURE(err)) {
+ return;
+ }
+ MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
+ if (formatters != nullptr && formatters[style] != nullptr) {
+ return;
+ }
+ }
+
+ // if not found the pattern for this plural count at all,
+ // fall-back to plural count "other"
+ if ( uprv_strcmp(searchPluralCount, gPluralCountOther) == 0 ) {
+ // set default fall back the same as the resource in root
+ LocalPointer<MessageFormat> messageFormat;
+ const char16_t *pattern = nullptr;
+ if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_SECOND ) {
+ pattern = DEFAULT_PATTERN_FOR_SECOND;
+ } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MINUTE ) {
+ pattern = DEFAULT_PATTERN_FOR_MINUTE;
+ } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_HOUR ) {
+ pattern = DEFAULT_PATTERN_FOR_HOUR;
+ } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_WEEK ) {
+ pattern = DEFAULT_PATTERN_FOR_WEEK;
+ } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_DAY ) {
+ pattern = DEFAULT_PATTERN_FOR_DAY;
+ } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MONTH ) {
+ pattern = DEFAULT_PATTERN_FOR_MONTH;
+ } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_YEAR ) {
+ pattern = DEFAULT_PATTERN_FOR_YEAR;
+ }
+ if (pattern != nullptr) {
+ messageFormat.adoptInsteadAndCheckErrorCode(
+ new MessageFormat(UnicodeString(true, pattern, -1), getLocale(err), err), err);
+ }
+ if (U_FAILURE(err)) {
+ return;
+ }
+ MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
+ if (formatters == nullptr) {
+ LocalMemory<MessageFormat *> localFormatters (
+ (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
+ if (localFormatters.isNull()) {
+ err = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ formatters = localFormatters.getAlias();
+ formatters[UTMUTFMT_FULL_STYLE] = nullptr;
+ formatters[UTMUTFMT_ABBREVIATED_STYLE] = nullptr;
+ countToPatterns->put(srcPluralCount, localFormatters.orphan(), err);
+ }
+ if (U_SUCCESS(err)) {
+ //delete formatters[style];
+ formatters[style] = messageFormat.orphan();
+ }
+ } else {
+ // fall back to rule "other", and search in parents
+ searchInLocaleChain(style, key, localeName, srcTimeUnitField, srcPluralCount,
+ gPluralCountOther, countToPatterns, err);
+ }
+}
+
+void
+TimeUnitFormat::setLocale(const Locale& locale, UErrorCode& status) {
+ if (setMeasureFormatLocale(locale, status)) {
+ setup(status);
+ }
+}
+
+
+void
+TimeUnitFormat::setNumberFormat(const NumberFormat& format, UErrorCode& status){
+ if (U_FAILURE(status)) {
+ return;
+ }
+ adoptNumberFormat(format.clone(), status);
+}
+
+
+void
+TimeUnitFormat::deleteHash(Hashtable* htable) {
+ int32_t pos = UHASH_FIRST;
+ const UHashElement* element = nullptr;
+ if ( htable ) {
+ while ( (element = htable->nextElement(pos)) != nullptr ) {
+ const UHashTok valueTok = element->value;
+ const MessageFormat** value = (const MessageFormat**)valueTok.pointer;
+ delete value[UTMUTFMT_FULL_STYLE];
+ delete value[UTMUTFMT_ABBREVIATED_STYLE];
+ //delete[] value;
+ uprv_free(value);
+ }
+ }
+ delete htable;
+}
+
+
+void
+TimeUnitFormat::copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status) {
+ if ( U_FAILURE(status) ) {
+ return;
+ }
+ int32_t pos = UHASH_FIRST;
+ const UHashElement* element = nullptr;
+ if ( source ) {
+ while ( (element = source->nextElement(pos)) != nullptr ) {
+ const UHashTok keyTok = element->key;
+ const UnicodeString* key = (UnicodeString*)keyTok.pointer;
+ const UHashTok valueTok = element->value;
+ const MessageFormat** value = (const MessageFormat**)valueTok.pointer;
+ MessageFormat** newVal = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*));
+ newVal[0] = value[0]->clone();
+ newVal[1] = value[1]->clone();
+ target->put(UnicodeString(*key), newVal, status);
+ if ( U_FAILURE(status) ) {
+ delete newVal[0];
+ delete newVal[1];
+ uprv_free(newVal);
+ return;
+ }
+ }
+ }
+}
+
+
+U_CDECL_BEGIN
+
+/**
+ * set hash table value comparator
+ *
+ * @param val1 one value in comparison
+ * @param val2 the other value in comparison
+ * @return true if 2 values are the same, false otherwise
+ */
+static UBool U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2);
+
+static UBool
+U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2) {
+ const MessageFormat** pattern1 = (const MessageFormat**)val1.pointer;
+ const MessageFormat** pattern2 = (const MessageFormat**)val2.pointer;
+ return *pattern1[0] == *pattern2[0] && *pattern1[1] == *pattern2[1];
+}
+
+U_CDECL_END
+
+Hashtable*
+TimeUnitFormat::initHash(UErrorCode& status) {
+ if ( U_FAILURE(status) ) {
+ return nullptr;
+ }
+ Hashtable* hTable;
+ if ( (hTable = new Hashtable(true, status)) == nullptr ) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return nullptr;
+ }
+ if ( U_FAILURE(status) ) {
+ delete hTable;
+ return nullptr;
+ }
+ hTable->setValueComparator(tmutfmtHashTableValueComparator);
+ return hTable;
+}
+
+
+const char*
+TimeUnitFormat::getTimeUnitName(TimeUnit::UTimeUnitFields unitField,
+ UErrorCode& status) {
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
+ switch (unitField) {
+ case TimeUnit::UTIMEUNIT_YEAR:
+ return gTimeUnitYear;
+ case TimeUnit::UTIMEUNIT_MONTH:
+ return gTimeUnitMonth;
+ case TimeUnit::UTIMEUNIT_DAY:
+ return gTimeUnitDay;
+ case TimeUnit::UTIMEUNIT_WEEK:
+ return gTimeUnitWeek;
+ case TimeUnit::UTIMEUNIT_HOUR:
+ return gTimeUnitHour;
+ case TimeUnit::UTIMEUNIT_MINUTE:
+ return gTimeUnitMinute;
+ case TimeUnit::UTIMEUNIT_SECOND:
+ return gTimeUnitSecond;
+ default:
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ return nullptr;
+ }
+}
+
+U_NAMESPACE_END
+
+#endif