| // © 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/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; |
| |
| // Mappping 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 getStringWithFallback( |
| const UResourceBundle *resource, |
| const char *key, |
| UnicodeString &result, |
| UErrorCode &status) { |
| int32_t len = 0; |
| const UChar *resStr = ures_getStringByKeyWithFallback( |
| resource, key, &len, &status); |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| result.setTo(TRUE, resStr, len); |
| return TRUE; |
| } |
| |
| |
| static UBool getStringByIndex( |
| const UResourceBundle *resource, |
| int32_t idx, |
| UnicodeString &result, |
| UErrorCode &status) { |
| int32_t len = 0; |
| const UChar *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 UChar narrow[7] = {0x002D, 0x006E, 0x0061, 0x0072, 0x0072, 0x006F, 0x0077}; |
| static const UChar 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) { |
| // 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 UBool getDateTimePattern( |
| const UResourceBundle *resource, |
| UnicodeString &result, |
| UErrorCode &status) { |
| UnicodeString defaultCalendarName; |
| if (!getStringWithFallback( |
| resource, |
| "calendar/default", |
| defaultCalendarName, |
| status)) { |
| return FALSE; |
| } |
| CharString pathBuffer; |
| pathBuffer.append("calendar/", status) |
| .appendInvariantChars(defaultCalendarName, status) |
| .append("/DateTimePatterns", status); |
| LocalUResourceBundlePointer topLevel( |
| ures_getByKeyWithFallback( |
| resource, pathBuffer.data(), nullptr, &status)); |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| int32_t size = ures_getSize(topLevel.getAlias()); |
| if (size <= 8) { |
| // 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(), 8, result, status); |
| } |
| |
| template<> U_I18N_API |
| 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(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 http://bugs.icu-project.org/trac/ticket/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, |
| UChar* 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, |
| UChar* 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 UChar * relativeDateString, |
| int32_t relativeDateStringLen, |
| const UChar * timeString, |
| int32_t timeStringLen, |
| UChar* 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 */ |