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