blob: 1832c4428f3b5feaf9e95041cb8dd59e06dc6074 [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 "quantityformatter.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 "ucln_in.h"
#include "mutex.h"
#include "charstr.h"
#include "sharedptr.h"
#include "sharedpluralrules.h"
#include "sharednumberformat.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
// RelativeDateTimeFormatter specific data for a single locale
class RelativeDateTimeCacheData: public SharedObject {
public:
RelativeDateTimeCacheData() : combinedDateAndTime(NULL) { }
virtual ~RelativeDateTimeCacheData();
// no numbers: e.g Next Tuesday; Yesterday; etc.
UnicodeString absoluteUnits[UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT];
// has numbers: e.g Next Tuesday; Yesterday; etc. For second index, 0
// means past e.g 5 days ago; 1 means future e.g in 5 days.
QuantityFormatter relativeUnits[UDAT_RELATIVE_UNIT_COUNT][2];
void adoptCombinedDateAndTime(MessageFormat *mfToAdopt) {
delete combinedDateAndTime;
combinedDateAndTime = mfToAdopt;
}
const MessageFormat *getCombinedDateAndTime() const {
return combinedDateAndTime;
}
private:
MessageFormat *combinedDateAndTime;
RelativeDateTimeCacheData(const RelativeDateTimeCacheData &other);
RelativeDateTimeCacheData& operator=(
const RelativeDateTimeCacheData &other);
};
RelativeDateTimeCacheData::~RelativeDateTimeCacheData() {
delete combinedDateAndTime;
}
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 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 initAbsoluteUnit(
const UResourceBundle *resource,
const UnicodeString &unitName,
UnicodeString *absoluteUnit,
UErrorCode &status) {
getStringWithFallback(
resource,
"-1",
absoluteUnit[UDAT_DIRECTION_LAST],
status);
getStringWithFallback(
resource,
"0",
absoluteUnit[UDAT_DIRECTION_THIS],
status);
getStringWithFallback(
resource,
"1",
absoluteUnit[UDAT_DIRECTION_NEXT],
status);
getOptionalStringWithFallback(
resource,
"-2",
absoluteUnit[UDAT_DIRECTION_LAST_2],
status);
getOptionalStringWithFallback(
resource,
"2",
absoluteUnit[UDAT_DIRECTION_NEXT_2],
status);
absoluteUnit[UDAT_DIRECTION_PLAIN] = unitName;
}
static void initQuantityFormatter(
const UResourceBundle *resource,
QuantityFormatter &formatter,
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;
}
UnicodeString rawPattern;
if (!getString(pluralBundle.getAlias(), rawPattern, status)) {
return;
}
if (!formatter.add(
ures_getKey(pluralBundle.getAlias()),
rawPattern,
status)) {
return;
}
}
}
static void initRelativeUnit(
const UResourceBundle *resource,
QuantityFormatter *relativeUnit,
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;
}
initQuantityFormatter(
futureBundle.getAlias(),
relativeUnit[1],
status);
LocalUResourceBundlePointer pastBundle(ures_getByKeyWithFallback(
topLevel.getAlias(), "past", NULL, &status));
if (U_FAILURE(status)) {
return;
}
initQuantityFormatter(
pastBundle.getAlias(),
relativeUnit[0],
status);
}
static void initRelativeUnit(
const UResourceBundle *resource,
const char *path,
QuantityFormatter *relativeUnit,
UErrorCode &status) {
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(resource, path, NULL, &status));
if (U_FAILURE(status)) {
return;
}
initRelativeUnit(topLevel.getAlias(), relativeUnit, status);
}
static void addTimeUnit(
const UResourceBundle *resource,
const char *path,
QuantityFormatter *relativeUnit,
UnicodeString *absoluteUnit,
UErrorCode &status) {
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(resource, path, NULL, &status));
if (U_FAILURE(status)) {
return;
}
initRelativeUnit(topLevel.getAlias(), relativeUnit, 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;
}
initAbsoluteUnit(
topLevel.getAlias(),
unitName,
absoluteUnit,
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,
UnicodeString absoluteUnits[][UDAT_DIRECTION_COUNT],
UErrorCode &status) {
LocalUResourceBundlePointer topLevel(
ures_getByKeyWithFallback(resource, path, NULL, &status));
if (U_FAILURE(status)) {
return;
}
initAbsoluteUnit(
topLevel.getAlias(),
daysOfWeek[absoluteUnit - UDAT_ABSOLUTE_SUNDAY],
absoluteUnits[absoluteUnit],
status);
}
static UBool loadUnitData(
const UResourceBundle *resource,
RelativeDateTimeCacheData &cacheData,
UErrorCode &status) {
addTimeUnit(
resource,
"fields/day",
cacheData.relativeUnits[UDAT_RELATIVE_DAYS],
cacheData.absoluteUnits[UDAT_ABSOLUTE_DAY],
status);
addTimeUnit(
resource,
"fields/week",
cacheData.relativeUnits[UDAT_RELATIVE_WEEKS],
cacheData.absoluteUnits[UDAT_ABSOLUTE_WEEK],
status);
addTimeUnit(
resource,
"fields/month",
cacheData.relativeUnits[UDAT_RELATIVE_MONTHS],
cacheData.absoluteUnits[UDAT_ABSOLUTE_MONTH],
status);
addTimeUnit(
resource,
"fields/year",
cacheData.relativeUnits[UDAT_RELATIVE_YEARS],
cacheData.absoluteUnits[UDAT_ABSOLUTE_YEAR],
status);
initRelativeUnit(
resource,
"fields/second",
cacheData.relativeUnits[UDAT_RELATIVE_SECONDS],
status);
initRelativeUnit(
resource,
"fields/minute",
cacheData.relativeUnits[UDAT_RELATIVE_MINUTES],
status);
initRelativeUnit(
resource,
"fields/hour",
cacheData.relativeUnits[UDAT_RELATIVE_HOURS],
status);
getStringWithFallback(
resource,
"fields/second/relative/0",
cacheData.absoluteUnits[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,
cacheData.absoluteUnits,
status);
addWeekDay(
resource,
"fields/tue/relative",
daysOfWeek,
UDAT_ABSOLUTE_TUESDAY,
cacheData.absoluteUnits,
status);
addWeekDay(
resource,
"fields/wed/relative",
daysOfWeek,
UDAT_ABSOLUTE_WEDNESDAY,
cacheData.absoluteUnits,
status);
addWeekDay(
resource,
"fields/thu/relative",
daysOfWeek,
UDAT_ABSOLUTE_THURSDAY,
cacheData.absoluteUnits,
status);
addWeekDay(
resource,
"fields/fri/relative",
daysOfWeek,
UDAT_ABSOLUTE_FRIDAY,
cacheData.absoluteUnits,
status);
addWeekDay(
resource,
"fields/sat/relative",
daysOfWeek,
UDAT_ABSOLUTE_SATURDAY,
cacheData.absoluteUnits,
status);
addWeekDay(
resource,
"fields/sun/relative",
daysOfWeek,
UDAT_ABSOLUTE_SUNDAY,
cacheData.absoluteUnits,
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);
}
// Creates RelativeDateTimeFormatter specific data for a given locale
static SharedObject *U_CALLCONV createData(
const char *localeId, UErrorCode &status) {
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,
status)) {
return NULL;
}
UnicodeString dateTimePattern;
if (!getDateTimePattern(topLevel.getAlias(), dateTimePattern, status)) {
return NULL;
}
result->adoptCombinedDateAndTime(
new MessageFormat(dateTimePattern, localeId, status));
if (U_FAILURE(status)) {
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 UBool getFromCache(
const char *locale,
const RelativeDateTimeCacheData *&ptr,
UErrorCode &status) {
umtx_initOnce(gCacheInitOnce, &cacheInit, status);
if (U_FAILURE(status)) {
return FALSE;
}
Mutex lock(&gCacheMutex);
gCache->get(locale, ptr, status);
return U_SUCCESS(status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status)
: cache(NULL), numberFormat(NULL), pluralRules(NULL) {
init(Locale::getDefault(), NULL, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale, UErrorCode& status)
: cache(NULL), numberFormat(NULL), pluralRules(NULL) {
init(locale, NULL, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status)
: cache(NULL), numberFormat(NULL), pluralRules(NULL) {
init(locale, nfToAdopt, status);
}
RelativeDateTimeFormatter::RelativeDateTimeFormatter(
const RelativeDateTimeFormatter& other)
: cache(other.cache),
numberFormat(other.numberFormat),
pluralRules(other.pluralRules) {
cache->addRef();
numberFormat->addRef();
pluralRules->addRef();
}
RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=(
const RelativeDateTimeFormatter& other) {
if (this != &other) {
SharedObject::copyPtr(other.cache, cache);
SharedObject::copyPtr(other.numberFormat, numberFormat);
SharedObject::copyPtr(other.pluralRules, pluralRules);
}
return *this;
}
RelativeDateTimeFormatter::~RelativeDateTimeFormatter() {
if (cache != NULL) {
cache->removeRef();
}
if (numberFormat != NULL) {
numberFormat->removeRef();
}
if (pluralRules != NULL) {
pluralRules->removeRef();
}
}
const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const {
return **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;
}
int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
FieldPosition pos(FieldPosition::DONT_CARE);
return cache->relativeUnits[unit][bFuture].format(
quantity,
**numberFormat,
**pluralRules,
appendTo,
pos,
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;
}
return appendTo.append(cache->absoluteUnits[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 cache->getCombinedDateAndTime()->format(
args, 2, appendTo, fpos, status);
}
void RelativeDateTimeFormatter::init(
const Locale &locale, NumberFormat *nfToAdopt, UErrorCode &status) {
LocalPointer<NumberFormat> nf(nfToAdopt);
if (!getFromCache(locale.getName(), cache, status)) {
return;
}
SharedObject::copyPtr(
PluralRules::createSharedInstance(
locale, UPLURAL_TYPE_CARDINAL, status),
pluralRules);
if (U_FAILURE(status)) {
return;
}
pluralRules->removeRef();
if (nf.isNull()) {
SharedObject::copyPtr(
NumberFormat::createSharedInstance(
locale, UNUM_DECIMAL, status),
numberFormat);
if (U_FAILURE(status)) {
return;
}
numberFormat->removeRef();
} else {
SharedNumberFormat *shared = new SharedNumberFormat(nf.getAlias());
if (shared == NULL) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
nf.orphan();
SharedObject::copyPtr(shared, numberFormat);
}
}
U_NAMESPACE_END
#endif /* !UCONFIG_NO_FORMATTING */