1417 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			1417 lines
		
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
| // © 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;
 | |
| 
 | |
|     // 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 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) 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 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 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,
 | |
|                     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 */
 |