You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1417 lines
48 KiB
1417 lines
48 KiB
// © 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 */
|