1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html 4 /* 5 ******************************************************************************* 6 * Copyright (C) 2013-2016, International Business Machines Corporation and 7 * others. All Rights Reserved. 8 ******************************************************************************* 9 */ 10 package android.icu.text; 11 12 import java.io.InvalidObjectException; 13 import java.text.AttributedCharacterIterator; 14 import java.text.Format; 15 import java.text.FieldPosition; 16 import java.util.EnumMap; 17 import java.util.Locale; 18 19 import android.icu.impl.CacheBase; 20 import android.icu.impl.FormattedStringBuilder; 21 import android.icu.impl.FormattedValueStringBuilderImpl; 22 import android.icu.impl.ICUData; 23 import android.icu.impl.ICUResourceBundle; 24 import android.icu.impl.SimpleFormatterImpl; 25 import android.icu.impl.SoftCache; 26 import android.icu.impl.StandardPlural; 27 import android.icu.impl.UResource; 28 import android.icu.impl.Utility; 29 import android.icu.impl.number.DecimalQuantity; 30 import android.icu.impl.number.DecimalQuantity_DualStorageBCD; 31 import android.icu.lang.UCharacter; 32 import android.icu.util.Calendar; 33 import android.icu.util.ICUException; 34 import android.icu.util.ULocale; 35 import android.icu.util.UResourceBundle; 36 37 38 /** 39 * Formats simple relative dates. There are two types of relative dates that 40 * it handles: 41 * <ul> 42 * <li>relative dates with a quantity e.g "in 5 days"</li> 43 * <li>relative dates without a quantity e.g "next Tuesday"</li> 44 * </ul> 45 * <p> 46 * This API is very basic and is intended to be a building block for more 47 * fancy APIs. The caller tells it exactly what to display in a locale 48 * independent way. While this class automatically provides the correct plural 49 * forms, the grammatical form is otherwise as neutral as possible. It is the 50 * caller's responsibility to handle cut-off logic such as deciding between 51 * displaying "in 7 days" or "in 1 week." This API supports relative dates 52 * involving one single unit. This API does not support relative dates 53 * involving compound units. 54 * e.g "in 5 days and 4 hours" nor does it support parsing. 55 * This class is both immutable and thread-safe. 56 * <p> 57 * Here are some examples of use: 58 * <blockquote> 59 * <pre> 60 * RelativeDateTimeFormatter fmt = RelativeDateTimeFormatter.getInstance(); 61 * fmt.format(1, Direction.NEXT, RelativeUnit.DAYS); // "in 1 day" 62 * fmt.format(3, Direction.NEXT, RelativeUnit.DAYS); // "in 3 days" 63 * fmt.format(3.2, Direction.LAST, RelativeUnit.YEARS); // "3.2 years ago" 64 * 65 * fmt.format(Direction.LAST, AbsoluteUnit.SUNDAY); // "last Sunday" 66 * fmt.format(Direction.THIS, AbsoluteUnit.SUNDAY); // "this Sunday" 67 * fmt.format(Direction.NEXT, AbsoluteUnit.SUNDAY); // "next Sunday" 68 * fmt.format(Direction.PLAIN, AbsoluteUnit.SUNDAY); // "Sunday" 69 * 70 * fmt.format(Direction.LAST, AbsoluteUnit.DAY); // "yesterday" 71 * fmt.format(Direction.THIS, AbsoluteUnit.DAY); // "today" 72 * fmt.format(Direction.NEXT, AbsoluteUnit.DAY); // "tomorrow" 73 * 74 * fmt.format(Direction.PLAIN, AbsoluteUnit.NOW); // "now" 75 * </pre> 76 * </blockquote> 77 * <p> 78 * The Style parameter allows selection of different length styles: 79 * LONG ("3 seconds ago"), SHORT ("3 sec. ago"), NARROW ("3s ago"). 80 * In the future, we may add more forms, such as relative day periods 81 * ("yesterday afternoon"), etc. 82 */ 83 public final class RelativeDateTimeFormatter { 84 85 /** 86 * The formatting style 87 * 88 */ 89 public static enum Style { 90 91 /** 92 * Everything spelled out. 93 */ 94 LONG, 95 96 /** 97 * Abbreviations used when possible. 98 */ 99 SHORT, 100 101 /** 102 * Use single letters when possible. 103 */ 104 NARROW; 105 106 private static final int INDEX_COUNT = 3; // NARROW.ordinal() + 1 107 } 108 109 /** 110 * Represents the unit for formatting a relative date. e.g "in 5 days" 111 * or "in 3 months" 112 */ 113 public static enum RelativeUnit { 114 115 /** 116 * Seconds 117 */ 118 SECONDS, 119 120 /** 121 * Minutes 122 */ 123 MINUTES, 124 125 /** 126 * Hours 127 */ 128 HOURS, 129 130 /** 131 * Days 132 */ 133 DAYS, 134 135 /** 136 * Weeks 137 */ 138 WEEKS, 139 140 /** 141 * Months 142 */ 143 MONTHS, 144 145 /** 146 * Years 147 */ 148 YEARS, 149 150 /** 151 * Quarters 152 * @hide draft / provisional / internal are hidden on Android 153 */ 154 QUARTERS, 155 156 /** 157 * Sundays 158 * @hide draft / provisional / internal are hidden on Android 159 */ 160 SUNDAYS, 161 162 /** 163 * Mondays 164 * @hide draft / provisional / internal are hidden on Android 165 */ 166 MONDAYS, 167 168 /** 169 * Tuesdays 170 * @hide draft / provisional / internal are hidden on Android 171 */ 172 TUESDAYS, 173 174 /** 175 * Wednesdays 176 * @hide draft / provisional / internal are hidden on Android 177 */ 178 WEDNESDAYS, 179 180 /** 181 * Thursdays 182 * @hide draft / provisional / internal are hidden on Android 183 */ 184 THURSDAYS, 185 186 /** 187 * Fridays 188 * @hide draft / provisional / internal are hidden on Android 189 */ 190 FRIDAYS, 191 192 /** 193 * Saturdays 194 * @hide draft / provisional / internal are hidden on Android 195 */ 196 SATURDAYS, 197 } 198 199 /** 200 * Represents an absolute unit. 201 */ 202 public static enum AbsoluteUnit { 203 204 /** 205 * Sunday 206 */ 207 SUNDAY, 208 209 /** 210 * Monday 211 */ 212 MONDAY, 213 214 /** 215 * Tuesday 216 */ 217 TUESDAY, 218 219 /** 220 * Wednesday 221 */ 222 WEDNESDAY, 223 224 /** 225 * Thursday 226 */ 227 THURSDAY, 228 229 /** 230 * Friday 231 */ 232 FRIDAY, 233 234 /** 235 * Saturday 236 */ 237 SATURDAY, 238 239 /** 240 * Day 241 */ 242 DAY, 243 244 /** 245 * Week 246 */ 247 WEEK, 248 249 /** 250 * Month 251 */ 252 MONTH, 253 254 /** 255 * Year 256 */ 257 YEAR, 258 259 /** 260 * Now 261 */ 262 NOW, 263 264 /** 265 * Quarter 266 */ 267 QUARTER, 268 269 /** 270 * Hour 271 */ 272 HOUR, 273 274 /** 275 * Minute 276 */ 277 MINUTE, 278 } 279 280 /** 281 * Represents a direction for an absolute unit e.g "Next Tuesday" 282 * or "Last Tuesday" 283 */ 284 public static enum Direction { 285 /** 286 * Two before. Not fully supported in every locale 287 */ 288 LAST_2, 289 290 /** 291 * Last 292 */ 293 LAST, 294 295 /** 296 * This 297 */ 298 THIS, 299 300 /** 301 * Next 302 */ 303 NEXT, 304 305 /** 306 * Two after. Not fully supported in every locale 307 */ 308 NEXT_2, 309 310 /** 311 * Plain, which means the absence of a qualifier 312 */ 313 PLAIN, 314 } 315 316 /** 317 * Represents the unit for formatting a relative date. e.g "in 5 days" 318 * or "next year" 319 */ 320 public static enum RelativeDateTimeUnit { 321 /** 322 * Specifies that relative unit is year, e.g. "last year", 323 * "in 5 years". 324 */ 325 YEAR, 326 /** 327 * Specifies that relative unit is quarter, e.g. "last quarter", 328 * "in 5 quarters". 329 */ 330 QUARTER, 331 /** 332 * Specifies that relative unit is month, e.g. "last month", 333 * "in 5 months". 334 */ 335 MONTH, 336 /** 337 * Specifies that relative unit is week, e.g. "last week", 338 * "in 5 weeks". 339 */ 340 WEEK, 341 /** 342 * Specifies that relative unit is day, e.g. "yesterday", 343 * "in 5 days". 344 */ 345 DAY, 346 /** 347 * Specifies that relative unit is hour, e.g. "1 hour ago", 348 * "in 5 hours". 349 */ 350 HOUR, 351 /** 352 * Specifies that relative unit is minute, e.g. "1 minute ago", 353 * "in 5 minutes". 354 */ 355 MINUTE, 356 /** 357 * Specifies that relative unit is second, e.g. "1 second ago", 358 * "in 5 seconds". 359 */ 360 SECOND, 361 /** 362 * Specifies that relative unit is Sunday, e.g. "last Sunday", 363 * "this Sunday", "next Sunday", "in 5 Sundays". 364 */ 365 SUNDAY, 366 /** 367 * Specifies that relative unit is Monday, e.g. "last Monday", 368 * "this Monday", "next Monday", "in 5 Mondays". 369 */ 370 MONDAY, 371 /** 372 * Specifies that relative unit is Tuesday, e.g. "last Tuesday", 373 * "this Tuesday", "next Tuesday", "in 5 Tuesdays". 374 */ 375 TUESDAY, 376 /** 377 * Specifies that relative unit is Wednesday, e.g. "last Wednesday", 378 * "this Wednesday", "next Wednesday", "in 5 Wednesdays". 379 */ 380 WEDNESDAY, 381 /** 382 * Specifies that relative unit is Thursday, e.g. "last Thursday", 383 * "this Thursday", "next Thursday", "in 5 Thursdays". 384 */ 385 THURSDAY, 386 /** 387 * Specifies that relative unit is Friday, e.g. "last Friday", 388 * "this Friday", "next Friday", "in 5 Fridays". 389 */ 390 FRIDAY, 391 /** 392 * Specifies that relative unit is Saturday, e.g. "last Saturday", 393 * "this Saturday", "next Saturday", "in 5 Saturdays". 394 */ 395 SATURDAY, 396 } 397 398 /** 399 * Field constants used when accessing field information for relative 400 * datetime strings in FormattedValue. 401 * <p> 402 * There is no public constructor to this class; the only instances are the 403 * constants defined here. 404 * <p> 405 * @hide Only a subset of ICU is exposed in Android 406 */ 407 public static class Field extends Format.Field { 408 private static final long serialVersionUID = -5327685528663492325L; 409 410 /** 411 * Represents a literal text string, like "tomorrow" or "days ago". 412 */ 413 public static final Field LITERAL = new Field("literal"); 414 415 /** 416 * Represents a number quantity, like "3" in "3 days ago". 417 */ 418 public static final Field NUMERIC = new Field("numeric"); 419 Field(String fieldName)420 private Field(String fieldName) { 421 super(fieldName); 422 } 423 424 /** 425 * Serizalization method resolve instances to the constant Field values 426 * 427 * @deprecated This API is ICU internal only. 428 * @hide draft / provisional / internal are hidden on Android 429 */ 430 @Deprecated 431 @Override readResolve()432 protected Object readResolve() throws InvalidObjectException { 433 if (this.getName().equals(LITERAL.getName())) 434 return LITERAL; 435 if (this.getName().equals(NUMERIC.getName())) 436 return NUMERIC; 437 438 throw new InvalidObjectException("An invalid object."); 439 } 440 } 441 442 /** 443 * Represents the result of a formatting operation of a relative datetime. 444 * Access the string value or field information. 445 * 446 * Instances of this class are immutable and thread-safe. 447 * 448 * Not intended for public subclassing. 449 * 450 * @author sffc 451 */ 452 public static class FormattedRelativeDateTime implements FormattedValue { 453 454 private final FormattedStringBuilder string; 455 FormattedRelativeDateTime(FormattedStringBuilder string)456 private FormattedRelativeDateTime(FormattedStringBuilder string) { 457 this.string = string; 458 } 459 460 /** 461 * {@inheritDoc} 462 */ 463 @Override toString()464 public String toString() { 465 return string.toString(); 466 } 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override length()472 public int length() { 473 return string.length(); 474 } 475 476 /** 477 * {@inheritDoc} 478 */ 479 @Override charAt(int index)480 public char charAt(int index) { 481 return string.charAt(index); 482 } 483 484 /** 485 * {@inheritDoc} 486 */ 487 @Override subSequence(int start, int end)488 public CharSequence subSequence(int start, int end) { 489 return string.subString(start, end); 490 } 491 492 /** 493 * {@inheritDoc} 494 */ 495 @Override appendTo(A appendable)496 public <A extends Appendable> A appendTo(A appendable) { 497 return Utility.appendTo(string, appendable); 498 } 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override nextPosition(ConstrainedFieldPosition cfpos)504 public boolean nextPosition(ConstrainedFieldPosition cfpos) { 505 return FormattedValueStringBuilderImpl.nextPosition(string, cfpos, Field.NUMERIC); 506 } 507 508 /** 509 * {@inheritDoc} 510 */ 511 @Override toCharacterIterator()512 public AttributedCharacterIterator toCharacterIterator() { 513 return FormattedValueStringBuilderImpl.toCharacterIterator(string, Field.NUMERIC); 514 } 515 } 516 517 /** 518 * Returns a RelativeDateTimeFormatter for the default locale. 519 */ getInstance()520 public static RelativeDateTimeFormatter getInstance() { 521 return getInstance(ULocale.getDefault(), null, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 522 } 523 524 /** 525 * Returns a RelativeDateTimeFormatter for a particular locale. 526 * 527 * @param locale the locale. 528 * @return An instance of RelativeDateTimeFormatter. 529 */ getInstance(ULocale locale)530 public static RelativeDateTimeFormatter getInstance(ULocale locale) { 531 return getInstance(locale, null, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 532 } 533 534 /** 535 * Returns a RelativeDateTimeFormatter for a particular {@link java.util.Locale}. 536 * 537 * @param locale the {@link java.util.Locale}. 538 * @return An instance of RelativeDateTimeFormatter. 539 */ getInstance(Locale locale)540 public static RelativeDateTimeFormatter getInstance(Locale locale) { 541 return getInstance(ULocale.forLocale(locale)); 542 } 543 544 /** 545 * Returns a RelativeDateTimeFormatter for a particular locale that uses a particular 546 * NumberFormat object. 547 * 548 * @param locale the locale 549 * @param nf the number format object. It is defensively copied to ensure thread-safety 550 * and immutability of this class. 551 * @return An instance of RelativeDateTimeFormatter. 552 */ getInstance(ULocale locale, NumberFormat nf)553 public static RelativeDateTimeFormatter getInstance(ULocale locale, NumberFormat nf) { 554 return getInstance(locale, nf, Style.LONG, DisplayContext.CAPITALIZATION_NONE); 555 } 556 557 /** 558 * Returns a RelativeDateTimeFormatter for a particular locale that uses a particular 559 * NumberFormat object, style, and capitalization context 560 * 561 * @param locale the locale 562 * @param nf the number format object. It is defensively copied to ensure thread-safety 563 * and immutability of this class. May be null. 564 * @param style the style. 565 * @param capitalizationContext the capitalization context. 566 */ getInstance( ULocale locale, NumberFormat nf, Style style, DisplayContext capitalizationContext)567 public static RelativeDateTimeFormatter getInstance( 568 ULocale locale, 569 NumberFormat nf, 570 Style style, 571 DisplayContext capitalizationContext) { 572 RelativeDateTimeFormatterData data = cache.get(locale); 573 if (nf == null) { 574 nf = NumberFormat.getInstance(locale); 575 } else { 576 nf = (NumberFormat) nf.clone(); 577 } 578 return new RelativeDateTimeFormatter( 579 data.qualitativeUnitMap, 580 data.relUnitPatternMap, 581 // Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717). 582 data.dateTimePattern, 583 PluralRules.forLocale(locale), 584 nf, 585 style, 586 capitalizationContext, 587 capitalizationContext == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ? 588 BreakIterator.getSentenceInstance(locale) : null, 589 locale); 590 } 591 592 /** 593 * Returns a RelativeDateTimeFormatter for a particular {@link java.util.Locale} that uses a 594 * particular NumberFormat object. 595 * 596 * @param locale the {@link java.util.Locale} 597 * @param nf the number format object. It is defensively copied to ensure thread-safety 598 * and immutability of this class. 599 * @return An instance of RelativeDateTimeFormatter. 600 */ getInstance(Locale locale, NumberFormat nf)601 public static RelativeDateTimeFormatter getInstance(Locale locale, NumberFormat nf) { 602 return getInstance(ULocale.forLocale(locale), nf); 603 } 604 605 /** 606 * Formats a relative date with a quantity such as "in 5 days" or 607 * "3 months ago". 608 * 609 * This method returns a String. To get more information about the 610 * formatting result, use formatToValue(). 611 * 612 * @param quantity The numerical amount e.g 5. This value is formatted 613 * according to this object's {@link NumberFormat} object. 614 * @param direction NEXT means a future relative date; LAST means a past 615 * relative date. 616 * @param unit the unit e.g day? month? year? 617 * @return the formatted string 618 * @throws IllegalArgumentException if direction is something other than 619 * NEXT or LAST. 620 */ format(double quantity, Direction direction, RelativeUnit unit)621 public String format(double quantity, Direction direction, RelativeUnit unit) { 622 FormattedStringBuilder output = formatImpl(quantity, direction, unit); 623 return adjustForContext(output.toString()); 624 } 625 626 /** 627 * Formats a relative date with a quantity such as "in 5 days" or 628 * "3 months ago". 629 * 630 * This method returns a FormattedRelativeDateTime, which exposes more 631 * information than the String returned by format(). 632 * 633 * @param quantity The numerical amount e.g 5. This value is formatted 634 * according to this object's {@link NumberFormat} object. 635 * @param direction NEXT means a future relative date; LAST means a past 636 * relative date. 637 * @param unit the unit e.g day? month? year? 638 * @return the formatted relative datetime 639 * @throws IllegalArgumentException if direction is something other than 640 * NEXT or LAST. 641 */ formatToValue(double quantity, Direction direction, RelativeUnit unit)642 public FormattedRelativeDateTime formatToValue(double quantity, Direction direction, RelativeUnit unit) { 643 checkNoAdjustForContext(); 644 return new FormattedRelativeDateTime(formatImpl(quantity, direction, unit)); 645 } 646 647 /** Implementation method for format and formatToValue with RelativeUnit */ formatImpl(double quantity, Direction direction, RelativeUnit unit)648 private FormattedStringBuilder formatImpl(double quantity, Direction direction, RelativeUnit unit) { 649 if (direction != Direction.LAST && direction != Direction.NEXT) { 650 throw new IllegalArgumentException("direction must be NEXT or LAST"); 651 } 652 int pastFutureIndex = (direction == Direction.NEXT ? 1 : 0); 653 654 FormattedStringBuilder output = new FormattedStringBuilder(); 655 String pluralKeyword; 656 if (numberFormat instanceof DecimalFormat) { 657 DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(quantity); 658 ((DecimalFormat) numberFormat).toNumberFormatter().formatImpl(dq, output); 659 pluralKeyword = pluralRules.select(dq); 660 } else { 661 String result = numberFormat.format(quantity); 662 output.append(result, null); 663 pluralKeyword = pluralRules.select(quantity); 664 } 665 StandardPlural pluralForm = StandardPlural.orOtherFromString(pluralKeyword); 666 667 String compiledPattern = getRelativeUnitPluralPattern(style, unit, pastFutureIndex, pluralForm); 668 SimpleFormatterImpl.formatPrefixSuffix(compiledPattern, Field.LITERAL, 0, output.length(), output); 669 return output; 670 } 671 672 /** 673 * Format a combination of RelativeDateTimeUnit and numeric offset 674 * using a numeric style, e.g. "1 week ago", "in 1 week", 675 * "5 weeks ago", "in 5 weeks". 676 * 677 * This method returns a String. To get more information about the 678 * formatting result, use formatNumericToValue(). 679 * 680 * @param offset The signed offset for the specified unit. This 681 * will be formatted according to this object's 682 * NumberFormat object. 683 * @param unit The unit to use when formatting the relative 684 * date, e.g. RelativeDateTimeUnit.WEEK, 685 * RelativeDateTimeUnit.FRIDAY. 686 * @return The formatted string (may be empty in case of error) 687 */ formatNumeric(double offset, RelativeDateTimeUnit unit)688 public String formatNumeric(double offset, RelativeDateTimeUnit unit) { 689 FormattedStringBuilder output = formatNumericImpl(offset, unit); 690 return adjustForContext(output.toString()); 691 } 692 693 /** 694 * Format a combination of RelativeDateTimeUnit and numeric offset 695 * using a numeric style, e.g. "1 week ago", "in 1 week", 696 * "5 weeks ago", "in 5 weeks". 697 * 698 * This method returns a FormattedRelativeDateTime, which exposes more 699 * information than the String returned by formatNumeric(). 700 * 701 * @param offset The signed offset for the specified unit. This 702 * will be formatted according to this object's 703 * NumberFormat object. 704 * @param unit The unit to use when formatting the relative 705 * date, e.g. RelativeDateTimeUnit.WEEK, 706 * RelativeDateTimeUnit.FRIDAY. 707 * @return The formatted string (may be empty in case of error) 708 */ formatNumericToValue(double offset, RelativeDateTimeUnit unit)709 public FormattedRelativeDateTime formatNumericToValue(double offset, RelativeDateTimeUnit unit) { 710 checkNoAdjustForContext(); 711 return new FormattedRelativeDateTime(formatNumericImpl(offset, unit)); 712 } 713 714 /** Implementation method for formatNumeric and formatNumericToValue */ formatNumericImpl(double offset, RelativeDateTimeUnit unit)715 private FormattedStringBuilder formatNumericImpl(double offset, RelativeDateTimeUnit unit) { 716 // TODO: 717 // The full implementation of this depends on CLDR data that is not yet available, 718 // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data. 719 // In the meantime do a quick bring-up by calling the old format method. When the 720 // new CLDR data is available, update the data storage accordingly, rewrite this 721 // to use it directly, and rewrite the old format method to call this new one; 722 // that is covered by https://unicode-org.atlassian.net/browse/ICU-12171. 723 RelativeUnit relunit = RelativeUnit.SECONDS; 724 switch (unit) { 725 case YEAR: relunit = RelativeUnit.YEARS; break; 726 case QUARTER: relunit = RelativeUnit.QUARTERS; break; 727 case MONTH: relunit = RelativeUnit.MONTHS; break; 728 case WEEK: relunit = RelativeUnit.WEEKS; break; 729 case DAY: relunit = RelativeUnit.DAYS; break; 730 case HOUR: relunit = RelativeUnit.HOURS; break; 731 case MINUTE: relunit = RelativeUnit.MINUTES; break; 732 case SECOND: break; // set above 733 case SUNDAY: relunit = RelativeUnit.SUNDAYS; break; 734 case MONDAY: relunit = RelativeUnit.MONDAYS; break; 735 case TUESDAY: relunit = RelativeUnit.TUESDAYS; break; 736 case WEDNESDAY: relunit = RelativeUnit.WEDNESDAYS; break; 737 case THURSDAY: relunit = RelativeUnit.THURSDAYS; break; 738 case FRIDAY: relunit = RelativeUnit.FRIDAYS; break; 739 case SATURDAY: relunit = RelativeUnit.SATURDAYS; break; 740 } 741 Direction direction = Direction.NEXT; 742 if (Double.compare(offset,0.0) < 0) { // needed to handle -0.0 743 direction = Direction.LAST; 744 offset = -offset; 745 } 746 return formatImpl(offset, direction, relunit); 747 } 748 749 private int[] styleToDateFormatSymbolsWidth = { 750 DateFormatSymbols.WIDE, DateFormatSymbols.SHORT, DateFormatSymbols.NARROW 751 }; 752 753 /** 754 * Formats a relative date without a quantity. 755 * 756 * This method returns a String. To get more information about the 757 * formatting result, use formatToValue(). 758 * 759 * @param direction NEXT, LAST, THIS, etc. 760 * @param unit e.g SATURDAY, DAY, MONTH 761 * @return the formatted string. If direction has a value that is documented as not being 762 * fully supported in every locale (for example NEXT_2 or LAST_2) then this function may 763 * return null to signal that no formatted string is available. 764 * @throws IllegalArgumentException if the direction is incompatible with 765 * unit this can occur with NOW which can only take PLAIN. 766 */ format(Direction direction, AbsoluteUnit unit)767 public String format(Direction direction, AbsoluteUnit unit) { 768 String result = formatAbsoluteImpl(direction, unit); 769 return result != null ? adjustForContext(result) : null; 770 } 771 772 /** 773 * Formats a relative date without a quantity. 774 * 775 * This method returns a FormattedRelativeDateTime, which exposes more 776 * information than the String returned by format(). 777 * 778 * @param direction NEXT, LAST, THIS, etc. 779 * @param unit e.g SATURDAY, DAY, MONTH 780 * @return the formatted string. If direction has a value that is documented as not being 781 * fully supported in every locale (for example NEXT_2 or LAST_2) then this function may 782 * return null to signal that no formatted string is available. 783 * @throws IllegalArgumentException if the direction is incompatible with 784 * unit this can occur with NOW which can only take PLAIN. 785 */ formatToValue(Direction direction, AbsoluteUnit unit)786 public FormattedRelativeDateTime formatToValue(Direction direction, AbsoluteUnit unit) { 787 checkNoAdjustForContext(); 788 String string = formatAbsoluteImpl(direction, unit); 789 if (string == null) { 790 return null; 791 } 792 FormattedStringBuilder nsb = new FormattedStringBuilder(); 793 nsb.append(string, Field.LITERAL); 794 return new FormattedRelativeDateTime(nsb); 795 } 796 797 /** Implementation method for format and formatToValue with AbsoluteUnit */ formatAbsoluteImpl(Direction direction, AbsoluteUnit unit)798 private String formatAbsoluteImpl(Direction direction, AbsoluteUnit unit) { 799 if (unit == AbsoluteUnit.NOW && direction != Direction.PLAIN) { 800 throw new IllegalArgumentException("NOW can only accept direction PLAIN."); 801 } 802 String result; 803 // Get plain day of week names from DateFormatSymbols. 804 if ((direction == Direction.PLAIN) && (AbsoluteUnit.SUNDAY.ordinal() <= unit.ordinal() && 805 unit.ordinal() <= AbsoluteUnit.SATURDAY.ordinal())) { 806 // Convert from AbsoluteUnit days to Calendar class indexing. 807 int dateSymbolsDayOrdinal = (unit.ordinal() - AbsoluteUnit.SUNDAY.ordinal()) + Calendar.SUNDAY; 808 String[] dayNames = 809 dateFormatSymbols.getWeekdays(DateFormatSymbols.STANDALONE, 810 styleToDateFormatSymbolsWidth[style.ordinal()]); 811 result = dayNames[dateSymbolsDayOrdinal]; 812 } else { 813 // Not PLAIN, or not a weekday. 814 result = getAbsoluteUnitString(style, unit, direction); 815 } 816 return result; 817 } 818 819 /** 820 * Format a combination of RelativeDateTimeUnit and numeric offset 821 * using a text style if possible, e.g. "last week", "this week", 822 * "next week", "yesterday", "tomorrow". Falls back to numeric 823 * style if no appropriate text term is available for the specified 824 * offset in the object’s locale. 825 * 826 * This method returns a String. To get more information about the 827 * formatting result, use formatToValue(). 828 * 829 * @param offset The signed offset for the specified field. 830 * @param unit The unit to use when formatting the relative 831 * date, e.g. RelativeDateTimeUnit.WEEK, 832 * RelativeDateTimeUnit.FRIDAY. 833 * @return The formatted string (may be empty in case of error) 834 */ format(double offset, RelativeDateTimeUnit unit)835 public String format(double offset, RelativeDateTimeUnit unit) { 836 return adjustForContext(formatRelativeImpl(offset, unit).toString()); 837 } 838 839 /** 840 * Format a combination of RelativeDateTimeUnit and numeric offset 841 * using a text style if possible, e.g. "last week", "this week", 842 * "next week", "yesterday", "tomorrow". Falls back to numeric 843 * style if no appropriate text term is available for the specified 844 * offset in the object’s locale. 845 * 846 * This method returns a FormattedRelativeDateTime, which exposes more 847 * information than the String returned by format(). 848 * 849 * @param offset The signed offset for the specified field. 850 * @param unit The unit to use when formatting the relative 851 * date, e.g. RelativeDateTimeUnit.WEEK, 852 * RelativeDateTimeUnit.FRIDAY. 853 * @return The formatted string (may be empty in case of error) 854 */ formatToValue(double offset, RelativeDateTimeUnit unit)855 public FormattedRelativeDateTime formatToValue(double offset, RelativeDateTimeUnit unit) { 856 checkNoAdjustForContext(); 857 CharSequence cs = formatRelativeImpl(offset, unit); 858 FormattedStringBuilder nsb; 859 if (cs instanceof FormattedStringBuilder) { 860 nsb = (FormattedStringBuilder) cs; 861 } else { 862 nsb = new FormattedStringBuilder(); 863 nsb.append(cs, Field.LITERAL); 864 } 865 return new FormattedRelativeDateTime(nsb); 866 } 867 868 869 /** Implementation method for format and formatToValue with RelativeDateTimeUnit. */ formatRelativeImpl(double offset, RelativeDateTimeUnit unit)870 private CharSequence formatRelativeImpl(double offset, RelativeDateTimeUnit unit) { 871 // TODO: 872 // The full implementation of this depends on CLDR data that is not yet available, 873 // see: http://unicode.org/cldr/trac/ticket/9165 Add more relative field data. 874 // In the meantime do a quick bring-up by calling the old format method. When the 875 // new CLDR data is available, update the data storage accordingly, rewrite this 876 // to use it directly, and rewrite the old format method to call this new one; 877 // that is covered by https://unicode-org.atlassian.net/browse/ICU-12171. 878 boolean useNumeric = true; 879 Direction direction = Direction.THIS; 880 if (offset > -2.1 && offset < 2.1) { 881 // Allow a 1% epsilon, so offsets in -1.01..-0.99 map to LAST 882 double offsetx100 = offset * 100.0; 883 int intoffsetx100 = (offsetx100 < 0)? (int)(offsetx100-0.5) : (int)(offsetx100+0.5); 884 switch (intoffsetx100) { 885 case -200/*-2*/: direction = Direction.LAST_2; useNumeric = false; break; 886 case -100/*-1*/: direction = Direction.LAST; useNumeric = false; break; 887 case 0/* 0*/: useNumeric = false; break; // direction = Direction.THIS was set above 888 case 100/* 1*/: direction = Direction.NEXT; useNumeric = false; break; 889 case 200/* 2*/: direction = Direction.NEXT_2; useNumeric = false; break; 890 default: break; 891 } 892 } 893 AbsoluteUnit absunit = AbsoluteUnit.NOW; 894 switch (unit) { 895 case YEAR: absunit = AbsoluteUnit.YEAR; break; 896 case QUARTER: absunit = AbsoluteUnit.QUARTER; break; 897 case MONTH: absunit = AbsoluteUnit.MONTH; break; 898 case WEEK: absunit = AbsoluteUnit.WEEK; break; 899 case DAY: absunit = AbsoluteUnit.DAY; break; 900 case SUNDAY: absunit = AbsoluteUnit.SUNDAY; break; 901 case MONDAY: absunit = AbsoluteUnit.MONDAY; break; 902 case TUESDAY: absunit = AbsoluteUnit.TUESDAY; break; 903 case WEDNESDAY: absunit = AbsoluteUnit.WEDNESDAY; break; 904 case THURSDAY: absunit = AbsoluteUnit.THURSDAY; break; 905 case FRIDAY: absunit = AbsoluteUnit.FRIDAY; break; 906 case SATURDAY: absunit = AbsoluteUnit.SATURDAY; break; 907 case HOUR: absunit = AbsoluteUnit.HOUR; break; 908 case MINUTE: absunit = AbsoluteUnit.MINUTE; break; 909 case SECOND: 910 if (direction == Direction.THIS) { 911 // absunit = AbsoluteUnit.NOW was set above 912 direction = Direction.PLAIN; 913 break; 914 } 915 // could just fall through here but that produces warnings 916 useNumeric = true; 917 break; 918 default: 919 useNumeric = true; 920 break; 921 } 922 if (!useNumeric) { 923 String result = formatAbsoluteImpl(direction, absunit); 924 if (result != null && result.length() > 0) { 925 return result; 926 } 927 } 928 // otherwise fallback to formatNumeric 929 return formatNumericImpl(offset, unit); 930 } 931 932 /** 933 * Gets the string value from qualitativeUnitMap with fallback based on style. 934 */ getAbsoluteUnitString(Style style, AbsoluteUnit unit, Direction direction)935 private String getAbsoluteUnitString(Style style, AbsoluteUnit unit, Direction direction) { 936 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> unitMap; 937 EnumMap<Direction, String> dirMap; 938 939 do { 940 unitMap = qualitativeUnitMap.get(style); 941 if (unitMap != null) { 942 dirMap = unitMap.get(unit); 943 if (dirMap != null) { 944 String result = dirMap.get(direction); 945 if (result != null) { 946 return result; 947 } 948 } 949 950 } 951 952 // Consider other styles from alias fallback. 953 // Data loading guaranteed no endless loops. 954 } while ((style = fallbackCache[style.ordinal()]) != null); 955 return null; 956 } 957 958 /** 959 * Combines a relative date string and a time string in this object's 960 * locale. This is done with the same date-time separator used for the 961 * default calendar in this locale. 962 * @param relativeDateString the relative date e.g 'yesterday' 963 * @param timeString the time e.g '3:45' 964 * @return the date and time concatenated according to the default 965 * calendar in this locale e.g 'yesterday, 3:45' 966 */ combineDateAndTime(String relativeDateString, String timeString)967 public String combineDateAndTime(String relativeDateString, String timeString) { 968 // BEGIN Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717). 969 MessageFormat msgFmt = new MessageFormat(""); 970 msgFmt.applyPattern(combinedDateAndTime, MessagePattern.ApostropheMode.DOUBLE_REQUIRED); 971 StringBuffer combinedDateTimeBuffer = new StringBuffer(128); 972 return msgFmt.format(new Object[] { timeString, relativeDateString}, 973 combinedDateTimeBuffer, new FieldPosition(0)).toString(); 974 // END Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717). 975 } 976 977 /** 978 * Returns a copy of the NumberFormat this object is using. 979 * @return A copy of the NumberFormat. 980 */ getNumberFormat()981 public NumberFormat getNumberFormat() { 982 // This class is thread-safe, yet numberFormat is not. To ensure thread-safety of this 983 // class we must guarantee that only one thread at a time uses our numberFormat. 984 synchronized (numberFormat) { 985 return (NumberFormat) numberFormat.clone(); 986 } 987 } 988 989 /** 990 * Return capitalization context. 991 * @return The capitalization context. 992 */ getCapitalizationContext()993 public DisplayContext getCapitalizationContext() { 994 return capitalizationContext; 995 } 996 997 /** 998 * Return style 999 * @return The formatting style. 1000 */ getFormatStyle()1001 public Style getFormatStyle() { 1002 return style; 1003 } 1004 adjustForContext(String originalFormattedString)1005 private String adjustForContext(String originalFormattedString) { 1006 if (breakIterator == null || originalFormattedString.length() == 0 1007 || !UCharacter.isLowerCase(UCharacter.codePointAt(originalFormattedString, 0))) { 1008 return originalFormattedString; 1009 } 1010 synchronized (breakIterator) { 1011 return UCharacter.toTitleCase( 1012 locale, 1013 originalFormattedString, 1014 breakIterator, 1015 UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT); 1016 } 1017 } 1018 checkNoAdjustForContext()1019 private void checkNoAdjustForContext() { 1020 if (breakIterator != null) { 1021 throw new UnsupportedOperationException("Capitalization context is not supported in formatV"); 1022 } 1023 } 1024 RelativeDateTimeFormatter( EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap, String combinedDateAndTime, PluralRules pluralRules, NumberFormat numberFormat, Style style, DisplayContext capitalizationContext, BreakIterator breakIterator, ULocale locale)1025 private RelativeDateTimeFormatter( 1026 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, 1027 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap, 1028 String combinedDateAndTime, 1029 PluralRules pluralRules, 1030 NumberFormat numberFormat, 1031 Style style, 1032 DisplayContext capitalizationContext, 1033 BreakIterator breakIterator, 1034 ULocale locale) { 1035 this.qualitativeUnitMap = qualitativeUnitMap; 1036 this.patternMap = patternMap; 1037 this.combinedDateAndTime = combinedDateAndTime; 1038 this.pluralRules = pluralRules; 1039 this.numberFormat = numberFormat; 1040 this.style = style; 1041 if (capitalizationContext.type() != DisplayContext.Type.CAPITALIZATION) { 1042 throw new IllegalArgumentException(capitalizationContext.toString()); 1043 } 1044 this.capitalizationContext = capitalizationContext; 1045 this.breakIterator = breakIterator; 1046 this.locale = locale; 1047 this.dateFormatSymbols = new DateFormatSymbols(locale); 1048 } 1049 getRelativeUnitPluralPattern( Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm)1050 private String getRelativeUnitPluralPattern( 1051 Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm) { 1052 if (pluralForm != StandardPlural.OTHER) { 1053 String formatter = getRelativeUnitPattern(style, unit, pastFutureIndex, pluralForm); 1054 if (formatter != null) { 1055 return formatter; 1056 } 1057 } 1058 return getRelativeUnitPattern(style, unit, pastFutureIndex, StandardPlural.OTHER); 1059 } 1060 getRelativeUnitPattern( Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm)1061 private String getRelativeUnitPattern( 1062 Style style, RelativeUnit unit, int pastFutureIndex, StandardPlural pluralForm) { 1063 int pluralIndex = pluralForm.ordinal(); 1064 do { 1065 EnumMap<RelativeUnit, String[][]> unitMap = patternMap.get(style); 1066 if (unitMap != null) { 1067 String[][] spfCompiledPatterns = unitMap.get(unit); 1068 if (spfCompiledPatterns != null) { 1069 if (spfCompiledPatterns[pastFutureIndex][pluralIndex] != null) { 1070 return spfCompiledPatterns[pastFutureIndex][pluralIndex]; 1071 } 1072 } 1073 1074 } 1075 1076 // Consider other styles from alias fallback. 1077 // Data loading guaranteed no endless loops. 1078 } while ((style = fallbackCache[style.ordinal()]) != null); 1079 return null; 1080 } 1081 1082 private final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap; 1083 private final EnumMap<Style, EnumMap<RelativeUnit, String[][]>> patternMap; 1084 1085 // Android-changed: use MessageFormat instead of SimpleFormatterImpl (b/63745717). 1086 private final String combinedDateAndTime; // MessageFormat pattern for combining date and time. 1087 private final PluralRules pluralRules; 1088 private final NumberFormat numberFormat; 1089 1090 private final Style style; 1091 private final DisplayContext capitalizationContext; 1092 private final BreakIterator breakIterator; 1093 private final ULocale locale; 1094 1095 private final DateFormatSymbols dateFormatSymbols; 1096 1097 private static final Style fallbackCache[] = new Style[Style.INDEX_COUNT]; 1098 1099 private static class RelativeDateTimeFormatterData { RelativeDateTimeFormatterData( EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap, String dateTimePattern)1100 public RelativeDateTimeFormatterData( 1101 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap, 1102 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap, 1103 String dateTimePattern) { 1104 this.qualitativeUnitMap = qualitativeUnitMap; 1105 this.relUnitPatternMap = relUnitPatternMap; 1106 1107 this.dateTimePattern = dateTimePattern; 1108 } 1109 1110 public final EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap; 1111 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> relUnitPatternMap; 1112 public final String dateTimePattern; // Example: "{1}, {0}" 1113 } 1114 1115 private static class Cache { 1116 private final CacheBase<String, RelativeDateTimeFormatterData, ULocale> cache = 1117 new SoftCache<String, RelativeDateTimeFormatterData, ULocale>() { 1118 @Override 1119 protected RelativeDateTimeFormatterData createInstance(String key, ULocale locale) { 1120 return new Loader(locale).load(); 1121 } 1122 }; 1123 get(ULocale locale)1124 public RelativeDateTimeFormatterData get(ULocale locale) { 1125 String key = locale.toString(); 1126 return cache.getInstance(key, locale); 1127 } 1128 } 1129 keyToDirection(UResource.Key key)1130 private static Direction keyToDirection(UResource.Key key) { 1131 if (key.contentEquals("-2")) { 1132 return Direction.LAST_2; 1133 } 1134 if (key.contentEquals("-1")) { 1135 return Direction.LAST; 1136 } 1137 if (key.contentEquals("0")) { 1138 return Direction.THIS; 1139 } 1140 if (key.contentEquals("1")) { 1141 return Direction.NEXT; 1142 } 1143 if (key.contentEquals("2")) { 1144 return Direction.NEXT_2; 1145 } 1146 return null; 1147 } 1148 1149 /** 1150 * Sink for enumerating all of the relative data time formatter names. 1151 * 1152 * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root): 1153 * Only store a value if it is still missing, that is, it has not been overridden. 1154 */ 1155 private static final class RelDateTimeDataSink extends UResource.Sink { 1156 1157 // For white list of units to handle in RelativeDateTimeFormatter. 1158 private enum DateTimeUnit { 1159 SECOND(RelativeUnit.SECONDS, null), 1160 MINUTE(RelativeUnit.MINUTES, AbsoluteUnit.MINUTE), 1161 HOUR(RelativeUnit.HOURS, AbsoluteUnit.HOUR), 1162 DAY(RelativeUnit.DAYS, AbsoluteUnit.DAY), 1163 WEEK(RelativeUnit.WEEKS, AbsoluteUnit.WEEK), 1164 MONTH(RelativeUnit.MONTHS, AbsoluteUnit.MONTH), 1165 QUARTER(RelativeUnit.QUARTERS, AbsoluteUnit.QUARTER), 1166 YEAR(RelativeUnit.YEARS, AbsoluteUnit.YEAR), 1167 SUNDAY(RelativeUnit.SUNDAYS, AbsoluteUnit.SUNDAY), 1168 MONDAY(RelativeUnit.MONDAYS, AbsoluteUnit.MONDAY), 1169 TUESDAY(RelativeUnit.TUESDAYS, AbsoluteUnit.TUESDAY), 1170 WEDNESDAY(RelativeUnit.WEDNESDAYS, AbsoluteUnit.WEDNESDAY), 1171 THURSDAY(RelativeUnit.THURSDAYS, AbsoluteUnit.THURSDAY), 1172 FRIDAY(RelativeUnit.FRIDAYS, AbsoluteUnit.FRIDAY), 1173 SATURDAY(RelativeUnit.SATURDAYS, AbsoluteUnit.SATURDAY); 1174 1175 RelativeUnit relUnit; 1176 AbsoluteUnit absUnit; 1177 DateTimeUnit(RelativeUnit relUnit, AbsoluteUnit absUnit)1178 DateTimeUnit(RelativeUnit relUnit, AbsoluteUnit absUnit) { 1179 this.relUnit = relUnit; 1180 this.absUnit = absUnit; 1181 } 1182 orNullFromString(CharSequence keyword)1183 private static final DateTimeUnit orNullFromString(CharSequence keyword) { 1184 // Quick check from string to enum. 1185 switch (keyword.length()) { 1186 case 3: 1187 if ("day".contentEquals(keyword)) { 1188 return DAY; 1189 } else if ("sun".contentEquals(keyword)) { 1190 return SUNDAY; 1191 } else if ("mon".contentEquals(keyword)) { 1192 return MONDAY; 1193 } else if ("tue".contentEquals(keyword)) { 1194 return TUESDAY; 1195 } else if ("wed".contentEquals(keyword)) { 1196 return WEDNESDAY; 1197 } else if ("thu".contentEquals(keyword)) { 1198 return THURSDAY; 1199 } else if ("fri".contentEquals(keyword)) { 1200 return FRIDAY; 1201 } else if ("sat".contentEquals(keyword)) { 1202 return SATURDAY; 1203 } 1204 break; 1205 case 4: 1206 if ("hour".contentEquals(keyword)) { 1207 return HOUR; 1208 } else if ("week".contentEquals(keyword)) { 1209 return WEEK; 1210 } else if ("year".contentEquals(keyword)) { 1211 return YEAR; 1212 } 1213 break; 1214 case 5: 1215 if ("month".contentEquals(keyword)) { 1216 return MONTH; 1217 } 1218 break; 1219 case 6: 1220 if ("minute".contentEquals(keyword)) { 1221 return MINUTE; 1222 }else if ("second".contentEquals(keyword)) { 1223 return SECOND; 1224 } 1225 break; 1226 case 7: 1227 if ("quarter".contentEquals(keyword)) { 1228 return QUARTER; // RelativeUnit.QUARTERS is deprecated 1229 } 1230 break; 1231 default: 1232 break; 1233 } 1234 return null; 1235 } 1236 } 1237 1238 EnumMap<Style, EnumMap<AbsoluteUnit, EnumMap<Direction, String>>> qualitativeUnitMap = 1239 new EnumMap<>(Style.class); 1240 EnumMap<Style, EnumMap<RelativeUnit, String[][]>> styleRelUnitPatterns = 1241 new EnumMap<>(Style.class); 1242 1243 StringBuilder sb = new StringBuilder(); 1244 1245 // Values keep between levels of parsing the CLDR data. 1246 int pastFutureIndex; 1247 Style style; // {LONG, SHORT, NARROW} Derived from unit key string. 1248 DateTimeUnit unit; // From the unit key string, with the style (e.g., "-short") separated out. 1249 styleFromKey(UResource.Key key)1250 private Style styleFromKey(UResource.Key key) { 1251 if (key.endsWith("-short")) { 1252 return Style.SHORT; 1253 } else if (key.endsWith("-narrow")) { 1254 return Style.NARROW; 1255 } else { 1256 return Style.LONG; 1257 } 1258 } 1259 styleFromAlias(UResource.Value value)1260 private Style styleFromAlias(UResource.Value value) { 1261 String s = value.getAliasString(); 1262 if (s.endsWith("-short")) { 1263 return Style.SHORT; 1264 } else if (s.endsWith("-narrow")) { 1265 return Style.NARROW; 1266 } else { 1267 return Style.LONG; 1268 } 1269 } 1270 styleSuffixLength(Style style)1271 private static int styleSuffixLength(Style style) { 1272 switch (style) { 1273 case SHORT: return 6; 1274 case NARROW: return 7; 1275 default: return 0; 1276 } 1277 } 1278 consumeTableRelative(UResource.Key key, UResource.Value value)1279 public void consumeTableRelative(UResource.Key key, UResource.Value value) { 1280 UResource.Table unitTypesTable = value.getTable(); 1281 for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) { 1282 if (value.getType() == ICUResourceBundle.STRING) { 1283 String valueString = value.getString(); 1284 1285 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> absMap = qualitativeUnitMap.get(style); 1286 1287 if (unit.relUnit == RelativeUnit.SECONDS) { 1288 if (key.contentEquals("0")) { 1289 // Handle Zero seconds for "now". 1290 EnumMap<Direction, String> unitStrings = absMap.get(AbsoluteUnit.NOW); 1291 if (unitStrings == null) { 1292 unitStrings = new EnumMap<>(Direction.class); 1293 absMap.put(AbsoluteUnit.NOW, unitStrings); 1294 } 1295 if (unitStrings.get(Direction.PLAIN) == null) { 1296 unitStrings.put(Direction.PLAIN, valueString); 1297 } 1298 continue; 1299 } 1300 } 1301 Direction keyDirection = keyToDirection(key); 1302 if (keyDirection == null) { 1303 continue; 1304 } 1305 AbsoluteUnit absUnit = unit.absUnit; 1306 if (absUnit == null) { 1307 continue; 1308 } 1309 1310 if (absMap == null) { 1311 absMap = new EnumMap<>(AbsoluteUnit.class); 1312 qualitativeUnitMap.put(style, absMap); 1313 } 1314 EnumMap<Direction, String> dirMap = absMap.get(absUnit); 1315 if (dirMap == null) { 1316 dirMap = new EnumMap<>(Direction.class); 1317 absMap.put(absUnit, dirMap); 1318 } 1319 if (dirMap.get(keyDirection) == null) { 1320 // Do not override values already entered. 1321 dirMap.put(keyDirection, value.getString()); 1322 } 1323 } 1324 } 1325 } 1326 1327 // Record past or future and consumeTableRelativeTime(UResource.Key key, UResource.Value value)1328 public void consumeTableRelativeTime(UResource.Key key, UResource.Value value) { 1329 if (unit.relUnit == null) { 1330 return; 1331 } 1332 UResource.Table unitTypesTable = value.getTable(); 1333 for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) { 1334 if (key.contentEquals("past")) { 1335 pastFutureIndex = 0; 1336 } else if (key.contentEquals("future")) { 1337 pastFutureIndex = 1; 1338 } else { 1339 continue; 1340 } 1341 // Get the details of the relative time. 1342 consumeTimeDetail(key, value); 1343 } 1344 } 1345 consumeTimeDetail(UResource.Key key, UResource.Value value)1346 public void consumeTimeDetail(UResource.Key key, UResource.Value value) { 1347 UResource.Table unitTypesTable = value.getTable(); 1348 1349 EnumMap<RelativeUnit, String[][]> unitPatterns = styleRelUnitPatterns.get(style); 1350 if (unitPatterns == null) { 1351 unitPatterns = new EnumMap<>(RelativeUnit.class); 1352 styleRelUnitPatterns.put(style, unitPatterns); 1353 } 1354 String[][] patterns = unitPatterns.get(unit.relUnit); 1355 if (patterns == null) { 1356 patterns = new String[2][StandardPlural.COUNT]; 1357 unitPatterns.put(unit.relUnit, patterns); 1358 } 1359 1360 // Stuff the pattern for the correct plural index with a simple formatter. 1361 for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) { 1362 if (value.getType() == ICUResourceBundle.STRING) { 1363 int pluralIndex = StandardPlural.indexFromString(key.toString()); 1364 if (patterns[pastFutureIndex][pluralIndex] == null) { 1365 patterns[pastFutureIndex][pluralIndex] = 1366 SimpleFormatterImpl.compileToStringMinMaxArguments( 1367 value.getString(), sb, 0, 1); 1368 } 1369 } 1370 } 1371 } 1372 handlePlainDirection(UResource.Key key, UResource.Value value)1373 private void handlePlainDirection(UResource.Key key, UResource.Value value) { 1374 AbsoluteUnit absUnit = unit.absUnit; 1375 if (absUnit == null) { 1376 return; // Not interesting. 1377 } 1378 EnumMap<AbsoluteUnit, EnumMap<Direction, String>> unitMap = 1379 qualitativeUnitMap.get(style); 1380 if (unitMap == null) { 1381 unitMap = new EnumMap<>(AbsoluteUnit.class); 1382 qualitativeUnitMap.put(style, unitMap); 1383 } 1384 EnumMap<Direction,String> dirMap = unitMap.get(absUnit); 1385 if (dirMap == null) { 1386 dirMap = new EnumMap<>(Direction.class); 1387 unitMap.put(absUnit, dirMap); 1388 } 1389 if (dirMap.get(Direction.PLAIN) == null) { 1390 dirMap.put(Direction.PLAIN, value.toString()); 1391 } 1392 } 1393 1394 // Handle at the Unit level, consumeTimeUnit(UResource.Key key, UResource.Value value)1395 public void consumeTimeUnit(UResource.Key key, UResource.Value value) { 1396 UResource.Table unitTypesTable = value.getTable(); 1397 for (int i = 0; unitTypesTable.getKeyAndValue(i, key, value); i++) { 1398 if (key.contentEquals("dn") && value.getType() == ICUResourceBundle.STRING) { 1399 handlePlainDirection(key, value); 1400 } 1401 if (value.getType() == ICUResourceBundle.TABLE) { 1402 if (key.contentEquals("relative")) { 1403 consumeTableRelative(key, value); 1404 } else if (key.contentEquals("relativeTime")) { 1405 consumeTableRelativeTime(key, value); 1406 } 1407 } 1408 } 1409 } 1410 handleAlias(UResource.Key key, UResource.Value value, boolean noFallback)1411 private void handleAlias(UResource.Key key, UResource.Value value, boolean noFallback) { 1412 Style sourceStyle = styleFromKey(key); 1413 int limit = key.length() - styleSuffixLength(sourceStyle); 1414 DateTimeUnit unit = DateTimeUnit.orNullFromString(key.substring(0, limit)); 1415 if (unit != null) { 1416 // Record the fallback chain for the values. 1417 // At formatting time, limit to 2 levels of fallback. 1418 Style targetStyle = styleFromAlias(value); 1419 if (sourceStyle == targetStyle) { 1420 throw new ICUException("Invalid style fallback from " + sourceStyle + " to itself"); 1421 } 1422 1423 // Check for inconsistent fallbacks. 1424 if (fallbackCache[sourceStyle.ordinal()] == null) { 1425 fallbackCache[sourceStyle.ordinal()] = targetStyle; 1426 } else if (fallbackCache[sourceStyle.ordinal()] != targetStyle) { 1427 throw new ICUException( 1428 "Inconsistent style fallback for style " + sourceStyle + " to " + targetStyle); 1429 } 1430 return; 1431 } 1432 } 1433 1434 @Override put(UResource.Key key, UResource.Value value, boolean noFallback)1435 public void put(UResource.Key key, UResource.Value value, boolean noFallback) { 1436 // Main entry point to sink 1437 if (value.getType() == ICUResourceBundle.ALIAS) { 1438 return; 1439 } 1440 1441 UResource.Table table = value.getTable(); 1442 // Process each key / value in this table. 1443 for (int i = 0; table.getKeyAndValue(i, key, value); i++) { 1444 if (value.getType() == ICUResourceBundle.ALIAS) { 1445 handleAlias(key, value, noFallback); 1446 } else { 1447 // Remember style and unit for deeper levels. 1448 style = styleFromKey(key); 1449 int limit = key.length() - styleSuffixLength(style); 1450 unit = DateTimeUnit.orNullFromString(key.substring(0, limit)); 1451 if (unit != null) { 1452 // Process only if unitString is in the white list. 1453 consumeTimeUnit(key, value); 1454 } 1455 } 1456 } 1457 } 1458 RelDateTimeDataSink()1459 RelDateTimeDataSink() { 1460 } 1461 } 1462 1463 private static class Loader { 1464 private final ULocale ulocale; 1465 Loader(ULocale ulocale)1466 public Loader(ULocale ulocale) { 1467 this.ulocale = ulocale; 1468 } 1469 getDateTimePattern()1470 private String getDateTimePattern() { 1471 Calendar cal = Calendar.getInstance(ulocale); 1472 return Calendar.getDateAtTimePattern(cal, ulocale, DateFormat.MEDIUM); 1473 } 1474 load()1475 public RelativeDateTimeFormatterData load() { 1476 // Sink for traversing data. 1477 RelDateTimeDataSink sink = new RelDateTimeDataSink(); 1478 1479 ICUResourceBundle r = (ICUResourceBundle)UResourceBundle. 1480 getBundleInstance(ICUData.ICU_BASE_NAME, ulocale); 1481 r.getAllItemsWithFallback("fields", sink); 1482 1483 // Check fallbacks array for loops or too many levels. 1484 for (Style testStyle : Style.values()) { 1485 Style newStyle1 = fallbackCache[testStyle.ordinal()]; 1486 // Data loading guaranteed newStyle1 != testStyle. 1487 if (newStyle1 != null) { 1488 Style newStyle2 = fallbackCache[newStyle1.ordinal()]; 1489 if (newStyle2 != null) { 1490 // No fallback should take more than 2 steps. 1491 if (fallbackCache[newStyle2.ordinal()] != null) { 1492 throw new IllegalStateException("Style fallback too deep"); 1493 } 1494 } 1495 } 1496 } 1497 1498 return new RelativeDateTimeFormatterData( 1499 sink.qualitativeUnitMap, sink.styleRelUnitPatterns, 1500 getDateTimePattern()); 1501 } 1502 } 1503 1504 private static final Cache cache = new Cache(); 1505 } 1506