blob: ad4eb188d70034f7d7c9f11888aabc5e2433eda1 [file] [log] [blame]
/*
******************************************************************************
* 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 "unicode/dtfmtsym.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"
// Copied from uscript_props.cpp
static UMutex gBrkIterMutex = U_MUTEX_INITIALIZER;
U_NAMESPACE_BEGIN
// RelativeDateTimeFormatter specific data for a single locale
class RelativeDateTimeCacheData: public SharedObject {
public:
RelativeDateTimeCacheData() : combinedDateAndTime(NULL) {
// Initialize the cache arrays
for (int32_t style = 0; style < UDAT_STYLE_COUNT; ++style) {
for (int32_t relUnit = 0; relUnit < UDAT_RELATIVE_UNIT_COUNT; ++relUnit) {
for (int32_t pl = 0; pl < StandardPlural::COUNT; ++pl) {
relativeUnitsFormatters[style][relUnit][0][pl] = NULL;
relativeUnitsFormatters[style][relUnit][1][pl] = NULL;
}
}
}
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_RELATIVE_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 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_RELATIVE_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;
}
// Use fallback cache for SimpleFormatter relativeUnits.
const SimpleFormatter* RelativeDateTimeCacheData::getRelativeUnitFormatter(
int32_t fStyle,
UDateRelativeUnit unit,
int32_t pastFutureIndex,
int32_t pluralUnit) const {
int32_t style = fStyle;
do {
if (relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit] != NULL) {
return relativeUnitsFormatters[style][unit][pastFutureIndex][pluralUnit];
}
style = fallBackCache[style];
} while (style != -1);
return NULL; // 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.
* Contains inner sink classes, each one corresponding to a level of the resource table.
*
* 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.
*
* C++: Each inner sink class has a reference to the main outer sink.
*/
struct RelDateTimeFmtDataSink : public ResourceTableSink {
/**
* 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_RELATIVE_SECONDS;
case MINUTE:
return UDAT_RELATIVE_MINUTES;
case HOUR:
return UDAT_RELATIVE_HOURS;
case DAY:
return UDAT_RELATIVE_DAYS;
case WEEK:
return UDAT_RELATIVE_WEEKS;
case MONTH:
return UDAT_RELATIVE_MONTHS;
/*
* case QUARTER:
* return UDATE_RELATIVE_QUARTERS;
*/
case YEAR:
return UDAT_RELATIVE_YEARS;
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;
/* TODO: Add in QUARTER
* 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;
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;
}
// Sinks for additional levels under /fields/*/relative/ and /fields/*/relativeTime/
/**
* Make list of simplePatternFmtList, for past and for future.
* Set a SimpleFormatter for the <style, relative unit, plurality>
*
* Fill in values for the particular plural given, e.g., ONE, FEW, OTHER, etc.
*/
struct RelDateTimeDetailSink : public ResourceTableSink {
RelDateTimeDetailSink(RelDateTimeFmtDataSink &sink) : outer(sink) {}
~RelDateTimeDetailSink();
virtual void put(const char *key, const ResourceValue &value,
UErrorCode &errorCode) {
if (U_FAILURE(errorCode)) { return; }
outer.relUnitIndex = relUnitFromGeneric(outer.genericUnit);
if (outer.relUnitIndex < 0) {
return;
}
/* Make two lists of simplePatternFmtList, one for past and one for future.
* Set a SimpleFormatter pattern for the <style, relative unit, plurality>
*
* Fill in values for the particular plural given, e.g., ONE, FEW, OTHER, etc.
*/
int32_t pluralIndex = StandardPlural::indexOrNegativeFromString(key);
if (pluralIndex >= 0) {
SimpleFormatter **patterns =
outer.outputData.relativeUnitsFormatters[outer.style][outer.relUnitIndex]
[outer.pastFutureIndex];
// Only set if not already established.
if (patterns[pluralIndex] == NULL) {
patterns[pluralIndex] = new SimpleFormatter(
value.getUnicodeString(errorCode), 0, 1, errorCode);
if (patterns[pluralIndex] == NULL) {
errorCode = U_MEMORY_ALLOCATION_ERROR;
}
}
}
}
RelDateTimeFmtDataSink &outer;
} relDateTimeDetailSink;
/*
* Handles "relativeTime" entries, e.g., under "day", "hour", "minute",
* "minute-short", etc.
*/
struct RelativeTimeSink : public ResourceTableSink {
RelativeTimeSink(RelDateTimeFmtDataSink &sink) : outer(sink) {}
~RelativeTimeSink();
virtual ResourceTableSink *getOrCreateTableSink(
const char *key, int32_t /* initialSize */, UErrorCode& errorCode) {
if (U_FAILURE(errorCode)) { return NULL; }
outer.relUnitIndex = relUnitFromGeneric(outer.genericUnit);
if (outer.relUnitIndex < 0) {
return NULL;
}
if (uprv_strcmp(key, "past") == 0) {
outer.pastFutureIndex = 0;
} else if (uprv_strcmp(key, "future") == 0) {
outer.pastFutureIndex = 1;
} else {
// Unknown key.
return NULL;
}
return &outer.relDateTimeDetailSink;
}
RelDateTimeFmtDataSink &outer;
} relativeTimeSink;
/*
* Handles "relative" entries, e.g., under "day", "day-short", "fri",
* "fri-narrow", "fri-short", etc.
*/
struct RelativeSink : public ResourceTableSink {
RelativeSink(RelDateTimeFmtDataSink &sink) : outer(sink) {}
~RelativeSink();
virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
if (U_FAILURE(errorCode)) { return; }
int32_t direction = keyToDirection(key);
if (direction < 0) {
return;
}
int32_t relUnitIndex = relUnitFromGeneric(outer.genericUnit);
if (relUnitIndex == UDAT_RELATIVE_SECONDS &&
direction == UDAT_DIRECTION_THIS &&
outer.outputData.absoluteUnits[outer.style][UDAT_ABSOLUTE_NOW]
[UDAT_DIRECTION_PLAIN].isEmpty()) {
// Handle "NOW"
outer.outputData.absoluteUnits[outer.style][UDAT_ABSOLUTE_NOW]
[UDAT_DIRECTION_PLAIN].fastCopyFrom(value.getUnicodeString(errorCode));
}
int32_t absUnitIndex = absUnitFromGeneric(outer.genericUnit);
if (absUnitIndex < 0) {
return;
}
// Only reset if slot is empty.
if (outer.outputData.absoluteUnits[outer.style][absUnitIndex][direction].isEmpty()) {
outer.outputData.absoluteUnits[outer.style][absUnitIndex]
[direction].fastCopyFrom(value.getUnicodeString(errorCode));
}
}
RelDateTimeFmtDataSink &outer;
} relativeSink;
/*
* Handles entries under "fields", recognizing "relative" and "relativeTime" entries.
*/
struct UnitSink : public ResourceTableSink {
UnitSink(RelDateTimeFmtDataSink &sink) : outer(sink) {}
~UnitSink();
virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
if (U_FAILURE(errorCode)) { return; }
if (uprv_strcmp(key, "dn") != 0) {
return;
}
// Handle Display Name for PLAIN direction for some units.
int32_t absUnit = absUnitFromGeneric(outer.genericUnit);
if (absUnit < 0) {
return; // Not interesting.
}
// TODO(Travis Keep): This is a hack to get around CLDR bug 6818.
UnicodeString displayName = value.getUnicodeString(errorCode);
if (U_SUCCESS(errorCode)) {
if (uprv_strcmp("en", outer.sinkLocaleId) == 0) {
displayName.toLower();
}
}
// end hack
// Store displayname if not set.
if (outer.outputData.absoluteUnits[outer.style]
[absUnit][UDAT_DIRECTION_PLAIN].isEmpty()) {
outer.outputData.absoluteUnits[outer.style]
[absUnit][UDAT_DIRECTION_PLAIN].fastCopyFrom(displayName);
return;
}
}
virtual ResourceTableSink *getOrCreateTableSink(
const char *key, int32_t /* initialSize */, UErrorCode &errorCode) {
if (U_FAILURE(errorCode)) { return NULL; }
if (uprv_strcmp(key, "relative") == 0) {
return &outer.relativeSink;
} else if (uprv_strcmp(key, "relativeTime") == 0) {
return &outer.relativeTimeSink;
}
return NULL;
}
RelDateTimeFmtDataSink &outer;
} unitSink;
// For hack for locale "en".
// TODO(Travis Keep): This is a hack to get around CLDR bug 6818.
const char* sinkLocaleId;
// Values kept between levels of parsing the CLDR data.
int32_t pastFutureIndex; // 0 == past or 1 == future
UDateRelativeDateTimeFormatterStyle style; // {LONG, SHORT, NARROW}
RelAbsUnit genericUnit;
int32_t relUnitIndex;
int32_t absUnitIndex;
RelativeDateTimeCacheData &outputData;
// Constructor
RelDateTimeFmtDataSink(RelativeDateTimeCacheData& cacheData, const char* localeId)
: relDateTimeDetailSink(*this), relativeTimeSink(*this), relativeSink(*this),
unitSink(*this), sinkLocaleId(localeId), 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 = 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;
}
// Member functions of top level sink.
virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) {
// Only handle aliases, storing information about alias fallback.
if (U_SUCCESS(errorCode)) {
if (value.getType() != URES_ALIAS) {
return;
}
const UnicodeString valueStr = value.getAliasUnicodeString(errorCode);
if (U_SUCCESS(errorCode)) {
UDateRelativeDateTimeFormatterStyle sourceStyle= styleFromString(key);
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;
}
}
return;
}
// Top level sink
virtual ResourceTableSink *getOrCreateTableSink(
const char *key, int32_t /* initialSize */, UErrorCode& /* errorCode */) {
style= styleFromString(key);
int32_t unitSize = uprv_strlen(key) - styleSuffixLength(style);
genericUnit = unitOrNegativeFromString(key, unitSize);
if (style < 0 || genericUnit == INVALID_UNIT) {
return NULL;
}
return &unitSink;
}
};
// Virtual destructors must be defined out of line.
RelDateTimeFmtDataSink::RelDateTimeDetailSink::~RelDateTimeDetailSink() {}
RelDateTimeFmtDataSink::RelativeTimeSink::~RelativeTimeSink() {}
RelDateTimeFmtDataSink::RelativeSink::~RelativeSink() {}
RelDateTimeFmtDataSink::UnitSink::~UnitSink() {}
RelDateTimeFmtDataSink::~RelDateTimeFmtDataSink() {}
} // namespace
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) {
Locale locale(localeId);
DateFormatSymbols dfSym(locale, status);
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, localeId);
ures_getAllTableItemsWithFallback(resource, "fields", sink, status);
// 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(), NULL, &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(NULL, localeId, &status));
if (U_FAILURE(status)) {
return NULL;
}
LocalPointer<RelativeDateTimeCacheData> result(
new RelativeDateTimeCacheData());
if (result.isNull()) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
if (!loadUnitData(
topLevel.getAlias(),
*result,
localeId,
status)) {
return NULL;
}
UnicodeString dateTimePattern;
if (!getDateTimePattern(topLevel.getAlias(), dateTimePattern, status)) {
return NULL;
}
result->adoptCombinedDateAndTime(
new SimpleFormatter(dateTimePattern, 2, 2, status));
if (U_FAILURE(status)) {
return NULL;
}
result->addRef();
return result.orphan();
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status) :
fCache(NULL),
fNumberFormat(NULL),
fPluralRules(NULL),
fStyle(UDAT_STYLE_LONG),
fContext(UDISPCTX_CAPITALIZATION_NONE),
fOptBreakIterator(NULL) {
init(NULL, NULL, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale, UErrorCode& status) :
fCache(NULL),
fNumberFormat(NULL),
fPluralRules(NULL),
fStyle(UDAT_STYLE_LONG),
fContext(UDISPCTX_CAPITALIZATION_NONE),
fOptBreakIterator(NULL),
fLocale(locale) {
init(NULL, NULL, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status) :
fCache(NULL),
fNumberFormat(NULL),
fPluralRules(NULL),
fStyle(UDAT_STYLE_LONG),
fContext(UDISPCTX_CAPITALIZATION_NONE),
fOptBreakIterator(NULL),
fLocale(locale) {
init(nfToAdopt, NULL, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale,
NumberFormat *nfToAdopt,
UDateRelativeDateTimeFormatterStyle styl,
UDisplayContext capitalizationContext,
UErrorCode& status) :
fCache(NULL),
fNumberFormat(NULL),
fPluralRules(NULL),
fStyle(styl),
fContext(capitalizationContext),
fOptBreakIterator(NULL),
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, NULL, 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 != NULL) {
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 != NULL) {
fCache->removeRef();
}
if (fNumberFormat != NULL) {
fNumberFormat->removeRef();
}
if (fPluralRules != NULL) {
fPluralRules->removeRef();
}
if (fOptBreakIterator != NULL) {
fOptBreakIterator->removeRef();
}
}
const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const {
return **fNumberFormat;
}
UDisplayContext RelativeDateTimeFormatter::getCapitalizationContext() const {
return fContext;
}
UDateRelativeDateTimeFormatterStyle RelativeDateTimeFormatter::getFormatStyle() const {
return fStyle;
}
UnicodeString& RelativeDateTimeFormatter::format(
double quantity, UDateDirection direction, UDateRelativeUnit unit,
UnicodeString& appendTo, UErrorCode& status) const {
if (U_FAILURE(status)) {
return appendTo;
}
if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
}
int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
FieldPosition pos(FieldPosition::DONT_CARE);
UnicodeString result;
UnicodeString formattedNumber;
StandardPlural::Form pluralIndex = QuantityFormatter::selectPlural(
quantity, **fNumberFormat, **fPluralRules, formattedNumber, pos,
status);
const SimpleFormatter* formatter =
fCache->getRelativeUnitFormatter(fStyle, unit, bFuture, pluralIndex);
if (formatter == NULL) {
// TODO: WARN - look at quantity formatter's action with an error.
status = U_INVALID_FORMAT_ERROR;
return appendTo;
}
formatter->format(formattedNumber, result, status);
adjustForContext(result);
return appendTo.append(result);
}
UnicodeString& RelativeDateTimeFormatter::formatNumeric(
double offset, URelativeDateTimeUnit unit,
UnicodeString& appendTo, UErrorCode& status) const {
if (U_FAILURE(status)) {
return appendTo;
}
// 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.
UDateRelativeUnit relunit = UDAT_RELATIVE_UNIT_COUNT;
switch (unit) {
case UDAT_REL_UNIT_YEAR: relunit = UDAT_RELATIVE_YEARS; break;
case UDAT_REL_UNIT_MONTH: relunit = UDAT_RELATIVE_MONTHS; break;
case UDAT_REL_UNIT_WEEK: relunit = UDAT_RELATIVE_WEEKS; break;
case UDAT_REL_UNIT_DAY: relunit = UDAT_RELATIVE_DAYS; break;
case UDAT_REL_UNIT_HOUR: relunit = UDAT_RELATIVE_HOURS; break;
case UDAT_REL_UNIT_MINUTE: relunit = UDAT_RELATIVE_MINUTES; break;
case UDAT_REL_UNIT_SECOND: relunit = UDAT_RELATIVE_SECONDS; break;
default: // a unit that the above method does not handle
status = U_UNSUPPORTED_ERROR;
return appendTo;
}
UDateDirection direction = UDAT_DIRECTION_NEXT;
if (offset < 0) {
direction = UDAT_DIRECTION_LAST;
offset = -offset;
}
return format(offset, direction, relunit, appendTo, status);
}
UnicodeString& RelativeDateTimeFormatter::format(
UDateDirection direction, UDateAbsoluteUnit unit,
UnicodeString& appendTo, UErrorCode& status) const {
if (U_FAILURE(status)) {
return appendTo;
}
if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return appendTo;
}
// Get string using fallback.
UnicodeString result;
result.fastCopyFrom(fCache->getAbsoluteUnitString(fStyle, unit, direction));
if (fOptBreakIterator != NULL) {
adjustForContext(result);
}
return appendTo.append(result);
}
UnicodeString& RelativeDateTimeFormatter::format(
double offset, URelativeDateTimeUnit unit,
UnicodeString& appendTo, UErrorCode& status) const {
if (U_FAILURE(status)) {
return appendTo;
}
// 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_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;
default: break;
}
if (direction != UDAT_DIRECTION_COUNT && absunit != UDAT_ABSOLUTE_UNIT_COUNT) {
const UnicodeString &unitFormatString =
fCache->getAbsoluteUnitString(fStyle, absunit, direction);
if (!unitFormatString.isEmpty()) {
if (fOptBreakIterator != NULL) {
UnicodeString result(unitFormatString);
adjustForContext(result);
return appendTo.append(result);
} else {
return appendTo.append(unitFormatString);
}
}
}
// otherwise fallback to formatNumeric
return formatNumeric(offset, unit, appendTo, status);
}
UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
const UnicodeString& relativeDateString, const UnicodeString& timeString,
UnicodeString& appendTo, UErrorCode& status) const {
return fCache->getCombinedDateAndTime()->format(
timeString, relativeDateString, appendTo, status);
}
void RelativeDateTimeFormatter::adjustForContext(UnicodeString &str) const {
if (fOptBreakIterator == NULL
|| str.length() == 0 || !u_islower(str.char32At(0))) {
return;
}
// Must guarantee that one thread at a time accesses the shared break
// iterator.
Mutex lock(&gBrkIterMutex);
str.toTitle(
fOptBreakIterator->get(),
fLocale,
U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT);
}
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 == NULL) {
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 == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
bi.orphan();
SharedObject::copyPtr(shared, fOptBreakIterator);
}
}
U_NAMESPACE_END
// Plain C API
U_NAMESPACE_USE
U_CAPI URelativeDateTimeFormatter* U_EXPORT2
ureldatefmt_open( const char* locale,
UNumberFormat* nfToAdopt,
UDateRelativeDateTimeFormatterStyle width,
UDisplayContext capitalizationContext,
UErrorCode* status )
{
if (U_FAILURE(*status)) {
return NULL;
}
LocalPointer<RelativeDateTimeFormatter> formatter(new RelativeDateTimeFormatter(Locale(locale),
(NumberFormat*)nfToAdopt, width,
capitalizationContext, *status), *status);
if (U_FAILURE(*status)) {
return NULL;
}
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 == NULL ? resultCapacity != 0 : resultCapacity < 0) {
*status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
UnicodeString res;
if (result != NULL) {
// NULL 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 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 == NULL ? resultCapacity != 0 : resultCapacity < 0) {
*status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
UnicodeString res;
if (result != NULL) {
// NULL 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 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 == NULL ? resultCapacity != 0 : resultCapacity < 0 ||
(relativeDateString == NULL ? relativeDateStringLen != 0 : relativeDateStringLen < -1) ||
(timeString == NULL ? 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 */