1/* 2 ******************************************************************************* 3 * Copyright (C) 2013-2015, International Business Machines Corporation and 4 * others. All Rights Reserved. 5 ******************************************************************************* 6 */ 7package com.ibm.icu.text; 8 9import java.util.EnumMap; 10import java.util.Locale; 11 12import com.ibm.icu.impl.CalendarData; 13import com.ibm.icu.impl.ICUCache; 14import com.ibm.icu.impl.ICUResourceBundle; 15import com.ibm.icu.impl.SimpleCache; 16import com.ibm.icu.lang.UCharacter; 17import com.ibm.icu.util.ULocale; 18import com.ibm.icu.util.UResourceBundle; 19 20 21/** 22 * Formats simple relative dates. There are two types of relative dates that 23 * it handles: 24 * <ul> 25 * <li>relative dates with a quantity e.g "in 5 days"</li> 26 * <li>relative dates without a quantity e.g "next Tuesday"</li> 27 * </ul> 28 * <p> 29 * This API is very basic and is intended to be a building block for more 30 * fancy APIs. The caller tells it exactly what to display in a locale 31 * independent way. While this class automatically provides the correct plural 32 * forms, the grammatical form is otherwise as neutral as possible. It is the 33 * caller's responsibility to handle cut-off logic such as deciding between 34 * displaying "in 7 days" or "in 1 week." This API supports relative dates 35 * involving one single unit. This API does not support relative dates 36 * involving compound units. 37 * e.g "in 5 days and 4 hours" nor does it support parsing. 38 * This class is both immutable and thread-safe. 39 * <p> 40 * Here are some examples of use: 41 * <blockquote> 42 * <pre> 43 * RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance(); 44 * fmt.format(1, Direction.NEXT, RelativeUnit.DAYS); // "in 1 day" 45 * fmt.format(3, Direction.NEXT, RelativeUnit.DAYS); // "in 3 days" 46 * fmt.format(3.2, Direction.LAST, RelativeUnit.YEARS); // "3.2 years ago" 47 * 48 * fmt.format(Direction.LAST, AbsoluteUnit.SUNDAY); // "last Sunday" 49 * fmt.format(Direction.THIS, AbsoluteUnit.SUNDAY); // "this Sunday" 50 * fmt.format(Direction.NEXT, AbsoluteUnit.SUNDAY); // "next Sunday" 51 * fmt.format(Direction.PLAIN, AbsoluteUnit.SUNDAY); // "Sunday" 52 * 53 * fmt.format(Direction.LAST, AbsoluteUnit.DAY); // "yesterday" 54 * fmt.format(Direction.THIS, AbsoluteUnit.DAY); // "today" 55 * fmt.format(Direction.NEXT, AbsoluteUnit.DAY); // "tomorrow" 56 * 57 * fmt.format(Direction.PLAIN, AbsoluteUnit.NOW); // "now" 58 * </pre> 59 * </blockquote> 60 * <p> 61 * In the future, we may add more forms, such as abbreviated/short forms 62 * (3 secs ago), and relative day periods ("yesterday afternoon"), etc. 63 * 64 * @stable ICU 53 65 */ 66public final class RelativeDateTimeFormatter { 67 68 /** 69 * The formatting style 70 * @draft ICU 54 71 * @provisional This API might change or be removed in a future release. 72 * 73 */ 74 public static enum Style { 75 76 /** 77 * Everything spelled out. 78 * @draft ICU 54 79 * @provisional This API might change or be removed in a future release. 80 */ 81 LONG, 82 83 /** 84 * Abbreviations used when possible. 85 * @draft ICU 54 86 * @provisional This API might change or be removed in a future release. 87 */ 88 SHORT, 89 90 /** 91 * Use single letters when possible. 92 * @draft ICU 54 93 * @provisional This API might change or be removed in a future release. 94 */ 95 NARROW, 96 } 97 98 /** 99 * Represents the unit for formatting a relative date. e.g "in 5 days" 100 * or "in 3 months" 101 * @stable ICU 53 102 */ 103 public static enum RelativeUnit { 104 105 /** 106 * Seconds 107 * @stable ICU 53 108 */ 109 SECONDS, 110 111 /** 112 * Minutes 113 * @stable ICU 53 114 */ 115 MINUTES, 116 117 /** 118 * Hours 119 * @stable ICU 53 120 */ 121 HOURS, 122 123 /** 124 * Days 125 * @stable ICU 53 126 */ 127 DAYS, 128 129 /** 130 * Weeks 131 * @stable ICU 53 132 */ 133 WEEKS, 134 135 /** 136 * Months 137 * @stable ICU 53 138 */ 139 MONTHS, 140 141 /** 142 * Years 143 * @stable ICU 53 144 */ 145 YEARS, 146 } 147 148 /** 149 * Represents an absolute unit. 150 * @stable ICU 53 151 */ 152 public static enum AbsoluteUnit { 153 154 /** 155 * Sunday 156 * @stable ICU 53 157 */ 158 SUNDAY, 159 160 /** 161 * Monday 162 * @stable ICU 53 163 */ 164 MONDAY, 165 166 /** 167 * Tuesday 168 * @stable ICU 53 169 */ 170 TUESDAY, 171 172 /** 173 * Wednesday 174 * @stable ICU 53 175 */ 176 WEDNESDAY, 177 178 /** 179 * Thursday 180 * @stable ICU 53 181 */ 182 THURSDAY, 183 184 /** 185 * Friday 186 * @stable ICU 53 187 */ 188 FRIDAY, 189 190 /** 191 * Saturday 192 * @stable ICU 53 193 */ 194 SATURDAY, 195 196 /** 197 * Day 198 * @stable ICU 53 199 */ 200 DAY, 201 202 /** 203 * Week 204 * @stable ICU 53 205 */ 206 WEEK, 207 208 /** 209 * Month 210 * @stable ICU 53 211 */ 212 MONTH, 213 214 /** 215 * Year 216 * @stable ICU 53 217 */ 218 YEAR, 219 220 /** 221 * Now 222 * @stable ICU 53 223 */ 224 NOW, 225 } 226 227 /** 228 * Represents a direction for an absolute unit e.g "Next Tuesday" 229 * or "Last Tuesday" 230 * @stable ICU 53 231 */ 232 public static enum Direction { 233 234 /** 235 * Two before. Not fully supported in every locale 236 * @stable ICU 53 237 */ 238 LAST_2, 239 240 /** 241 * Last 242 * @stable ICU 53 243 */ 244 LAST, 245 246 /** 247 * This 248 * @stable ICU 53 249 */ 250 THIS, 251 252 /** 253 * Next 254 * @stable ICU 53 255 */ 256 NEXT, 257 258 /** 259 * Two after. Not fully supported in every locale 260 * @stable ICU 53 261 */ 262 NEXT_2, 263 264 /** 265 * Plain, which means the absence of a qualifier 266 * @stable ICU 53 267 */ 268 PLAIN; 269 } 270 271 /** 272 * Returns a RelativeDateTimeFormatter for the default locale. 273 * @stable ICU 53 274 */ 275 public static RelativeDateTimeFormatter getInstance() { 276 return getInstance(ULocale.getDefault(), null, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 277 } 278 279 /** 280 * Returns a RelativeDateTimeFormatter for a particular locale. 281 * 282 * @param locale the locale. 283 * @return An instance of RelativeDateTimeFormatter. 284 * @stable ICU 53 285 */ 286 public static RelativeDateTimeFormatter getInstance(ULocale locale) { 287 return getInstance(locale, null, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 288 } 289 290 /** 291 * Returns a RelativeDateTimeFormatter for a particular JDK locale. 292 * 293 * @param locale the JDK locale. 294 * @return An instance of RelativeDateTimeFormatter. 295 * @draft ICU 54 296 * @provisional This API might change or be removed in a future release. 297 */ 298 public static RelativeDateTimeFormatter getInstance(Locale locale) { 299 return getInstance(ULocale.forLocale(locale)); 300 } 301 302 /** 303 * Returns a RelativeDateTimeFormatter for a particular locale that uses a particular 304 * NumberFormat object. 305 * 306 * @param locale the locale 307 * @param nf the number format object. It is defensively copied to ensure thread-safety 308 * and immutability of this class. 309 * @return An instance of RelativeDateTimeFormatter. 310 * @stable ICU 53 311 */ 312 public static RelativeDateTimeFormatter getInstance(ULocale locale, NumberFormat nf) { 313 return getInstance(locale, nf, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 314 } 315 316 /** 317 * Returns a RelativeDateTimeFormatter for a particular locale that uses a particular 318 * NumberFormat object, style, and capitalization context 319 * 320 * @param locale the locale 321 * @param nf the number format object. It is defensively copied to ensure thread-safety 322 * and immutability of this class. May be null. 323 * @param style the style. 324 * @param capitalizationContext the capitalization context. 325 * @draft ICU 54 326 * @provisional This API might change or be removed in a future release. 327 */ 328 public static RelativeDateTimeFormatter getInstance( 329 ULocale locale, 330 NumberFormat nf, 331 Style style, 332 DisplayContext capitalizationContext) { 333 RelativeDateTimeFormatterData data = cache.get(locale); 334 if (nf == null) { 335 nf = NumberFormat.getInstance(locale); 336 } else { 337 nf = (NumberFormat) nf.clone(); 338 } 339 return new RelativeDateTimeFormatter( 340 data.qualitativeUnitMap, 341 data.quantitativeUnitMap, 342 new MessageFormat(data.dateTimePattern), 343 PluralRules.forLocale(locale), 344 nf, 345 style, 346 capitalizationContext, 347 capitalizationContext == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ? 348 BreakIterator.getSentenceInstance(locale) : null, 349 locale); 350 351 } 352 353 /** 354 * Returns a RelativeDateTimeFormatter for a particular JDK locale that uses a particular 355 * NumberFormat object. 356 * 357 * @param locale the JDK locale 358 * @param nf the number format object. It is defensively copied to ensure thread-safety 359 * and immutability of this class. 360 * @return An instance of RelativeDateTimeFormatter. 361 * @draft ICU 54 362 * @provisional This API might change or be removed in a future release. 363 */ 364 public static RelativeDateTimeFormatter getInstance(Locale locale, NumberFormat nf) { 365 return getInstance(ULocale.forLocale(locale), nf); 366 } 367 368 /** 369 * Formats a relative date with a quantity such as "in 5 days" or 370 * "3 months ago" 371 * @param quantity The numerical amount e.g 5. This value is formatted 372 * according to this object's {@link NumberFormat} object. 373 * @param direction NEXT means a future relative date; LAST means a past 374 * relative date. 375 * @param unit the unit e.g day? month? year? 376 * @return the formatted string 377 * @throws IllegalArgumentException if direction is something other than 378 * NEXT or LAST. 379 * @stable ICU 53 380 */ 381 public String format(double quantity, Direction direction, RelativeUnit unit) { 382 if (direction != Direction.LAST && direction != Direction.NEXT) { 383 throw new IllegalArgumentException("direction must be NEXT or LAST"); 384 } 385 String result; 386 // This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this 387 // class we must guarantee that only one thread at a time uses our numberFormat. 388 synchronized (numberFormat) { 389 result = getQuantity( 390 unit, direction == Direction.NEXT).format( 391 quantity, numberFormat, pluralRules); 392 } 393 return adjustForContext(result); 394 } 395 396 397 /** 398 * Formats a relative date without a quantity. 399 * @param direction NEXT, LAST, THIS, etc. 400 * @param unit e.g SATURDAY, DAY, MONTH 401 * @return the formatted string. If direction has a value that is documented as not being 402 * fully supported in every locale (for example NEXT_2 or LAST_2) then this function may 403 * return null to signal that no formatted string is available. 404 * @throws IllegalArgumentException if the direction is incompatible with 405 * unit this can occur with NOW which can only take PLAIN. 406 * @stable ICU 53 407 */ 408 public String format(Direction direction, AbsoluteUnit unit) { 409 if (unit == AbsoluteUnit.NOW && direction != Direction.PLAIN) { 410 throw new IllegalArgumentException("NOW can only accept direction PLAIN."); 411 } 412 String result = this.qualitativeUnitMap.get(style).get(unit).get(direction); 413 return result != null ? adjustForContext(result) : null; 414 } 415 416 /** 417 * Combines a relative date string and a time string in this object's 418 * locale. This is done with the same date-time separator used for the 419 * default calendar in this locale. 420 * @param relativeDateString the relative date e.g 'yesterday' 421 * @param timeString the time e.g '3:45' 422 * @return the date and time concatenated according to the default 423 * calendar in this locale e.g 'yesterday, 3:45' 424 * @stable ICU 53 425 */ 426 public String combineDateAndTime(String relativeDateString, String timeString) { 427 return this.combinedDateAndTime.format( 428 new Object[]{timeString, relativeDateString}, new StringBuffer(), null).toString(); 429 } 430 431 /** 432 * Returns a copy of the NumberFormat this object is using. 433 * @return A copy of the NumberFormat. 434 * @stable ICU 53 435 */ 436 public NumberFormat getNumberFormat() { 437 // This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this 438 // class we must guarantee that only one thread at a time uses our numberFormat. 439 synchronized (numberFormat) { 440 return (NumberFormat) numberFormat.clone(); 441 } 442 } 443 444 /** 445 * Return capitalization context. 446 * 447 * @draft ICU 54 448 * @provisional This API might change or be removed in a future release. 449 */ 450 public DisplayContext getCapitalizationContext() { 451 return capitalizationContext; 452 } 453 454 /** 455 * Return style 456 * 457 * @draft ICU 54 458 * @provisional This API might change or be removed in a future release. 459 */ 460 public Style getFormatStyle() { 461 return style; 462 } 463 464 private String adjustForContext(String originalFormattedString) { 465 if (breakIterator == null || originalFormattedString.length() == 0 466 || !UCharacter.isLowerCase(UCharacter.codePointAt(originalFormattedString, 0))) { 467 return originalFormattedString; 468 } 469 synchronized (breakIterator) { 470 return UCharacter.toTitleCase( 471 locale, 472 originalFormattedString, 473 breakIterator, 474 UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT); 475 } 476 } 477 478 private static void addQualitativeUnit( 479 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> qualitativeUnits, 480 AbsoluteUnit unit, 481 String current) { 482 EnumMap<Direction, String> unitStrings = 483 new EnumMap<Direction, String>(Direction.class); 484 unitStrings.put(Direction.PLAIN, current); 485 qualitativeUnits.put(unit, unitStrings); 486 } 487 488 private static void addQualitativeUnit( 489 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> qualitativeUnits, 490 AbsoluteUnit unit, ICUResourceBundle bundle, String plain) { 491 EnumMap<Direction, String> unitStrings = 492 new EnumMap<Direction, String>(Direction.class); 493 unitStrings.put(Direction.LAST, bundle.getStringWithFallback("-1")); 494 unitStrings.put(Direction.THIS, bundle.getStringWithFallback("0")); 495 unitStrings.put(Direction.NEXT, bundle.getStringWithFallback("1")); 496 addOptionalDirection(unitStrings, Direction.LAST_2, bundle, "-2"); 497 addOptionalDirection(unitStrings, Direction.NEXT_2, bundle, "2"); 498 unitStrings.put(Direction.PLAIN, plain); 499 qualitativeUnits.put(unit, unitStrings); 500 } 501 502 private static void addOptionalDirection( 503 EnumMap<Direction, String> unitStrings, 504 Direction direction, 505 ICUResourceBundle bundle, 506 String key) { 507 String s = bundle.findStringWithFallback(key); 508 if (s != null) { 509 unitStrings.put(direction, s); 510 } 511 } 512 513 private RelativeDateTimeFormatter( 514 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, 515 EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap, 516 MessageFormat combinedDateAndTime, 517 PluralRules pluralRules, 518 NumberFormat numberFormat, 519 Style style, 520 DisplayContext capitalizationContext, 521 BreakIterator breakIterator, 522 ULocale locale) { 523 this.qualitativeUnitMap = qualitativeUnitMap; 524 this.quantitativeUnitMap = quantitativeUnitMap; 525 this.combinedDateAndTime = combinedDateAndTime; 526 this.pluralRules = pluralRules; 527 this.numberFormat = numberFormat; 528 this.style = style; 529 if (capitalizationContext.type() != DisplayContext.Type.CAPITALIZATION) { 530 throw new IllegalArgumentException(capitalizationContext.toString()); 531 } 532 this.capitalizationContext = capitalizationContext; 533 this.breakIterator = breakIterator; 534 this.locale = locale; 535 } 536 537 private QuantityFormatter getQuantity(RelativeUnit unit, boolean isFuture) { 538 QuantityFormatter[] quantities = quantitativeUnitMap.get(style).get(unit); 539 return isFuture ? quantities[1] : quantities[0]; 540 } 541 542 private final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap; 543 private final EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap; 544 private final MessageFormat combinedDateAndTime; 545 private final PluralRules pluralRules; 546 private final NumberFormat numberFormat; 547 private final Style style; 548 private final DisplayContext capitalizationContext; 549 private final BreakIterator breakIterator; 550 private final ULocale locale; 551 552 private static class RelativeDateTimeFormatterData { 553 public RelativeDateTimeFormatterData( 554 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, 555 EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap, 556 String dateTimePattern) { 557 this.qualitativeUnitMap = qualitativeUnitMap; 558 this.quantitativeUnitMap = quantitativeUnitMap; 559 this.dateTimePattern = dateTimePattern; 560 } 561 562 public final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap; 563 public final EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap; 564 public final String dateTimePattern; // Example: "{1}, {0}" 565 } 566 567 private static class Cache { 568 private final ICUCache<String, RelativeDateTimeFormatterData> cache = 569 new SimpleCache<String, RelativeDateTimeFormatterData>(); 570 571 public RelativeDateTimeFormatterData get(ULocale locale) { 572 String key = locale.toString(); 573 RelativeDateTimeFormatterData result = cache.get(key); 574 if (result == null) { 575 result = new Loader(locale).load(); 576 cache.put(key, result); 577 } 578 return result; 579 } 580 } 581 582 private static class Loader { 583 private final ULocale ulocale; 584 585 public Loader(ULocale ulocale) { 586 this.ulocale = ulocale; 587 } 588 589 public RelativeDateTimeFormatterData load() { 590 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap = 591 new EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>>(Style.class); 592 593 EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap = 594 new EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>>(Style.class); 595 596 for (Style style : Style.values()) { 597 qualitativeUnitMap.put(style, new EnumMap<AbsoluteUnit, EnumMap<Direction, String>>(AbsoluteUnit.class)); 598 quantitativeUnitMap.put(style, new EnumMap<RelativeUnit, QuantityFormatter[]>(RelativeUnit.class)); 599 } 600 601 ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. 602 getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, ulocale); 603 addTimeUnits( 604 r, 605 "fields/day", "fields/day-short", "fields/day-narrow", 606 RelativeUnit.DAYS, 607 AbsoluteUnit.DAY, 608 quantitativeUnitMap, 609 qualitativeUnitMap); 610 addTimeUnits( 611 r, 612 "fields/week", "fields/week-short", "fields/week-narrow", 613 RelativeUnit.WEEKS, 614 AbsoluteUnit.WEEK, 615 quantitativeUnitMap, 616 qualitativeUnitMap); 617 addTimeUnits( 618 r, 619 "fields/month", "fields/month-short", "fields/month-narrow", 620 RelativeUnit.MONTHS, 621 AbsoluteUnit.MONTH, 622 quantitativeUnitMap, 623 qualitativeUnitMap); 624 addTimeUnits( 625 r, 626 "fields/year", "fields/year-short", "fields/year-narrow", 627 RelativeUnit.YEARS, 628 AbsoluteUnit.YEAR, 629 quantitativeUnitMap, 630 qualitativeUnitMap); 631 initRelativeUnits( 632 r, 633 "fields/second", "fields/second-short", "fields/second-narrow", 634 RelativeUnit.SECONDS, 635 quantitativeUnitMap); 636 initRelativeUnits( 637 r, 638 "fields/minute", "fields/minute-short", "fields/minute-narrow", 639 RelativeUnit.MINUTES, 640 quantitativeUnitMap); 641 initRelativeUnits( 642 r, 643 "fields/hour", "fields/hour-short", "fields/hour-narrow", 644 RelativeUnit.HOURS, 645 quantitativeUnitMap); 646 647 addQualitativeUnit( 648 qualitativeUnitMap.get(Style.LONG), 649 AbsoluteUnit.NOW, 650 r.getStringWithFallback("fields/second/relative/0")); 651 addQualitativeUnit( 652 qualitativeUnitMap.get(Style.SHORT), 653 AbsoluteUnit.NOW, 654 r.getStringWithFallback("fields/second-short/relative/0")); 655 addQualitativeUnit( 656 qualitativeUnitMap.get(Style.NARROW), 657 AbsoluteUnit.NOW, 658 r.getStringWithFallback("fields/second-narrow/relative/0")); 659 660 EnumMap<Style, EnumMap<AbsoluteUnit, String>> dayOfWeekMap = 661 new EnumMap<Style, EnumMap<AbsoluteUnit, String>>(Style.class); 662 dayOfWeekMap.put(Style.LONG, readDaysOfWeek( 663 r.getWithFallback("calendar/gregorian/dayNames/stand-alone/wide"))); 664 dayOfWeekMap.put(Style.SHORT, readDaysOfWeek( 665 r.getWithFallback("calendar/gregorian/dayNames/stand-alone/short"))); 666 dayOfWeekMap.put(Style.NARROW, readDaysOfWeek( 667 r.getWithFallback("calendar/gregorian/dayNames/stand-alone/narrow"))); 668 669 addWeekDays( 670 r, 671 "fields/mon/relative", 672 "fields/mon-short/relative", 673 "fields/mon-narrow/relative", 674 dayOfWeekMap, 675 AbsoluteUnit.MONDAY, 676 qualitativeUnitMap); 677 addWeekDays( 678 r, 679 "fields/tue/relative", 680 "fields/tue-short/relative", 681 "fields/tue-narrow/relative", 682 dayOfWeekMap, 683 AbsoluteUnit.TUESDAY, 684 qualitativeUnitMap); 685 addWeekDays( 686 r, 687 "fields/wed/relative", 688 "fields/wed-short/relative", 689 "fields/wed-narrow/relative", 690 dayOfWeekMap, 691 AbsoluteUnit.WEDNESDAY, 692 qualitativeUnitMap); 693 addWeekDays( 694 r, 695 "fields/thu/relative", 696 "fields/thu-short/relative", 697 "fields/thu-narrow/relative", 698 dayOfWeekMap, 699 AbsoluteUnit.THURSDAY, 700 qualitativeUnitMap); 701 addWeekDays( 702 r, 703 "fields/fri/relative", 704 "fields/fri-short/relative", 705 "fields/fri-narrow/relative", 706 dayOfWeekMap, 707 AbsoluteUnit.FRIDAY, 708 qualitativeUnitMap); 709 addWeekDays( 710 r, 711 "fields/sat/relative", 712 "fields/sat-short/relative", 713 "fields/sat-narrow/relative", 714 dayOfWeekMap, 715 AbsoluteUnit.SATURDAY, 716 qualitativeUnitMap); 717 addWeekDays( 718 r, 719 "fields/sun/relative", 720 "fields/sun-short/relative", 721 "fields/sun-narrow/relative", 722 dayOfWeekMap, 723 AbsoluteUnit.SUNDAY, 724 qualitativeUnitMap); 725 CalendarData calData = new CalendarData( 726 ulocale, r.getStringWithFallback("calendar/default")); 727 return new RelativeDateTimeFormatterData( 728 qualitativeUnitMap, quantitativeUnitMap, calData.getDateTimePattern()); 729 } 730 731 private void addTimeUnits( 732 ICUResourceBundle r, 733 String path, String pathShort, String pathNarrow, 734 RelativeUnit relativeUnit, 735 AbsoluteUnit absoluteUnit, 736 EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap, 737 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap) { 738 addTimeUnit( 739 r.getWithFallback(path), 740 relativeUnit, 741 absoluteUnit, 742 quantitativeUnitMap.get(Style.LONG), 743 qualitativeUnitMap.get(Style.LONG)); 744 addTimeUnit( 745 r.getWithFallback(pathShort), 746 relativeUnit, 747 absoluteUnit, 748 quantitativeUnitMap.get(Style.SHORT), 749 qualitativeUnitMap.get(Style.SHORT)); 750 addTimeUnit( 751 r.getWithFallback(pathNarrow), 752 relativeUnit, 753 absoluteUnit, 754 quantitativeUnitMap.get(Style.NARROW), 755 qualitativeUnitMap.get(Style.NARROW)); 756 757 } 758 759 private void addTimeUnit( 760 ICUResourceBundle timeUnitBundle, 761 RelativeUnit relativeUnit, 762 AbsoluteUnit absoluteUnit, 763 EnumMap<RelativeUnit, QuantityFormatter[]> quantitativeUnitMap, 764 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> qualitativeUnitMap) { 765 addTimeUnit(timeUnitBundle, relativeUnit, quantitativeUnitMap); 766 String unitName = timeUnitBundle.getStringWithFallback("dn"); 767 // TODO(Travis Keep): This is a hack to get around CLDR bug 6818. 768 if (ulocale.getLanguage().equals("en")) { 769 unitName = unitName.toLowerCase(); 770 } 771 timeUnitBundle = timeUnitBundle.getWithFallback("relative"); 772 addQualitativeUnit( 773 qualitativeUnitMap, 774 absoluteUnit, 775 timeUnitBundle, 776 unitName); 777 } 778 779 private void initRelativeUnits( 780 ICUResourceBundle r, 781 String path, 782 String pathShort, 783 String pathNarrow, 784 RelativeUnit relativeUnit, 785 EnumMap<Style, EnumMap<RelativeUnit, QuantityFormatter[]>> quantitativeUnitMap) { 786 addTimeUnit( 787 r.getWithFallback(path), 788 relativeUnit, 789 quantitativeUnitMap.get(Style.LONG)); 790 addTimeUnit( 791 r.getWithFallback(pathShort), 792 relativeUnit, 793 quantitativeUnitMap.get(Style.SHORT)); 794 addTimeUnit( 795 r.getWithFallback(pathNarrow), 796 relativeUnit, 797 quantitativeUnitMap.get(Style.NARROW)); 798 } 799 800 private static void addTimeUnit( 801 ICUResourceBundle timeUnitBundle, 802 RelativeUnit relativeUnit, 803 EnumMap<RelativeUnit, QuantityFormatter[]> quantitativeUnitMap) { 804 QuantityFormatter.Builder future = new QuantityFormatter.Builder(); 805 QuantityFormatter.Builder past = new QuantityFormatter.Builder(); 806 timeUnitBundle = timeUnitBundle.getWithFallback("relativeTime"); 807 addTimeUnit( 808 timeUnitBundle.getWithFallback("future"), 809 future); 810 addTimeUnit( 811 timeUnitBundle.getWithFallback("past"), 812 past); 813 quantitativeUnitMap.put( 814 relativeUnit, new QuantityFormatter[] { past.build(), future.build() }); 815 } 816 817 private static void addTimeUnit( 818 ICUResourceBundle pastOrFuture, QuantityFormatter.Builder builder) { 819 int size = pastOrFuture.getSize(); 820 for (int i = 0; i < size; i++) { 821 UResourceBundle r = pastOrFuture.get(i); 822 builder.add(r.getKey(), r.getString()); 823 } 824 } 825 826 private void addWeekDays( 827 ICUResourceBundle r, 828 String path, 829 String pathShort, 830 String pathNarrow, 831 EnumMap<Style, EnumMap<AbsoluteUnit, String>> dayOfWeekMap, 832 AbsoluteUnit weekDay, 833 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap) { 834 addQualitativeUnit( 835 qualitativeUnitMap.get(Style.LONG), 836 weekDay, 837 r.findWithFallback(path), 838 dayOfWeekMap.get(Style.LONG).get(weekDay)); 839 addQualitativeUnit( 840 qualitativeUnitMap.get(Style.SHORT), 841 weekDay, 842 r.findWithFallback(pathShort), 843 dayOfWeekMap.get(Style.SHORT).get(weekDay)); 844 addQualitativeUnit( 845 qualitativeUnitMap.get(Style.NARROW), 846 weekDay, 847 r.findWithFallback(pathNarrow), 848 dayOfWeekMap.get(Style.NARROW).get(weekDay)); 849 850 } 851 852 private static EnumMap<AbsoluteUnit, String> readDaysOfWeek(ICUResourceBundle daysOfWeekBundle) { 853 EnumMap<AbsoluteUnit, String> dayOfWeekMap = new EnumMap<AbsoluteUnit, String>(AbsoluteUnit.class); 854 if (daysOfWeekBundle.getSize() != 7) { 855 throw new IllegalStateException(String.format("Expect 7 days in a week, got %d", daysOfWeekBundle.getSize())); 856 } 857 // Sunday always comes first in CLDR data. 858 int idx = 0; 859 dayOfWeekMap.put(AbsoluteUnit.SUNDAY, daysOfWeekBundle.getString(idx++)); 860 dayOfWeekMap.put(AbsoluteUnit.MONDAY, daysOfWeekBundle.getString(idx++)); 861 dayOfWeekMap.put(AbsoluteUnit.TUESDAY, daysOfWeekBundle.getString(idx++)); 862 dayOfWeekMap.put(AbsoluteUnit.WEDNESDAY, daysOfWeekBundle.getString(idx++)); 863 dayOfWeekMap.put(AbsoluteUnit.THURSDAY, daysOfWeekBundle.getString(idx++)); 864 dayOfWeekMap.put(AbsoluteUnit.FRIDAY, daysOfWeekBundle.getString(idx++)); 865 dayOfWeekMap.put(AbsoluteUnit.SATURDAY, daysOfWeekBundle.getString(idx++)); 866 return dayOfWeekMap; 867 } 868 } 869 870 private static final Cache cache = new Cache(); 871} 872