]> git.saurik.com Git - apple/icu.git/blame - icuSources/i18n/reldatefmt.cpp
ICU-531.48.tar.gz
[apple/icu.git] / icuSources / i18n / reldatefmt.cpp
CommitLineData
57a6839d
A
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