/* ****************************************************************************** * 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 */