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