2 ******************************************************************************
3 * Copyright (C) 2014, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 ******************************************************************************
8 ******************************************************************************
11 #include "unicode/reldatefmt.h"
13 #if !UCONFIG_NO_FORMATTING
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"
23 #include "unicode/ures.h"
29 #include "sharedptr.h"
30 #include "sharedpluralrules.h"
31 #include "sharednumberformat.h"
33 // Copied from uscript_props.cpp
34 #define LENGTHOF(array) (int32_t)(sizeof(array)/sizeof((array)[0]))
36 static icu::LRUCache
*gCache
= NULL
;
37 static UMutex gCacheMutex
= U_MUTEX_INITIALIZER
;
38 static icu::UInitOnce gCacheInitOnce
= U_INITONCE_INITIALIZER
;
41 static UBool U_CALLCONV
reldatefmt_cleanup() {
42 gCacheInitOnce
.reset();
53 // RelativeDateTimeFormatter specific data for a single locale
54 class RelativeDateTimeCacheData
: public SharedObject
{
56 RelativeDateTimeCacheData() : combinedDateAndTime(NULL
) { }
57 virtual ~RelativeDateTimeCacheData();
59 // no numbers: e.g Next Tuesday; Yesterday; etc.
60 UnicodeString absoluteUnits
[UDAT_ABSOLUTE_UNIT_COUNT
][UDAT_DIRECTION_COUNT
];
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];
66 void adoptCombinedDateAndTime(MessageFormat
*mfToAdopt
) {
67 delete combinedDateAndTime
;
68 combinedDateAndTime
= mfToAdopt
;
70 const MessageFormat
*getCombinedDateAndTime() const {
71 return combinedDateAndTime
;
74 MessageFormat
*combinedDateAndTime
;
75 RelativeDateTimeCacheData(const RelativeDateTimeCacheData
&other
);
76 RelativeDateTimeCacheData
& operator=(
77 const RelativeDateTimeCacheData
&other
);
80 RelativeDateTimeCacheData::~RelativeDateTimeCacheData() {
81 delete combinedDateAndTime
;
84 static UBool
getStringWithFallback(
85 const UResourceBundle
*resource
,
87 UnicodeString
&result
,
90 const UChar
*resStr
= ures_getStringByKeyWithFallback(
91 resource
, key
, &len
, &status
);
92 if (U_FAILURE(status
)) {
95 result
.setTo(TRUE
, resStr
, len
);
99 static UBool
getOptionalStringWithFallback(
100 const UResourceBundle
*resource
,
102 UnicodeString
&result
,
103 UErrorCode
&status
) {
104 if (U_FAILURE(status
)) {
108 const UChar
*resStr
= ures_getStringByKey(
109 resource
, key
, &len
, &status
);
110 if (status
== U_MISSING_RESOURCE_ERROR
) {
112 status
= U_ZERO_ERROR
;
115 if (U_FAILURE(status
)) {
118 result
.setTo(TRUE
, resStr
, len
);
122 static UBool
getString(
123 const UResourceBundle
*resource
,
124 UnicodeString
&result
,
125 UErrorCode
&status
) {
127 const UChar
*resStr
= ures_getString(resource
, &len
, &status
);
128 if (U_FAILURE(status
)) {
131 result
.setTo(TRUE
, resStr
, len
);
135 static UBool
getStringByIndex(
136 const UResourceBundle
*resource
,
138 UnicodeString
&result
,
139 UErrorCode
&status
) {
141 const UChar
*resStr
= ures_getStringByIndex(
142 resource
, idx
, &len
, &status
);
143 if (U_FAILURE(status
)) {
146 result
.setTo(TRUE
, resStr
, len
);
150 static void initAbsoluteUnit(
151 const UResourceBundle
*resource
,
152 const UnicodeString
&unitName
,
153 UnicodeString
*absoluteUnit
,
154 UErrorCode
&status
) {
155 getStringWithFallback(
158 absoluteUnit
[UDAT_DIRECTION_LAST
],
160 getStringWithFallback(
163 absoluteUnit
[UDAT_DIRECTION_THIS
],
165 getStringWithFallback(
168 absoluteUnit
[UDAT_DIRECTION_NEXT
],
170 getOptionalStringWithFallback(
173 absoluteUnit
[UDAT_DIRECTION_LAST_2
],
175 getOptionalStringWithFallback(
178 absoluteUnit
[UDAT_DIRECTION_NEXT_2
],
180 absoluteUnit
[UDAT_DIRECTION_PLAIN
] = unitName
;
183 static void initQuantityFormatter(
184 const UResourceBundle
*resource
,
185 QuantityFormatter
&formatter
,
186 UErrorCode
&status
) {
187 if (U_FAILURE(status
)) {
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
)) {
197 UnicodeString rawPattern
;
198 if (!getString(pluralBundle
.getAlias(), rawPattern
, status
)) {
202 ures_getKey(pluralBundle
.getAlias()),
210 static 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
)) {
220 LocalUResourceBundlePointer
futureBundle(ures_getByKeyWithFallback(
221 topLevel
.getAlias(), "future", NULL
, &status
));
222 if (U_FAILURE(status
)) {
225 initQuantityFormatter(
226 futureBundle
.getAlias(),
229 LocalUResourceBundlePointer
pastBundle(ures_getByKeyWithFallback(
230 topLevel
.getAlias(), "past", NULL
, &status
));
231 if (U_FAILURE(status
)) {
234 initQuantityFormatter(
235 pastBundle
.getAlias(),
240 static void initRelativeUnit(
241 const UResourceBundle
*resource
,
243 QuantityFormatter
*relativeUnit
,
244 UErrorCode
&status
) {
245 LocalUResourceBundlePointer
topLevel(
246 ures_getByKeyWithFallback(resource
, path
, NULL
, &status
));
247 if (U_FAILURE(status
)) {
250 initRelativeUnit(topLevel
.getAlias(), relativeUnit
, status
);
253 static void addTimeUnit(
254 const UResourceBundle
*resource
,
256 QuantityFormatter
*relativeUnit
,
257 UnicodeString
*absoluteUnit
,
258 UErrorCode
&status
) {
259 LocalUResourceBundlePointer
topLevel(
260 ures_getByKeyWithFallback(resource
, path
, NULL
, &status
));
261 if (U_FAILURE(status
)) {
264 initRelativeUnit(topLevel
.getAlias(), relativeUnit
, status
);
265 UnicodeString unitName
;
266 if (!getStringWithFallback(topLevel
.getAlias(), "dn", unitName
, status
)) {
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
)) {
275 Locale
locale(localeId
);
276 if (uprv_strcmp("en", locale
.getLanguage()) == 0) {
280 ures_getByKeyWithFallback(
281 topLevel
.getAlias(), "relative", topLevel
.getAlias(), &status
);
282 if (U_FAILURE(status
)) {
292 static void readDaysOfWeek(
293 const UResourceBundle
*resource
,
295 UnicodeString
*daysOfWeek
,
296 UErrorCode
&status
) {
297 LocalUResourceBundlePointer
topLevel(
298 ures_getByKeyWithFallback(resource
, path
, NULL
, &status
));
299 if (U_FAILURE(status
)) {
302 int32_t size
= ures_getSize(topLevel
.getAlias());
304 status
= U_INTERNAL_PROGRAM_ERROR
;
307 for (int32_t i
= 0; i
< size
; ++i
) {
308 if (!getStringByIndex(topLevel
.getAlias(), i
, daysOfWeek
[i
], status
)) {
314 static void addWeekDay(
315 const UResourceBundle
*resource
,
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
)) {
328 daysOfWeek
[absoluteUnit
- UDAT_ABSOLUTE_SUNDAY
],
329 absoluteUnits
[absoluteUnit
],
333 static UBool
loadUnitData(
334 const UResourceBundle
*resource
,
335 RelativeDateTimeCacheData
&cacheData
,
336 UErrorCode
&status
) {
340 cacheData
.relativeUnits
[UDAT_RELATIVE_DAYS
],
341 cacheData
.absoluteUnits
[UDAT_ABSOLUTE_DAY
],
346 cacheData
.relativeUnits
[UDAT_RELATIVE_WEEKS
],
347 cacheData
.absoluteUnits
[UDAT_ABSOLUTE_WEEK
],
352 cacheData
.relativeUnits
[UDAT_RELATIVE_MONTHS
],
353 cacheData
.absoluteUnits
[UDAT_ABSOLUTE_MONTH
],
358 cacheData
.relativeUnits
[UDAT_RELATIVE_YEARS
],
359 cacheData
.absoluteUnits
[UDAT_ABSOLUTE_YEAR
],
364 cacheData
.relativeUnits
[UDAT_RELATIVE_SECONDS
],
369 cacheData
.relativeUnits
[UDAT_RELATIVE_MINUTES
],
374 cacheData
.relativeUnits
[UDAT_RELATIVE_HOURS
],
376 getStringWithFallback(
378 "fields/second/relative/0",
379 cacheData
.absoluteUnits
[UDAT_ABSOLUTE_NOW
][UDAT_DIRECTION_PLAIN
],
381 UnicodeString daysOfWeek
[7];
384 "calendar/gregorian/dayNames/stand-alone/wide",
389 "fields/mon/relative",
391 UDAT_ABSOLUTE_MONDAY
,
392 cacheData
.absoluteUnits
,
396 "fields/tue/relative",
398 UDAT_ABSOLUTE_TUESDAY
,
399 cacheData
.absoluteUnits
,
403 "fields/wed/relative",
405 UDAT_ABSOLUTE_WEDNESDAY
,
406 cacheData
.absoluteUnits
,
410 "fields/thu/relative",
412 UDAT_ABSOLUTE_THURSDAY
,
413 cacheData
.absoluteUnits
,
417 "fields/fri/relative",
419 UDAT_ABSOLUTE_FRIDAY
,
420 cacheData
.absoluteUnits
,
424 "fields/sat/relative",
426 UDAT_ABSOLUTE_SATURDAY
,
427 cacheData
.absoluteUnits
,
431 "fields/sun/relative",
433 UDAT_ABSOLUTE_SUNDAY
,
434 cacheData
.absoluteUnits
,
436 return U_SUCCESS(status
);
439 static UBool
getDateTimePattern(
440 const UResourceBundle
*resource
,
441 UnicodeString
&result
,
442 UErrorCode
&status
) {
443 UnicodeString defaultCalendarName
;
444 if (!getStringWithFallback(
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
)) {
461 int32_t size
= ures_getSize(topLevel
.getAlias());
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}");
468 return getStringByIndex(topLevel
.getAlias(), 8, result
, status
);
471 // Creates RelativeDateTimeFormatter specific data for a given locale
472 static SharedObject
*U_CALLCONV
createData(
473 const char *localeId
, UErrorCode
&status
) {
474 LocalUResourceBundlePointer
topLevel(ures_open(NULL
, localeId
, &status
));
475 if (U_FAILURE(status
)) {
478 LocalPointer
<RelativeDateTimeCacheData
> result(
479 new RelativeDateTimeCacheData());
480 if (result
.isNull()) {
481 status
= U_MEMORY_ALLOCATION_ERROR
;
490 UnicodeString dateTimePattern
;
491 if (!getDateTimePattern(topLevel
.getAlias(), dateTimePattern
, status
)) {
494 result
->adoptCombinedDateAndTime(
495 new MessageFormat(dateTimePattern
, localeId
, status
));
496 if (U_FAILURE(status
)) {
499 return result
.orphan();
502 static 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
)) {
512 static UBool
getFromCache(
514 const RelativeDateTimeCacheData
*&ptr
,
515 UErrorCode
&status
) {
516 umtx_initOnce(gCacheInitOnce
, &cacheInit
, status
);
517 if (U_FAILURE(status
)) {
520 Mutex
lock(&gCacheMutex
);
521 gCache
->get(locale
, ptr
, status
);
522 return U_SUCCESS(status
);
525 RelativeDateTimeFormatter::RelativeDateTimeFormatter(UErrorCode
& status
)
526 : cache(NULL
), numberFormat(NULL
), pluralRules(NULL
) {
527 init(Locale::getDefault(), NULL
, status
);
530 RelativeDateTimeFormatter::RelativeDateTimeFormatter(
531 const Locale
& locale
, UErrorCode
& status
)
532 : cache(NULL
), numberFormat(NULL
), pluralRules(NULL
) {
533 init(locale
, NULL
, status
);
536 RelativeDateTimeFormatter::RelativeDateTimeFormatter(
537 const Locale
& locale
, NumberFormat
*nfToAdopt
, UErrorCode
& status
)
538 : cache(NULL
), numberFormat(NULL
), pluralRules(NULL
) {
539 init(locale
, nfToAdopt
, status
);
542 RelativeDateTimeFormatter::RelativeDateTimeFormatter(
543 const RelativeDateTimeFormatter
& other
)
544 : cache(other
.cache
),
545 numberFormat(other
.numberFormat
),
546 pluralRules(other
.pluralRules
) {
548 numberFormat
->addRef();
549 pluralRules
->addRef();
552 RelativeDateTimeFormatter
& 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
);
562 RelativeDateTimeFormatter::~RelativeDateTimeFormatter() {
566 if (numberFormat
!= NULL
) {
567 numberFormat
->removeRef();
569 if (pluralRules
!= NULL
) {
570 pluralRules
->removeRef();
574 const NumberFormat
& RelativeDateTimeFormatter::getNumberFormat() const {
575 return **numberFormat
;
578 UnicodeString
& RelativeDateTimeFormatter::format(
579 double quantity
, UDateDirection direction
, UDateRelativeUnit unit
,
580 UnicodeString
& appendTo
, UErrorCode
& status
) const {
581 if (U_FAILURE(status
)) {
584 if (direction
!= UDAT_DIRECTION_LAST
&& direction
!= UDAT_DIRECTION_NEXT
) {
585 status
= U_ILLEGAL_ARGUMENT_ERROR
;
588 int32_t bFuture
= direction
== UDAT_DIRECTION_NEXT
? 1 : 0;
589 FieldPosition
pos(FieldPosition::DONT_CARE
);
590 return cache
->relativeUnits
[unit
][bFuture
].format(
599 UnicodeString
& RelativeDateTimeFormatter::format(
600 UDateDirection direction
, UDateAbsoluteUnit unit
,
601 UnicodeString
& appendTo
, UErrorCode
& status
) const {
602 if (U_FAILURE(status
)) {
605 if (unit
== UDAT_ABSOLUTE_NOW
&& direction
!= UDAT_DIRECTION_PLAIN
) {
606 status
= U_ILLEGAL_ARGUMENT_ERROR
;
609 return appendTo
.append(cache
->absoluteUnits
[unit
][direction
]);
612 UnicodeString
& 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
);
621 void RelativeDateTimeFormatter::init(
622 const Locale
&locale
, NumberFormat
*nfToAdopt
, UErrorCode
&status
) {
623 LocalPointer
<NumberFormat
> nf(nfToAdopt
);
624 if (!getFromCache(locale
.getName(), cache
, status
)) {
627 SharedObject::copyPtr(
628 PluralRules::createSharedInstance(
629 locale
, UPLURAL_TYPE_CARDINAL
, status
),
631 if (U_FAILURE(status
)) {
634 pluralRules
->removeRef();
636 SharedObject::copyPtr(
637 NumberFormat::createSharedInstance(
638 locale
, UNUM_DECIMAL
, status
),
640 if (U_FAILURE(status
)) {
643 numberFormat
->removeRef();
645 SharedNumberFormat
*shared
= new SharedNumberFormat(nf
.getAlias());
646 if (shared
== NULL
) {
647 status
= U_MEMORY_ALLOCATION_ERROR
;
651 SharedObject::copyPtr(shared
, numberFormat
);
658 #endif /* !UCONFIG_NO_FORMATTING */