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