blob: c1341dfc211c73632afb9bc83f7af493a3dded88 [file] [log] [blame]
/*
******************************************************************************
* Copyright (C) 2014, International Business Machines Corporation and
* others. All Rights Reserved.
******************************************************************************
*
* File RELDATEFMT.CPP
******************************************************************************
*/
#include "unicode/reldatefmt.h"
#if !UCONFIG_NO_FORMATTING
#include "unicode/localpointer.h"
#include "unicode/plurrule.h"
#include "unicode/msgfmt.h"
#include "unicode/decimfmt.h"
#include "unicode/numfmt.h"
#include "lrucache.h"
#include "uresimp.h"
#include "unicode/ures.h"
#include "cstring.h"
#include "plurrule_impl.h"
#include "ucln_in.h"
#include "mutex.h"
#include "charstr.h"
#include "sharedptr.h"
// Copied from uscript_props.cpp
#define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof((array)[0]))
static icu::LRUCache *gCache = NULL;
static UMutex gCacheMutex = U_MUTEX_INITIALIZER;
static icu::UInitOnce gCacheInitOnce = U_INITONCE_INITIALIZER;
U_CDECL_BEGIN
static UBool U_CALLCONV reldatefmt_cleanup() {
gCacheInitOnce.reset();
if (gCache) {
delete gCache;
gCache = NULL;
}
return TRUE;
}
U_CDECL_END
U_NAMESPACE_BEGIN
// other must always be first.
static const char * const gPluralForms[] = {
"other", "zero", "one", "two", "few", "many"};
static const UChar gPlaceholder[] = { 0x7b, 0x30, 0x7d}; /* {0} */
class QualitativeUnits : public UMemory {
public:
QualitativeUnits() { }
UnicodeString data[UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT];
private:
QualitativeUnits(const QualitativeUnits &other);
QualitativeUnits &operator=(const QualitativeUnits& other);
};
struct UnitPattern {
UnicodeString pattern;
int32_t offset; // Offset of "{0}". -1 means no {0}.
UBool valid; // True if initialize, false otherwise.
UnitPattern() : pattern(), offset(0), valid(FALSE) {
}
void set(const UnicodeString &patternStr) {
pattern = patternStr;
offset = patternStr.indexOf(gPlaceholder, LENGTHOF(gPlaceholder), 0);
valid = TRUE;
}
UnicodeString& append(
double quantity,
const NumberFormat &nf,
UnicodeString &appendTo) const {
if (!valid) {
return appendTo;
}
if (offset == -1) {
appendTo.append(pattern);
return appendTo;
}
appendTo.append(pattern, 0, offset);
nf.format(quantity, appendTo);
appendTo.append(pattern,
offset + LENGTHOF(gPlaceholder),
0x7fffffff);
return appendTo;
}
};
class QuantitativeUnits : public UMemory {
public:
QuantitativeUnits() { }
UnitPattern data[UDAT_RELATIVE_UNIT_COUNT][2][LENGTHOF(gPluralForms)];
private:
QuantitativeUnits(const QuantitativeUnits &other);
QuantitativeUnits &operator=(const QuantitativeUnits& other);
};
class RelativeDateTimeData : public SharedObject {
public:
SharedPtr<QualitativeUnits> qualitativeUnits;
SharedPtr<QuantitativeUnits> quantitativeUnits;
SharedPtr<MessageFormat> combinedDateAndTime;
SharedPtr<PluralRules> pluralRules;
SharedPtr<NumberFormat> numberFormat;
virtual ~RelativeDateTimeData();
private:
RelativeDateTimeData &operator=(const RelativeDateTimeData& other);
};
RelativeDateTimeData::~RelativeDateTimeData() {
}
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 getOptionalStringWithFallback(
const UResourceBundle *resource,
const char *key,
UnicodeString &result,
UErrorCode &status) {
if (U_FAILURE(status)) {
return FALSE;
}
int32_t len = 0;
const UChar *resStr = ures_getStringByKey(
resource, key, &len, &status);
if (status == U_MISSING_RESOURCE_ERROR) {
result.remove();
status = U_ZERO_ERROR;
return TRUE;
}
if (U_FAILURE(status)) {
return FALSE;
}
result.setTo(TRUE, resStr, len);
return TRUE;
}
static UBool getString(
const UResourceBundle *resource,
UnicodeString &result,
UErrorCode &status) {
int32_t len = 0;
const UChar *resStr = ures_getString(resource, &len, &status);
if (U_FAILURE(status)) {
return FALSE;
}
result.setTo(TRUE, resStr, len);
return TRUE;
}
static UBool getUnitPattern(
const UResourceBundle *resource,
UnitPattern &result,
UErrorCode &status) {
if (U_FAILURE(status)) {
return FALSE;
}
UnicodeString rawPattern;
if (!getString(resource, rawPattern, status)) {
return FALSE;
}
result.set(rawPattern);
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;
}
static void addQualitativeUnit(
const UResourceBundle *resource,
UDateAbsoluteUnit absoluteUnit,
const UnicodeString &unitName,
QualitativeUnits &qualitativeUnits,
UErrorCode &status) {
getStringWithFallback(
resource,
"-1",
qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_LAST],
status);
getStringWithFallback(
resource,
"0",
qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_THIS],
status);
getStringWithFallback(
resource,
"1",
qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_NEXT],
status);
getOptionalStringWithFallback(
resource,
"-2",
qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_LAST_2],
status);
getOptionalStringWithFallback(
resource,
"2",
qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_NEXT_2],
status);
qualitativeUnits.data[absoluteUnit][UDAT_DIRECTION_PLAIN] = unitName;
}
static int32_t getPluralIndex(const char *pluralForm) {
int32_t len = LENGTHOF(gPluralForms);
for (int32_t i = 0; i < len; ++i) {
if (uprv_strcmp(pluralForm, gPluralForms[i]) == 0) {
return i;
}
}
return -1;
}
static void addTimeUnit(
const UResourceBundle *resource,
UDateRelativeUnit relativeUnit,
int32_t pastOrFuture,
QuantitativeUnits &quantitativeUnits,
UErrorCode &status) {
if (U_FAILURE(status)) {
return;
}
int32_t size = ures_getSize(resource);
for (int32_t i = 0; i < size; ++i) {
LocalUResourceBundlePointer pluralBundle(
ures_getByIndex(resource, i, NULL, &status));
if (U_FAILURE(status)) {
return;
}
int32_t pluralIndex = getPluralIndex(
ures_getKey(pluralBundle.getAlias()));
if (pluralIndex != -1) {
if (!getUnitPattern(
pluralBundle.getAlias(),
quantitativeUnits.data[relativeUnit][pastOrFuture][pluralIndex],
status)) {
return;
}
}
}
}
static void addTimeUnit(
const UResourceBundle *resource,
UDateRelativeUnit relativeUnit,
QuantitativeUnits &quantitativeUnits,
UErrorCode &status) {
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(
resource, "relativeTime", NULL, &status));
if (U_FAILURE(status)) {
return;
}
LocalUResourceBundlePointer futureBundle(ures_getByKeyWithFallback(
topLevel.getAlias(), "future", NULL, &status));
if (U_FAILURE(status)) {
return;
}
addTimeUnit(
futureBundle.getAlias(),
relativeUnit,
1,
quantitativeUnits,
status);
LocalUResourceBundlePointer pastBundle(ures_getByKeyWithFallback(
topLevel.getAlias(), "past", NULL, &status));
if (U_FAILURE(status)) {
return;
}
addTimeUnit(
pastBundle.getAlias(),
relativeUnit,
0,
quantitativeUnits,
status);
}
static void addTimeUnit(
const UResourceBundle *resource,
const char *path,
UDateRelativeUnit relativeUnit,
QuantitativeUnits &quantitativeUnits,
UErrorCode &status) {
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(resource, path, NULL, &status));
if (U_FAILURE(status)) {
return;
}
addTimeUnit(topLevel.getAlias(), relativeUnit, quantitativeUnits, status);
}
static void addTimeUnit(
const UResourceBundle *resource,
const char *path,
UDateRelativeUnit relativeUnit,
UDateAbsoluteUnit absoluteUnit,
QuantitativeUnits &quantitativeUnits,
QualitativeUnits &qualitativeUnits,
UErrorCode &status) {
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(resource, path, NULL, &status));
if (U_FAILURE(status)) {
return;
}
addTimeUnit(topLevel.getAlias(), relativeUnit, quantitativeUnits, status);
UnicodeString unitName;
if (!getStringWithFallback(topLevel.getAlias(), "dn", unitName, status)) {
return;
}
// TODO(Travis Keep): This is a hack to get around CLDR bug 6818.
const char *localeId = ures_getLocaleByType(
topLevel.getAlias(), ULOC_ACTUAL_LOCALE, &status);
if (U_FAILURE(status)) {
return;
}
Locale locale(localeId);
if (uprv_strcmp("en", locale.getLanguage()) == 0) {
unitName.toLower();
}
// end hack
ures_getByKeyWithFallback(
topLevel.getAlias(), "relative", topLevel.getAlias(), &status);
if (U_FAILURE(status)) {
return;
}
addQualitativeUnit(
topLevel.getAlias(),
absoluteUnit,
unitName,
qualitativeUnits,
status);
}
static void readDaysOfWeek(
const UResourceBundle *resource,
const char *path,
UnicodeString *daysOfWeek,
UErrorCode &status) {
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(resource, path, NULL, &status));
if (U_FAILURE(status)) {
return;
}
int32_t size = ures_getSize(topLevel.getAlias());
if (size != 7) {
status = U_INTERNAL_PROGRAM_ERROR;
return;
}
for (int32_t i = 0; i < size; ++i) {
if (!getStringByIndex(topLevel.getAlias(), i, daysOfWeek[i], status)) {
return;
}
}
}
static void addWeekDay(
const UResourceBundle *resource,
const char *path,
const UnicodeString *daysOfWeek,
UDateAbsoluteUnit absoluteUnit,
QualitativeUnits &qualitativeUnits,
UErrorCode &status) {
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(resource, path, NULL, &status));
if (U_FAILURE(status)) {
return;
}
addQualitativeUnit(
topLevel.getAlias(),
absoluteUnit,
daysOfWeek[absoluteUnit - UDAT_ABSOLUTE_SUNDAY],
qualitativeUnits,
status);
}
static UBool load(
const UResourceBundle *resource,
QualitativeUnits &qualitativeUnits,
QuantitativeUnits &quantitativeUnits,
UErrorCode &status) {
addTimeUnit(
resource,
"fields/day",
UDAT_RELATIVE_DAYS,
UDAT_ABSOLUTE_DAY,
quantitativeUnits,
qualitativeUnits,
status);
addTimeUnit(
resource,
"fields/week",
UDAT_RELATIVE_WEEKS,
UDAT_ABSOLUTE_WEEK,
quantitativeUnits,
qualitativeUnits,
status);
addTimeUnit(
resource,
"fields/month",
UDAT_RELATIVE_MONTHS,
UDAT_ABSOLUTE_MONTH,
quantitativeUnits,
qualitativeUnits,
status);
addTimeUnit(
resource,
"fields/year",
UDAT_RELATIVE_YEARS,
UDAT_ABSOLUTE_YEAR,
quantitativeUnits,
qualitativeUnits,
status);
addTimeUnit(
resource,
"fields/second",
UDAT_RELATIVE_SECONDS,
quantitativeUnits,
status);
addTimeUnit(
resource,
"fields/minute",
UDAT_RELATIVE_MINUTES,
quantitativeUnits,
status);
addTimeUnit(
resource,
"fields/hour",
UDAT_RELATIVE_HOURS,
quantitativeUnits,
status);
getStringWithFallback(
resource,
"fields/second/relative/0",
qualitativeUnits.data[UDAT_ABSOLUTE_NOW][UDAT_DIRECTION_PLAIN],
status);
UnicodeString daysOfWeek[7];
readDaysOfWeek(
resource,
"calendar/gregorian/dayNames/stand-alone/wide",
daysOfWeek,
status);
addWeekDay(
resource,
"fields/mon/relative",
daysOfWeek,
UDAT_ABSOLUTE_MONDAY,
qualitativeUnits,
status);
addWeekDay(
resource,
"fields/tue/relative",
daysOfWeek,
UDAT_ABSOLUTE_TUESDAY,
qualitativeUnits,
status);
addWeekDay(
resource,
"fields/wed/relative",
daysOfWeek,
UDAT_ABSOLUTE_WEDNESDAY,
qualitativeUnits,
status);
addWeekDay(
resource,
"fields/thu/relative",
daysOfWeek,
UDAT_ABSOLUTE_THURSDAY,
qualitativeUnits,
status);
addWeekDay(
resource,
"fields/fri/relative",
daysOfWeek,
UDAT_ABSOLUTE_FRIDAY,
qualitativeUnits,
status);
addWeekDay(
resource,
"fields/sat/relative",
daysOfWeek,
UDAT_ABSOLUTE_SATURDAY,
qualitativeUnits,
status);
addWeekDay(
resource,
"fields/sun/relative",
daysOfWeek,
UDAT_ABSOLUTE_SUNDAY,
qualitativeUnits,
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 to 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);
}
static SharedObject *U_CALLCONV createData(
const char *localeId, UErrorCode &status) {
LocalUResourceBundlePointer topLevel(ures_open(NULL, localeId, &status));
if (U_FAILURE(status)) {
return NULL;
}
LocalPointer<RelativeDateTimeData> result(new RelativeDateTimeData());
LocalPointer<QualitativeUnits> qualitativeUnits(new QualitativeUnits());
LocalPointer<QuantitativeUnits> quantitativeUnits(new QuantitativeUnits());
if (result.getAlias() == NULL
|| qualitativeUnits.getAlias() == NULL
|| quantitativeUnits.getAlias() == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
if (!load(
topLevel.getAlias(),
*qualitativeUnits,
*quantitativeUnits,
status)) {
return NULL;
}
if (!result->qualitativeUnits.reset(qualitativeUnits.orphan())) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
if (!result->quantitativeUnits.reset(quantitativeUnits.orphan())) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
UnicodeString dateTimePattern;
if (!getDateTimePattern(topLevel.getAlias(), dateTimePattern, status)) {
return NULL;
}
LocalPointer<MessageFormat> mf(
new MessageFormat(dateTimePattern, localeId, status));
if (U_FAILURE(status)) {
return NULL;
}
if (mf.getAlias() == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
if (!result->combinedDateAndTime.reset(mf.orphan())) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
LocalPointer<PluralRules> pr(PluralRules::forLocale(localeId, status));
if (U_FAILURE(status)) {
return NULL;
}
if (!result->pluralRules.reset(pr.orphan())) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
LocalPointer<NumberFormat> nf(
NumberFormat::createInstance(localeId, status));
if (U_FAILURE(status)) {
return NULL;
}
if (!result->numberFormat.reset(nf.orphan())) {
status = U_MEMORY_ALLOCATION_ERROR;
return NULL;
}
return result.orphan();
}
static void U_CALLCONV cacheInit(UErrorCode &status) {
U_ASSERT(gCache == NULL);
ucln_i18n_registerCleanup(UCLN_I18N_RELDATEFMT, reldatefmt_cleanup);
gCache = new SimpleLRUCache(100, &createData, status);
if (U_FAILURE(status)) {
delete gCache;
gCache = NULL;
}
}
static void getFromCache(
const char *locale,
const RelativeDateTimeData *&ptr,
UErrorCode &status) {
umtx_initOnce(gCacheInitOnce, &cacheInit, status);
if (U_FAILURE(status)) {
return;
}
Mutex lock(&gCacheMutex);
gCache->get(locale, ptr, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status)
: ptr(NULL) {
getFromCache(Locale::getDefault().getName(), ptr, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale, UErrorCode& status) : ptr(NULL) {
getFromCache(locale.getName(), ptr, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status)
: ptr(NULL) {
getFromCache(locale.getName(), ptr, status);
if (U_FAILURE(status)) {
return;
}
RelativeDateTimeData* wptr = SharedObject::copyOnWrite(ptr);
if (wptr == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
if (!wptr->numberFormat.reset(nfToAdopt)) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const RelativeDateTimeFormatter& other) : ptr(other.ptr) {
ptr->addRef();
}
RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=(
const RelativeDateTimeFormatter& other) {
if (this != &other) {
SharedObject::copyPtr(other.ptr, ptr);
}
return *this;
}
RelativeDateTimeFormatter::~RelativeDateTimeFormatter() {
if (ptr != NULL) {
ptr->removeRef();
}
}
const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const {
return *ptr->numberFormat;
}
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;
}
FixedDecimal dec(quantity);
const DecimalFormat *decFmt = dynamic_cast<const DecimalFormat *>(
ptr->numberFormat.readOnly());
if (decFmt != NULL) {
dec = decFmt->getFixedDecimal(quantity, status);
}
CharString buffer;
buffer.appendInvariantChars(ptr->pluralRules->select(dec), status);
if (U_FAILURE(status)) {
return appendTo;
}
int32_t pluralIndex = getPluralIndex(buffer.data());
if (pluralIndex == -1) {
pluralIndex = 0;
}
int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
const UnitPattern *pattern = &ptr->quantitativeUnits->data[unit][bFuture][pluralIndex];
if (!pattern->valid) {
pattern = &ptr->quantitativeUnits->data[unit][bFuture][0];
}
return pattern->append(quantity, *ptr->numberFormat, appendTo);
}
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;
}
return appendTo.append(ptr->qualitativeUnits->data[unit][direction]);
}
UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
const UnicodeString& relativeDateString, const UnicodeString& timeString,
UnicodeString& appendTo, UErrorCode& status) const {
Formattable args[2] = {timeString, relativeDateString};
FieldPosition fpos(0);
return ptr->combinedDateAndTime->format(args, 2, appendTo, fpos, status);
}
U_NAMESPACE_END
#endif /* !UCONFIG_NO_FORMATTING */