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 * Copyright (C) 2008-2016, International Business Machines 6 * Corporation and others. All Rights Reserved. 7 */ 8 9 package ohos.global.icu.text; 10 11 import java.io.IOException; 12 import java.io.InvalidObjectException; 13 import java.io.ObjectInputStream; 14 import java.text.AttributedCharacterIterator; 15 import java.text.FieldPosition; 16 import java.text.ParsePosition; 17 import java.util.ArrayList; 18 import java.util.Collections; 19 import java.util.HashMap; 20 import java.util.List; 21 import java.util.Locale; 22 import java.util.Map; 23 24 import ohos.global.icu.impl.FormattedValueFieldPositionIteratorImpl; 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.SimpleFormatterImpl; 30 import ohos.global.icu.impl.Utility; 31 import ohos.global.icu.text.DateIntervalInfo.PatternInfo; 32 import ohos.global.icu.util.Calendar; 33 import ohos.global.icu.util.DateInterval; 34 import ohos.global.icu.util.Output; 35 import ohos.global.icu.util.TimeZone; 36 import ohos.global.icu.util.ULocale; 37 import ohos.global.icu.util.ULocale.Category; 38 import ohos.global.icu.util.UResourceBundle; 39 40 41 /** 42 * DateIntervalFormat is a class for formatting and parsing date 43 * intervals in a language-independent manner. 44 * Only formatting is supported. Parsing is not supported. 45 * 46 * <P> 47 * Date interval means from one date to another date, 48 * for example, from "Jan 11, 2008" to "Jan 18, 2008". 49 * We introduced class DateInterval to represent it. 50 * DateInterval is a pair of UDate, which is 51 * the standard milliseconds since 24:00 GMT, Jan 1, 1970. 52 * 53 * <P> 54 * DateIntervalFormat formats a DateInterval into 55 * text as compactly as possible. 56 * For example, the date interval format from "Jan 11, 2008" to "Jan 18,. 2008" 57 * is "Jan 11-18, 2008" for English. 58 * And it parses text into DateInterval, 59 * although initially, parsing is not supported. 60 * 61 * <P> 62 * There is no structural information in date time patterns. 63 * For any punctuations and string literals inside a date time pattern, 64 * we do not know whether it is just a separator, or a prefix, or a suffix. 65 * Without such information, so, it is difficult to generate a sub-pattern 66 * (or super-pattern) by algorithm. 67 * So, formatting a DateInterval is pattern-driven. It is very 68 * similar to formatting in SimpleDateFormat. 69 * We introduce class DateIntervalInfo to save date interval 70 * patterns, similar to date time pattern in SimpleDateFormat. 71 * 72 * <P> 73 * Logically, the interval patterns are mappings 74 * from (skeleton, the_largest_different_calendar_field) 75 * to (date_interval_pattern). 76 * 77 * <P> 78 * A skeleton 79 * <ol> 80 * <li> 81 * only keeps the field pattern letter and ignores all other parts 82 * in a pattern, such as space, punctuations, and string literals. 83 * <li> 84 * hides the order of fields. 85 * <li> 86 * might hide a field's pattern letter length. 87 * 88 * For those non-digit calendar fields, the pattern letter length is 89 * important, such as MMM, MMMM, and MMMMM; EEE and EEEE, 90 * and the field's pattern letter length is honored. 91 * 92 * For the digit calendar fields, such as M or MM, d or dd, yy or yyyy, 93 * the field pattern length is ignored and the best match, which is defined 94 * in date time patterns, will be returned without honor the field pattern 95 * letter length in skeleton. 96 * </ol> 97 * 98 * <P> 99 * The calendar fields we support for interval formatting are: 100 * year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and 101 * second (though we do not currently have specific intervalFormat data for 102 * skeletons with seconds). 103 * Those calendar fields can be defined in the following order: 104 * year > month > date > hour (in day) > minute > second 105 * 106 * The largest different calendar fields between 2 calendars is the 107 * first different calendar field in above order. 108 * 109 * For example: the largest different calendar fields between "Jan 10, 2007" 110 * and "Feb 20, 2008" is year. 111 * 112 * <P> 113 * For other calendar fields, the compact interval formatting is not 114 * supported. And the interval format will be fall back to fall-back 115 * patterns, which is mostly "{date0} - {date1}". 116 * 117 * <P> 118 * There is a set of pre-defined static skeleton strings in DateFormat, 119 * There are pre-defined interval patterns for those pre-defined skeletons 120 * in locales' resource files. 121 * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is "yMMMd", 122 * in en_US, if the largest different calendar field between date1 and date2 123 * is "year", the date interval pattern is "MMM d, yyyy - MMM d, yyyy", 124 * such as "Jan 10, 2007 - Jan 10, 2008". 125 * If the largest different calendar field between date1 and date2 is "month", 126 * the date interval pattern is "MMM d - MMM d, yyyy", 127 * such as "Jan 10 - Feb 10, 2007". 128 * If the largest different calendar field between date1 and date2 is "day", 129 * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007". 130 * 131 * For date skeleton, the interval patterns when year, or month, or date is 132 * different are defined in resource files. 133 * For time skeleton, the interval patterns when am/pm, or hour, or minute is 134 * different are defined in resource files. 135 * 136 * <P> 137 * If a skeleton is not found in a locale's DateIntervalInfo, which means 138 * the interval patterns for the skeleton is not defined in resource file, 139 * the interval pattern will falls back to the interval "fallback" pattern 140 * defined in resource file. 141 * If the interval "fallback" pattern is not defined, the default fall-back 142 * is "{date0} - {data1}". 143 * 144 * <P> 145 * For the combination of date and time, 146 * The rule to genearte interval patterns are: 147 * <ol> 148 * <li> 149 * when the year, month, or day differs, falls back to fall-back 150 * interval pattern, which mostly is the concatenate the two original 151 * expressions with a separator between, 152 * For example, interval pattern from "Jan 10, 2007 10:10 am" 153 * to "Jan 11, 2007 10:10am" is 154 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am" 155 * <li> 156 * otherwise, present the date followed by the range expression 157 * for the time. 158 * For example, interval pattern from "Jan 10, 2007 10:10 am" 159 * to "Jan 10, 2007 11:10am" is "Jan 10, 2007 10:10 am - 11:10am" 160 * </ol> 161 * 162 * 163 * <P> 164 * If two dates are the same, the interval pattern is the single date pattern. 165 * For example, interval pattern from "Jan 10, 2007" to "Jan 10, 2007" is 166 * "Jan 10, 2007". 167 * 168 * Or if the presenting fields between 2 dates have the exact same values, 169 * the interval pattern is the single date pattern. 170 * For example, if user only requests year and month, 171 * the interval pattern from "Jan 10, 2007" to "Jan 20, 2007" is "Jan 2007". 172 * 173 * <P> 174 * DateIntervalFormat needs the following information for correct 175 * formatting: time zone, calendar type, pattern, date format symbols, 176 * and date interval patterns. 177 * It can be instantiated in several ways: 178 * <ol> 179 * <li> 180 * create an instance using default or given locale plus given skeleton. 181 * Users are encouraged to created date interval formatter this way and 182 * to use the pre-defined skeleton macros, such as 183 * YEAR_NUM_MONTH, which consists the calendar fields and 184 * the format style. 185 * </li> 186 * <li> 187 * create an instance using default or given locale plus given skeleton 188 * plus a given DateIntervalInfo. 189 * This factory method is for powerful users who want to provide their own 190 * interval patterns. 191 * Locale provides the timezone, calendar, and format symbols information. 192 * Local plus skeleton provides full pattern information. 193 * DateIntervalInfo provides the date interval patterns. 194 * </li> 195 * </ol> 196 * 197 * <P> 198 * For the calendar field pattern letter, such as G, y, M, d, a, h, H, m, s etc. 199 * DateIntervalFormat uses the same syntax as that of 200 * DateTime format. 201 * 202 * <P> 203 * Code Sample: general usage 204 * <pre> 205 * 206 * // the date interval object which the DateIntervalFormat formats on 207 * // and parses into 208 * DateInterval dtInterval = new DateInterval(1000*3600*24L, 1000*3600*24*2L); 209 * DateIntervalFormat dtIntervalFmt = DateIntervalFormat.getInstance( 210 * YEAR_MONTH_DAY, Locale("en", "GB", "")); 211 * StringBuffer str = new StringBuffer(""); 212 * FieldPosition pos = new FieldPosition(0); 213 * // formatting 214 * dtIntervalFmt.format(dtInterval, dateIntervalString, pos); 215 * 216 * </pre> 217 * 218 * <P> 219 * Code Sample: for powerful users who wants to use their own interval pattern 220 * <pre> 221 * 222 * import ohos.global.icu.text.DateIntervalInfo; 223 * import ohos.global.icu.text.DateIntervalFormat; 224 * .................... 225 * 226 * // Get DateIntervalFormat instance using default locale 227 * DateIntervalFormat dtitvfmt = DateIntervalFormat.getInstance(YEAR_MONTH_DAY); 228 * 229 * // Create an empty DateIntervalInfo object, which does not have any interval patterns inside. 230 * dtitvinf = new DateIntervalInfo(); 231 * 232 * // a series of set interval patterns. 233 * // Only ERA, YEAR, MONTH, DATE, DAY_OF_MONTH, DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, 234 * MINUTE, SECOND and MILLISECOND are supported. 235 * dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'"); 236 * dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d"); 237 * dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d"); 238 * dtitvinf.setIntervalPattern("yMMMd", Calendar.HOUR_OF_DAY, "yyyy MMM d HH:mm ~ HH:mm"); 239 * 240 * // Set fallback interval pattern. Fallback pattern is used when interval pattern is not found. 241 * // If the fall-back pattern is not set, falls back to {date0} - {date1} if interval pattern is not found. 242 * dtitvinf.setFallbackIntervalPattern("{0} - {1}"); 243 * 244 * // Set above DateIntervalInfo object as the interval patterns of date interval formatter 245 * dtitvfmt.setDateIntervalInfo(dtitvinf); 246 * 247 * // Prepare to format 248 * pos = new FieldPosition(0); 249 * str = new StringBuffer(""); 250 * 251 * // The 2 calendars should be equivalent, otherwise, IllegalArgumentException will be thrown by format() 252 * Calendar fromCalendar = (Calendar) dtfmt.getCalendar().clone(); 253 * Calendar toCalendar = (Calendar) dtfmt.getCalendar().clone(); 254 * fromCalendar.setTimeInMillis(....); 255 * toCalendar.setTimeInMillis(...); 256 * 257 * //Formatting given 2 calendars 258 * dtitvfmt.format(fromCalendar, toCalendar, str, pos); 259 * 260 * 261 * </pre> 262 * <h3>Synchronization</h3> 263 * 264 * The format methods of DateIntervalFormat may be used concurrently from multiple threads. 265 * Functions that alter the state of a DateIntervalFormat object (setters) 266 * may not be used concurrently with any other functions. 267 */ 268 269 public class DateIntervalFormat extends UFormat { 270 271 /** 272 * An immutable class containing the result of a date interval formatting operation. 273 * 274 * Instances of this class are immutable and thread-safe. 275 * 276 * Not intended for public subclassing. 277 */ 278 public static final class FormattedDateInterval implements FormattedValue { 279 private final String string; 280 private final List<FieldPosition> attributes; 281 FormattedDateInterval(CharSequence cs, List<FieldPosition> attributes)282 FormattedDateInterval(CharSequence cs, List<FieldPosition> attributes) { 283 this.string = cs.toString(); 284 this.attributes = Collections.unmodifiableList(attributes); 285 } 286 287 /** 288 * {@inheritDoc} 289 */ 290 @Override toString()291 public String toString() { 292 return string; 293 } 294 295 /** 296 * {@inheritDoc} 297 */ 298 @Override length()299 public int length() { 300 return string.length(); 301 } 302 303 /** 304 * {@inheritDoc} 305 */ 306 @Override charAt(int index)307 public char charAt(int index) { 308 return string.charAt(index); 309 } 310 311 /** 312 * {@inheritDoc} 313 */ 314 @Override subSequence(int start, int end)315 public CharSequence subSequence(int start, int end) { 316 return string.subSequence(start, end); 317 } 318 319 /** 320 * {@inheritDoc} 321 */ 322 @Override appendTo(A appendable)323 public <A extends Appendable> A appendTo(A appendable) { 324 return Utility.appendTo(string, appendable); 325 } 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override nextPosition(ConstrainedFieldPosition cfpos)331 public boolean nextPosition(ConstrainedFieldPosition cfpos) { 332 return FormattedValueFieldPositionIteratorImpl.nextPosition(attributes, cfpos); 333 } 334 335 /** 336 * {@inheritDoc} 337 */ 338 @Override toCharacterIterator()339 public AttributedCharacterIterator toCharacterIterator() { 340 return FormattedValueFieldPositionIteratorImpl.toCharacterIterator(string, attributes); 341 } 342 } 343 344 /** 345 * Class for span fields in FormattedDateInterval. 346 * 347 * @hide exposed on OHOS 348 */ 349 public static final class SpanField extends UFormat.SpanField { 350 private static final long serialVersionUID = -6330879259553618133L; 351 352 /** 353 * The concrete field used for spans in FormattedDateInterval. 354 * 355 * Instances of DATE_INTERVAL_SPAN should have an associated value. If 356 * 0, the date fields within the span are for the "from" date; if 1, 357 * the date fields within the span are for the "to" date. 358 */ 359 public static final SpanField DATE_INTERVAL_SPAN = new SpanField("date-interval-span"); 360 SpanField(String name)361 private SpanField(String name) { 362 super(name); 363 } 364 365 /** 366 * serialization method resolve instances to the constant 367 * DateIntervalFormat.SpanField values 368 * @hide draft / provisional / internal are hidden on OHOS 369 */ 370 @Override readResolve()371 protected Object readResolve() throws InvalidObjectException { 372 if (this.getName().equals(DATE_INTERVAL_SPAN.getName())) 373 return DATE_INTERVAL_SPAN; 374 375 throw new InvalidObjectException("An invalid object."); 376 } 377 } 378 379 private static final long serialVersionUID = 1; 380 381 /** 382 * Used to save the information for a skeleton's best match skeleton. 383 * It is package accessible since it is used in DateIntervalInfo too. 384 */ 385 static final class BestMatchInfo { 386 // the best match skeleton 387 final String bestMatchSkeleton; 388 // 0 means the best matched skeleton is the same as input skeleton 389 // 1 means the fields are the same, but field width are different 390 // 2 means the only difference between fields are v/z, 391 // -1 means there are other fields difference 392 final int bestMatchDistanceInfo; BestMatchInfo(String bestSkeleton, int difference)393 BestMatchInfo(String bestSkeleton, int difference) { 394 bestMatchSkeleton = bestSkeleton; 395 bestMatchDistanceInfo = difference; 396 } 397 } 398 399 400 /* 401 * Used to save the information on a skeleton and its best match. 402 */ 403 private static final class SkeletonAndItsBestMatch { 404 final String skeleton; 405 final String bestMatchSkeleton; SkeletonAndItsBestMatch(String skeleton, String bestMatch)406 SkeletonAndItsBestMatch(String skeleton, String bestMatch) { 407 this.skeleton = skeleton; 408 bestMatchSkeleton = bestMatch; 409 } 410 } 411 412 /** Used to output information during formatting. */ 413 private static final class FormatOutput { 414 int firstIndex = -1; 415 register(int i)416 public void register(int i) { 417 if (firstIndex == -1) { 418 firstIndex = i; 419 } 420 } 421 } 422 423 424 // Cache for the locale interval pattern 425 private static ICUCache<String, Map<String, PatternInfo>> LOCAL_PATTERN_CACHE = 426 new SimpleCache<>(); 427 428 /* 429 * The interval patterns for this locale. 430 */ 431 private DateIntervalInfo fInfo; 432 433 /* 434 * The DateFormat object used to format single pattern. 435 * Because fDateFormat is modified during format operations, all 436 * access to it from logically const, thread safe functions must be synchronized. 437 */ 438 private SimpleDateFormat fDateFormat; 439 440 /* 441 * The 2 calendars with the from and to date. 442 * could re-use the calendar in fDateFormat, 443 * but keeping 2 calendars make it clear and clean. 444 * Because these Calendars are modified during format operations, all 445 * access to them from logically const, thread safe functions must be synchronized. 446 */ 447 private Calendar fFromCalendar; 448 private Calendar fToCalendar; 449 450 /* 451 * Following are transient interval information 452 * relevant (locale) to this formatter. 453 */ 454 private String fSkeleton = null; 455 456 /* 457 * Needed for efficient deserialization. If set, it means we can use the 458 * cache to initialize fIntervalPatterns. 459 */ 460 private boolean isDateIntervalInfoDefault; 461 462 /** 463 * Interval patterns for this instance's locale. 464 */ 465 private transient Map<String, PatternInfo> fIntervalPatterns = null; 466 467 /* 468 * Patterns for fallback formatting. 469 */ 470 private String fDatePattern = null; 471 private String fTimePattern = null; 472 private String fDateTimeFormat = null; 473 474 475 /* 476 * default constructor; private because we don't want anyone to use 477 */ 478 @SuppressWarnings("unused") DateIntervalFormat()479 private DateIntervalFormat() { 480 } 481 482 /** 483 * Construct a DateIntervalFormat from DateFormat, 484 * a DateIntervalInfo, and skeleton. 485 * DateFormat provides the timezone, calendar, 486 * full pattern, and date format symbols information. 487 * It should be a SimpleDateFormat object which 488 * has a pattern in it. 489 * the DateIntervalInfo provides the interval patterns. 490 * 491 * @param skeleton the skeleton of the date formatter 492 * @param dtItvInfo the DateIntervalInfo object to be adopted. 493 * @param simpleDateFormat will be used for formatting 494 * 495 * @deprecated This API is ICU internal only. 496 * @hide deprecated on icu4j-org 497 * @hide draft / provisional / internal are hidden on OHOS 498 */ 499 @Deprecated DateIntervalFormat(String skeleton, DateIntervalInfo dtItvInfo, SimpleDateFormat simpleDateFormat)500 public DateIntervalFormat(String skeleton, DateIntervalInfo dtItvInfo, 501 SimpleDateFormat simpleDateFormat) 502 { 503 fDateFormat = simpleDateFormat; 504 // freeze date interval info 505 dtItvInfo.freeze(); 506 fSkeleton = skeleton; 507 fInfo = dtItvInfo; 508 isDateIntervalInfoDefault = false; 509 fFromCalendar = (Calendar) fDateFormat.getCalendar().clone(); 510 fToCalendar = (Calendar) fDateFormat.getCalendar().clone(); 511 initializePattern(null); 512 } 513 DateIntervalFormat(String skeleton, ULocale locale, SimpleDateFormat simpleDateFormat)514 private DateIntervalFormat(String skeleton, ULocale locale, 515 SimpleDateFormat simpleDateFormat) 516 { 517 fDateFormat = simpleDateFormat; 518 fSkeleton = skeleton; 519 fInfo = new DateIntervalInfo(locale).freeze(); 520 isDateIntervalInfoDefault = true; 521 fFromCalendar = (Calendar) fDateFormat.getCalendar().clone(); 522 fToCalendar = (Calendar) fDateFormat.getCalendar().clone(); 523 initializePattern(LOCAL_PATTERN_CACHE); 524 } 525 526 527 /** 528 * Construct a DateIntervalFormat from skeleton and the default <code>FORMAT</code> locale. 529 * 530 * This is a convenient override of 531 * getInstance(String skeleton, ULocale locale) 532 * with the value of locale as default <code>FORMAT</code> locale. 533 * 534 * @param skeleton the skeleton on which interval format based. 535 * @return a date time interval formatter. 536 * @see Category#FORMAT 537 */ 538 public static final DateIntervalFormat getInstance(String skeleton)539 getInstance(String skeleton) 540 541 { 542 return getInstance(skeleton, ULocale.getDefault(Category.FORMAT)); 543 } 544 545 546 /** 547 * Construct a DateIntervalFormat from skeleton and a given locale. 548 * 549 * This is a convenient override of 550 * getInstance(String skeleton, ULocale locale) 551 * 552 * <p>Example code:{@sample external/icu/ohos_icu4j/src/samples/java/ohos/global/icu/samples/text/dateintervalformat/DateIntervalFormatSample.java dtitvfmtPreDefinedExample} 553 * @param skeleton the skeleton on which interval format based. 554 * @param locale the given locale 555 * @return a date time interval formatter. 556 */ 557 public static final DateIntervalFormat getInstance(String skeleton, Locale locale)558 getInstance(String skeleton, Locale locale) 559 { 560 return getInstance(skeleton, ULocale.forLocale(locale)); 561 } 562 563 564 /** 565 * Construct a DateIntervalFormat from skeleton and a given locale. 566 * <P> 567 * In this factory method, 568 * the date interval pattern information is load from resource files. 569 * Users are encouraged to created date interval formatter this way and 570 * to use the pre-defined skeleton macros. 571 * 572 * <P> 573 * There are pre-defined skeletons in DateFormat, 574 * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc. 575 * 576 * Those skeletons have pre-defined interval patterns in resource files. 577 * Users are encouraged to use them. 578 * For example: 579 * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc); 580 * 581 * The given Locale provides the interval patterns. 582 * For example, for en_GB, if skeleton is YEAR_ABBR_MONTH_WEEKDAY_DAY, 583 * which is "yMMMEEEd", 584 * the interval patterns defined in resource file to above skeleton are: 585 * "EEE, d MMM, yyyy - EEE, d MMM, yyyy" for year differs, 586 * "EEE, d MMM - EEE, d MMM, yyyy" for month differs, 587 * "EEE, d - EEE, d MMM, yyyy" for day differs, 588 * @param skeleton the skeleton on which interval format based. 589 * @param locale the given locale 590 * @return a date time interval formatter. 591 */ 592 public static final DateIntervalFormat getInstance(String skeleton, ULocale locale)593 getInstance(String skeleton, ULocale locale) 594 { 595 DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale); 596 return new DateIntervalFormat(skeleton, locale, new SimpleDateFormat(generator.getBestPattern(skeleton), locale)); 597 } 598 599 600 601 /** 602 * Construct a DateIntervalFormat from skeleton 603 * DateIntervalInfo, and the default <code>FORMAT</code> locale. 604 * 605 * This is a convenient override of 606 * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf) 607 * with the locale value as default <code>FORMAT</code> locale. 608 * 609 * @param skeleton the skeleton on which interval format based. 610 * @param dtitvinf the DateIntervalInfo object to be adopted. 611 * @return a date time interval formatter. 612 * @see Category#FORMAT 613 */ getInstance(String skeleton, DateIntervalInfo dtitvinf)614 public static final DateIntervalFormat getInstance(String skeleton, 615 DateIntervalInfo dtitvinf) 616 { 617 return getInstance(skeleton, ULocale.getDefault(Category.FORMAT), dtitvinf); 618 } 619 620 621 622 /** 623 * Construct a DateIntervalFormat from skeleton 624 * a DateIntervalInfo, and the given locale. 625 * 626 * This is a convenient override of 627 * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf) 628 * 629 * <p>Example code:{@sample external/icu/ohos_icu4j/src/samples/java/ohos/global/icu/samples/text/dateintervalformat/DateIntervalFormatSample.java dtitvfmtCustomizedExample} 630 * @param skeleton the skeleton on which interval format based. 631 * @param locale the given locale 632 * @param dtitvinf the DateIntervalInfo object to be adopted. 633 * @return a date time interval formatter. 634 */ getInstance(String skeleton, Locale locale, DateIntervalInfo dtitvinf)635 public static final DateIntervalFormat getInstance(String skeleton, 636 Locale locale, 637 DateIntervalInfo dtitvinf) 638 { 639 return getInstance(skeleton, ULocale.forLocale(locale), dtitvinf); 640 } 641 642 643 644 /** 645 * Construct a DateIntervalFormat from skeleton 646 * a DateIntervalInfo, and the given locale. 647 * 648 * <P> 649 * In this factory method, user provides its own date interval pattern 650 * information, instead of using those pre-defined data in resource file. 651 * This factory method is for powerful users who want to provide their own 652 * interval patterns. 653 * 654 * <P> 655 * There are pre-defined skeleton in DateFormat, 656 * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc. 657 * 658 * Those skeletons have pre-defined interval patterns in resource files. 659 * Users are encouraged to use them. 660 * For example: 661 * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc,itvinf); 662 * 663 * the DateIntervalInfo provides the interval patterns. 664 * 665 * User are encouraged to set default interval pattern in DateIntervalInfo 666 * as well, if they want to set other interval patterns ( instead of 667 * reading the interval patterns from resource files). 668 * When the corresponding interval pattern for a largest calendar different 669 * field is not found ( if user not set it ), interval format fallback to 670 * the default interval pattern. 671 * If user does not provide default interval pattern, it fallback to 672 * "{date0} - {date1}" 673 * 674 * @param skeleton the skeleton on which interval format based. 675 * @param locale the given locale 676 * @param dtitvinf the DateIntervalInfo object to be adopted. 677 * @return a date time interval formatter. 678 */ getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)679 public static final DateIntervalFormat getInstance(String skeleton, 680 ULocale locale, 681 DateIntervalInfo dtitvinf) 682 { 683 // clone. If it is frozen, clone returns itself, otherwise, clone 684 // returns a copy. 685 dtitvinf = (DateIntervalInfo)dtitvinf.clone(); 686 DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale); 687 return new DateIntervalFormat(skeleton, dtitvinf, new SimpleDateFormat(generator.getBestPattern(skeleton), locale)); 688 } 689 690 691 /** 692 * Clone this Format object polymorphically. 693 * @return A copy of the object. 694 */ 695 @Override clone()696 public synchronized Object clone() 697 { 698 DateIntervalFormat other = (DateIntervalFormat) super.clone(); 699 other.fDateFormat = (SimpleDateFormat) fDateFormat.clone(); 700 other.fInfo = (DateIntervalInfo) fInfo.clone(); 701 other.fFromCalendar = (Calendar) fFromCalendar.clone(); 702 other.fToCalendar = (Calendar) fToCalendar.clone(); 703 other.fDatePattern = fDatePattern; 704 other.fTimePattern = fTimePattern; 705 other.fDateTimeFormat = fDateTimeFormat; 706 return other; 707 } 708 709 710 /** 711 * Format an object to produce a string. This method handles Formattable 712 * objects with a DateInterval type. 713 * If a the Formattable object type is not a DateInterval, 714 * IllegalArgumentException is thrown. 715 * 716 * @param obj The object to format. 717 * Must be a DateInterval. 718 * @param appendTo Output parameter to receive result. 719 * Result is appended to existing contents. 720 * @param fieldPosition On input: an alignment field, if desired. 721 * On output: the offsets of the alignment field. 722 * There may be multiple instances of a given field type 723 * in an interval format; in this case the fieldPosition 724 * offsets refer to the first instance. 725 * @return Reference to 'appendTo' parameter. 726 * @throws IllegalArgumentException if the formatted object is not 727 * DateInterval object 728 */ 729 @Override 730 public final StringBuffer format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition)731 format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition) 732 { 733 if ( obj instanceof DateInterval ) { 734 return format( (DateInterval)obj, appendTo, fieldPosition); 735 } 736 else { 737 throw new IllegalArgumentException("Cannot format given Object (" + obj.getClass().getName() + ") as a DateInterval"); 738 } 739 } 740 741 /** 742 * Format a DateInterval to produce a string. 743 * 744 * @param dtInterval DateInterval to be formatted. 745 * @param appendTo Output parameter to receive result. 746 * Result is appended to existing contents. 747 * @param fieldPosition On input: an alignment field, if desired. 748 * On output: the offsets of the alignment field. 749 * There may be multiple instances of a given field type 750 * in an interval format; in this case the fieldPosition 751 * offsets refer to the first instance. 752 * @return Reference to 'appendTo' parameter. 753 */ format(DateInterval dtInterval, StringBuffer appendTo, FieldPosition fieldPosition)754 public final StringBuffer format(DateInterval dtInterval, 755 StringBuffer appendTo, 756 FieldPosition fieldPosition) { 757 return formatIntervalImpl(dtInterval, appendTo, fieldPosition, null, null); 758 } 759 760 /** 761 * Format a DateInterval to produce a FormattedDateInterval. 762 * 763 * The FormattedDateInterval exposes field information about the formatted string. 764 * 765 * @param dtInterval DateInterval to be formatted. 766 * @return A FormattedDateInterval containing the format result. 767 */ formatToValue(DateInterval dtInterval)768 public FormattedDateInterval formatToValue(DateInterval dtInterval) { 769 StringBuffer sb = new StringBuffer(); 770 FieldPosition ignore = new FieldPosition(0); 771 FormatOutput output = new FormatOutput(); 772 List<FieldPosition> attributes = new ArrayList<>(); 773 formatIntervalImpl(dtInterval, sb, ignore, output, attributes); 774 if (output.firstIndex != -1) { 775 FormattedValueFieldPositionIteratorImpl.addOverlapSpans( 776 attributes, SpanField.DATE_INTERVAL_SPAN, output.firstIndex); 777 FormattedValueFieldPositionIteratorImpl.sort(attributes); 778 } 779 return new FormattedDateInterval(sb, attributes); 780 } 781 formatIntervalImpl( DateInterval dtInterval, StringBuffer appendTo, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes)782 private synchronized StringBuffer formatIntervalImpl( 783 DateInterval dtInterval, 784 StringBuffer appendTo, 785 FieldPosition pos, 786 FormatOutput output, 787 List<FieldPosition> attributes) { 788 fFromCalendar.setTimeInMillis(dtInterval.getFromDate()); 789 fToCalendar.setTimeInMillis(dtInterval.getToDate()); 790 return formatImpl(fFromCalendar, fToCalendar, appendTo, pos, output, attributes); 791 } 792 793 /** 794 * @deprecated This API is ICU internal only. 795 * @hide deprecated on icu4j-org 796 * @hide draft / provisional / internal are hidden on OHOS 797 */ 798 @Deprecated getPatterns(Calendar fromCalendar, Calendar toCalendar, Output<String> part2)799 public String getPatterns(Calendar fromCalendar, 800 Calendar toCalendar, 801 Output<String> part2) { 802 // First, find the largest different calendar field. 803 int field; 804 if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) { 805 field = Calendar.ERA; 806 } else if ( fromCalendar.get(Calendar.YEAR) != 807 toCalendar.get(Calendar.YEAR) ) { 808 field = Calendar.YEAR; 809 } else if ( fromCalendar.get(Calendar.MONTH) != 810 toCalendar.get(Calendar.MONTH) ) { 811 field = Calendar.MONTH; 812 } else if ( fromCalendar.get(Calendar.DATE) != 813 toCalendar.get(Calendar.DATE) ) { 814 field = Calendar.DATE; 815 } else if ( fromCalendar.get(Calendar.AM_PM) != 816 toCalendar.get(Calendar.AM_PM) ) { 817 field = Calendar.AM_PM; 818 } else if ( fromCalendar.get(Calendar.HOUR) != 819 toCalendar.get(Calendar.HOUR) ) { 820 field = Calendar.HOUR; 821 } else if ( fromCalendar.get(Calendar.MINUTE) != 822 toCalendar.get(Calendar.MINUTE) ) { 823 field = Calendar.MINUTE; 824 } else if ( fromCalendar.get(Calendar.SECOND) != 825 toCalendar.get(Calendar.SECOND) ) { 826 field = Calendar.SECOND; 827 } else if ( fromCalendar.get(Calendar.MILLISECOND) != 828 toCalendar.get(Calendar.MILLISECOND) ) { 829 field = Calendar.MILLISECOND; 830 } else { 831 return null; 832 } 833 PatternInfo intervalPattern = fIntervalPatterns.get( 834 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]); 835 part2.value = intervalPattern.getSecondPart(); 836 return intervalPattern.getFirstPart(); 837 } 838 839 /** 840 * Format 2 Calendars to produce a string. 841 * 842 * @param fromCalendar calendar set to the from date in date interval 843 * to be formatted into date interval string 844 * @param toCalendar calendar set to the to date in date interval 845 * to be formatted into date interval string 846 * @param appendTo Output parameter to receive result. 847 * Result is appended to existing contents. 848 * @param pos On input: an alignment field, if desired. 849 * On output: the offsets of the alignment field. 850 * There may be multiple instances of a given field type 851 * in an interval format; in this case the fieldPosition 852 * offsets refer to the first instance. 853 * @return Reference to 'appendTo' parameter. 854 * @throws IllegalArgumentException if the two calendars are not equivalent. 855 */ format(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos)856 public final StringBuffer format(Calendar fromCalendar, 857 Calendar toCalendar, 858 StringBuffer appendTo, 859 FieldPosition pos) { 860 return formatImpl(fromCalendar, toCalendar, appendTo, pos, null, null); 861 } 862 863 /** 864 * Format 2 Calendars to produce a FormattedDateInterval. 865 * 866 * The FormattedDateInterval exposes field information about the formatted string. 867 * 868 * @param fromCalendar calendar set to the from date in date interval 869 * to be formatted into date interval string 870 * @param toCalendar calendar set to the to date in date interval 871 * to be formatted into date interval string 872 * @return A FormattedDateInterval containing the format result. 873 */ formatToValue(Calendar fromCalendar, Calendar toCalendar)874 public FormattedDateInterval formatToValue(Calendar fromCalendar, Calendar toCalendar) { 875 StringBuffer sb = new StringBuffer(); 876 FieldPosition ignore = new FieldPosition(0); 877 FormatOutput output = new FormatOutput(); 878 List<FieldPosition> attributes = new ArrayList<>(); 879 formatImpl(fromCalendar, toCalendar, sb, ignore, output, attributes); 880 if (output.firstIndex != -1) { 881 FormattedValueFieldPositionIteratorImpl.addOverlapSpans( 882 attributes, SpanField.DATE_INTERVAL_SPAN, output.firstIndex); 883 FormattedValueFieldPositionIteratorImpl.sort(attributes); 884 } 885 return new FormattedDateInterval(sb, attributes); 886 } 887 formatImpl(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes)888 private synchronized StringBuffer formatImpl(Calendar fromCalendar, 889 Calendar toCalendar, 890 StringBuffer appendTo, 891 FieldPosition pos, 892 FormatOutput output, 893 List<FieldPosition> attributes) 894 { 895 // not support different calendar types and time zones 896 if ( !fromCalendar.isEquivalentTo(toCalendar) ) { 897 throw new IllegalArgumentException("can not format on two different calendars"); 898 } 899 900 // First, find the largest different calendar field. 901 int field = -1; //init with an invalid value. 902 903 if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) { 904 field = Calendar.ERA; 905 } else if ( fromCalendar.get(Calendar.YEAR) != 906 toCalendar.get(Calendar.YEAR) ) { 907 field = Calendar.YEAR; 908 } else if ( fromCalendar.get(Calendar.MONTH) != 909 toCalendar.get(Calendar.MONTH) ) { 910 field = Calendar.MONTH; 911 } else if ( fromCalendar.get(Calendar.DATE) != 912 toCalendar.get(Calendar.DATE) ) { 913 field = Calendar.DATE; 914 } else if ( fromCalendar.get(Calendar.AM_PM) != 915 toCalendar.get(Calendar.AM_PM) ) { 916 field = Calendar.AM_PM; 917 } else if ( fromCalendar.get(Calendar.HOUR) != 918 toCalendar.get(Calendar.HOUR) ) { 919 field = Calendar.HOUR; 920 } else if ( fromCalendar.get(Calendar.MINUTE) != 921 toCalendar.get(Calendar.MINUTE) ) { 922 field = Calendar.MINUTE; 923 } else if ( fromCalendar.get(Calendar.SECOND) != 924 toCalendar.get(Calendar.SECOND) ) { 925 field = Calendar.SECOND; 926 } else if ( fromCalendar.get(Calendar.MILLISECOND) != 927 toCalendar.get(Calendar.MILLISECOND) ) { 928 field = Calendar.MILLISECOND; 929 } else { 930 /* ignore the millisecond etc. small fields' difference. 931 * use single date when all the above are the same. 932 */ 933 return fDateFormat.format(fromCalendar, appendTo, pos, attributes); 934 } 935 boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND || field==Calendar.MILLISECOND); 936 937 // get interval pattern 938 PatternInfo intervalPattern = fIntervalPatterns.get( 939 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]); 940 941 if ( intervalPattern == null ) { 942 if ( fDateFormat.isFieldUnitIgnored(field) ) { 943 /* the largest different calendar field is small than 944 * the smallest calendar field in pattern, 945 * return single date format. 946 */ 947 return fDateFormat.format(fromCalendar, appendTo, pos, attributes); 948 } 949 950 return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, 951 output, attributes); 952 } 953 954 // If the first part in interval pattern is empty, 955 // the 2nd part of it saves the full-pattern used in fall-back. 956 // For a 'real' interval pattern, the first part will never be empty. 957 if ( intervalPattern.getFirstPart() == null ) { 958 // fall back 959 return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, 960 output, attributes, intervalPattern.getSecondPart()); 961 } 962 Calendar firstCal; 963 Calendar secondCal; 964 if ( intervalPattern.firstDateInPtnIsLaterDate() ) { 965 if (output != null) { 966 output.register(1); 967 } 968 firstCal = toCalendar; 969 secondCal = fromCalendar; 970 } else { 971 if (output != null) { 972 output.register(0); 973 } 974 firstCal = fromCalendar; 975 secondCal = toCalendar; 976 } 977 // break the interval pattern into 2 parts 978 // first part should not be empty, 979 String originalPattern = fDateFormat.toPattern(); 980 fDateFormat.applyPattern(intervalPattern.getFirstPart()); 981 fDateFormat.format(firstCal, appendTo, pos, attributes); 982 // Only accept the first instance of the field 983 if (pos.getEndIndex() > 0) { 984 pos = new FieldPosition(0); 985 } 986 if ( intervalPattern.getSecondPart() != null ) { 987 fDateFormat.applyPattern(intervalPattern.getSecondPart()); 988 fDateFormat.format(secondCal, appendTo, pos, attributes); 989 } 990 fDateFormat.applyPattern(originalPattern); 991 return appendTo; 992 } 993 994 /** Like fallbackFormat, but specifically for ranges. */ fallbackFormatRange(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, StringBuilder patternSB, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes)995 private final void fallbackFormatRange(Calendar fromCalendar, 996 Calendar toCalendar, 997 StringBuffer appendTo, 998 StringBuilder patternSB, 999 FieldPosition pos, 1000 FormatOutput output, 1001 List<FieldPosition> attributes) { 1002 String compiledPattern = SimpleFormatterImpl.compileToStringMinMaxArguments( 1003 fInfo.getFallbackIntervalPattern(), patternSB, 2, 2); 1004 long state = 0; 1005 while (true) { 1006 state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo); 1007 if (state == SimpleFormatterImpl.IterInternal.DONE) { 1008 break; 1009 } 1010 if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) { 1011 if (output != null) { 1012 output.register(0); 1013 } 1014 fDateFormat.format(fromCalendar, appendTo, pos, attributes); 1015 } else { 1016 if (output != null) { 1017 output.register(1); 1018 } 1019 fDateFormat.format(toCalendar, appendTo, pos, attributes); 1020 } 1021 // Only accept the first instance of the field 1022 if (pos.getEndIndex() > 0) { 1023 pos = new FieldPosition(0); 1024 } 1025 } 1026 } 1027 1028 /* 1029 * Format 2 Calendars to using fall-back interval pattern 1030 * 1031 * The full pattern used in this fall-back format is the 1032 * full pattern of the date formatter. 1033 * 1034 * @param fromCalendar calendar set to the from date in date interval 1035 * to be formatted into date interval string 1036 * @param toCalendar calendar set to the to date in date interval 1037 * to be formatted into date interval string 1038 * @param appendTo Output parameter to receive result. 1039 * Result is appended to existing contents. 1040 * @param pos On input: an alignment field, if desired. 1041 * On output: the offsets of the alignment field. 1042 * @return Reference to 'appendTo' parameter. 1043 */ fallbackFormat(Calendar fromCalendar, Calendar toCalendar, boolean fromToOnSameDay, StringBuffer appendTo, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes)1044 private final StringBuffer fallbackFormat(Calendar fromCalendar, 1045 Calendar toCalendar, 1046 boolean fromToOnSameDay, 1047 StringBuffer appendTo, 1048 FieldPosition pos, 1049 FormatOutput output, 1050 List<FieldPosition> attributes) { 1051 StringBuilder patternSB = new StringBuilder(); 1052 boolean formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern != null && fTimePattern != null); 1053 if (formatDatePlusTimeRange) { 1054 String compiledPattern = SimpleFormatterImpl.compileToStringMinMaxArguments( 1055 fDateTimeFormat, patternSB, 2, 2); 1056 1057 String fullPattern; // for saving the pattern in fDateFormat 1058 fullPattern = fDateFormat.toPattern(); // save current pattern, restore later 1059 1060 // {0} is time range 1061 // {1} is single date portion 1062 long state = 0; 1063 while (true) { 1064 state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo); 1065 if (state == SimpleFormatterImpl.IterInternal.DONE) { 1066 break; 1067 } 1068 if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) { 1069 fDateFormat.applyPattern(fTimePattern); 1070 fallbackFormatRange(fromCalendar, toCalendar, appendTo, patternSB, pos, output, attributes); 1071 } else { 1072 fDateFormat.applyPattern(fDatePattern); 1073 fDateFormat.format(fromCalendar, appendTo, pos, attributes); 1074 } 1075 // Only accept the first instance of the field 1076 if (pos.getEndIndex() > 0) { 1077 pos = new FieldPosition(0); 1078 } 1079 } 1080 1081 // restore full pattern 1082 fDateFormat.applyPattern(fullPattern); 1083 } else { 1084 fallbackFormatRange(fromCalendar, toCalendar, appendTo, patternSB, pos, output, attributes); 1085 } 1086 return appendTo; 1087 } 1088 1089 1090 /* 1091 * Format 2 Calendars to using fall-back interval pattern 1092 * 1093 * This fall-back pattern is generated on a given full pattern, 1094 * not the full pattern of the date formatter. 1095 * 1096 * @param fromCalendar calendar set to the from date in date interval 1097 * to be formatted into date interval string 1098 * @param toCalendar calendar set to the to date in date interval 1099 * to be formatted into date interval string 1100 * @param appendTo Output parameter to receive result. 1101 * Result is appended to existing contents. 1102 * @param pos On input: an alignment field, if desired. 1103 * On output: the offsets of the alignment field. 1104 * @param fullPattern the full pattern need to apply to date formatter 1105 * @return Reference to 'appendTo' parameter. 1106 */ fallbackFormat(Calendar fromCalendar, Calendar toCalendar, boolean fromToOnSameDay, StringBuffer appendTo, FieldPosition pos, FormatOutput output, List<FieldPosition> attributes, String fullPattern)1107 private final StringBuffer fallbackFormat(Calendar fromCalendar, 1108 Calendar toCalendar, 1109 boolean fromToOnSameDay, 1110 StringBuffer appendTo, 1111 FieldPosition pos, 1112 FormatOutput output, 1113 List<FieldPosition> attributes, 1114 String fullPattern) { 1115 String originalPattern = fDateFormat.toPattern(); 1116 fDateFormat.applyPattern(fullPattern); 1117 fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, output, attributes); 1118 fDateFormat.applyPattern(originalPattern); 1119 return appendTo; 1120 } 1121 1122 1123 /** 1124 * Date interval parsing is not supported. 1125 * <P> 1126 * This method should handle parsing of 1127 * date time interval strings into Formattable objects with 1128 * DateInterval type, which is a pair of UDate. 1129 * <P> 1130 * Before calling, set parse_pos.index to the offset you want to start 1131 * parsing at in the source. After calling, parse_pos.index is the end of 1132 * the text you parsed. If error occurs, index is unchanged. 1133 * <P> 1134 * When parsing, leading whitespace is discarded (with a successful parse), 1135 * while trailing whitespace is left as is. 1136 * <P> 1137 * See Format.parseObject() for more. 1138 * 1139 * @param source The string to be parsed into an object. 1140 * @param parse_pos The position to start parsing at. Since no parsing 1141 * is supported, upon return this param is unchanged. 1142 * @return A newly created Formattable* object, or NULL 1143 * on failure. 1144 * @deprecated This API is ICU internal only. 1145 * @hide deprecated on icu4j-org 1146 * @hide draft / provisional / internal are hidden on OHOS 1147 */ 1148 @Override 1149 @Deprecated parseObject(String source, ParsePosition parse_pos)1150 public Object parseObject(String source, ParsePosition parse_pos) 1151 { 1152 throw new UnsupportedOperationException("parsing is not supported"); 1153 } 1154 1155 1156 /** 1157 * Gets the date time interval patterns. 1158 * @return a copy of the date time interval patterns associated with 1159 * this date interval formatter. 1160 */ getDateIntervalInfo()1161 public DateIntervalInfo getDateIntervalInfo() 1162 { 1163 return (DateIntervalInfo)fInfo.clone(); 1164 } 1165 1166 1167 /** 1168 * Set the date time interval patterns. 1169 * @param newItvPattern the given interval patterns to copy. 1170 */ setDateIntervalInfo(DateIntervalInfo newItvPattern)1171 public void setDateIntervalInfo(DateIntervalInfo newItvPattern) 1172 { 1173 // clone it. If it is frozen, the clone returns itself. 1174 // Otherwise, clone returns a copy 1175 fInfo = (DateIntervalInfo)newItvPattern.clone(); 1176 this.isDateIntervalInfoDefault = false; 1177 fInfo.freeze(); // freeze it 1178 if ( fDateFormat != null ) { 1179 initializePattern(null); 1180 } 1181 } 1182 1183 /** 1184 * Get the TimeZone 1185 * @return A copy of the TimeZone associated with this date interval formatter. 1186 */ getTimeZone()1187 public TimeZone getTimeZone() 1188 { 1189 if ( fDateFormat != null ) { 1190 // Here we clone, like other getters here, but unlike 1191 // DateFormat.getTimeZone() and Calendar.getTimeZone() 1192 // which return the TimeZone from the Calendar's zone variable 1193 return (TimeZone)(fDateFormat.getTimeZone().clone()); 1194 } 1195 // If fDateFormat is null (unexpected), return default timezone. 1196 return TimeZone.getDefault(); 1197 } 1198 1199 1200 /** 1201 * Set the TimeZone for the calendar used by this DateIntervalFormat object. 1202 * @param zone The new TimeZone, will be cloned for use by this DateIntervalFormat. 1203 */ setTimeZone(TimeZone zone)1204 public void setTimeZone(TimeZone zone) 1205 { 1206 // zone is cloned once for all three usages below: 1207 TimeZone zoneToSet = (TimeZone)zone.clone(); 1208 if (fDateFormat != null) { 1209 fDateFormat.setTimeZone(zoneToSet); 1210 } 1211 // fDateFormat has the master calendar for the DateIntervalFormat; 1212 // fFromCalendar and fToCalendar are internal work clones of that calendar. 1213 if (fFromCalendar != null) { 1214 fFromCalendar.setTimeZone(zoneToSet); 1215 } 1216 if (fToCalendar != null) { 1217 fToCalendar.setTimeZone(zoneToSet); 1218 } 1219 } 1220 1221 /** 1222 * Gets the date formatter 1223 * @return a copy of the date formatter associated with 1224 * this date interval formatter. 1225 */ getDateFormat()1226 public synchronized DateFormat getDateFormat() 1227 { 1228 return (DateFormat)fDateFormat.clone(); 1229 } 1230 1231 1232 /* 1233 * Below are for generating interval patterns locale to the formatter 1234 */ 1235 1236 /* 1237 * Initialize interval patterns locale to this formatter. 1238 */ initializePattern(ICUCache<String, Map<String, PatternInfo>> cache)1239 private void initializePattern(ICUCache<String, Map<String, PatternInfo>> cache) { 1240 String fullPattern = fDateFormat.toPattern(); 1241 ULocale locale = fDateFormat.getLocale(); 1242 String key = null; 1243 Map<String, PatternInfo> patterns = null; 1244 if (cache != null) { 1245 if ( fSkeleton != null ) { 1246 key = locale.toString() + "+" + fullPattern + "+" + fSkeleton; 1247 } else { 1248 key = locale.toString() + "+" + fullPattern; 1249 } 1250 patterns = cache.get(key); 1251 } 1252 if (patterns == null) { 1253 Map<String, PatternInfo> intervalPatterns = initializeIntervalPattern(fullPattern, locale); 1254 patterns = Collections.unmodifiableMap(intervalPatterns); 1255 if (cache != null) { 1256 cache.put(key, patterns); 1257 } 1258 } 1259 fIntervalPatterns = patterns; 1260 } 1261 1262 1263 1264 /* 1265 * Initialize interval patterns locale to this formatter 1266 * 1267 * This code is a bit complicated since 1268 * 1. the interval patterns saved in resource bundle files are interval 1269 * patterns based on date or time only. 1270 * It does not have interval patterns based on both date and time. 1271 * Interval patterns on both date and time are algorithm generated. 1272 * 1273 * For example, it has interval patterns on skeleton "dMy" and "hm", 1274 * but it does not have interval patterns on skeleton "dMyhm". 1275 * 1276 * The rule to generate interval patterns for both date and time skeleton are 1277 * 1) when the year, month, or day differs, concatenate the two original 1278 * expressions with a separator between, 1279 * For example, interval pattern from "Jan 10, 2007 10:10 am" 1280 * to "Jan 11, 2007 10:10am" is 1281 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am" 1282 * 1283 * 2) otherwise, present the date followed by the range expression 1284 * for the time. 1285 * For example, interval pattern from "Jan 10, 2007 10:10 am" 1286 * to "Jan 10, 2007 11:10am" is 1287 * "Jan 10, 2007 10:10 am - 11:10am" 1288 * 1289 * 2. even a pattern does not request a certain calendar field, 1290 * the interval pattern needs to include such field if such fields are 1291 * different between 2 dates. 1292 * For example, a pattern/skeleton is "hm", but the interval pattern 1293 * includes year, month, and date when year, month, and date differs. 1294 * 1295 * 1296 * @param fullPattern formatter's full pattern 1297 * @param locale the given locale. 1298 * @return interval patterns' hash map 1299 */ initializeIntervalPattern(String fullPattern, ULocale locale)1300 private Map<String, PatternInfo> initializeIntervalPattern(String fullPattern, ULocale locale) { 1301 DateTimePatternGenerator dtpng = DateTimePatternGenerator.getInstance(locale); 1302 if ( fSkeleton == null ) { 1303 // fSkeleton is already set by getDateIntervalInstance() 1304 // or by getInstance(String skeleton, .... ) 1305 fSkeleton = dtpng.getSkeleton(fullPattern); 1306 } 1307 String skeleton = fSkeleton; 1308 1309 HashMap<String, PatternInfo> intervalPatterns = new HashMap<>(); 1310 1311 /* Check whether the skeleton is a combination of date and time. 1312 * For the complication reason 1 explained above. 1313 */ 1314 StringBuilder date = new StringBuilder(skeleton.length()); 1315 StringBuilder normalizedDate = new StringBuilder(skeleton.length()); 1316 StringBuilder time = new StringBuilder(skeleton.length()); 1317 StringBuilder normalizedTime = new StringBuilder(skeleton.length()); 1318 1319 /* the difference between time skeleton and normalizedTimeSkeleton are: 1320 * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true) 1321 * 2. 'a' is omitted in normalized time skeleton. 1322 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized 1323 * time skeleton 1324 * 1325 * The difference between date skeleton and normalizedDateSkeleton are: 1326 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton 1327 * 2. 'E' and 'EE' are normalized into 'EEE' 1328 * 3. 'MM' is normalized into 'M' 1329 */ 1330 getDateTimeSkeleton(skeleton, date, normalizedDate, 1331 time, normalizedTime); 1332 1333 String dateSkeleton = date.toString(); 1334 String timeSkeleton = time.toString(); 1335 String normalizedDateSkeleton = normalizedDate.toString(); 1336 String normalizedTimeSkeleton = normalizedTime.toString(); 1337 1338 // move this up here since we need it for fallbacks 1339 if (time.length() != 0 && date.length() != 0) { 1340 // Need the Date/Time pattern for concatenating the date with 1341 // the time interval. 1342 // The date/time pattern ( such as {0} {1} ) is saved in 1343 // calendar, that is why need to get the CalendarData here. 1344 fDateTimeFormat = getConcatenationPattern(locale); 1345 } 1346 1347 boolean found = genSeparateDateTimePtn(normalizedDateSkeleton, 1348 normalizedTimeSkeleton, 1349 intervalPatterns, dtpng); 1350 1351 // for skeletons with seconds, found is false and we enter this block 1352 if ( found == false ) { 1353 // use fallback 1354 // TODO: if user asks "m", but "d" differ 1355 //StringBuffer skeleton = new StringBuffer(skeleton); 1356 if ( time.length() != 0 ) { 1357 //genFallbackForNotFound(Calendar.MINUTE, skeleton); 1358 //genFallbackForNotFound(Calendar.HOUR, skeleton); 1359 //genFallbackForNotFound(Calendar.AM_PM, skeleton); 1360 if ( date.length() == 0 ) { 1361 // prefix with yMd 1362 timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton; 1363 String pattern =dtpng.getBestPattern(timeSkeleton); 1364 // for fall back interval patterns, 1365 // the first part of the pattern is empty, 1366 // the second part of the pattern is the full-pattern 1367 // should be used in fall-back. 1368 PatternInfo ptn = new PatternInfo(null, pattern, 1369 fInfo.getDefaultOrder()); 1370 intervalPatterns.put(DateIntervalInfo. 1371 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn); 1372 // share interval pattern 1373 intervalPatterns.put(DateIntervalInfo. 1374 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn); 1375 // share interval pattern 1376 intervalPatterns.put(DateIntervalInfo. 1377 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn); 1378 } else { 1379 //genFallbackForNotFound(Calendar.DATE, skeleton); 1380 //genFallbackForNotFound(Calendar.MONTH, skeleton); 1381 //genFallbackForNotFound(Calendar.YEAR, skeleton); 1382 } 1383 } else { 1384 //genFallbackForNotFound(Calendar.DATE, skeleton); 1385 //genFallbackForNotFound(Calendar.MONTH, skeleton); 1386 //genFallbackForNotFound(Calendar.YEAR, skeleton); 1387 } 1388 return intervalPatterns; 1389 } // end of skeleton not found 1390 // interval patterns for skeleton are found in resource 1391 if ( time.length() == 0 ) { 1392 // done 1393 } else if ( date.length() == 0 ) { 1394 // need to set up patterns for y/M/d differ 1395 /* result from following looks confusing. 1396 * for example: 10 10:10 - 11 10:10, it is not 1397 * clear that the first 10 is the 10th day 1398 time.insert(0, 'd'); 1399 genFallbackPattern(Calendar.DATE, time); 1400 time.insert(0, 'M'); 1401 genFallbackPattern(Calendar.MONTH, time); 1402 time.insert(0, 'y'); 1403 genFallbackPattern(Calendar.YEAR, time); 1404 */ 1405 // prefix with yMd 1406 timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton; 1407 String pattern =dtpng.getBestPattern(timeSkeleton); 1408 // for fall back interval patterns, 1409 // the first part of the pattern is empty, 1410 // the second part of the pattern is the full-pattern 1411 // should be used in fall-back. 1412 PatternInfo ptn = new PatternInfo( 1413 null, pattern, fInfo.getDefaultOrder()); 1414 intervalPatterns.put(DateIntervalInfo. 1415 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn); 1416 intervalPatterns.put(DateIntervalInfo. 1417 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn); 1418 intervalPatterns.put(DateIntervalInfo. 1419 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn); 1420 } else { 1421 /* if both present, 1422 * 1) when the year, month, or day differs, 1423 * concatenate the two original expressions with a separator between, 1424 * 2) otherwise, present the date followed by the 1425 * range expression for the time. 1426 */ 1427 /* 1428 * 1) when the year, month, or day differs, 1429 * concatenate the two original expressions with a separator between, 1430 */ 1431 // if field exists, use fall back 1432 if ( !fieldExistsInSkeleton(Calendar.DATE, dateSkeleton) ) { 1433 // prefix skeleton with 'd' 1434 skeleton = DateIntervalInfo. 1435 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE] + skeleton; 1436 genFallbackPattern(Calendar.DATE, skeleton, intervalPatterns, dtpng); 1437 } 1438 if ( !fieldExistsInSkeleton(Calendar.MONTH, dateSkeleton) ) { 1439 // then prefix skeleton with 'M' 1440 skeleton = DateIntervalInfo. 1441 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH] + skeleton; 1442 genFallbackPattern(Calendar.MONTH, skeleton, intervalPatterns, dtpng); 1443 } 1444 if ( !fieldExistsInSkeleton(Calendar.YEAR, dateSkeleton) ) { 1445 // then prefix skeleton with 'y' 1446 skeleton = DateIntervalInfo. 1447 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR] + skeleton; 1448 genFallbackPattern(Calendar.YEAR, skeleton, intervalPatterns, dtpng); 1449 } 1450 1451 /* 1452 * 2) otherwise, present the date followed by the 1453 * range expression for the time. 1454 */ 1455 if (fDateTimeFormat == null) { 1456 fDateTimeFormat = "{1} {0}"; 1457 } 1458 String datePattern =dtpng.getBestPattern(dateSkeleton); 1459 concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.AM_PM, intervalPatterns); 1460 concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.HOUR, intervalPatterns); 1461 concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.MINUTE, intervalPatterns); 1462 } 1463 1464 return intervalPatterns; 1465 } 1466 1467 /** 1468 * Retrieves the concatenation DateTime pattern from the resource bundle. 1469 * @param locale Locale to retrieve. 1470 * @return Concatenation DateTime pattern. 1471 */ getConcatenationPattern(ULocale locale)1472 private String getConcatenationPattern(ULocale locale) { 1473 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale); 1474 ICUResourceBundle dtPatternsRb = rb.getWithFallback("calendar/gregorian/DateTimePatterns"); 1475 ICUResourceBundle concatenationPatternRb = (ICUResourceBundle) dtPatternsRb.get(8); 1476 if (concatenationPatternRb.getType() == UResourceBundle.STRING) { 1477 return concatenationPatternRb.getString(); 1478 } else { 1479 return concatenationPatternRb.getString(0); 1480 } 1481 } 1482 1483 /* 1484 * Generate fall back interval pattern given a calendar field, 1485 * a skeleton, and a date time pattern generator 1486 * @param field the largest different calendar field 1487 * @param skeleton a skeleton 1488 * @param dtpng date time pattern generator 1489 * @param intervalPatterns interval patterns 1490 */ genFallbackPattern(int field, String skeleton, Map<String, PatternInfo> intervalPatterns, DateTimePatternGenerator dtpng)1491 private void genFallbackPattern(int field, String skeleton, 1492 Map<String, PatternInfo> intervalPatterns, 1493 DateTimePatternGenerator dtpng) { 1494 String pattern = dtpng.getBestPattern(skeleton); 1495 // for fall back interval patterns, 1496 // the first part of the pattern is empty, 1497 // the second part of the pattern is the full-pattern 1498 // should be used in fall-back. 1499 PatternInfo ptn = new PatternInfo( 1500 null, pattern, fInfo.getDefaultOrder()); 1501 intervalPatterns.put( 1502 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptn); 1503 } 1504 1505 1506 1507 /* 1508 private void genFallbackForNotFound(String field, StringBuffer skeleton) { 1509 if ( SimpleDateFormat.isFieldUnitIgnored(skeleton.toString(), field) ) { 1510 // single date 1511 DateIntervalInfo.PatternInfo ptnInfo = 1512 new DateIntervalInfo.PatternInfo(null, fDateFormat.toPattern(), 1513 fInfo.getDefaultOrder()); 1514 fIntervalPatterns.put(field, ptnInfo); 1515 return; 1516 } else if ( skeleton.indexOf(field) == -1 ) { 1517 skeleton.insert(0,field); 1518 genFallbackPattern(field, skeleton, dtpng); 1519 } 1520 } 1521 */ 1522 1523 /* 1524 * get separated date and time skeleton from a combined skeleton. 1525 * 1526 * The difference between date skeleton and normalizedDateSkeleton are: 1527 * 1. both 'y' and 'd' are appeared only once in normalizeDateSkeleton 1528 * 2. 'E' and 'EE' are normalized into 'EEE' 1529 * 3. 'MM' is normalized into 'M' 1530 * 1531 ** the difference between time skeleton and normalizedTimeSkeleton are: 1532 * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton, 1533 * 2. 'a' is omitted in normalized time skeleton. 1534 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized time 1535 * skeleton 1536 * 1537 * 1538 * @param skeleton given combined skeleton. 1539 * @param date Output parameter for date only skeleton. 1540 * @param normalizedDate Output parameter for normalized date only 1541 * 1542 * @param time Output parameter for time only skeleton. 1543 * @param normalizedTime Output parameter for normalized time only 1544 * skeleton. 1545 */ getDateTimeSkeleton(String skeleton, StringBuilder dateSkeleton, StringBuilder normalizedDateSkeleton, StringBuilder timeSkeleton, StringBuilder normalizedTimeSkeleton)1546 private static void getDateTimeSkeleton(String skeleton, 1547 StringBuilder dateSkeleton, 1548 StringBuilder normalizedDateSkeleton, 1549 StringBuilder timeSkeleton, 1550 StringBuilder normalizedTimeSkeleton) 1551 { 1552 // dateSkeleton follows the sequence of y*M*E*d* 1553 // timeSkeleton follows the sequence of hm*[v|z]? 1554 int i; 1555 int ECount = 0; 1556 int dCount = 0; 1557 int MCount = 0; 1558 int yCount = 0; 1559 int hCount = 0; 1560 int HCount = 0; 1561 int mCount = 0; 1562 int vCount = 0; 1563 int zCount = 0; 1564 1565 for (i = 0; i < skeleton.length(); ++i) { 1566 char ch = skeleton.charAt(i); 1567 switch ( ch ) { 1568 case 'E': 1569 dateSkeleton.append(ch); 1570 ++ECount; 1571 break; 1572 case 'd': 1573 dateSkeleton.append(ch); 1574 ++dCount; 1575 break; 1576 case 'M': 1577 dateSkeleton.append(ch); 1578 ++MCount; 1579 break; 1580 case 'y': 1581 dateSkeleton.append(ch); 1582 ++yCount; 1583 break; 1584 case 'G': 1585 case 'Y': 1586 case 'u': 1587 case 'Q': 1588 case 'q': 1589 case 'L': 1590 case 'l': 1591 case 'W': 1592 case 'w': 1593 case 'D': 1594 case 'F': 1595 case 'g': 1596 case 'e': 1597 case 'c': 1598 case 'U': 1599 case 'r': 1600 normalizedDateSkeleton.append(ch); 1601 dateSkeleton.append(ch); 1602 break; 1603 case 'a': 1604 // 'a' is implicitly handled 1605 timeSkeleton.append(ch); 1606 break; 1607 case 'h': 1608 timeSkeleton.append(ch); 1609 ++hCount; 1610 break; 1611 case 'H': 1612 timeSkeleton.append(ch); 1613 ++HCount; 1614 break; 1615 case 'm': 1616 timeSkeleton.append(ch); 1617 ++mCount; 1618 break; 1619 case 'z': 1620 ++zCount; 1621 timeSkeleton.append(ch); 1622 break; 1623 case 'v': 1624 ++vCount; 1625 timeSkeleton.append(ch); 1626 break; 1627 case 'V': 1628 case 'Z': 1629 case 'k': 1630 case 'K': 1631 case 'j': 1632 case 's': 1633 case 'S': 1634 case 'A': 1635 timeSkeleton.append(ch); 1636 normalizedTimeSkeleton.append(ch); 1637 break; 1638 } 1639 } 1640 1641 /* generate normalized form for date*/ 1642 if ( yCount != 0 ) { 1643 for (i = 0; i < yCount; i++) { 1644 normalizedDateSkeleton.append('y'); 1645 } 1646 } 1647 if ( MCount != 0 ) { 1648 if ( MCount < 3 ) { 1649 normalizedDateSkeleton.append('M'); 1650 } else { 1651 for ( i = 0; i < MCount && i < 5; ++i ) { 1652 normalizedDateSkeleton.append('M'); 1653 } 1654 } 1655 } 1656 if ( ECount != 0 ) { 1657 if ( ECount <= 3 ) { 1658 normalizedDateSkeleton.append('E'); 1659 } else { 1660 for ( i = 0; i < ECount && i < 5; ++i ) { 1661 normalizedDateSkeleton.append('E'); 1662 } 1663 } 1664 } 1665 if ( dCount != 0 ) { 1666 normalizedDateSkeleton.append('d'); 1667 } 1668 1669 /* generate normalized form for time */ 1670 if ( HCount != 0 ) { 1671 normalizedTimeSkeleton.append('H'); 1672 } 1673 else if ( hCount != 0 ) { 1674 normalizedTimeSkeleton.append('h'); 1675 } 1676 if ( mCount != 0 ) { 1677 normalizedTimeSkeleton.append('m'); 1678 } 1679 if ( zCount != 0 ) { 1680 normalizedTimeSkeleton.append('z'); 1681 } 1682 if ( vCount != 0 ) { 1683 normalizedTimeSkeleton.append('v'); 1684 } 1685 } 1686 1687 1688 1689 /* 1690 * Generate date or time interval pattern from resource. 1691 * 1692 * It needs to handle the following: 1693 * 1. need to adjust field width. 1694 * For example, the interval patterns saved in DateIntervalInfo 1695 * includes "dMMMy", but not "dMMMMy". 1696 * Need to get interval patterns for dMMMMy from dMMMy. 1697 * Another example, the interval patterns saved in DateIntervalInfo 1698 * includes "hmv", but not "hmz". 1699 * Need to get interval patterns for "hmz' from 'hmv' 1700 * 1701 * 2. there might be no pattern for 'y' differ for skeleton "Md", 1702 * in order to get interval patterns for 'y' differ, 1703 * need to look for it from skeleton 'yMd' 1704 * 1705 * @param dateSkeleton normalized date skeleton 1706 * @param timeSkeleton normalized time skeleton 1707 * @param intervalPatterns interval patterns 1708 * @return whether there is interval patterns for the skeleton. 1709 * true if there is, false otherwise 1710 */ genSeparateDateTimePtn(String dateSkeleton, String timeSkeleton, Map<String, PatternInfo> intervalPatterns, DateTimePatternGenerator dtpng)1711 private boolean genSeparateDateTimePtn(String dateSkeleton, 1712 String timeSkeleton, 1713 Map<String, PatternInfo> intervalPatterns, 1714 DateTimePatternGenerator dtpng) 1715 { 1716 String skeleton; 1717 // if both date and time skeleton present, 1718 // the final interval pattern might include time interval patterns 1719 // ( when, am_pm, hour, minute, second differ ), 1720 // but not date interval patterns ( when year, month, day differ ). 1721 // For year/month/day differ, it falls back to fall-back pattern. 1722 if ( timeSkeleton.length() != 0 ) { 1723 skeleton = timeSkeleton; 1724 } else { 1725 skeleton = dateSkeleton; 1726 } 1727 1728 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy") 1729 * are defined in resource, 1730 * interval patterns for skeleton "dMMMMy" are calculated by 1731 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy" 1732 * 2. get the interval patterns for "dMMMy", 1733 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy" 1734 * getBestSkeleton() is step 1. 1735 */ 1736 // best skeleton, and the difference information 1737 BestMatchInfo retValue = fInfo.getBestSkeleton(skeleton); 1738 String bestSkeleton = retValue.bestMatchSkeleton; 1739 int differenceInfo = retValue.bestMatchDistanceInfo; 1740 1741 // Set patterns for fallback use, need to do this 1742 // before returning if differenceInfo == -1 1743 if (dateSkeleton.length() != 0 ) { 1744 fDatePattern = dtpng.getBestPattern(dateSkeleton); 1745 } 1746 if (timeSkeleton.length() != 0 ) { 1747 fTimePattern = dtpng.getBestPattern(timeSkeleton); 1748 } 1749 1750 // difference: 1751 // 0 means the best matched skeleton is the same as input skeleton 1752 // 1 means the fields are the same, but field width are different 1753 // 2 means the only difference between fields are v/z, 1754 // -1 means there are other fields difference 1755 // (this will happen, for instance, if the supplied skeleton has seconds, 1756 // but no skeletons in the intervalFormats data do) 1757 if ( differenceInfo == -1 ) { 1758 // skeleton has different fields, not only v/z difference 1759 return false; 1760 } 1761 1762 if ( timeSkeleton.length() == 0 ) { 1763 // only has date skeleton 1764 genIntervalPattern(Calendar.DATE, skeleton, bestSkeleton, differenceInfo, intervalPatterns); 1765 SkeletonAndItsBestMatch skeletons = genIntervalPattern( 1766 Calendar.MONTH, skeleton, 1767 bestSkeleton, differenceInfo, 1768 intervalPatterns); 1769 if ( skeletons != null ) { 1770 bestSkeleton = skeletons.skeleton; 1771 skeleton = skeletons.bestMatchSkeleton; 1772 } 1773 genIntervalPattern(Calendar.YEAR, skeleton, bestSkeleton, differenceInfo, intervalPatterns); 1774 genIntervalPattern(Calendar.ERA, skeleton, bestSkeleton, differenceInfo, intervalPatterns); 1775 } else { 1776 genIntervalPattern(Calendar.MINUTE, skeleton, bestSkeleton, differenceInfo, intervalPatterns); 1777 genIntervalPattern(Calendar.HOUR, skeleton, bestSkeleton, differenceInfo, intervalPatterns); 1778 genIntervalPattern(Calendar.AM_PM, skeleton, bestSkeleton, differenceInfo, intervalPatterns); 1779 } 1780 return true; 1781 1782 } 1783 1784 1785 1786 /* 1787 * Generate interval pattern from existing resource 1788 * 1789 * It not only save the interval patterns, 1790 * but also return the skeleton and its best match skeleton. 1791 * 1792 * @param field largest different calendar field 1793 * @param skeleton skeleton 1794 * @param bestSkeleton the best match skeleton which has interval pattern 1795 * defined in resource 1796 * @param differenceInfo the difference between skeleton and best skeleton 1797 * 0 means the best matched skeleton is the same as input skeleton 1798 * 1 means the fields are the same, but field width are different 1799 * 2 means the only difference between fields are v/z, 1800 * -1 means there are other fields difference 1801 * 1802 * @param intervalPatterns interval patterns 1803 * 1804 * @return an extended skeleton or extended best skeleton if applicable. 1805 * null otherwise. 1806 */ genIntervalPattern( int field, String skeleton, String bestSkeleton, int differenceInfo, Map<String, PatternInfo> intervalPatterns)1807 private SkeletonAndItsBestMatch genIntervalPattern( 1808 int field, String skeleton, String bestSkeleton, 1809 int differenceInfo, Map<String, PatternInfo> intervalPatterns) { 1810 SkeletonAndItsBestMatch retValue = null; 1811 PatternInfo pattern = fInfo.getIntervalPattern( 1812 bestSkeleton, field); 1813 if ( pattern == null ) { 1814 // single date 1815 if ( SimpleDateFormat.isFieldUnitIgnored(bestSkeleton, field) ) { 1816 PatternInfo ptnInfo = 1817 new PatternInfo(fDateFormat.toPattern(), 1818 null, 1819 fInfo.getDefaultOrder()); 1820 intervalPatterns.put(DateIntervalInfo. 1821 CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptnInfo); 1822 return null; 1823 } 1824 1825 // for 24 hour system, interval patterns in resource file 1826 // might not include pattern when am_pm differ, 1827 // which should be the same as hour differ. 1828 // add it here for simplicity 1829 if ( field == Calendar.AM_PM ) { 1830 pattern = fInfo.getIntervalPattern(bestSkeleton, 1831 Calendar.HOUR); 1832 if ( pattern != null ) { 1833 // share 1834 intervalPatterns.put(DateIntervalInfo. 1835 CALENDAR_FIELD_TO_PATTERN_LETTER[field], 1836 pattern); 1837 } 1838 return null; 1839 } 1840 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton, 1841 // first, get best match pattern "MMMd", 1842 // since there is no pattern for 'y' differs for skeleton 'MMMd', 1843 // need to look for it from skeleton 'yMMMd', 1844 // if found, adjust field width in interval pattern from 1845 // "MMM" to "MMMM". 1846 String fieldLetter = 1847 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]; 1848 bestSkeleton = fieldLetter + bestSkeleton; 1849 skeleton = fieldLetter + skeleton; 1850 // for example, looking for patterns when 'y' differ for 1851 // skeleton "MMMM". 1852 pattern = fInfo.getIntervalPattern(bestSkeleton, field); 1853 if ( pattern == null && differenceInfo == 0 ) { 1854 // if there is no skeleton "yMMMM" defined, 1855 // look for the best match skeleton, for example: "yMMM" 1856 BestMatchInfo tmpRetValue = fInfo.getBestSkeleton(skeleton); 1857 String tmpBestSkeleton = tmpRetValue.bestMatchSkeleton; 1858 differenceInfo = tmpRetValue.bestMatchDistanceInfo; 1859 if ( tmpBestSkeleton.length() != 0 && differenceInfo != -1 ) { 1860 pattern = fInfo.getIntervalPattern(tmpBestSkeleton, field); 1861 bestSkeleton = tmpBestSkeleton; 1862 } 1863 } 1864 if ( pattern != null ) { 1865 retValue = new SkeletonAndItsBestMatch(skeleton, bestSkeleton); 1866 } 1867 } 1868 if ( pattern != null ) { 1869 if ( differenceInfo != 0 ) { 1870 String part1 = adjustFieldWidth(skeleton, bestSkeleton, 1871 pattern.getFirstPart(), differenceInfo); 1872 String part2 = adjustFieldWidth(skeleton, bestSkeleton, 1873 pattern.getSecondPart(), differenceInfo); 1874 pattern = new PatternInfo(part1, part2, 1875 pattern.firstDateInPtnIsLaterDate()); 1876 } else { 1877 // pattern is immutable, no need to clone; 1878 // pattern = (PatternInfo)pattern.clone(); 1879 } 1880 intervalPatterns.put( 1881 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], pattern); 1882 } 1883 return retValue; 1884 } 1885 1886 /* 1887 * Adjust field width in best match interval pattern to match 1888 * the field width in input skeleton. 1889 * 1890 * TODO (xji) make a general solution 1891 * The adjusting rule can be: 1892 * 1. always adjust 1893 * 2. never adjust 1894 * 3. default adjust, which means adjust according to the following rules 1895 * 3.1 always adjust string, such as MMM and MMMM 1896 * 3.2 never adjust between string and numeric, such as MM and MMM 1897 * 3.3 always adjust year 1898 * 3.4 do not adjust 'd', 'h', or 'm' if h presents 1899 * 3.5 do not adjust 'M' if it is numeric(?) 1900 * 1901 * Since date interval format is well-formed format, 1902 * date and time skeletons are normalized previously, 1903 * till this stage, the adjust here is only "adjust strings, such as MMM 1904 * and MMMM, EEE and EEEE. 1905 * 1906 * @param inputSkeleton the input skeleton 1907 * @param bestMatchSkeleton the best match skeleton 1908 * @param bestMatchIntervalpattern the best match interval pattern 1909 * @param differenceInfo the difference between 2 skeletons 1910 * 1 means only field width differs 1911 * 2 means v/z exchange 1912 * @return the adjusted interval pattern 1913 */ adjustFieldWidth(String inputSkeleton, String bestMatchSkeleton, String bestMatchIntervalPattern, int differenceInfo )1914 private static String adjustFieldWidth(String inputSkeleton, 1915 String bestMatchSkeleton, 1916 String bestMatchIntervalPattern, 1917 int differenceInfo ) { 1918 1919 if ( bestMatchIntervalPattern == null ) { 1920 return null; // the 2nd part could be null 1921 } 1922 int[] inputSkeletonFieldWidth = new int[58]; 1923 int[] bestMatchSkeletonFieldWidth = new int[58]; 1924 1925 /* initialize as following 1926 { 1927 // A B C D E F G H I J K L M N O 1928 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1929 // P Q R S T U V W X Y Z 1930 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1931 // a b c d e f g h i j k l m n o 1932 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1933 // p q r s t u v w x y z 1934 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1935 }; 1936 */ 1937 1938 1939 DateIntervalInfo.parseSkeleton(inputSkeleton, inputSkeletonFieldWidth); 1940 DateIntervalInfo.parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth); 1941 if ( differenceInfo == 2 ) { 1942 bestMatchIntervalPattern = bestMatchIntervalPattern.replace('v', 'z'); 1943 } 1944 1945 StringBuilder adjustedPtn = new StringBuilder(bestMatchIntervalPattern); 1946 1947 boolean inQuote = false; 1948 char prevCh = 0; 1949 int count = 0; 1950 1951 int PATTERN_CHAR_BASE = 0x41; 1952 1953 // loop through the pattern string character by character 1954 int adjustedPtnLength = adjustedPtn.length(); 1955 for (int i = 0; i < adjustedPtnLength; ++i) { 1956 char ch = adjustedPtn.charAt(i); 1957 if (ch != prevCh && count > 0) { 1958 // check the repeativeness of pattern letter 1959 char skeletonChar = prevCh; 1960 if ( skeletonChar == 'L' ) { 1961 // for skeleton "M+", the pattern is "...L..." 1962 skeletonChar = 'M'; 1963 } 1964 int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE]; 1965 int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE]; 1966 if ( fieldCount == count && inputFieldCount > fieldCount ) { 1967 count = inputFieldCount - fieldCount; 1968 for ( int j = 0; j < count; ++j ) { 1969 adjustedPtn.insert(i, prevCh); 1970 } 1971 i += count; 1972 adjustedPtnLength += count; 1973 } 1974 count = 0; 1975 } 1976 if (ch == '\'') { 1977 // Consecutive single quotes are a single quote literal, 1978 // either outside of quotes or between quotes 1979 if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == '\'') { 1980 ++i; 1981 } else { 1982 inQuote = ! inQuote; 1983 } 1984 } 1985 else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) 1986 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) { 1987 // ch is a date-time pattern character 1988 prevCh = ch; 1989 ++count; 1990 } 1991 } 1992 if ( count > 0 ) { 1993 // last item 1994 // check the repeativeness of pattern letter 1995 char skeletonChar = prevCh; 1996 if ( skeletonChar == 'L' ) { 1997 // for skeleton "M+", the pattern is "...L..." 1998 skeletonChar = 'M'; 1999 } 2000 int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE]; 2001 int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE]; 2002 if ( fieldCount == count && inputFieldCount > fieldCount ) { 2003 count = inputFieldCount - fieldCount; 2004 for ( int j = 0; j < count; ++j ) { 2005 adjustedPtn.append(prevCh); 2006 } 2007 } 2008 } 2009 return adjustedPtn.toString(); 2010 } 2011 2012 2013 /* 2014 * Concat a single date pattern with a time interval pattern, 2015 * set it into the intervalPatterns, while field is time field. 2016 * This is used to handle time interval patterns on skeleton with 2017 * both time and date. Present the date followed by 2018 * the range expression for the time. 2019 * @param dtfmt date and time format 2020 * @param datePattern date pattern 2021 * @param field time calendar field: AM_PM, HOUR, MINUTE 2022 * @param intervalPatterns interval patterns 2023 */ concatSingleDate2TimeInterval(String dtfmt, String datePattern, int field, Map<String, PatternInfo> intervalPatterns)2024 private void concatSingleDate2TimeInterval(String dtfmt, 2025 String datePattern, 2026 int field, 2027 Map<String, PatternInfo> intervalPatterns) 2028 { 2029 2030 PatternInfo timeItvPtnInfo = 2031 intervalPatterns.get(DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]); 2032 if ( timeItvPtnInfo != null ) { 2033 String timeIntervalPattern = timeItvPtnInfo.getFirstPart() + 2034 timeItvPtnInfo.getSecondPart(); 2035 String pattern = SimpleFormatterImpl.formatRawPattern( 2036 dtfmt, 2, 2, timeIntervalPattern, datePattern); 2037 timeItvPtnInfo = DateIntervalInfo.genPatternInfo(pattern, 2038 timeItvPtnInfo.firstDateInPtnIsLaterDate()); 2039 intervalPatterns.put( 2040 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], timeItvPtnInfo); 2041 } 2042 // else: fall back 2043 // it should not happen if the interval format defined is valid 2044 } 2045 2046 2047 /* 2048 * check whether a calendar field present in a skeleton. 2049 * @param field calendar field need to check 2050 * @param skeleton given skeleton on which to check the calendar field 2051 * @return true if field present in a skeleton. 2052 */ fieldExistsInSkeleton(int field, String skeleton)2053 private static boolean fieldExistsInSkeleton(int field, String skeleton) 2054 { 2055 String fieldChar = DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]; 2056 return ( (skeleton.indexOf(fieldChar) == -1) ? false : true ) ; 2057 } 2058 2059 2060 /* 2061 * readObject. 2062 */ readObject(ObjectInputStream stream)2063 private void readObject(ObjectInputStream stream) 2064 throws IOException, ClassNotFoundException { 2065 stream.defaultReadObject(); 2066 initializePattern(isDateIntervalInfoDefault ? LOCAL_PATTERN_CACHE : null); 2067 } 2068 2069 /** 2070 * Get the internal patterns for the skeleton 2071 * @deprecated This API is ICU internal only. 2072 * @hide deprecated on icu4j-org 2073 * @hide draft / provisional / internal are hidden on OHOS 2074 */ 2075 @Deprecated getRawPatterns()2076 public Map<String, PatternInfo> getRawPatterns() { 2077 // this is unmodifiable, so ok to return directly 2078 return fIntervalPatterns; 2079 } 2080 } 2081