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) 2008-2016, International Business Machines Corporation and 7 * others. All Rights Reserved. 8 ******************************************************************************* 9 */ 10 11 package ohos.global.icu.text; 12 13 import java.io.Serializable; 14 import java.util.HashMap; 15 import java.util.HashSet; 16 import java.util.LinkedHashMap; 17 import java.util.LinkedHashSet; 18 import java.util.Locale; 19 import java.util.Map; 20 import java.util.Map.Entry; 21 import java.util.MissingResourceException; 22 import java.util.Objects; 23 import java.util.Set; 24 25 import ohos.global.icu.impl.ICUCache; 26 import ohos.global.icu.impl.ICUData; 27 import ohos.global.icu.impl.ICUResourceBundle; 28 import ohos.global.icu.impl.SimpleCache; 29 import ohos.global.icu.impl.UResource; 30 import ohos.global.icu.impl.UResource.Key; 31 import ohos.global.icu.impl.UResource.Value; 32 import ohos.global.icu.util.Calendar; 33 import ohos.global.icu.util.Freezable; 34 import ohos.global.icu.util.ICUCloneNotSupportedException; 35 import ohos.global.icu.util.ICUException; 36 import ohos.global.icu.util.ULocale; 37 import ohos.global.icu.util.UResourceBundle; 38 39 /** 40 * DateIntervalInfo is a public class for encapsulating localizable 41 * date time interval patterns. It is used by DateIntervalFormat. 42 * 43 * <P> 44 * For most users, ordinary use of DateIntervalFormat does not need to create 45 * DateIntervalInfo object directly. 46 * DateIntervalFormat will take care of it when creating a date interval 47 * formatter when user pass in skeleton and locale. 48 * 49 * <P> 50 * For power users, who want to create their own date interval patterns, 51 * or want to re-set date interval patterns, they could do so by 52 * directly creating DateIntervalInfo and manipulating it. 53 * 54 * <P> 55 * Logically, the interval patterns are mappings 56 * from (skeleton, the_largest_different_calendar_field) 57 * to (date_interval_pattern). 58 * 59 * <P> 60 * A skeleton 61 * <ol> 62 * <li> 63 * only keeps the field pattern letter and ignores all other parts 64 * in a pattern, such as space, punctuations, and string literals. 65 * <li> 66 * hides the order of fields. 67 * <li> 68 * might hide a field's pattern letter length. 69 * 70 * For those non-digit calendar fields, the pattern letter length is 71 * important, such as MMM, MMMM, and MMMMM; EEE and EEEE, 72 * and the field's pattern letter length is honored. 73 * 74 * For the digit calendar fields, such as M or MM, d or dd, yy or yyyy, 75 * the field pattern length is ignored and the best match, which is defined 76 * in date time patterns, will be returned without honor the field pattern 77 * letter length in skeleton. 78 * </ol> 79 * 80 * <P> 81 * The calendar fields we support for interval formatting are: 82 * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and 83 * second (though we do not currently have specific intervalFormat data for 84 * skeletons with seconds). 85 * Those calendar fields can be defined in the following order: 86 * year > month > date > am-pm > hour > minute > second 87 * 88 * The largest different calendar fields between 2 calendars is the 89 * first different calendar field in above order. 90 * 91 * For example: the largest different calendar fields between "Jan 10, 2007" 92 * and "Feb 20, 2008" is year. 93 * 94 * <P> 95 * There is a set of pre-defined static skeleton strings. 96 * There are pre-defined interval patterns for those pre-defined skeletons 97 * in locales' resource files. 98 * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is "yMMMd", 99 * in en_US, if the largest different calendar field between date1 and date2 100 * is "year", the date interval pattern is "MMM d, yyyy - MMM d, yyyy", 101 * such as "Jan 10, 2007 - Jan 10, 2008". 102 * If the largest different calendar field between date1 and date2 is "month", 103 * the date interval pattern is "MMM d - MMM d, yyyy", 104 * such as "Jan 10 - Feb 10, 2007". 105 * If the largest different calendar field between date1 and date2 is "day", 106 * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007". 107 * 108 * For date skeleton, the interval patterns when year, or month, or date is 109 * different are defined in resource files. 110 * For time skeleton, the interval patterns when am/pm, or hour, or minute is 111 * different are defined in resource files. 112 * 113 * 114 * <P> 115 * There are 2 dates in interval pattern. For most locales, the first date 116 * in an interval pattern is the earlier date. There might be a locale in which 117 * the first date in an interval pattern is the later date. 118 * We use fallback format for the default order for the locale. 119 * For example, if the fallback format is "{0} - {1}", it means 120 * the first date in the interval pattern for this locale is earlier date. 121 * If the fallback format is "{1} - {0}", it means the first date is the 122 * later date. 123 * For a particular interval pattern, the default order can be overriden 124 * by prefixing "latestFirst:" or "earliestFirst:" to the interval pattern. 125 * For example, if the fallback format is "{0}-{1}", 126 * but for skeleton "yMMMd", the interval pattern when day is different is 127 * "latestFirst:d-d MMM yy", it means by default, the first date in interval 128 * pattern is the earlier date. But for skeleton "yMMMd", when day is different, 129 * the first date in "d-d MMM yy" is the later date. 130 * 131 * <P> 132 * The recommended way to create a DateIntervalFormat object is to pass in 133 * the locale. 134 * By using a Locale parameter, the DateIntervalFormat object is 135 * initialized with the pre-defined interval patterns for a given or 136 * default locale. 137 * <P> 138 * Users can also create DateIntervalFormat object 139 * by supplying their own interval patterns. 140 * It provides flexibility for power usage. 141 * 142 * <P> 143 * After a DateIntervalInfo object is created, clients may modify 144 * the interval patterns using setIntervalPattern function as so desired. 145 * Currently, users can only set interval patterns when the following 146 * calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH, 147 * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND. 148 * Interval patterns when other calendar fields are different is not supported. 149 * <P> 150 * DateIntervalInfo objects are cloneable. 151 * When clients obtain a DateIntervalInfo object, 152 * they can feel free to modify it as necessary. 153 * <P> 154 * DateIntervalInfo are not expected to be subclassed. 155 * Data for a calendar is loaded out of resource bundles. 156 * Through ICU 4.4, date interval patterns are only supported in the Gregoria 157 * calendar; non-Gregorian calendars are supported from ICU 4.4.1. 158 */ 159 160 public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>, Serializable { 161 162 /* Save the interval pattern information. 163 * Interval pattern consists of 2 single date patterns and the separator. 164 * For example, interval pattern "MMM d - MMM d, yyyy" consists 165 * a single date pattern "MMM d", another single date pattern "MMM d, yyyy", 166 * and a separator "-". 167 * Also, the first date appears in an interval pattern could be 168 * the earlier date or the later date. 169 * And such information is saved in the interval pattern as well. 170 */ 171 static final int currentSerialVersion = 1; 172 173 /** 174 * PatternInfo class saves the first and second part of interval pattern, 175 * and whether the interval pattern is earlier date first. 176 */ 177 public static final class PatternInfo implements Cloneable, Serializable { 178 static final int currentSerialVersion = 1; 179 private static final long serialVersionUID = 1; 180 private final String fIntervalPatternFirstPart; 181 private final String fIntervalPatternSecondPart; 182 /* 183 * Whether the first date in interval pattern is later date or not. 184 * Fallback format set the default ordering. 185 * And for a particular interval pattern, the order can be 186 * overriden by prefixing the interval pattern with "latestFirst:" or 187 * "earliestFirst:" 188 * For example, given 2 date, Jan 10, 2007 to Feb 10, 2007. 189 * if the fallback format is "{0} - {1}", 190 * and the pattern is "d MMM - d MMM yyyy", the interval format is 191 * "10 Jan - 10 Feb, 2007". 192 * If the pattern is "latestFirst:d MMM - d MMM yyyy", 193 * the interval format is "10 Feb - 10 Jan, 2007" 194 */ 195 private final boolean fFirstDateInPtnIsLaterDate; 196 197 /** 198 * Constructs a <code>PatternInfo</code> object. 199 * @param firstPart The first part of interval pattern. 200 * @param secondPart The second part of interval pattern. 201 * @param firstDateInPtnIsLaterDate Whether the first date in interval patter is later date or not. 202 */ PatternInfo(String firstPart, String secondPart, boolean firstDateInPtnIsLaterDate)203 public PatternInfo(String firstPart, String secondPart, 204 boolean firstDateInPtnIsLaterDate) { 205 fIntervalPatternFirstPart = firstPart; 206 fIntervalPatternSecondPart = secondPart; 207 fFirstDateInPtnIsLaterDate = firstDateInPtnIsLaterDate; 208 } 209 210 /** 211 * Returns the first part of interval pattern. 212 * @return The first part of interval pattern. 213 */ getFirstPart()214 public String getFirstPart() { 215 return fIntervalPatternFirstPart; 216 } 217 218 /** 219 * Returns the second part of interval pattern. 220 * @return The second part of interval pattern. 221 */ getSecondPart()222 public String getSecondPart() { 223 return fIntervalPatternSecondPart; 224 } 225 226 /** 227 * Returns whether the first date in interval patter is later date or not. 228 * @return Whether the first date in interval patter is later date or not. 229 */ firstDateInPtnIsLaterDate()230 public boolean firstDateInPtnIsLaterDate() { 231 return fFirstDateInPtnIsLaterDate; 232 } 233 234 /** 235 * Compares the specified object with this <code>PatternInfo</code> for equality. 236 * @param a The object to be compared. 237 * @return <code>true</code> if the specified object is equal to this <code>PatternInfo</code>. 238 */ 239 @Override equals(Object a)240 public boolean equals(Object a) { 241 if (a instanceof PatternInfo) { 242 PatternInfo patternInfo = (PatternInfo)a; 243 return Objects.equals(fIntervalPatternFirstPart, patternInfo.fIntervalPatternFirstPart) && 244 Objects.equals(fIntervalPatternSecondPart, patternInfo.fIntervalPatternSecondPart) && 245 fFirstDateInPtnIsLaterDate == patternInfo.fFirstDateInPtnIsLaterDate; 246 } 247 return false; 248 } 249 250 /** 251 * Returns the hash code of this <code>PatternInfo</code>. 252 * @return A hash code value for this object. 253 */ 254 @Override hashCode()255 public int hashCode() { 256 int hash = fIntervalPatternFirstPart != null ? fIntervalPatternFirstPart.hashCode() : 0; 257 if (fIntervalPatternSecondPart != null) { 258 hash ^= fIntervalPatternSecondPart.hashCode(); 259 } 260 if (fFirstDateInPtnIsLaterDate) { 261 hash ^= -1; 262 } 263 return hash; 264 } 265 266 /** 267 * {@inheritDoc} 268 */ 269 @Override toString()270 public String toString() { 271 return "{first=«" + fIntervalPatternFirstPart + "», second=«" + fIntervalPatternSecondPart + "», reversed:" + fFirstDateInPtnIsLaterDate + "}"; 272 } 273 } 274 275 // Following is package protected since 276 // it is shared with DateIntervalFormat. 277 static final String[] CALENDAR_FIELD_TO_PATTERN_LETTER = 278 { 279 "G", "y", "M", 280 "w", "W", "d", 281 "D", "E", "F", 282 "a", "h", "H", 283 "m", "s", "S", // MINUTE, SECOND, MILLISECOND 284 "z", " ", "Y", // ZONE_OFFSET, DST_OFFSET, YEAR_WOY 285 "e", "u", "g", // DOW_LOCAL, EXTENDED_YEAR, JULIAN_DAY 286 "A", " ", " ", // MILLISECONDS_IN_DAY, IS_LEAP_MONTH. 287 }; 288 289 290 private static final long serialVersionUID = 1; 291 private static final int MINIMUM_SUPPORTED_CALENDAR_FIELD = 292 Calendar.MILLISECOND; 293 //private static boolean DEBUG = true; 294 295 private static String CALENDAR_KEY = "calendar"; 296 private static String INTERVAL_FORMATS_KEY = "intervalFormats"; 297 private static String FALLBACK_STRING = "fallback"; 298 private static String LATEST_FIRST_PREFIX = "latestFirst:"; 299 private static String EARLIEST_FIRST_PREFIX = "earliestFirst:"; 300 301 // DateIntervalInfo cache 302 private final static ICUCache<String, DateIntervalInfo> DIICACHE = new SimpleCache<>(); 303 304 305 // default interval pattern on the skeleton, {0} - {1} 306 private String fFallbackIntervalPattern; 307 // default order 308 private boolean fFirstDateInPtnIsLaterDate = false; 309 310 // HashMap( skeleton, HashMap(largest_different_field, pattern) ) 311 private Map<String, Map<String, PatternInfo>> fIntervalPatterns = null; 312 313 private transient volatile boolean frozen = false; 314 315 // If true, fIntervalPatterns should not be modified in-place because it 316 // is shared with other objects. Unlike frozen which is always true once 317 // set to true, this field can go from true to false as long as frozen is 318 // false. 319 private transient boolean fIntervalPatternsReadOnly = false; 320 321 322 /** 323 * Create empty instance. 324 * It does not initialize any interval patterns except 325 * that it initialize default fall-back pattern as "{0} - {1}", 326 * which can be reset by setFallbackIntervalPattern(). 327 * 328 * It should be followed by setFallbackIntervalPattern() and 329 * setIntervalPattern(), 330 * and is recommended to be used only for power users who 331 * wants to create their own interval patterns and use them to create 332 * date interval formatter. 333 * @deprecated This API is ICU internal only. 334 * @hide deprecated on icu4j-org 335 * @hide draft / provisional / internal are hidden on OHOS 336 */ 337 @Deprecated DateIntervalInfo()338 public DateIntervalInfo() 339 { 340 fIntervalPatterns = new HashMap<>(); 341 fFallbackIntervalPattern = "{0} \u2013 {1}"; 342 } 343 344 345 /** 346 * Construct DateIntervalInfo for the given locale, 347 * @param locale the interval patterns are loaded from the appropriate 348 * calendar data (specified calendar or default calendar) 349 * in this locale. 350 */ DateIntervalInfo(ULocale locale)351 public DateIntervalInfo(ULocale locale) 352 { 353 initializeData(locale); 354 } 355 356 357 /** 358 * Construct DateIntervalInfo for the given {@link java.util.Locale}. 359 * @param locale the interval patterns are loaded from the appropriate 360 * calendar data (specified calendar or default calendar) 361 * in this locale. 362 */ DateIntervalInfo(Locale locale)363 public DateIntervalInfo(Locale locale) 364 { 365 this(ULocale.forLocale(locale)); 366 } 367 368 /* 369 * Initialize the DateIntervalInfo from locale 370 * @param locale the given locale. 371 */ initializeData(ULocale locale)372 private void initializeData(ULocale locale) 373 { 374 String key = locale.toString(); 375 DateIntervalInfo dii = DIICACHE.get(key); 376 if ( dii == null ) { 377 // initialize data from scratch 378 setup(locale); 379 // Marking fIntervalPatterns read-only makes cloning cheaper. 380 fIntervalPatternsReadOnly = true; 381 // We freeze what goes in the cache without freezing this object. 382 DIICACHE.put(key, ((DateIntervalInfo) clone()).freeze()); 383 } else { 384 initializeFromReadOnlyPatterns(dii); 385 } 386 } 387 388 389 390 /** 391 * Initialize this object 392 * @param dii must have read-only fIntervalPatterns. 393 */ initializeFromReadOnlyPatterns(DateIntervalInfo dii)394 private void initializeFromReadOnlyPatterns(DateIntervalInfo dii) { 395 fFallbackIntervalPattern = dii.fFallbackIntervalPattern; 396 fFirstDateInPtnIsLaterDate = dii.fFirstDateInPtnIsLaterDate; 397 fIntervalPatterns = dii.fIntervalPatterns; 398 fIntervalPatternsReadOnly = true; 399 } 400 401 402 403 /** 404 * Sink for enumerating all of the date interval skeletons. 405 */ 406 private static final class DateIntervalSink extends UResource.Sink { 407 408 /** 409 * Accepted pattern letters: 410 * Calendar.YEAR 411 * Calendar.MONTH 412 * Calendar.DATE 413 * Calendar.AM_PM 414 * Calendar.HOUR 415 * Calendar.HOUR_OF_DAY 416 * Calendar.MINUTE 417 * Calendar.SECOND 418 * Calendar.MILLISECOND 419 */ 420 private static final String ACCEPTED_PATTERN_LETTERS = "GyMdahHmsS"; 421 422 // Output data 423 DateIntervalInfo dateIntervalInfo; 424 425 // Alias handling 426 String nextCalendarType; 427 428 // Constructor DateIntervalSink(DateIntervalInfo dateIntervalInfo)429 public DateIntervalSink(DateIntervalInfo dateIntervalInfo) { 430 this.dateIntervalInfo = dateIntervalInfo; 431 } 432 433 @Override put(Key key, Value value, boolean noFallback)434 public void put(Key key, Value value, boolean noFallback) { 435 // Iterate over all the calendar entries and only pick the 'intervalFormats' table. 436 UResource.Table dateIntervalData = value.getTable(); 437 for (int i = 0; dateIntervalData.getKeyAndValue(i, key, value); i++) { 438 if (!key.contentEquals(INTERVAL_FORMATS_KEY)) { 439 continue; 440 } 441 442 // Handle aliases and tables. Ignore the rest. 443 if (value.getType() == ICUResourceBundle.ALIAS) { 444 // Get the calendar type from the alias path. 445 nextCalendarType = getCalendarTypeFromPath(value.getAliasString()); 446 break; 447 448 } else if (value.getType() == ICUResourceBundle.TABLE) { 449 // Iterate over all the skeletons in the 'intervalFormat' table. 450 UResource.Table skeletonData = value.getTable(); 451 for (int j = 0; skeletonData.getKeyAndValue(j, key, value); j++) { 452 if (value.getType() == ICUResourceBundle.TABLE) { 453 // Process the skeleton 454 processSkeletonTable(key, value); 455 } 456 } 457 break; 458 } 459 } 460 } 461 462 /** Processes the patterns for a skeleton table. */ processSkeletonTable(Key key, Value value)463 public void processSkeletonTable(Key key, Value value) { 464 // Iterate over all the patterns in the current skeleton table 465 String currentSkeleton = key.toString(); 466 UResource.Table patternData = value.getTable(); 467 for (int k = 0; patternData.getKeyAndValue(k, key, value); k++) { 468 if (value.getType() == ICUResourceBundle.STRING) { 469 // Process the key 470 CharSequence patternLetter = validateAndProcessPatternLetter(key); 471 472 // If the calendar field has a valid value 473 if (patternLetter != null) { 474 // Get the largest different calendar unit 475 String lrgDiffCalUnit = patternLetter.toString(); 476 477 // Set the interval pattern 478 setIntervalPatternIfAbsent(currentSkeleton, lrgDiffCalUnit, value); 479 } 480 } 481 } 482 } 483 484 /** 485 * Returns and resets the next calendar type. 486 * @return Next calendar type 487 */ getAndResetNextCalendarType()488 public String getAndResetNextCalendarType() { 489 String tmpCalendarType = nextCalendarType; 490 nextCalendarType = null; 491 return tmpCalendarType; 492 } 493 494 // Alias' path prefix and suffix. 495 private static final String DATE_INTERVAL_PATH_PREFIX = 496 "/LOCALE/" + CALENDAR_KEY + "/"; 497 private static final String DATE_INTERVAL_PATH_SUFFIX = 498 "/" + INTERVAL_FORMATS_KEY; 499 500 /** 501 * Extracts the calendar type from the path 502 * @param path 503 * @return Calendar Type 504 */ getCalendarTypeFromPath(String path)505 private String getCalendarTypeFromPath(String path) { 506 if (path.startsWith(DATE_INTERVAL_PATH_PREFIX) && 507 path.endsWith(DATE_INTERVAL_PATH_SUFFIX)) { 508 return path.substring(DATE_INTERVAL_PATH_PREFIX.length(), 509 path.length() - DATE_INTERVAL_PATH_SUFFIX.length()); 510 } 511 throw new ICUException("Malformed 'intervalFormat' alias path: " + path); 512 } 513 514 /** 515 * Processes the pattern letter 516 * @param patternLetter 517 * @return Pattern letter 518 */ validateAndProcessPatternLetter(CharSequence patternLetter)519 private CharSequence validateAndProcessPatternLetter(CharSequence patternLetter) { 520 // Check that patternLetter is just one letter 521 if (patternLetter.length() != 1) { return null; } 522 523 // Check that the pattern letter is accepted 524 char letter = patternLetter.charAt(0); 525 if (ACCEPTED_PATTERN_LETTERS.indexOf(letter) < 0) { 526 return null; 527 } 528 529 // Replace 'h' for 'H' 530 if (letter == CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR_OF_DAY].charAt(0)) { 531 patternLetter = CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR]; 532 } 533 534 return patternLetter; 535 } 536 537 /** 538 * Stores the interval pattern for the current skeleton in the internal data structure 539 * if it's not present. 540 * @param lrgDiffCalUnit 541 * @param intervalPattern 542 */ setIntervalPatternIfAbsent(String currentSkeleton, String lrgDiffCalUnit, Value intervalPattern)543 private void setIntervalPatternIfAbsent(String currentSkeleton, String lrgDiffCalUnit, Value intervalPattern) { 544 // Check if the pattern has already been stored on the data structure. 545 Map<String, PatternInfo> patternsOfOneSkeleton = 546 dateIntervalInfo.fIntervalPatterns.get(currentSkeleton); 547 if (patternsOfOneSkeleton == null || !patternsOfOneSkeleton.containsKey(lrgDiffCalUnit)) { 548 // Store the pattern 549 dateIntervalInfo.setIntervalPatternInternally(currentSkeleton, lrgDiffCalUnit, 550 intervalPattern.toString()); 551 } 552 } 553 } 554 555 556 /* 557 * Initialize DateIntervalInfo from calendar data 558 * @param calData calendar data 559 */ setup(ULocale locale)560 private void setup(ULocale locale) { 561 int DEFAULT_HASH_SIZE = 19; 562 fIntervalPatterns = new HashMap<>(DEFAULT_HASH_SIZE); 563 // initialize to guard if there is no interval date format defined in 564 // resource files 565 fFallbackIntervalPattern = "{0} \u2013 {1}"; 566 567 try { 568 // Get the correct calendar type 569 String calendarTypeToUse = locale.getKeywordValue("calendar"); 570 if ( calendarTypeToUse == null ) { 571 String[] preferredCalendarTypes = 572 Calendar.getKeywordValuesForLocale("calendar", locale, true); 573 calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar 574 } 575 if ( calendarTypeToUse == null ) { 576 calendarTypeToUse = "gregorian"; // fallback 577 } 578 579 // Instantiate the sink to process the data and the resource bundle 580 DateIntervalSink sink = new DateIntervalSink(this); 581 ICUResourceBundle resource = 582 (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale); 583 584 // Get the fallback pattern 585 String fallbackPattern = resource.getStringWithFallback(CALENDAR_KEY + "/" + calendarTypeToUse 586 + "/" + INTERVAL_FORMATS_KEY + "/" + FALLBACK_STRING); 587 setFallbackIntervalPattern(fallbackPattern); 588 589 // Already loaded calendar types 590 Set<String> loadedCalendarTypes = new HashSet<>(); 591 592 while (calendarTypeToUse != null) { 593 // Throw an exception when a loop is detected 594 if (loadedCalendarTypes.contains(calendarTypeToUse)) { 595 throw new ICUException("Loop in calendar type fallback: " + calendarTypeToUse); 596 } 597 598 // Register the calendar type to avoid loops 599 loadedCalendarTypes.add(calendarTypeToUse); 600 601 // Get all resources for this calendar type 602 String pathToIntervalFormats = CALENDAR_KEY + "/" + calendarTypeToUse; 603 resource.getAllItemsWithFallback(pathToIntervalFormats, sink); 604 605 // Get next calendar type to load if there was an alias pointing at it 606 calendarTypeToUse = sink.getAndResetNextCalendarType(); 607 } 608 } catch ( MissingResourceException e) { 609 // Will fallback to {data0} - {date1} 610 } 611 } 612 613 614 /* 615 * Split interval patterns into 2 part. 616 * @param intervalPattern interval pattern 617 * @return the index in interval pattern which split the pattern into 2 part 618 */ splitPatternInto2Part(String intervalPattern)619 private static int splitPatternInto2Part(String intervalPattern) { 620 boolean inQuote = false; 621 char prevCh = 0; 622 int count = 0; 623 624 /* repeatedPattern used to record whether a pattern has already seen. 625 It is a pattern applies to first calendar if it is first time seen, 626 otherwise, it is a pattern applies to the second calendar 627 */ 628 int[] patternRepeated = new int[58]; 629 630 int PATTERN_CHAR_BASE = 0x41; 631 632 /* loop through the pattern string character by character looking for 633 * the first repeated pattern letter, which breaks the interval pattern 634 * into 2 parts. 635 */ 636 int i; 637 boolean foundRepetition = false; 638 for (i = 0; i < intervalPattern.length(); ++i) { 639 char ch = intervalPattern.charAt(i); 640 641 if (ch != prevCh && count > 0) { 642 // check the repeativeness of pattern letter 643 int repeated = patternRepeated[prevCh - PATTERN_CHAR_BASE]; 644 if ( repeated == 0 ) { 645 patternRepeated[prevCh - PATTERN_CHAR_BASE] = 1; 646 } else { 647 foundRepetition = true; 648 break; 649 } 650 count = 0; 651 } 652 if (ch == '\'') { 653 // Consecutive single quotes are a single quote literal, 654 // either outside of quotes or between quotes 655 if ((i+1) < intervalPattern.length() && 656 intervalPattern.charAt(i+1) == '\'') { 657 ++i; 658 } else { 659 inQuote = ! inQuote; 660 } 661 } 662 else if (!inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) 663 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) { 664 // ch is a date-time pattern character 665 prevCh = ch; 666 ++count; 667 } 668 } 669 // check last pattern char, distinguish 670 // "dd MM" ( no repetition ), 671 // "d-d"(last char repeated ), and 672 // "d-d MM" ( repetition found ) 673 if ( count > 0 && foundRepetition == false ) { 674 if ( patternRepeated[prevCh - PATTERN_CHAR_BASE] == 0 ) { 675 count = 0; 676 } 677 } 678 return (i - count); 679 } 680 681 682 /** 683 * Provides a way for client to build interval patterns. 684 * User could construct DateIntervalInfo by providing 685 * a list of skeletons and their patterns. 686 * <P> 687 * For example: 688 * <pre> 689 * DateIntervalInfo dIntervalInfo = new DateIntervalInfo(); 690 * dIntervalInfo.setIntervalPattern("yMd", Calendar.YEAR, "'from' yyyy-M-d 'to' yyyy-M-d"); 691 * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.MONTH, "'from' yyyy MMM d 'to' MMM d"); 692 * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.DAY, "yyyy MMM d-d"); 693 * dIntervalInfo.setFallbackIntervalPattern("{0} ~ {1}"); 694 * </pre> 695 * 696 * Restriction: 697 * Currently, users can only set interval patterns when the following 698 * calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH, 699 * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, and MILLISECOND. 700 * Interval patterns when other calendar fields are different are 701 * not supported. 702 * 703 * @param skeleton the skeleton on which interval pattern based 704 * @param lrgDiffCalUnit the largest different calendar unit. 705 * @param intervalPattern the interval pattern on the largest different 706 * calendar unit. 707 * For example, if lrgDiffCalUnit is 708 * "year", the interval pattern for en_US when year 709 * is different could be "'from' yyyy 'to' yyyy". 710 * @throws IllegalArgumentException if setting interval pattern on 711 * a calendar field that is smaller 712 * than the MINIMUM_SUPPORTED_CALENDAR_FIELD 713 * @throws UnsupportedOperationException if the object is frozen 714 */ setIntervalPattern(String skeleton, int lrgDiffCalUnit, String intervalPattern)715 public void setIntervalPattern(String skeleton, 716 int lrgDiffCalUnit, 717 String intervalPattern) 718 { 719 if ( frozen ) { 720 throw new UnsupportedOperationException("no modification is allowed after DII is frozen"); 721 } 722 if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD ) { 723 throw new IllegalArgumentException("calendar field is larger than MINIMUM_SUPPORTED_CALENDAR_FIELD"); 724 } 725 if (fIntervalPatternsReadOnly) { 726 fIntervalPatterns = cloneIntervalPatterns(fIntervalPatterns); 727 fIntervalPatternsReadOnly = false; 728 } 729 PatternInfo ptnInfo = setIntervalPatternInternally(skeleton, 730 CALENDAR_FIELD_TO_PATTERN_LETTER[lrgDiffCalUnit], 731 intervalPattern); 732 if ( lrgDiffCalUnit == Calendar.HOUR_OF_DAY ) { 733 setIntervalPattern(skeleton, 734 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM], 735 ptnInfo); 736 setIntervalPattern(skeleton, 737 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR], 738 ptnInfo); 739 } else if ( lrgDiffCalUnit == Calendar.DAY_OF_MONTH || 740 lrgDiffCalUnit == Calendar.DAY_OF_WEEK ) { 741 setIntervalPattern(skeleton, 742 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], 743 ptnInfo); 744 } 745 } 746 747 748 /* Set Interval pattern. 749 * 750 * It generates the interval pattern info, 751 * afer which, not only sets the interval pattern info into the hash map, 752 * but also returns the interval pattern info to the caller 753 * so that caller can re-use it. 754 * 755 * @param skeleton skeleton on which the interval pattern based 756 * @param lrgDiffCalUnit the largest different calendar unit. 757 * @param intervalPattern the interval pattern on the largest different 758 * calendar unit. 759 * @return the interval pattern pattern information 760 */ setIntervalPatternInternally(String skeleton, String lrgDiffCalUnit, String intervalPattern)761 private PatternInfo setIntervalPatternInternally(String skeleton, 762 String lrgDiffCalUnit, 763 String intervalPattern) { 764 Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton); 765 boolean emptyHash = false; 766 if (patternsOfOneSkeleton == null) { 767 patternsOfOneSkeleton = new HashMap<>(); 768 emptyHash = true; 769 } 770 boolean order = fFirstDateInPtnIsLaterDate; 771 // check for "latestFirst:" or "earliestFirst:" prefix 772 if ( intervalPattern.startsWith(LATEST_FIRST_PREFIX) ) { 773 order = true; 774 int prefixLength = LATEST_FIRST_PREFIX.length(); 775 intervalPattern = intervalPattern.substring(prefixLength, intervalPattern.length()); 776 } else if ( intervalPattern.startsWith(EARLIEST_FIRST_PREFIX) ) { 777 order = false; 778 int earliestFirstLength = EARLIEST_FIRST_PREFIX.length(); 779 intervalPattern = intervalPattern.substring(earliestFirstLength, intervalPattern.length()); 780 } 781 PatternInfo itvPtnInfo = genPatternInfo(intervalPattern, order); 782 783 patternsOfOneSkeleton.put(lrgDiffCalUnit, itvPtnInfo); 784 if ( emptyHash == true ) { 785 fIntervalPatterns.put(skeleton, patternsOfOneSkeleton); 786 } 787 788 return itvPtnInfo; 789 } 790 791 792 /* Set Interval pattern. 793 * 794 * @param skeleton skeleton on which the interval pattern based 795 * @param lrgDiffCalUnit the largest different calendar unit. 796 * @param ptnInfo interval pattern infomration 797 */ setIntervalPattern(String skeleton, String lrgDiffCalUnit, PatternInfo ptnInfo)798 private void setIntervalPattern(String skeleton, 799 String lrgDiffCalUnit, 800 PatternInfo ptnInfo) { 801 Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton); 802 patternsOfOneSkeleton.put(lrgDiffCalUnit, ptnInfo); 803 } 804 805 806 /** 807 * Break interval patterns as 2 part and save them into pattern info. 808 * @param intervalPattern interval pattern 809 * @param laterDateFirst whether the first date in intervalPattern 810 * is earlier date or later date 811 * @return pattern info object 812 * @deprecated This API is ICU internal only. 813 * @hide deprecated on icu4j-org 814 * @hide draft / provisional / internal are hidden on OHOS 815 */ 816 @Deprecated genPatternInfo(String intervalPattern, boolean laterDateFirst)817 public static PatternInfo genPatternInfo(String intervalPattern, 818 boolean laterDateFirst) { 819 int splitPoint = splitPatternInto2Part(intervalPattern); 820 821 String firstPart = intervalPattern.substring(0, splitPoint); 822 String secondPart = null; 823 if ( splitPoint < intervalPattern.length() ) { 824 secondPart = intervalPattern.substring(splitPoint, intervalPattern.length()); 825 } 826 827 return new PatternInfo(firstPart, secondPart, laterDateFirst); 828 } 829 830 831 /** 832 * Get the interval pattern given the largest different calendar field. 833 * @param skeleton the skeleton 834 * @param field the largest different calendar field 835 * @return interval pattern return null if interval pattern is not found. 836 * @throws IllegalArgumentException if getting interval pattern on 837 * a calendar field that is smaller 838 * than the MINIMUM_SUPPORTED_CALENDAR_FIELD 839 */ getIntervalPattern(String skeleton, int field)840 public PatternInfo getIntervalPattern(String skeleton, int field) 841 { 842 if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD ) { 843 throw new IllegalArgumentException("no support for field less than MILLISECOND"); 844 } 845 Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton); 846 if ( patternsOfOneSkeleton != null ) { 847 PatternInfo intervalPattern = patternsOfOneSkeleton. 848 get(CALENDAR_FIELD_TO_PATTERN_LETTER[field]); 849 if ( intervalPattern != null ) { 850 return intervalPattern; 851 } 852 } 853 return null; 854 } 855 856 857 858 /** 859 * Get the fallback interval pattern. 860 * @return fallback interval pattern 861 */ getFallbackIntervalPattern()862 public String getFallbackIntervalPattern() 863 { 864 return fFallbackIntervalPattern; 865 } 866 867 868 /** 869 * Re-set the fallback interval pattern. 870 * 871 * In construction, default fallback pattern is set as "{0} - {1}". 872 * And constructor taking locale as parameter will set the 873 * fallback pattern as what defined in the locale resource file. 874 * 875 * This method provides a way for user to replace the fallback pattern. 876 * 877 * @param fallbackPattern fall-back interval pattern. 878 * @throws UnsupportedOperationException if the object is frozen 879 * @throws IllegalArgumentException if there is no pattern {0} or 880 * pattern {1} in fallbakckPattern 881 */ setFallbackIntervalPattern(String fallbackPattern)882 public void setFallbackIntervalPattern(String fallbackPattern) 883 { 884 if ( frozen ) { 885 throw new UnsupportedOperationException("no modification is allowed after DII is frozen"); 886 } 887 int firstPatternIndex = fallbackPattern.indexOf("{0}"); 888 int secondPatternIndex = fallbackPattern.indexOf("{1}"); 889 if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) { 890 throw new IllegalArgumentException("no pattern {0} or pattern {1} in fallbackPattern"); 891 } 892 if ( firstPatternIndex > secondPatternIndex ) { 893 fFirstDateInPtnIsLaterDate = true; 894 } 895 fFallbackIntervalPattern = fallbackPattern; 896 } 897 898 899 /** 900 * Get default order -- whether the first date in pattern is later date 901 * or not. 902 * 903 * return default date ordering in interval pattern. TRUE if the first date 904 * in pattern is later date, FALSE otherwise. 905 */ getDefaultOrder()906 public boolean getDefaultOrder() 907 { 908 return fFirstDateInPtnIsLaterDate; 909 } 910 911 912 /** 913 * Clone this object. 914 * @return a copy of the object 915 */ 916 @Override clone()917 public Object clone() 918 { 919 if ( frozen ) { 920 return this; 921 } 922 return cloneUnfrozenDII(); 923 } 924 925 926 /* 927 * Clone an unfrozen DateIntervalInfo object. 928 * @return a copy of the object 929 */ cloneUnfrozenDII()930 private Object cloneUnfrozenDII() //throws IllegalStateException 931 { 932 try { 933 DateIntervalInfo other = (DateIntervalInfo) super.clone(); 934 other.fFallbackIntervalPattern=fFallbackIntervalPattern; 935 other.fFirstDateInPtnIsLaterDate = fFirstDateInPtnIsLaterDate; 936 if (fIntervalPatternsReadOnly) { 937 other.fIntervalPatterns = fIntervalPatterns; 938 other.fIntervalPatternsReadOnly = true; 939 } else { 940 other.fIntervalPatterns = cloneIntervalPatterns(fIntervalPatterns); 941 other.fIntervalPatternsReadOnly = false; 942 } 943 other.frozen = false; 944 return other; 945 } catch ( CloneNotSupportedException e ) { 946 ///CLOVER:OFF 947 throw new ICUCloneNotSupportedException("clone is not supported", e); 948 ///CLOVER:ON 949 } 950 } 951 cloneIntervalPatterns( Map<String, Map<String, PatternInfo>> patterns)952 private static Map<String, Map<String, PatternInfo>> cloneIntervalPatterns( 953 Map<String, Map<String, PatternInfo>> patterns) { 954 Map<String, Map<String, PatternInfo>> result = new HashMap<>(); 955 for (Entry<String, Map<String, PatternInfo>> skeletonEntry : patterns.entrySet()) { 956 String skeleton = skeletonEntry.getKey(); 957 Map<String, PatternInfo> patternsOfOneSkeleton = skeletonEntry.getValue(); 958 Map<String, PatternInfo> oneSetPtn = new HashMap<>(); 959 for (Entry<String, PatternInfo> calEntry : patternsOfOneSkeleton.entrySet()) { 960 String calField = calEntry.getKey(); 961 PatternInfo value = calEntry.getValue(); 962 oneSetPtn.put(calField, value); 963 } 964 result.put(skeleton, oneSetPtn); 965 } 966 return result; 967 } 968 969 970 971 /** 972 * {@inheritDoc} 973 */ 974 @Override isFrozen()975 public boolean isFrozen() { 976 return frozen; 977 } 978 979 /** 980 * {@inheritDoc} 981 */ 982 @Override freeze()983 public DateIntervalInfo freeze() { 984 fIntervalPatternsReadOnly = true; 985 frozen = true; 986 return this; 987 } 988 989 /** 990 * {@inheritDoc} 991 */ 992 @Override cloneAsThawed()993 public DateIntervalInfo cloneAsThawed() { 994 DateIntervalInfo result = (DateIntervalInfo) (this.cloneUnfrozenDII()); 995 return result; 996 } 997 998 999 /** 1000 * Parse skeleton, save each field's width. 1001 * It is used for looking for best match skeleton, 1002 * and adjust pattern field width. 1003 * @param skeleton skeleton to be parsed 1004 * @param skeletonFieldWidth parsed skeleton field width 1005 */ parseSkeleton(String skeleton, int[] skeletonFieldWidth)1006 static void parseSkeleton(String skeleton, int[] skeletonFieldWidth) { 1007 int PATTERN_CHAR_BASE = 0x41; 1008 for ( int i = 0; i < skeleton.length(); ++i ) { 1009 ++skeletonFieldWidth[skeleton.charAt(i) - PATTERN_CHAR_BASE]; 1010 } 1011 } 1012 1013 1014 1015 /* 1016 * Check whether one field width is numeric while the other is string. 1017 * 1018 * TODO (xji): make it general 1019 * 1020 * @param fieldWidth one field width 1021 * @param anotherFieldWidth another field width 1022 * @param patternLetter pattern letter char 1023 * @return true if one field width is numeric and the other is string, 1024 * false otherwise. 1025 */ stringNumeric(int fieldWidth, int anotherFieldWidth, char patternLetter)1026 private static boolean stringNumeric(int fieldWidth, 1027 int anotherFieldWidth, 1028 char patternLetter) { 1029 if ( patternLetter == 'M' ) { 1030 if ( fieldWidth <= 2 && anotherFieldWidth > 2 || 1031 fieldWidth > 2 && anotherFieldWidth <= 2 ) { 1032 return true; 1033 } 1034 } 1035 return false; 1036 } 1037 1038 1039 /* 1040 * given an input skeleton, get the best match skeleton 1041 * which has pre-defined interval pattern in resource file. 1042 * 1043 * TODO (xji): set field weight or 1044 * isolate the funtionality in DateTimePatternGenerator 1045 * @param inputSkeleton input skeleton 1046 * @return 0, if there is exact match for input skeleton 1047 * 1, if there is only field width difference between 1048 * the best match and the input skeleton 1049 * 2, the only field difference is 'v' and 'z' 1050 * -1, if there is calendar field difference between 1051 * the best match and the input skeleton 1052 */ getBestSkeleton(String inputSkeleton)1053 DateIntervalFormat.BestMatchInfo getBestSkeleton(String inputSkeleton) { 1054 String bestSkeleton = inputSkeleton; 1055 int[] inputSkeletonFieldWidth = new int[58]; 1056 int[] skeletonFieldWidth = new int[58]; 1057 1058 final int DIFFERENT_FIELD = 0x1000; 1059 final int STRING_NUMERIC_DIFFERENCE = 0x100; 1060 final int BASE = 0x41; 1061 1062 // TODO: this is a hack for 'v' and 'z' 1063 // resource bundle only have time skeletons ending with 'v', 1064 // but not for time skeletons ending with 'z'. 1065 boolean replaceZWithV = false; 1066 if ( inputSkeleton.indexOf('z') != -1 ) { 1067 inputSkeleton = inputSkeleton.replace('z', 'v'); 1068 replaceZWithV = true; 1069 } 1070 1071 parseSkeleton(inputSkeleton, inputSkeletonFieldWidth); 1072 int bestDistance = Integer.MAX_VALUE; 1073 // 0 means exact the same skeletons; 1074 // 1 means having the same field, but with different length, 1075 // 2 means only z/v differs 1076 // -1 means having different field. 1077 int bestFieldDifference = 0; 1078 for (String skeleton : fIntervalPatterns.keySet()) { 1079 // clear skeleton field width 1080 for ( int i = 0; i < skeletonFieldWidth.length; ++i ) { 1081 skeletonFieldWidth[i] = 0; 1082 } 1083 parseSkeleton(skeleton, skeletonFieldWidth); 1084 // calculate distance 1085 int distance = 0; 1086 int fieldDifference = 1; 1087 for ( int i = 0; i < inputSkeletonFieldWidth.length; ++i ) { 1088 int inputFieldWidth = inputSkeletonFieldWidth[i]; 1089 int fieldWidth = skeletonFieldWidth[i]; 1090 if ( inputFieldWidth == fieldWidth ) { 1091 continue; 1092 } 1093 if ( inputFieldWidth == 0 ) { 1094 fieldDifference = -1; 1095 distance += DIFFERENT_FIELD; 1096 } else if ( fieldWidth == 0 ) { 1097 fieldDifference = -1; 1098 distance += DIFFERENT_FIELD; 1099 } else if (stringNumeric(inputFieldWidth, fieldWidth, 1100 (char)(i+BASE) ) ) { 1101 distance += STRING_NUMERIC_DIFFERENCE; 1102 } else { 1103 distance += Math.abs(inputFieldWidth - fieldWidth); 1104 } 1105 } 1106 if ( distance < bestDistance ) { 1107 bestSkeleton = skeleton; 1108 bestDistance = distance; 1109 bestFieldDifference = fieldDifference; 1110 } 1111 if ( distance == 0 ) { 1112 bestFieldDifference = 0; 1113 break; 1114 } 1115 } 1116 if ( replaceZWithV && bestFieldDifference != -1 ) { 1117 bestFieldDifference = 2; 1118 } 1119 return new DateIntervalFormat.BestMatchInfo(bestSkeleton, bestFieldDifference); 1120 } 1121 1122 /** 1123 * Override equals 1124 */ 1125 @Override equals(Object a)1126 public boolean equals(Object a) { 1127 if ( a instanceof DateIntervalInfo ) { 1128 DateIntervalInfo dtInfo = (DateIntervalInfo)a; 1129 return fIntervalPatterns.equals(dtInfo.fIntervalPatterns); 1130 } 1131 return false; 1132 } 1133 1134 /** 1135 * Override hashcode 1136 */ 1137 @Override hashCode()1138 public int hashCode() { 1139 return fIntervalPatterns.hashCode(); 1140 } 1141 1142 /** 1143 * @deprecated This API is ICU internal only. 1144 * @hide deprecated on icu4j-org 1145 * @hide draft / provisional / internal are hidden on OHOS 1146 */ 1147 @Deprecated getPatterns()1148 public Map<String,Set<String>> getPatterns() { 1149 LinkedHashMap<String,Set<String>> result = new LinkedHashMap<>(); 1150 for (Entry<String, Map<String, PatternInfo>> entry : fIntervalPatterns.entrySet()) { 1151 result.put(entry.getKey(), new LinkedHashSet<>(entry.getValue().keySet())); 1152 } 1153 return result; 1154 } 1155 1156 /** 1157 * Get the internal patterns, with a deep clone for safety. 1158 * @deprecated This API is ICU internal only. 1159 * @hide deprecated on icu4j-org 1160 * @hide draft / provisional / internal are hidden on OHOS 1161 */ 1162 @Deprecated getRawPatterns()1163 public Map<String, Map<String, PatternInfo>> getRawPatterns() { 1164 LinkedHashMap<String, Map<String, PatternInfo>> result = new LinkedHashMap<>(); 1165 for (Entry<String, Map<String, PatternInfo>> entry : fIntervalPatterns.entrySet()) { 1166 result.put(entry.getKey(), new LinkedHashMap<>(entry.getValue())); 1167 } 1168 return result; 1169 } 1170 }// end class DateIntervalInfo 1171