summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/reldatefmt.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/icu/source/i18n/reldatefmt.cpp')
-rw-r--r--intl/icu/source/i18n/reldatefmt.cpp1424
1 files changed, 1424 insertions, 0 deletions
diff --git a/intl/icu/source/i18n/reldatefmt.cpp b/intl/icu/source/i18n/reldatefmt.cpp
new file mode 100644
index 0000000000..24d22a4b4b
--- /dev/null
+++ b/intl/icu/source/i18n/reldatefmt.cpp
@@ -0,0 +1,1424 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+******************************************************************************
+* Copyright (C) 2014-2016, International Business Machines Corporation and
+* others. All Rights Reserved.
+******************************************************************************
+*
+* File reldatefmt.cpp
+******************************************************************************
+*/
+
+#include "unicode/reldatefmt.h"
+
+#if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION
+
+#include <cmath>
+#include <functional>
+#include "unicode/calendar.h"
+#include "unicode/datefmt.h"
+#include "unicode/dtfmtsym.h"
+#include "unicode/ucasemap.h"
+#include "unicode/ureldatefmt.h"
+#include "unicode/udisplaycontext.h"
+#include "unicode/unum.h"
+#include "unicode/localpointer.h"
+#include "unicode/plurrule.h"
+#include "unicode/simpleformatter.h"
+#include "unicode/decimfmt.h"
+#include "unicode/numfmt.h"
+#include "unicode/brkiter.h"
+#include "unicode/simpleformatter.h"
+#include "uresimp.h"
+#include "unicode/ures.h"
+#include "cstring.h"
+#include "ucln_in.h"
+#include "mutex.h"
+#include "charstr.h"
+#include "uassert.h"
+#include "quantityformatter.h"
+#include "resource.h"
+#include "sharedbreakiterator.h"
+#include "sharedpluralrules.h"
+#include "sharednumberformat.h"
+#include "standardplural.h"
+#include "unifiedcache.h"
+#include "util.h"
+#include "formatted_string_builder.h"
+#include "number_utypes.h"
+#include "number_modifiers.h"
+#include "formattedval_impl.h"
+#include "number_utils.h"
+
+// Copied from uscript_props.cpp
+
+U_NAMESPACE_BEGIN
+
+// RelativeDateTimeFormatter specific data for a single locale
+class RelativeDateTimeCacheData: public SharedObject {
+public:
+ RelativeDateTimeCacheData() : combinedDateAndTime(nullptr) {
+ // Initialize the cache arrays
+ for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
+ for (int32_t relUnit = 0; relUnit < UDAT_REL_UNIT_COUNT; ++relUnit) {
+ for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) {
+ relativeUnitsFormatters[style][relUnit][0][pl] = nullptr;
+ relativeUnitsFormatters[style][relUnit][1][pl] = nullptr;
+ }
+ }
+ }
+ for (int32_t i = 0; i < UDAT_STYLE_COUNT; ++i) {
+ fallBackCache[i] = -1;
+ }
+ }
+ virtual ~RelativeDateTimeCacheData();
+
+ // no numbers: e.g Next Tuesday; Yesterday; etc.
+ UnicodeString absoluteUnits[UDAT_STYLE_COUNT][UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT];
+
+ // SimpleFormatter pointers for relative unit format,
+ // e.g., Next Tuesday; Yesterday; etc. For third index, 0
+ // means past, e.g., 5 days ago; 1 means future, e.g., in 5 days.
+ SimpleFormatter *relativeUnitsFormatters[UDAT_STYLE_COUNT]
+ [UDAT_REL_UNIT_COUNT][2][StandardPlural::COUNT];
+
+ const UnicodeString& getAbsoluteUnitString(int32_t fStyle,
+ UDateAbsoluteUnit unit,
+ UDateDirection direction) const;
+ const SimpleFormatter* getRelativeUnitFormatter(int32_t fStyle,
+ UDateRelativeUnit unit,
+ int32_t pastFutureIndex,
+ int32_t pluralUnit) const;
+ const SimpleFormatter* getRelativeDateTimeUnitFormatter(int32_t fStyle,
+ URelativeDateTimeUnit unit,
+ int32_t pastFutureIndex,
+ int32_t pluralUnit) const;
+
+ const UnicodeString emptyString;
+
+ // Mapping from source to target styles for alias fallback.
+ int32_t fallBackCache[UDAT_STYLE_COUNT];
+
+ void adoptCombinedDateAndTime(SimpleFormatter *fmtToAdopt) {
+ delete combinedDateAndTime;
+ combinedDateAndTime = fmtToAdopt;
+ }
+ const SimpleFormatter *getCombinedDateAndTime() const {
+ return combinedDateAndTime;
+ }
+
+private:
+ SimpleFormatter *combinedDateAndTime;
+ RelativeDateTimeCacheData(const RelativeDateTimeCacheData &other);
+ RelativeDateTimeCacheData& operator=(
+ const RelativeDateTimeCacheData &other);
+};
+
+RelativeDateTimeCacheData::~RelativeDateTimeCacheData() {
+ // clear out the cache arrays
+ for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
+ for (int32_t relUnit = 0; relUnit < UDAT_REL_UNIT_COUNT; ++relUnit) {
+ for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) {
+ delete relativeUnitsFormatters[style][relUnit][0][pl];
+ delete relativeUnitsFormatters[style][relUnit][1][pl];
+ }
+ }
+ }
+ delete combinedDateAndTime;
+}
+
+
+// Use fallback cache for absolute units.
+const UnicodeString& RelativeDateTimeCacheData::getAbsoluteUnitString(
+ int32_t fStyle, UDateAbsoluteUnit unit, UDateDirection direction) const {
+ int32_t style = fStyle;
+ do {
+ if (!absoluteUnits[style][unit][direction].isEmpty()) {
+ return absoluteUnits[style][unit][direction];
+ }
+ style = fallBackCache[style];
+ } while (style != -1);
+ return emptyString;
+}
+
+ const SimpleFormatter* RelativeDateTimeCacheData::getRelativeUnitFormatter(
+ int32_t fStyle,
+ UDateRelativeUnit unit,
+ int32_t pastFutureIndex,
+ int32_t pluralUnit) const {
+ URelativeDateTimeUnit rdtunit = UDAT_REL_UNIT_COUNT;
+ switch (unit) {
+ case UDAT_RELATIVE_YEARS: rdtunit = UDAT_REL_UNIT_YEAR; break;
+ case UDAT_RELATIVE_MONTHS: rdtunit = UDAT_REL_UNIT_MONTH; break;
+ case UDAT_RELATIVE_WEEKS: rdtunit = UDAT_REL_UNIT_WEEK; break;
+ case UDAT_RELATIVE_DAYS: rdtunit = UDAT_REL_UNIT_DAY; break;
+ case UDAT_RELATIVE_HOURS: rdtunit = UDAT_REL_UNIT_HOUR; break;
+ case UDAT_RELATIVE_MINUTES: rdtunit = UDAT_REL_UNIT_MINUTE; break;
+ case UDAT_RELATIVE_SECONDS: rdtunit = UDAT_REL_UNIT_SECOND; break;
+ default: // a unit that the above method does not handle
+ return nullptr;
+ }
+
+ return getRelativeDateTimeUnitFormatter(fStyle, rdtunit, pastFutureIndex, pluralUnit);
+ }
+
+ // Use fallback cache for SimpleFormatter relativeUnits.
+ const SimpleFormatter* RelativeDateTimeCacheData::getRelativeDateTimeUnitFormatter(
+ int32_t fStyle,
+ URelativeDateTimeUnit unit,
+ int32_t pastFutureIndex,
+ int32_t pluralUnit) const {
+ while (true) {
+ int32_t style = fStyle;
+ do {
+ if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != nullptr) {
+ return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit];
+ }
+ style = fallBackCache[style];
+ } while (style != -1);
+
+ if (pluralUnit == StandardPlural::OTHER) {
+ break;
+ }
+ pluralUnit = StandardPlural::OTHER;
+ }
+ return nullptr; // No formatter found.
+ }
+
+static UBool getStringByIndex(
+ const UResourceBundle *resource,
+ int32_t idx,
+ UnicodeString &result,
+ UErrorCode &status) {
+ int32_t len = 0;
+ const char16_t *resStr = ures_getStringByIndex(
+ resource, idx, &len, &status);
+ if (U_FAILURE(status)) {
+ return false;
+ }
+ result.setTo(true, resStr, len);
+ return true;
+}
+
+namespace {
+
+/**
+ * Sink for enumerating all of the measurement unit display names.
+ *
+ * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
+ * Only store a value if it is still missing, that is, it has not been overridden.
+ */
+struct RelDateTimeFmtDataSink : public ResourceSink {
+
+ /**
+ * Sink for patterns for relative dates and times. For example,
+ * fields/relative/...
+ */
+
+ // Generic unit enum for storing Unit info.
+ typedef enum RelAbsUnit {
+ INVALID_UNIT = -1,
+ SECOND,
+ MINUTE,
+ HOUR,
+ DAY,
+ WEEK,
+ MONTH,
+ QUARTER,
+ YEAR,
+ SUNDAY,
+ MONDAY,
+ TUESDAY,
+ WEDNESDAY,
+ THURSDAY,
+ FRIDAY,
+ SATURDAY
+ } RelAbsUnit;
+
+ static int32_t relUnitFromGeneric(RelAbsUnit genUnit) {
+ // Converts the generic units to UDAT_RELATIVE version.
+ switch (genUnit) {
+ case SECOND:
+ return UDAT_REL_UNIT_SECOND;
+ case MINUTE:
+ return UDAT_REL_UNIT_MINUTE;
+ case HOUR:
+ return UDAT_REL_UNIT_HOUR;
+ case DAY:
+ return UDAT_REL_UNIT_DAY;
+ case WEEK:
+ return UDAT_REL_UNIT_WEEK;
+ case MONTH:
+ return UDAT_REL_UNIT_MONTH;
+ case QUARTER:
+ return UDAT_REL_UNIT_QUARTER;
+ case YEAR:
+ return UDAT_REL_UNIT_YEAR;
+ case SUNDAY:
+ return UDAT_REL_UNIT_SUNDAY;
+ case MONDAY:
+ return UDAT_REL_UNIT_MONDAY;
+ case TUESDAY:
+ return UDAT_REL_UNIT_TUESDAY;
+ case WEDNESDAY:
+ return UDAT_REL_UNIT_WEDNESDAY;
+ case THURSDAY:
+ return UDAT_REL_UNIT_THURSDAY;
+ case FRIDAY:
+ return UDAT_REL_UNIT_FRIDAY;
+ case SATURDAY:
+ return UDAT_REL_UNIT_SATURDAY;
+ default:
+ return -1;
+ }
+ }
+
+ static int32_t absUnitFromGeneric(RelAbsUnit genUnit) {
+ // Converts the generic units to UDAT_RELATIVE version.
+ switch (genUnit) {
+ case DAY:
+ return UDAT_ABSOLUTE_DAY;
+ case WEEK:
+ return UDAT_ABSOLUTE_WEEK;
+ case MONTH:
+ return UDAT_ABSOLUTE_MONTH;
+ case QUARTER:
+ return UDAT_ABSOLUTE_QUARTER;
+ case YEAR:
+ return UDAT_ABSOLUTE_YEAR;
+ case SUNDAY:
+ return UDAT_ABSOLUTE_SUNDAY;
+ case MONDAY:
+ return UDAT_ABSOLUTE_MONDAY;
+ case TUESDAY:
+ return UDAT_ABSOLUTE_TUESDAY;
+ case WEDNESDAY:
+ return UDAT_ABSOLUTE_WEDNESDAY;
+ case THURSDAY:
+ return UDAT_ABSOLUTE_THURSDAY;
+ case FRIDAY:
+ return UDAT_ABSOLUTE_FRIDAY;
+ case SATURDAY:
+ return UDAT_ABSOLUTE_SATURDAY;
+ case HOUR:
+ return UDAT_ABSOLUTE_HOUR;
+ case MINUTE:
+ return UDAT_ABSOLUTE_MINUTE;
+ default:
+ return -1;
+ }
+ }
+
+ static int32_t keyToDirection(const char* key) {
+ if (uprv_strcmp(key, "-2") == 0) {
+ return UDAT_DIRECTION_LAST_2;
+ }
+ if (uprv_strcmp(key, "-1") == 0) {
+ return UDAT_DIRECTION_LAST;
+ }
+ if (uprv_strcmp(key, "0") == 0) {
+ return UDAT_DIRECTION_THIS;
+ }
+ if (uprv_strcmp(key, "1") == 0) {
+ return UDAT_DIRECTION_NEXT;
+ }
+ if (uprv_strcmp(key, "2") == 0) {
+ return UDAT_DIRECTION_NEXT_2;
+ }
+ return -1;
+ }
+
+ // Values kept between levels of parsing the CLDR data.
+ int32_t pastFutureIndex; // 0 == past or 1 == future
+ UDateRelativeDateTimeFormatterStyle style; // {LONG, SHORT, NARROW}
+ RelAbsUnit genericUnit;
+
+ RelativeDateTimeCacheData &outputData;
+
+ // Constructor
+ RelDateTimeFmtDataSink(RelativeDateTimeCacheData& cacheData)
+ : outputData(cacheData) {
+ // Clear cacheData.fallBackCache
+ cacheData.fallBackCache[UDAT_STYLE_LONG] = -1;
+ cacheData.fallBackCache[UDAT_STYLE_SHORT] = -1;
+ cacheData.fallBackCache[UDAT_STYLE_NARROW] = -1;
+ }
+
+ ~RelDateTimeFmtDataSink();
+
+ // Utility functions
+ static UDateRelativeDateTimeFormatterStyle styleFromString(const char *s) {
+ int32_t len = static_cast<int32_t>(uprv_strlen(s));
+ if (len >= 7 && uprv_strcmp(s + len - 7, "-narrow") == 0) {
+ return UDAT_STYLE_NARROW;
+ }
+ if (len >= 6 && uprv_strcmp(s + len - 6, "-short") == 0) {
+ return UDAT_STYLE_SHORT;
+ }
+ return UDAT_STYLE_LONG;
+ }
+
+ static int32_t styleSuffixLength(UDateRelativeDateTimeFormatterStyle style) {
+ switch (style) {
+ case UDAT_STYLE_NARROW:
+ return 7;
+ case UDAT_STYLE_SHORT:
+ return 6;
+ default:
+ return 0;
+ }
+ }
+
+ // Utility functions
+ static UDateRelativeDateTimeFormatterStyle styleFromAliasUnicodeString(UnicodeString s) {
+ static const char16_t narrow[7] = {0x002D, 0x006E, 0x0061, 0x0072, 0x0072, 0x006F, 0x0077};
+ static const char16_t sshort[6] = {0x002D, 0x0073, 0x0068, 0x006F, 0x0072, 0x0074,};
+ if (s.endsWith(narrow, 7)) {
+ return UDAT_STYLE_NARROW;
+ }
+ if (s.endsWith(sshort, 6)) {
+ return UDAT_STYLE_SHORT;
+ }
+ return UDAT_STYLE_LONG;
+ }
+
+ static RelAbsUnit unitOrNegativeFromString(const char* keyword, int32_t length) {
+ // Quick check from string to enum.
+ switch (length) {
+ case 3:
+ if (uprv_strncmp(keyword, "day", length) == 0) {
+ return DAY;
+ } else if (uprv_strncmp(keyword, "sun", length) == 0) {
+ return SUNDAY;
+ } else if (uprv_strncmp(keyword, "mon", length) == 0) {
+ return MONDAY;
+ } else if (uprv_strncmp(keyword, "tue", length) == 0) {
+ return TUESDAY;
+ } else if (uprv_strncmp(keyword, "wed", length) == 0) {
+ return WEDNESDAY;
+ } else if (uprv_strncmp(keyword, "thu", length) == 0) {
+ return THURSDAY;
+ } else if (uprv_strncmp(keyword, "fri", length) == 0) {
+ return FRIDAY;
+ } else if (uprv_strncmp(keyword, "sat", length) == 0) {
+ return SATURDAY;
+ }
+ break;
+ case 4:
+ if (uprv_strncmp(keyword, "hour", length) == 0) {
+ return HOUR;
+ } else if (uprv_strncmp(keyword, "week", length) == 0) {
+ return WEEK;
+ } else if (uprv_strncmp(keyword, "year", length) == 0) {
+ return YEAR;
+ }
+ break;
+ case 5:
+ if (uprv_strncmp(keyword, "month", length) == 0) {
+ return MONTH;
+ }
+ break;
+ case 6:
+ if (uprv_strncmp(keyword, "minute", length) == 0) {
+ return MINUTE;
+ } else if (uprv_strncmp(keyword, "second", length) == 0) {
+ return SECOND;
+ }
+ break;
+ case 7:
+ if (uprv_strncmp(keyword, "quarter", length) == 0) {
+ return QUARTER; // TODO: Check @provisional
+ }
+ break;
+ default:
+ break;
+ }
+ return INVALID_UNIT;
+ }
+
+ void handlePlainDirection(ResourceValue &value, UErrorCode &errorCode) {
+ // Handle Display Name for PLAIN direction for some units.
+ if (U_FAILURE(errorCode)) { return; }
+
+ int32_t absUnit = absUnitFromGeneric(genericUnit);
+ if (absUnit < 0) {
+ return; // Not interesting.
+ }
+
+ // Store displayname if not set.
+ if (outputData.absoluteUnits[style]
+ [absUnit][UDAT_DIRECTION_PLAIN].isEmpty()) {
+ outputData.absoluteUnits[style]
+ [absUnit][UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode));
+ return;
+ }
+ }
+
+ void consumeTableRelative(const char *key, ResourceValue &value, UErrorCode &errorCode) {
+ ResourceTable unitTypesTable = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+
+ for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
+ if (value.getType() == URES_STRING) {
+ int32_t direction = keyToDirection(key);
+ if (direction < 0) {
+ continue;
+ }
+
+ int32_t relUnitIndex = relUnitFromGeneric(genericUnit);
+ if (relUnitIndex == UDAT_REL_UNIT_SECOND && uprv_strcmp(key, "0") == 0 &&
+ outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW][UDAT_DIRECTION_PLAIN].isEmpty()) {
+ // Handle "NOW"
+ outputData.absoluteUnits[style][UDAT_ABSOLUTE_NOW]
+ [UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode));
+ }
+
+ int32_t absUnitIndex = absUnitFromGeneric(genericUnit);
+ if (absUnitIndex < 0) {
+ continue;
+ }
+ // Only reset if slot is empty.
+ if (outputData.absoluteUnits[style][absUnitIndex][direction].isEmpty()) {
+ outputData.absoluteUnits[style][absUnitIndex]
+ [direction].fastCopyFrom(value.getUnicodeString(errorCode));
+ }
+ }
+ }
+ }
+
+ void consumeTimeDetail(int32_t relUnitIndex,
+ const char *key, ResourceValue &value, UErrorCode &errorCode) {
+ ResourceTable unitTypesTable = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+
+ for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
+ if (value.getType() == URES_STRING) {
+ int32_t pluralIndex = StandardPlural::indexOrNegativeFromString(key);
+ if (pluralIndex >= 0) {
+ SimpleFormatter **patterns =
+ outputData.relativeUnitsFormatters[style][relUnitIndex]
+ [pastFutureIndex];
+ // Only set if not already established.
+ if (patterns[pluralIndex] == nullptr) {
+ patterns[pluralIndex] = new SimpleFormatter(
+ value.getUnicodeString(errorCode), 0, 1, errorCode);
+ if (patterns[pluralIndex] == nullptr) {
+ errorCode = U_MEMORY_ALLOCATION_ERROR;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void consumeTableRelativeTime(const char *key, ResourceValue &value, UErrorCode &errorCode) {
+ ResourceTable relativeTimeTable = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+
+ int32_t relUnitIndex = relUnitFromGeneric(genericUnit);
+ if (relUnitIndex < 0) {
+ return;
+ }
+ for (int32_t i = 0; relativeTimeTable.getKeyAndValue(i, key, value); ++i) {
+ if (uprv_strcmp(key, "past") == 0) {
+ pastFutureIndex = 0;
+ } else if (uprv_strcmp(key, "future") == 0) {
+ pastFutureIndex = 1;
+ } else {
+ // Unknown key.
+ continue;
+ }
+ consumeTimeDetail(relUnitIndex, key, value, errorCode);
+ }
+ }
+
+ void consumeAlias(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
+
+ UDateRelativeDateTimeFormatterStyle sourceStyle = styleFromString(key);
+ const UnicodeString valueStr = value.getAliasUnicodeString(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+
+ UDateRelativeDateTimeFormatterStyle targetStyle =
+ styleFromAliasUnicodeString(valueStr);
+
+ if (sourceStyle == targetStyle) {
+ errorCode = U_INVALID_FORMAT_ERROR;
+ return;
+ }
+ if (outputData.fallBackCache[sourceStyle] != -1 &&
+ outputData.fallBackCache[sourceStyle] != targetStyle) {
+ errorCode = U_INVALID_FORMAT_ERROR;
+ return;
+ }
+ outputData.fallBackCache[sourceStyle] = targetStyle;
+ }
+
+ void consumeTimeUnit(const char *key, ResourceValue &value, UErrorCode &errorCode) {
+ ResourceTable unitTypesTable = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+
+ for (int32_t i = 0; unitTypesTable.getKeyAndValue(i, key, value); ++i) {
+ // Handle display name.
+ if (uprv_strcmp(key, "dn") == 0 && value.getType() == URES_STRING) {
+ handlePlainDirection(value, errorCode);
+ }
+ if (value.getType() == URES_TABLE) {
+ if (uprv_strcmp(key, "relative") == 0) {
+ consumeTableRelative(key, value, errorCode);
+ } else if (uprv_strcmp(key, "relativeTime") == 0) {
+ consumeTableRelativeTime(key, value, errorCode);
+ }
+ }
+ }
+ }
+
+ virtual void put(const char *key, ResourceValue &value,
+ UBool /*noFallback*/, UErrorCode &errorCode) override {
+ // Main entry point to sink
+ ResourceTable table = value.getTable(errorCode);
+ if (U_FAILURE(errorCode)) { return; }
+ for (int32_t i = 0; table.getKeyAndValue(i, key, value); ++i) {
+ if (value.getType() == URES_ALIAS) {
+ consumeAlias(key, value, errorCode);
+ } else {
+ style = styleFromString(key);
+ int32_t unitSize = static_cast<int32_t>(uprv_strlen(key)) - styleSuffixLength(style);
+ genericUnit = unitOrNegativeFromString(key, unitSize);
+ if (style >= 0 && genericUnit != INVALID_UNIT) {
+ consumeTimeUnit(key, value, errorCode);
+ }
+ }
+ }
+ }
+
+};
+
+// Virtual destructors must be defined out of line.
+RelDateTimeFmtDataSink::~RelDateTimeFmtDataSink() {}
+} // namespace
+
+static const DateFormatSymbols::DtWidthType styleToDateFormatSymbolWidth[UDAT_STYLE_COUNT] = {
+ DateFormatSymbols::WIDE, DateFormatSymbols::SHORT, DateFormatSymbols::NARROW
+};
+
+// Get days of weeks from the DateFormatSymbols class.
+static void loadWeekdayNames(UnicodeString absoluteUnits[UDAT_STYLE_COUNT]
+ [UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT],
+ const char* localeId,
+ UErrorCode& status) {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ Locale locale(localeId);
+ DateFormatSymbols dfSym(locale, status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+ for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
+ DateFormatSymbols::DtWidthType dtfmtWidth = styleToDateFormatSymbolWidth[style];
+ int32_t count;
+ const UnicodeString* weekdayNames =
+ dfSym.getWeekdays(count, DateFormatSymbols::STANDALONE, dtfmtWidth);
+ for (int32_t dayIndex = UDAT_ABSOLUTE_SUNDAY;
+ dayIndex <= UDAT_ABSOLUTE_SATURDAY; ++ dayIndex) {
+ int32_t dateSymbolIndex = (dayIndex - UDAT_ABSOLUTE_SUNDAY) + UCAL_SUNDAY;
+ absoluteUnits[style][dayIndex][UDAT_DIRECTION_PLAIN].fastCopyFrom(
+ weekdayNames[dateSymbolIndex]);
+ }
+ }
+}
+
+static UBool loadUnitData(
+ const UResourceBundle *resource,
+ RelativeDateTimeCacheData &cacheData,
+ const char* localeId,
+ UErrorCode &status) {
+
+ RelDateTimeFmtDataSink sink(cacheData);
+
+ ures_getAllItemsWithFallback(resource, "fields", sink, status);
+ if (U_FAILURE(status)) {
+ return false;
+ }
+
+ // Get the weekday names from DateFormatSymbols.
+ loadWeekdayNames(cacheData.absoluteUnits, localeId, status);
+ return U_SUCCESS(status);
+}
+
+static const int32_t cTypeBufMax = 32;
+
+static UBool getDateTimePattern(
+ Locale locale,
+ const UResourceBundle *resource,
+ UnicodeString &result,
+ UErrorCode &status) {
+ if (U_FAILURE(status)) {
+ return false;
+ }
+ char cType[cTypeBufMax + 1];
+ Calendar::getCalendarTypeFromLocale(locale, cType, cTypeBufMax, status);
+ cType[cTypeBufMax] = 0;
+ if (U_FAILURE(status) || cType[0] == 0) {
+ status = U_ZERO_ERROR;
+ uprv_strcpy(cType, "gregorian");
+ }
+
+ LocalUResourceBundlePointer topLevel;
+ int32_t dateTimeFormatOffset = DateFormat::kMedium;
+ CharString pathBuffer;
+ // Currently, for compatibility with pre-CLDR-42 data, we default to the "atTime"
+ // combining patterns. Depending on guidance in CLDR 42 spec and on DisplayOptions,
+ // we may change this.
+ pathBuffer.append("calendar/", status)
+ .append(cType, status)
+ .append("/DateTimePatterns%atTime", status);
+ topLevel.adoptInstead(
+ ures_getByKeyWithFallback(
+ resource, pathBuffer.data(), nullptr, &status));
+ if (U_FAILURE(status) || ures_getSize(topLevel.getAlias()) < 4) {
+ // Fall back to standard combining patterns
+ status = U_ZERO_ERROR;
+ dateTimeFormatOffset = DateFormat::kDateTime;
+ pathBuffer.clear();
+ pathBuffer.append("calendar/", status)
+ .append(cType, status)
+ .append("/DateTimePatterns", status);
+ topLevel.adoptInstead(
+ ures_getByKeyWithFallback(
+ resource, pathBuffer.data(), nullptr, &status));
+ }
+ if (U_FAILURE(status)) {
+ return false;
+ }
+ if (dateTimeFormatOffset == DateFormat::kDateTime && ures_getSize(topLevel.getAlias()) <= DateFormat::kDateTime) {
+ // Oops, size is too small to access the index that we want, fallback
+ // to a hard-coded value.
+ result = UNICODE_STRING_SIMPLE("{1} {0}");
+ return true;
+ }
+ return getStringByIndex(topLevel.getAlias(), dateTimeFormatOffset, result, status);
+}
+
+template<>
+const RelativeDateTimeCacheData *LocaleCacheKey<RelativeDateTimeCacheData>::createObject(const void * /*unused*/, UErrorCode &status) const {
+ const char *localeId = fLoc.getName();
+ LocalUResourceBundlePointer topLevel(ures_open(nullptr, localeId, &status));
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
+ LocalPointer<RelativeDateTimeCacheData> result(
+ new RelativeDateTimeCacheData());
+ if (result.isNull()) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return nullptr;
+ }
+ if (!loadUnitData(
+ topLevel.getAlias(),
+ *result,
+ localeId,
+ status)) {
+ return nullptr;
+ }
+ UnicodeString dateTimePattern;
+ if (!getDateTimePattern(fLoc, topLevel.getAlias(), dateTimePattern, status)) {
+ return nullptr;
+ }
+ result->adoptCombinedDateAndTime(
+ new SimpleFormatter(dateTimePattern, 2, 2, status));
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
+ result->addRef();
+ return result.orphan();
+}
+
+
+
+static constexpr FormattedStringBuilder::Field kRDTNumericField
+ = {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_NUMERIC_FIELD};
+
+static constexpr FormattedStringBuilder::Field kRDTLiteralField
+ = {UFIELD_CATEGORY_RELATIVE_DATETIME, UDAT_REL_LITERAL_FIELD};
+
+class FormattedRelativeDateTimeData : public FormattedValueStringBuilderImpl {
+public:
+ FormattedRelativeDateTimeData() : FormattedValueStringBuilderImpl(kRDTNumericField) {}
+ virtual ~FormattedRelativeDateTimeData();
+};
+
+FormattedRelativeDateTimeData::~FormattedRelativeDateTimeData() = default;
+
+
+UPRV_FORMATTED_VALUE_SUBCLASS_AUTO_IMPL(FormattedRelativeDateTime)
+
+
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) :
+ fCache(nullptr),
+ fNumberFormat(nullptr),
+ fPluralRules(nullptr),
+ fStyle(UDAT_STYLE_LONG),
+ fContext(UDISPCTX_CAPITALIZATION_NONE),
+ fOptBreakIterator(nullptr) {
+ init(nullptr, nullptr, status);
+}
+
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(
+ const Locale& locale, UErrorCode& status) :
+ fCache(nullptr),
+ fNumberFormat(nullptr),
+ fPluralRules(nullptr),
+ fStyle(UDAT_STYLE_LONG),
+ fContext(UDISPCTX_CAPITALIZATION_NONE),
+ fOptBreakIterator(nullptr),
+ fLocale(locale) {
+ init(nullptr, nullptr, status);
+}
+
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(
+ const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status) :
+ fCache(nullptr),
+ fNumberFormat(nullptr),
+ fPluralRules(nullptr),
+ fStyle(UDAT_STYLE_LONG),
+ fContext(UDISPCTX_CAPITALIZATION_NONE),
+ fOptBreakIterator(nullptr),
+ fLocale(locale) {
+ init(nfToAdopt, nullptr, status);
+}
+
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(
+ const Locale& locale,
+ NumberFormat *nfToAdopt,
+ UDateRelativeDateTimeFormatterStyle styl,
+ UDisplayContext capitalizationContext,
+ UErrorCode& status) :
+ fCache(nullptr),
+ fNumberFormat(nullptr),
+ fPluralRules(nullptr),
+ fStyle(styl),
+ fContext(capitalizationContext),
+ fOptBreakIterator(nullptr),
+ fLocale(locale) {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ if ((capitalizationContext >> 8) != UDISPCTX_TYPE_CAPITALIZATION) {
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
+ if (capitalizationContext == UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE) {
+ BreakIterator *bi = BreakIterator::createSentenceInstance(locale, status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+ init(nfToAdopt, bi, status);
+ } else {
+ init(nfToAdopt, nullptr, status);
+ }
+}
+
+RelativeDateTimeFormatter::RelativeDateTimeFormatter(
+ const RelativeDateTimeFormatter& other)
+ : UObject(other),
+ fCache(other.fCache),
+ fNumberFormat(other.fNumberFormat),
+ fPluralRules(other.fPluralRules),
+ fStyle(other.fStyle),
+ fContext(other.fContext),
+ fOptBreakIterator(other.fOptBreakIterator),
+ fLocale(other.fLocale) {
+ fCache->addRef();
+ fNumberFormat->addRef();
+ fPluralRules->addRef();
+ if (fOptBreakIterator != nullptr) {
+ fOptBreakIterator->addRef();
+ }
+}
+
+RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=(
+ const RelativeDateTimeFormatter& other) {
+ if (this != &other) {
+ SharedObject::copyPtr(other.fCache, fCache);
+ SharedObject::copyPtr(other.fNumberFormat, fNumberFormat);
+ SharedObject::copyPtr(other.fPluralRules, fPluralRules);
+ SharedObject::copyPtr(other.fOptBreakIterator, fOptBreakIterator);
+ fStyle = other.fStyle;
+ fContext = other.fContext;
+ fLocale = other.fLocale;
+ }
+ return *this;
+}
+
+RelativeDateTimeFormatter::~RelativeDateTimeFormatter() {
+ if (fCache != nullptr) {
+ fCache->removeRef();
+ }
+ if (fNumberFormat != nullptr) {
+ fNumberFormat->removeRef();
+ }
+ if (fPluralRules != nullptr) {
+ fPluralRules->removeRef();
+ }
+ if (fOptBreakIterator != nullptr) {
+ fOptBreakIterator->removeRef();
+ }
+}
+
+const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const {
+ return **fNumberFormat;
+}
+
+UDisplayContext RelativeDateTimeFormatter::getCapitalizationContext() const {
+ return fContext;
+}
+
+UDateRelativeDateTimeFormatterStyle RelativeDateTimeFormatter::getFormatStyle() const {
+ return fStyle;
+}
+
+
+// To reduce boilerplate code, we use a helper function that forwards variadic
+// arguments to the formatImpl function.
+
+template<typename F, typename... Args>
+UnicodeString& RelativeDateTimeFormatter::doFormat(
+ F callback,
+ UnicodeString& appendTo,
+ UErrorCode& status,
+ Args... args) const {
+ FormattedRelativeDateTimeData output;
+ (this->*callback)(std::forward<Args>(args)..., output, status);
+ if (U_FAILURE(status)) {
+ return appendTo;
+ }
+ UnicodeString result = output.getStringRef().toUnicodeString();
+ return appendTo.append(adjustForContext(result));
+}
+
+template<typename F, typename... Args>
+FormattedRelativeDateTime RelativeDateTimeFormatter::doFormatToValue(
+ F callback,
+ UErrorCode& status,
+ Args... args) const {
+ if (!checkNoAdjustForContext(status)) {
+ return FormattedRelativeDateTime(status);
+ }
+ LocalPointer<FormattedRelativeDateTimeData> output(
+ new FormattedRelativeDateTimeData(), status);
+ if (U_FAILURE(status)) {
+ return FormattedRelativeDateTime(status);
+ }
+ (this->*callback)(std::forward<Args>(args)..., *output, status);
+ output->getStringRef().writeTerminator(status);
+ return FormattedRelativeDateTime(output.orphan());
+}
+
+UnicodeString& RelativeDateTimeFormatter::format(
+ double quantity,
+ UDateDirection direction,
+ UDateRelativeUnit unit,
+ UnicodeString& appendTo,
+ UErrorCode& status) const {
+ return doFormat(
+ &RelativeDateTimeFormatter::formatImpl,
+ appendTo,
+ status,
+ quantity,
+ direction,
+ unit);
+}
+
+FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
+ double quantity,
+ UDateDirection direction,
+ UDateRelativeUnit unit,
+ UErrorCode& status) const {
+ return doFormatToValue(
+ &RelativeDateTimeFormatter::formatImpl,
+ status,
+ quantity,
+ direction,
+ unit);
+}
+
+void RelativeDateTimeFormatter::formatImpl(
+ double quantity,
+ UDateDirection direction,
+ UDateRelativeUnit unit,
+ FormattedRelativeDateTimeData& output,
+ UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
+ int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
+
+ StandardPlural::Form pluralForm;
+ QuantityFormatter::formatAndSelect(
+ quantity,
+ **fNumberFormat,
+ **fPluralRules,
+ output.getStringRef(),
+ pluralForm,
+ status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+
+ const SimpleFormatter* formatter =
+ fCache->getRelativeUnitFormatter(fStyle, unit, bFuture, pluralForm);
+ if (formatter == nullptr) {
+ // TODO: WARN - look at quantity formatter's action with an error.
+ status = U_INVALID_FORMAT_ERROR;
+ return;
+ }
+
+ number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false);
+ modifier.formatAsPrefixSuffix(
+ output.getStringRef(), 0, output.getStringRef().length(), status);
+}
+
+UnicodeString& RelativeDateTimeFormatter::formatNumeric(
+ double offset,
+ URelativeDateTimeUnit unit,
+ UnicodeString& appendTo,
+ UErrorCode& status) const {
+ return doFormat(
+ &RelativeDateTimeFormatter::formatNumericImpl,
+ appendTo,
+ status,
+ offset,
+ unit);
+}
+
+FormattedRelativeDateTime RelativeDateTimeFormatter::formatNumericToValue(
+ double offset,
+ URelativeDateTimeUnit unit,
+ UErrorCode& status) const {
+ return doFormatToValue(
+ &RelativeDateTimeFormatter::formatNumericImpl,
+ status,
+ offset,
+ unit);
+}
+
+void RelativeDateTimeFormatter::formatNumericImpl(
+ double offset,
+ URelativeDateTimeUnit unit,
+ FormattedRelativeDateTimeData& output,
+ UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ UDateDirection direction = UDAT_DIRECTION_NEXT;
+ if (std::signbit(offset)) { // needed to handle -0.0
+ direction = UDAT_DIRECTION_LAST;
+ offset = -offset;
+ }
+ if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
+ int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
+
+ StandardPlural::Form pluralForm;
+ QuantityFormatter::formatAndSelect(
+ offset,
+ **fNumberFormat,
+ **fPluralRules,
+ output.getStringRef(),
+ pluralForm,
+ status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+
+ const SimpleFormatter* formatter =
+ fCache->getRelativeDateTimeUnitFormatter(fStyle, unit, bFuture, pluralForm);
+ if (formatter == nullptr) {
+ // TODO: WARN - look at quantity formatter's action with an error.
+ status = U_INVALID_FORMAT_ERROR;
+ return;
+ }
+
+ number::impl::SimpleModifier modifier(*formatter, kRDTLiteralField, false);
+ modifier.formatAsPrefixSuffix(
+ output.getStringRef(), 0, output.getStringRef().length(), status);
+}
+
+UnicodeString& RelativeDateTimeFormatter::format(
+ UDateDirection direction,
+ UDateAbsoluteUnit unit,
+ UnicodeString& appendTo,
+ UErrorCode& status) const {
+ return doFormat(
+ &RelativeDateTimeFormatter::formatAbsoluteImpl,
+ appendTo,
+ status,
+ direction,
+ unit);
+}
+
+FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
+ UDateDirection direction,
+ UDateAbsoluteUnit unit,
+ UErrorCode& status) const {
+ return doFormatToValue(
+ &RelativeDateTimeFormatter::formatAbsoluteImpl,
+ status,
+ direction,
+ unit);
+}
+
+void RelativeDateTimeFormatter::formatAbsoluteImpl(
+ UDateDirection direction,
+ UDateAbsoluteUnit unit,
+ FormattedRelativeDateTimeData& output,
+ UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) {
+ status = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
+
+ // Get string using fallback.
+ output.getStringRef().append(
+ fCache->getAbsoluteUnitString(fStyle, unit, direction),
+ kRDTLiteralField,
+ status);
+}
+
+UnicodeString& RelativeDateTimeFormatter::format(
+ double offset,
+ URelativeDateTimeUnit unit,
+ UnicodeString& appendTo,
+ UErrorCode& status) const {
+ return doFormat(
+ &RelativeDateTimeFormatter::formatRelativeImpl,
+ appendTo,
+ status,
+ offset,
+ unit);
+}
+
+FormattedRelativeDateTime RelativeDateTimeFormatter::formatToValue(
+ double offset,
+ URelativeDateTimeUnit unit,
+ UErrorCode& status) const {
+ return doFormatToValue(
+ &RelativeDateTimeFormatter::formatRelativeImpl,
+ status,
+ offset,
+ unit);
+}
+
+void RelativeDateTimeFormatter::formatRelativeImpl(
+ double offset,
+ URelativeDateTimeUnit unit,
+ FormattedRelativeDateTimeData& output,
+ UErrorCode& status) const {
+ if (U_FAILURE(status)) {
+ return;
+ }
+ // TODO:
+ // The full implementation of this depends on CLDR data that is not yet available,
+ // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data.
+ // In the meantime do a quick bring-up by calling the old format method; this
+ // leaves some holes (even for data that is currently available, such as quarter).
+ // When the new CLDR data is available, update the data storage accordingly,
+ // rewrite this to use it directly, and rewrite the old format method to call this
+ // new one; that is covered by https://unicode-org.atlassian.net/browse/ICU-12171.
+ UDateDirection direction = UDAT_DIRECTION_COUNT;
+ if (offset > -2.1 && offset < 2.1) {
+ // Allow a 1% epsilon, so offsets in -1.01..-0.99 map to LAST
+ double offsetx100 = offset * 100.0;
+ int32_t intoffset = (offsetx100 < 0)? (int32_t)(offsetx100-0.5) : (int32_t)(offsetx100+0.5);
+ switch (intoffset) {
+ case -200/*-2*/: direction = UDAT_DIRECTION_LAST_2; break;
+ case -100/*-1*/: direction = UDAT_DIRECTION_LAST; break;
+ case 0/* 0*/: direction = UDAT_DIRECTION_THIS; break;
+ case 100/* 1*/: direction = UDAT_DIRECTION_NEXT; break;
+ case 200/* 2*/: direction = UDAT_DIRECTION_NEXT_2; break;
+ default: break;
+ }
+ }
+ UDateAbsoluteUnit absunit = UDAT_ABSOLUTE_UNIT_COUNT;
+ switch (unit) {
+ case UDAT_REL_UNIT_YEAR: absunit = UDAT_ABSOLUTE_YEAR; break;
+ case UDAT_REL_UNIT_QUARTER: absunit = UDAT_ABSOLUTE_QUARTER; break;
+ case UDAT_REL_UNIT_MONTH: absunit = UDAT_ABSOLUTE_MONTH; break;
+ case UDAT_REL_UNIT_WEEK: absunit = UDAT_ABSOLUTE_WEEK; break;
+ case UDAT_REL_UNIT_DAY: absunit = UDAT_ABSOLUTE_DAY; break;
+ case UDAT_REL_UNIT_SECOND:
+ if (direction == UDAT_DIRECTION_THIS) {
+ absunit = UDAT_ABSOLUTE_NOW;
+ direction = UDAT_DIRECTION_PLAIN;
+ }
+ break;
+ case UDAT_REL_UNIT_SUNDAY: absunit = UDAT_ABSOLUTE_SUNDAY; break;
+ case UDAT_REL_UNIT_MONDAY: absunit = UDAT_ABSOLUTE_MONDAY; break;
+ case UDAT_REL_UNIT_TUESDAY: absunit = UDAT_ABSOLUTE_TUESDAY; break;
+ case UDAT_REL_UNIT_WEDNESDAY: absunit = UDAT_ABSOLUTE_WEDNESDAY; break;
+ case UDAT_REL_UNIT_THURSDAY: absunit = UDAT_ABSOLUTE_THURSDAY; break;
+ case UDAT_REL_UNIT_FRIDAY: absunit = UDAT_ABSOLUTE_FRIDAY; break;
+ case UDAT_REL_UNIT_SATURDAY: absunit = UDAT_ABSOLUTE_SATURDAY; break;
+ case UDAT_REL_UNIT_HOUR: absunit = UDAT_ABSOLUTE_HOUR; break;
+ case UDAT_REL_UNIT_MINUTE: absunit = UDAT_ABSOLUTE_MINUTE; break;
+ default: break;
+ }
+ if (direction != UDAT_DIRECTION_COUNT && absunit != UDAT_ABSOLUTE_UNIT_COUNT) {
+ formatAbsoluteImpl(direction, absunit, output, status);
+ if (output.getStringRef().length() != 0) {
+ return;
+ }
+ }
+ // otherwise fallback to formatNumeric
+ formatNumericImpl(offset, unit, output, status);
+}
+
+UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
+ const UnicodeString& relativeDateString, const UnicodeString& timeString,
+ UnicodeString& appendTo, UErrorCode& status) const {
+ return fCache->getCombinedDateAndTime()->format(
+ timeString, relativeDateString, appendTo, status);
+}
+
+UnicodeString& RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
+ if (fOptBreakIterator == nullptr
+ || str.length() == 0 || !u_islower(str.char32At(0))) {
+ return str;
+ }
+
+ // Must guarantee that one thread at a time accesses the shared break
+ // iterator.
+ static UMutex gBrkIterMutex;
+ Mutex lock(&gBrkIterMutex);
+ str.toTitle(
+ fOptBreakIterator->get(),
+ fLocale,
+ U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT);
+ return str;
+}
+
+UBool RelativeDateTimeFormatter::checkNoAdjustForContext(UErrorCode& status) const {
+ // This is unsupported because it's hard to keep fields in sync with title
+ // casing. The code could be written and tested if there is demand.
+ if (fOptBreakIterator != nullptr) {
+ status = U_UNSUPPORTED_ERROR;
+ return false;
+ }
+ return true;
+}
+
+void RelativeDateTimeFormatter::init(
+ NumberFormat *nfToAdopt,
+ BreakIterator *biToAdopt,
+ UErrorCode &status) {
+ LocalPointer<NumberFormat> nf(nfToAdopt);
+ LocalPointer<BreakIterator> bi(biToAdopt);
+ UnifiedCache::getByLocale(fLocale, fCache, status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+ const SharedPluralRules *pr = PluralRules::createSharedInstance(
+ fLocale, UPLURAL_TYPE_CARDINAL, status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+ SharedObject::copyPtr(pr, fPluralRules);
+ pr->removeRef();
+ if (nf.isNull()) {
+ const SharedNumberFormat *shared = NumberFormat::createSharedInstance(
+ fLocale, UNUM_DECIMAL, status);
+ if (U_FAILURE(status)) {
+ return;
+ }
+ SharedObject::copyPtr(shared, fNumberFormat);
+ shared->removeRef();
+ } else {
+ SharedNumberFormat *shared = new SharedNumberFormat(nf.getAlias());
+ if (shared == nullptr) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ nf.orphan();
+ SharedObject::copyPtr(shared, fNumberFormat);
+ }
+ if (bi.isNull()) {
+ SharedObject::clearPtr(fOptBreakIterator);
+ } else {
+ SharedBreakIterator *shared = new SharedBreakIterator(bi.getAlias());
+ if (shared == nullptr) {
+ status = U_MEMORY_ALLOCATION_ERROR;
+ return;
+ }
+ bi.orphan();
+ SharedObject::copyPtr(shared, fOptBreakIterator);
+ }
+}
+
+U_NAMESPACE_END
+
+// Plain C API
+
+U_NAMESPACE_USE
+
+
+// Magic number: "FRDT" (FormattedRelativeDateTime) in ASCII
+UPRV_FORMATTED_VALUE_CAPI_AUTO_IMPL(
+ FormattedRelativeDateTime,
+ UFormattedRelativeDateTime,
+ UFormattedRelativeDateTimeImpl,
+ UFormattedRelativeDateTimeApiHelper,
+ ureldatefmt,
+ 0x46524454)
+
+
+U_CAPI URelativeDateTimeFormatter* U_EXPORT2
+ureldatefmt_open( const char* locale,
+ UNumberFormat* nfToAdopt,
+ UDateRelativeDateTimeFormatterStyle width,
+ UDisplayContext capitalizationContext,
+ UErrorCode* status )
+{
+ if (U_FAILURE(*status)) {
+ return nullptr;
+ }
+ LocalPointer<RelativeDateTimeFormatter> formatter(new RelativeDateTimeFormatter(Locale(locale),
+ (NumberFormat*)nfToAdopt, width,
+ capitalizationContext, *status), *status);
+ if (U_FAILURE(*status)) {
+ return nullptr;
+ }
+ return (URelativeDateTimeFormatter*)formatter.orphan();
+}
+
+U_CAPI void U_EXPORT2
+ureldatefmt_close(URelativeDateTimeFormatter *reldatefmt)
+{
+ delete (RelativeDateTimeFormatter*)reldatefmt;
+}
+
+U_CAPI int32_t U_EXPORT2
+ureldatefmt_formatNumeric( const URelativeDateTimeFormatter* reldatefmt,
+ double offset,
+ URelativeDateTimeUnit unit,
+ char16_t* result,
+ int32_t resultCapacity,
+ UErrorCode* status)
+{
+ if (U_FAILURE(*status)) {
+ return 0;
+ }
+ if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0) {
+ *status = U_ILLEGAL_ARGUMENT_ERROR;
+ return 0;
+ }
+ UnicodeString res;
+ if (result != nullptr) {
+ // nullptr destination for pure preflighting: empty dummy string
+ // otherwise, alias the destination buffer (copied from udat_format)
+ res.setTo(result, 0, resultCapacity);
+ }
+ ((RelativeDateTimeFormatter*)reldatefmt)->formatNumeric(offset, unit, res, *status);
+ if (U_FAILURE(*status)) {
+ return 0;
+ }
+ return res.extract(result, resultCapacity, *status);
+}
+
+U_CAPI void U_EXPORT2
+ureldatefmt_formatNumericToResult(
+ const URelativeDateTimeFormatter* reldatefmt,
+ double offset,
+ URelativeDateTimeUnit unit,
+ UFormattedRelativeDateTime* result,
+ UErrorCode* status) {
+ if (U_FAILURE(*status)) {
+ return;
+ }
+ auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt);
+ auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status);
+ resultImpl->fImpl = fmt->formatNumericToValue(offset, unit, *status);
+}
+
+U_CAPI int32_t U_EXPORT2
+ureldatefmt_format( const URelativeDateTimeFormatter* reldatefmt,
+ double offset,
+ URelativeDateTimeUnit unit,
+ char16_t* result,
+ int32_t resultCapacity,
+ UErrorCode* status)
+{
+ if (U_FAILURE(*status)) {
+ return 0;
+ }
+ if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0) {
+ *status = U_ILLEGAL_ARGUMENT_ERROR;
+ return 0;
+ }
+ UnicodeString res;
+ if (result != nullptr) {
+ // nullptr destination for pure preflighting: empty dummy string
+ // otherwise, alias the destination buffer (copied from udat_format)
+ res.setTo(result, 0, resultCapacity);
+ }
+ ((RelativeDateTimeFormatter*)reldatefmt)->format(offset, unit, res, *status);
+ if (U_FAILURE(*status)) {
+ return 0;
+ }
+ return res.extract(result, resultCapacity, *status);
+}
+
+U_CAPI void U_EXPORT2
+ureldatefmt_formatToResult(
+ const URelativeDateTimeFormatter* reldatefmt,
+ double offset,
+ URelativeDateTimeUnit unit,
+ UFormattedRelativeDateTime* result,
+ UErrorCode* status) {
+ if (U_FAILURE(*status)) {
+ return;
+ }
+ auto* fmt = reinterpret_cast<const RelativeDateTimeFormatter*>(reldatefmt);
+ auto* resultImpl = UFormattedRelativeDateTimeApiHelper::validate(result, *status);
+ resultImpl->fImpl = fmt->formatToValue(offset, unit, *status);
+}
+
+U_CAPI int32_t U_EXPORT2
+ureldatefmt_combineDateAndTime( const URelativeDateTimeFormatter* reldatefmt,
+ const char16_t * relativeDateString,
+ int32_t relativeDateStringLen,
+ const char16_t * timeString,
+ int32_t timeStringLen,
+ char16_t* result,
+ int32_t resultCapacity,
+ UErrorCode* status )
+{
+ if (U_FAILURE(*status)) {
+ return 0;
+ }
+ if (result == nullptr ? resultCapacity != 0 : resultCapacity < 0 ||
+ (relativeDateString == nullptr ? relativeDateStringLen != 0 : relativeDateStringLen < -1) ||
+ (timeString == nullptr ? timeStringLen != 0 : timeStringLen < -1)) {
+ *status = U_ILLEGAL_ARGUMENT_ERROR;
+ return 0;
+ }
+ UnicodeString relDateStr((UBool)(relativeDateStringLen == -1), relativeDateString, relativeDateStringLen);
+ UnicodeString timeStr((UBool)(timeStringLen == -1), timeString, timeStringLen);
+ UnicodeString res(result, 0, resultCapacity);
+ ((RelativeDateTimeFormatter*)reldatefmt)->combineDateAndTime(relDateStr, timeStr, res, *status);
+ if (U_FAILURE(*status)) {
+ return 0;
+ }
+ return res.extract(result, resultCapacity, *status);
+}
+
+#endif /* !UCONFIG_NO_FORMATTING */