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