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