1/*
2******************************************************************************
3* Copyright (C) 2014, International Business Machines Corporation and
4* others. All Rights Reserved.
5******************************************************************************
6*
7* File RELDATEFMT.CPP
8******************************************************************************
9*/
10
11#include "unicode/reldatefmt.h"
12
13#if !UCONFIG_NO_FORMATTING
14
15#include "unicode/localpointer.h"
16#include "quantityformatter.h"
17#include "unicode/plurrule.h"
18#include "unicode/msgfmt.h"
19#include "unicode/decimfmt.h"
20#include "unicode/numfmt.h"
21#include "lrucache.h"
22#include "uresimp.h"
23#include "unicode/ures.h"
24#include "cstring.h"
25#include "ucln_in.h"
26#include "mutex.h"
27#include "charstr.h"
28
29#include "sharedptr.h"
30#include "sharedpluralrules.h"
31#include "sharednumberformat.h"
32
33// Copied from uscript_props.cpp
34#define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof((array)[0]))
35
36static icu::LRUCache *gCache = NULL;
37static UMutex gCacheMutex = U_MUTEX_INITIALIZER;
38static icu::UInitOnce gCacheInitOnce = U_INITONCE_INITIALIZER;
39
40U_CDECL_BEGIN
41static UBool U_CALLCONV reldatefmt_cleanup() {
42    gCacheInitOnce.reset();
43    if (gCache) {
44        delete gCache;
45        gCache = NULL;
46    }
47    return TRUE;
48}
49U_CDECL_END
50
51U_NAMESPACE_BEGIN
52
53// RelativeDateTimeFormatter specific data for a single locale
54class RelativeDateTimeCacheData: public SharedObject {
55public:
56    RelativeDateTimeCacheData() : combinedDateAndTime(NULL) { }
57    virtual ~RelativeDateTimeCacheData();
58
59    // no numbers: e.g Next Tuesday; Yesterday; etc.
60    UnicodeString absoluteUnits[UDAT_ABSOLUTE_UNIT_COUNT][UDAT_DIRECTION_COUNT];
61
62    // has numbers: e.g Next Tuesday; Yesterday; etc. For second index, 0
63    // means past e.g 5 days ago; 1 means future e.g in 5 days.
64    QuantityFormatter relativeUnits[UDAT_RELATIVE_UNIT_COUNT][2];
65
66    void adoptCombinedDateAndTime(MessageFormat *mfToAdopt) {
67        delete combinedDateAndTime;
68        combinedDateAndTime = mfToAdopt;
69    }
70    const MessageFormat *getCombinedDateAndTime() const {
71        return combinedDateAndTime;
72    }
73private:
74    MessageFormat *combinedDateAndTime;
75    RelativeDateTimeCacheData(const RelativeDateTimeCacheData &other);
76    RelativeDateTimeCacheData& operator=(
77            const RelativeDateTimeCacheData &other);
78};
79
80RelativeDateTimeCacheData::~RelativeDateTimeCacheData() {
81    delete combinedDateAndTime;
82}
83
84static UBool getStringWithFallback(
85        const UResourceBundle *resource,
86        const char *key,
87        UnicodeString &result,
88        UErrorCode &status) {
89    int32_t len = 0;
90    const UChar *resStr = ures_getStringByKeyWithFallback(
91        resource, key, &len, &status);
92    if (U_FAILURE(status)) {
93        return FALSE;
94    }
95    result.setTo(TRUE, resStr, len);
96    return TRUE;
97}
98
99static UBool getOptionalStringWithFallback(
100        const UResourceBundle *resource,
101        const char *key,
102        UnicodeString &result,
103        UErrorCode &status) {
104    if (U_FAILURE(status)) {
105        return FALSE;
106    }
107    int32_t len = 0;
108    const UChar *resStr = ures_getStringByKey(
109        resource, key, &len, &status);
110    if (status == U_MISSING_RESOURCE_ERROR) {
111        result.remove();
112        status = U_ZERO_ERROR;
113        return TRUE;
114    }
115    if (U_FAILURE(status)) {
116        return FALSE;
117    }
118    result.setTo(TRUE, resStr, len);
119    return TRUE;
120}
121
122static UBool getString(
123        const UResourceBundle *resource,
124        UnicodeString &result,
125        UErrorCode &status) {
126    int32_t len = 0;
127    const UChar *resStr = ures_getString(resource, &len, &status);
128    if (U_FAILURE(status)) {
129        return FALSE;
130    }
131    result.setTo(TRUE, resStr, len);
132    return TRUE;
133}
134
135static UBool getStringByIndex(
136        const UResourceBundle *resource,
137        int32_t idx,
138        UnicodeString &result,
139        UErrorCode &status) {
140    int32_t len = 0;
141    const UChar *resStr = ures_getStringByIndex(
142            resource, idx, &len, &status);
143    if (U_FAILURE(status)) {
144        return FALSE;
145    }
146    result.setTo(TRUE, resStr, len);
147    return TRUE;
148}
149
150static void initAbsoluteUnit(
151            const UResourceBundle *resource,
152            const UnicodeString &unitName,
153            UnicodeString *absoluteUnit,
154            UErrorCode &status) {
155    getStringWithFallback(
156            resource,
157            "-1",
158            absoluteUnit[UDAT_DIRECTION_LAST],
159            status);
160    getStringWithFallback(
161            resource,
162            "0",
163            absoluteUnit[UDAT_DIRECTION_THIS],
164            status);
165    getStringWithFallback(
166            resource,
167            "1",
168            absoluteUnit[UDAT_DIRECTION_NEXT],
169            status);
170    getOptionalStringWithFallback(
171            resource,
172            "-2",
173            absoluteUnit[UDAT_DIRECTION_LAST_2],
174            status);
175    getOptionalStringWithFallback(
176            resource,
177            "2",
178            absoluteUnit[UDAT_DIRECTION_NEXT_2],
179            status);
180    absoluteUnit[UDAT_DIRECTION_PLAIN] = unitName;
181}
182
183static void initQuantityFormatter(
184        const UResourceBundle *resource,
185        QuantityFormatter &formatter,
186        UErrorCode &status) {
187    if (U_FAILURE(status)) {
188        return;
189    }
190    int32_t size = ures_getSize(resource);
191    for (int32_t i = 0; i < size; ++i) {
192        LocalUResourceBundlePointer pluralBundle(
193                ures_getByIndex(resource, i, NULL, &status));
194        if (U_FAILURE(status)) {
195            return;
196        }
197        UnicodeString rawPattern;
198        if (!getString(pluralBundle.getAlias(), rawPattern, status)) {
199            return;
200        }
201        if (!formatter.add(
202                ures_getKey(pluralBundle.getAlias()),
203                rawPattern,
204                status)) {
205            return;
206        }
207    }
208}
209
210static void initRelativeUnit(
211        const UResourceBundle *resource,
212        QuantityFormatter *relativeUnit,
213        UErrorCode &status) {
214    LocalUResourceBundlePointer topLevel(
215            ures_getByKeyWithFallback(
216                    resource, "relativeTime", NULL, &status));
217    if (U_FAILURE(status)) {
218        return;
219    }
220    LocalUResourceBundlePointer futureBundle(ures_getByKeyWithFallback(
221            topLevel.getAlias(), "future", NULL, &status));
222    if (U_FAILURE(status)) {
223        return;
224    }
225    initQuantityFormatter(
226            futureBundle.getAlias(),
227            relativeUnit[1],
228            status);
229    LocalUResourceBundlePointer pastBundle(ures_getByKeyWithFallback(
230            topLevel.getAlias(), "past", NULL, &status));
231    if (U_FAILURE(status)) {
232        return;
233    }
234    initQuantityFormatter(
235            pastBundle.getAlias(),
236            relativeUnit[0],
237            status);
238}
239
240static void initRelativeUnit(
241        const UResourceBundle *resource,
242        const char *path,
243        QuantityFormatter *relativeUnit,
244        UErrorCode &status) {
245    LocalUResourceBundlePointer topLevel(
246            ures_getByKeyWithFallback(resource, path, NULL, &status));
247    if (U_FAILURE(status)) {
248        return;
249    }
250    initRelativeUnit(topLevel.getAlias(), relativeUnit, status);
251}
252
253static void addTimeUnit(
254        const UResourceBundle *resource,
255        const char *path,
256        QuantityFormatter *relativeUnit,
257        UnicodeString *absoluteUnit,
258        UErrorCode &status) {
259    LocalUResourceBundlePointer topLevel(
260            ures_getByKeyWithFallback(resource, path, NULL, &status));
261    if (U_FAILURE(status)) {
262        return;
263    }
264    initRelativeUnit(topLevel.getAlias(), relativeUnit, status);
265    UnicodeString unitName;
266    if (!getStringWithFallback(topLevel.getAlias(), "dn", unitName, status)) {
267        return;
268    }
269    // TODO(Travis Keep): This is a hack to get around CLDR bug 6818.
270    const char *localeId = ures_getLocaleByType(
271            topLevel.getAlias(), ULOC_ACTUAL_LOCALE, &status);
272    if (U_FAILURE(status)) {
273        return;
274    }
275    Locale locale(localeId);
276    if (uprv_strcmp("en", locale.getLanguage()) == 0) {
277         unitName.toLower();
278    }
279    // end hack
280    ures_getByKeyWithFallback(
281            topLevel.getAlias(), "relative", topLevel.getAlias(), &status);
282    if (U_FAILURE(status)) {
283        return;
284    }
285    initAbsoluteUnit(
286            topLevel.getAlias(),
287            unitName,
288            absoluteUnit,
289            status);
290}
291
292static void readDaysOfWeek(
293        const UResourceBundle *resource,
294        const char *path,
295        UnicodeString *daysOfWeek,
296        UErrorCode &status) {
297    LocalUResourceBundlePointer topLevel(
298            ures_getByKeyWithFallback(resource, path, NULL, &status));
299    if (U_FAILURE(status)) {
300        return;
301    }
302    int32_t size = ures_getSize(topLevel.getAlias());
303    if (size != 7) {
304        status = U_INTERNAL_PROGRAM_ERROR;
305        return;
306    }
307    for (int32_t i = 0; i < size; ++i) {
308        if (!getStringByIndex(topLevel.getAlias(), i, daysOfWeek[i], status)) {
309            return;
310        }
311    }
312}
313
314static void addWeekDay(
315        const UResourceBundle *resource,
316        const char *path,
317        const UnicodeString *daysOfWeek,
318        UDateAbsoluteUnit absoluteUnit,
319        UnicodeString absoluteUnits[][UDAT_DIRECTION_COUNT],
320        UErrorCode &status) {
321    LocalUResourceBundlePointer topLevel(
322            ures_getByKeyWithFallback(resource, path, NULL, &status));
323    if (U_FAILURE(status)) {
324        return;
325    }
326    initAbsoluteUnit(
327            topLevel.getAlias(),
328            daysOfWeek[absoluteUnit - UDAT_ABSOLUTE_SUNDAY],
329            absoluteUnits[absoluteUnit],
330            status);
331}
332
333static UBool loadUnitData(
334        const UResourceBundle *resource,
335        RelativeDateTimeCacheData &cacheData,
336        UErrorCode &status) {
337    addTimeUnit(
338            resource,
339            "fields/day",
340            cacheData.relativeUnits[UDAT_RELATIVE_DAYS],
341            cacheData.absoluteUnits[UDAT_ABSOLUTE_DAY],
342            status);
343    addTimeUnit(
344            resource,
345            "fields/week",
346            cacheData.relativeUnits[UDAT_RELATIVE_WEEKS],
347            cacheData.absoluteUnits[UDAT_ABSOLUTE_WEEK],
348            status);
349    addTimeUnit(
350            resource,
351            "fields/month",
352            cacheData.relativeUnits[UDAT_RELATIVE_MONTHS],
353            cacheData.absoluteUnits[UDAT_ABSOLUTE_MONTH],
354            status);
355    addTimeUnit(
356            resource,
357            "fields/year",
358            cacheData.relativeUnits[UDAT_RELATIVE_YEARS],
359            cacheData.absoluteUnits[UDAT_ABSOLUTE_YEAR],
360            status);
361    initRelativeUnit(
362            resource,
363            "fields/second",
364            cacheData.relativeUnits[UDAT_RELATIVE_SECONDS],
365            status);
366    initRelativeUnit(
367            resource,
368            "fields/minute",
369            cacheData.relativeUnits[UDAT_RELATIVE_MINUTES],
370            status);
371    initRelativeUnit(
372            resource,
373            "fields/hour",
374            cacheData.relativeUnits[UDAT_RELATIVE_HOURS],
375            status);
376    getStringWithFallback(
377            resource,
378            "fields/second/relative/0",
379            cacheData.absoluteUnits[UDAT_ABSOLUTE_NOW][UDAT_DIRECTION_PLAIN],
380            status);
381    UnicodeString daysOfWeek[7];
382    readDaysOfWeek(
383            resource,
384            "calendar/gregorian/dayNames/stand-alone/wide",
385            daysOfWeek,
386            status);
387    addWeekDay(
388            resource,
389            "fields/mon/relative",
390            daysOfWeek,
391            UDAT_ABSOLUTE_MONDAY,
392            cacheData.absoluteUnits,
393            status);
394    addWeekDay(
395            resource,
396            "fields/tue/relative",
397            daysOfWeek,
398            UDAT_ABSOLUTE_TUESDAY,
399            cacheData.absoluteUnits,
400            status);
401    addWeekDay(
402            resource,
403            "fields/wed/relative",
404            daysOfWeek,
405            UDAT_ABSOLUTE_WEDNESDAY,
406            cacheData.absoluteUnits,
407            status);
408    addWeekDay(
409            resource,
410            "fields/thu/relative",
411            daysOfWeek,
412            UDAT_ABSOLUTE_THURSDAY,
413            cacheData.absoluteUnits,
414            status);
415    addWeekDay(
416            resource,
417            "fields/fri/relative",
418            daysOfWeek,
419            UDAT_ABSOLUTE_FRIDAY,
420            cacheData.absoluteUnits,
421            status);
422    addWeekDay(
423            resource,
424            "fields/sat/relative",
425            daysOfWeek,
426            UDAT_ABSOLUTE_SATURDAY,
427            cacheData.absoluteUnits,
428            status);
429    addWeekDay(
430            resource,
431            "fields/sun/relative",
432            daysOfWeek,
433            UDAT_ABSOLUTE_SUNDAY,
434            cacheData.absoluteUnits,
435            status);
436    return U_SUCCESS(status);
437}
438
439static UBool getDateTimePattern(
440        const UResourceBundle *resource,
441        UnicodeString &result,
442        UErrorCode &status) {
443    UnicodeString defaultCalendarName;
444    if (!getStringWithFallback(
445            resource,
446            "calendar/default",
447            defaultCalendarName,
448            status)) {
449        return FALSE;
450    }
451    CharString pathBuffer;
452    pathBuffer.append("calendar/", status)
453            .appendInvariantChars(defaultCalendarName, status)
454            .append("/DateTimePatterns", status);
455    LocalUResourceBundlePointer topLevel(
456            ures_getByKeyWithFallback(
457                    resource, pathBuffer.data(), NULL, &status));
458    if (U_FAILURE(status)) {
459        return FALSE;
460    }
461    int32_t size = ures_getSize(topLevel.getAlias());
462    if (size <= 8) {
463        // Oops, size is to small to access the index that we want, fallback
464        // to a hard-coded value.
465        result = UNICODE_STRING_SIMPLE("{1} {0}");
466        return TRUE;
467    }
468    return getStringByIndex(topLevel.getAlias(), 8, result, status);
469}
470
471// Creates RelativeDateTimeFormatter specific data for a given locale
472static SharedObject *U_CALLCONV createData(
473        const char *localeId, UErrorCode &status) {
474    LocalUResourceBundlePointer topLevel(ures_open(NULL, localeId, &status));
475    if (U_FAILURE(status)) {
476        return NULL;
477    }
478    LocalPointer<RelativeDateTimeCacheData> result(
479            new RelativeDateTimeCacheData());
480    if (result.isNull()) {
481        status = U_MEMORY_ALLOCATION_ERROR;
482        return NULL;
483    }
484    if (!loadUnitData(
485            topLevel.getAlias(),
486            *result,
487            status)) {
488        return NULL;
489    }
490    UnicodeString dateTimePattern;
491    if (!getDateTimePattern(topLevel.getAlias(), dateTimePattern, status)) {
492        return NULL;
493    }
494    result->adoptCombinedDateAndTime(
495            new MessageFormat(dateTimePattern, localeId, status));
496    if (U_FAILURE(status)) {
497        return NULL;
498    }
499    return result.orphan();
500}
501
502static void U_CALLCONV cacheInit(UErrorCode &status) {
503    U_ASSERT(gCache == NULL);
504    ucln_i18n_registerCleanup(UCLN_I18N_RELDATEFMT, reldatefmt_cleanup);
505    gCache = new SimpleLRUCache(100, &createData, status);
506    if (U_FAILURE(status)) {
507        delete gCache;
508        gCache = NULL;
509    }
510}
511
512static UBool getFromCache(
513        const char *locale,
514        const RelativeDateTimeCacheData *&ptr,
515        UErrorCode &status) {
516    umtx_initOnce(gCacheInitOnce, &cacheInit, status);
517    if (U_FAILURE(status)) {
518        return FALSE;
519    }
520    Mutex lock(&gCacheMutex);
521    gCache->get(locale, ptr, status);
522    return U_SUCCESS(status);
523}
524
525RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode& status)
526        : cache(NULL), numberFormat(NULL), pluralRules(NULL) {
527    init(Locale::getDefault(), NULL, status);
528}
529
530RelativeDateTimeFormatter::RelativeDateTimeFormatter(
531        const Locale& locale, UErrorCode& status)
532        : cache(NULL), numberFormat(NULL), pluralRules(NULL) {
533    init(locale, NULL, status);
534}
535
536RelativeDateTimeFormatter::RelativeDateTimeFormatter(
537        const Locale& locale, NumberFormat *nfToAdopt, UErrorCode& status)
538        : cache(NULL), numberFormat(NULL), pluralRules(NULL) {
539    init(locale, nfToAdopt, status);
540}
541
542RelativeDateTimeFormatter::RelativeDateTimeFormatter(
543        const RelativeDateTimeFormatter& other)
544        : cache(other.cache),
545          numberFormat(other.numberFormat),
546          pluralRules(other.pluralRules) {
547    cache->addRef();
548    numberFormat->addRef();
549    pluralRules->addRef();
550}
551
552RelativeDateTimeFormatter& RelativeDateTimeFormatter::operator=(
553        const RelativeDateTimeFormatter& other) {
554    if (this != &other) {
555        SharedObject::copyPtr(other.cache, cache);
556        SharedObject::copyPtr(other.numberFormat, numberFormat);
557        SharedObject::copyPtr(other.pluralRules, pluralRules);
558    }
559    return *this;
560}
561
562RelativeDateTimeFormatter::~RelativeDateTimeFormatter() {
563    if (cache != NULL) {
564        cache->removeRef();
565    }
566    if (numberFormat != NULL) {
567        numberFormat->removeRef();
568    }
569    if (pluralRules != NULL) {
570        pluralRules->removeRef();
571    }
572}
573
574const NumberFormat& RelativeDateTimeFormatter::getNumberFormat() const {
575    return **numberFormat;
576}
577
578UnicodeString& RelativeDateTimeFormatter::format(
579        double quantity, UDateDirection direction, UDateRelativeUnit unit,
580        UnicodeString& appendTo, UErrorCode& status) const {
581    if (U_FAILURE(status)) {
582        return appendTo;
583    }
584    if (direction != UDAT_DIRECTION_LAST && direction != UDAT_DIRECTION_NEXT) {
585        status = U_ILLEGAL_ARGUMENT_ERROR;
586        return appendTo;
587    }
588    int32_t bFuture = direction == UDAT_DIRECTION_NEXT ? 1 : 0;
589    FieldPosition pos(FieldPosition::DONT_CARE);
590    return cache->relativeUnits[unit][bFuture].format(
591            quantity,
592            **numberFormat,
593            **pluralRules,
594            appendTo,
595            pos,
596            status);
597}
598
599UnicodeString& RelativeDateTimeFormatter::format(
600        UDateDirection direction, UDateAbsoluteUnit unit,
601        UnicodeString& appendTo, UErrorCode& status) const {
602    if (U_FAILURE(status)) {
603        return appendTo;
604    }
605    if (unit == UDAT_ABSOLUTE_NOW && direction != UDAT_DIRECTION_PLAIN) {
606        status = U_ILLEGAL_ARGUMENT_ERROR;
607        return appendTo;
608    }
609    return appendTo.append(cache->absoluteUnits[unit][direction]);
610}
611
612UnicodeString& RelativeDateTimeFormatter::combineDateAndTime(
613    const UnicodeString& relativeDateString, const UnicodeString& timeString,
614    UnicodeString& appendTo, UErrorCode& status) const {
615    Formattable args[2] = {timeString, relativeDateString};
616    FieldPosition fpos(0);
617    return cache->getCombinedDateAndTime()->format(
618            args, 2, appendTo, fpos, status);
619}
620
621void RelativeDateTimeFormatter::init(
622        const Locale &locale, NumberFormat *nfToAdopt, UErrorCode &status) {
623    LocalPointer<NumberFormat> nf(nfToAdopt);
624    if (!getFromCache(locale.getName(), cache, status)) {
625        return;
626    }
627    SharedObject::copyPtr(
628            PluralRules::createSharedInstance(
629                    locale, UPLURAL_TYPE_CARDINAL, status),
630            pluralRules);
631    if (U_FAILURE(status)) {
632        return;
633    }
634    pluralRules->removeRef();
635    if (nf.isNull()) {
636       SharedObject::copyPtr(
637               NumberFormat::createSharedInstance(
638                       locale, UNUM_DECIMAL, status),
639               numberFormat);
640        if (U_FAILURE(status)) {
641            return;
642        }
643        numberFormat->removeRef();
644    } else {
645        SharedNumberFormat *shared = new SharedNumberFormat(nf.getAlias());
646        if (shared == NULL) {
647            status = U_MEMORY_ALLOCATION_ERROR;
648            return;
649        }
650        nf.orphan();
651        SharedObject::copyPtr(shared, numberFormat);
652    }
653}
654
655
656U_NAMESPACE_END
657
658#endif /* !UCONFIG_NO_FORMATTING */
659
660