2 * Copyright (C) 2008-2013, International Business Machines
3 * Corporation and others. All Rights Reserved.
6 package com.ibm.icu.text;
8 import java.io.IOException;
9 import java.io.ObjectInputStream;
10 import java.text.FieldPosition;
11 import java.text.ParsePosition;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.Locale;
17 import com.ibm.icu.impl.CalendarData;
18 import com.ibm.icu.impl.ICUCache;
19 import com.ibm.icu.impl.SimpleCache;
20 import com.ibm.icu.text.DateIntervalInfo.PatternInfo;
21 import com.ibm.icu.util.Calendar;
22 import com.ibm.icu.util.DateInterval;
23 import com.ibm.icu.util.Output;
24 import com.ibm.icu.util.ULocale;
25 import com.ibm.icu.util.ULocale.Category;
29 * DateIntervalFormat is a class for formatting and parsing date
30 * intervals in a language-independent manner.
31 * Only formatting is supported. Parsing is not supported.
34 * Date interval means from one date to another date,
35 * for example, from "Jan 11, 2008" to "Jan 18, 2008".
36 * We introduced class DateInterval to represent it.
37 * DateInterval is a pair of UDate, which is
38 * the standard milliseconds since 24:00 GMT, Jan 1, 1970.
41 * DateIntervalFormat formats a DateInterval into
42 * text as compactly as possible.
43 * For example, the date interval format from "Jan 11, 2008" to "Jan 18,. 2008"
44 * is "Jan 11-18, 2008" for English.
45 * And it parses text into DateInterval,
46 * although initially, parsing is not supported.
49 * There is no structural information in date time patterns.
50 * For any punctuations and string literals inside a date time pattern,
51 * we do not know whether it is just a separator, or a prefix, or a suffix.
52 * Without such information, so, it is difficult to generate a sub-pattern
53 * (or super-pattern) by algorithm.
54 * So, formatting a DateInterval is pattern-driven. It is very
55 * similar to formatting in SimpleDateFormat.
56 * We introduce class DateIntervalInfo to save date interval
57 * patterns, similar to date time pattern in SimpleDateFormat.
60 * Logically, the interval patterns are mappings
61 * from (skeleton, the_largest_different_calendar_field)
62 * to (date_interval_pattern).
68 * only keeps the field pattern letter and ignores all other parts
69 * in a pattern, such as space, punctuations, and string literals.
71 * hides the order of fields.
73 * might hide a field's pattern letter length.
75 * For those non-digit calendar fields, the pattern letter length is
76 * important, such as MMM, MMMM, and MMMMM; EEE and EEEE,
77 * and the field's pattern letter length is honored.
79 * For the digit calendar fields, such as M or MM, d or dd, yy or yyyy,
80 * the field pattern length is ignored and the best match, which is defined
81 * in date time patterns, will be returned without honor the field pattern
82 * letter length in skeleton.
86 * The calendar fields we support for interval formatting are:
87 * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.
88 * Those calendar fields can be defined in the following order:
89 * year > month > date > hour (in day) > minute
91 * The largest different calendar fields between 2 calendars is the
92 * first different calendar field in above order.
94 * For example: the largest different calendar fields between "Jan 10, 2007"
95 * and "Feb 20, 2008" is year.
98 * For other calendar fields, the compact interval formatting is not
99 * supported. And the interval format will be fall back to fall-back
100 * patterns, which is mostly "{date0} - {date1}".
103 * There is a set of pre-defined static skeleton strings in DateFormat,
104 * There are pre-defined interval patterns for those pre-defined skeletons
105 * in locales' resource files.
106 * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is "yMMMd",
107 * in en_US, if the largest different calendar field between date1 and date2
108 * is "year", the date interval pattern is "MMM d, yyyy - MMM d, yyyy",
109 * such as "Jan 10, 2007 - Jan 10, 2008".
110 * If the largest different calendar field between date1 and date2 is "month",
111 * the date interval pattern is "MMM d - MMM d, yyyy",
112 * such as "Jan 10 - Feb 10, 2007".
113 * If the largest different calendar field between date1 and date2 is "day",
114 * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".
116 * For date skeleton, the interval patterns when year, or month, or date is
117 * different are defined in resource files.
118 * For time skeleton, the interval patterns when am/pm, or hour, or minute is
119 * different are defined in resource files.
122 * If a skeleton is not found in a locale's DateIntervalInfo, which means
123 * the interval patterns for the skeleton is not defined in resource file,
124 * the interval pattern will falls back to the interval "fallback" pattern
125 * defined in resource file.
126 * If the interval "fallback" pattern is not defined, the default fall-back
127 * is "{date0} - {data1}".
130 * For the combination of date and time,
131 * The rule to genearte interval patterns are:
134 * when the year, month, or day differs, falls back to fall-back
135 * interval pattern, which mostly is the concatenate the two original
136 * expressions with a separator between,
137 * For example, interval pattern from "Jan 10, 2007 10:10 am"
138 * to "Jan 11, 2007 10:10am" is
139 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
141 * otherwise, present the date followed by the range expression
143 * For example, interval pattern from "Jan 10, 2007 10:10 am"
144 * to "Jan 10, 2007 11:10am" is "Jan 10, 2007 10:10 am - 11:10am"
149 * If two dates are the same, the interval pattern is the single date pattern.
150 * For example, interval pattern from "Jan 10, 2007" to "Jan 10, 2007" is
153 * Or if the presenting fields between 2 dates have the exact same values,
154 * the interval pattern is the single date pattern.
155 * For example, if user only requests year and month,
156 * the interval pattern from "Jan 10, 2007" to "Jan 20, 2007" is "Jan 2007".
159 * DateIntervalFormat needs the following information for correct
160 * formatting: time zone, calendar type, pattern, date format symbols,
161 * and date interval patterns.
162 * It can be instantiated in several ways:
165 * create an instance using default or given locale plus given skeleton.
166 * Users are encouraged to created date interval formatter this way and
167 * to use the pre-defined skeleton macros, such as
168 * YEAR_NUM_MONTH, which consists the calendar fields and
172 * create an instance using default or given locale plus given skeleton
173 * plus a given DateIntervalInfo.
174 * This factory method is for powerful users who want to provide their own
176 * Locale provides the timezone, calendar, and format symbols information.
177 * Local plus skeleton provides full pattern information.
178 * DateIntervalInfo provides the date interval patterns.
183 * For the calendar field pattern letter, such as G, y, M, d, a, h, H, m, s etc.
184 * DateIntervalFormat uses the same syntax as that of
188 * Code Sample: general usage
191 * // the date interval object which the DateIntervalFormat formats on
193 * DateInterval dtInterval = new DateInterval(1000*3600*24L, 1000*3600*24*2L);
194 * DateIntervalFormat dtIntervalFmt = DateIntervalFormat.getInstance(
195 * YEAR_MONTH_DAY, Locale("en", "GB", ""));
196 * StringBuffer str = new StringBuffer("");
197 * FieldPosition pos = new FieldPosition(0);
199 * dtIntervalFmt.format(dtInterval, dateIntervalString, pos);
204 * Code Sample: for powerful users who wants to use their own interval pattern
207 * import com.ibm.icu.text.DateIntervalInfo;
208 * import com.ibm.icu.text.DateIntervalFormat;
209 * ....................
211 * // Get DateIntervalFormat instance using default locale
212 * DateIntervalFormat dtitvfmt = DateIntervalFormat.getInstance(YEAR_MONTH_DAY);
214 * // Create an empty DateIntervalInfo object, which does not have any interval patterns inside.
215 * dtitvinf = new DateIntervalInfo();
217 * // a series of set interval patterns.
218 * // Only ERA, YEAR, MONTH, DATE, DAY_OF_MONTH, DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, and MINUTE are supported.
219 * dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'");
220 * dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d");
221 * dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d");
222 * dtitvinf.setIntervalPattern("yMMMd", Calendar.HOUR_OF_DAY, "yyyy MMM d HH:mm ~ HH:mm");
224 * // Set fallback interval pattern. Fallback pattern is used when interval pattern is not found.
225 * // If the fall-back pattern is not set, falls back to {date0} - {date1} if interval pattern is not found.
226 * dtitvinf.setFallbackIntervalPattern("{0} - {1}");
228 * // Set above DateIntervalInfo object as the interval patterns of date interval formatter
229 * dtitvfmt.setDateIntervalInfo(dtitvinf);
231 * // Prepare to format
232 * pos = new FieldPosition(0);
233 * str = new StringBuffer("");
235 * // The 2 calendars should be equivalent, otherwise, IllegalArgumentException will be thrown by format()
236 * Calendar fromCalendar = (Calendar) dtfmt.getCalendar().clone();
237 * Calendar toCalendar = (Calendar) dtfmt.getCalendar().clone();
238 * fromCalendar.setTimeInMillis(....);
239 * toCalendar.setTimeInMillis(...);
241 * //Formatting given 2 calendars
242 * dtitvfmt.format(fromCalendar, toCalendar, str, pos);
249 public class DateIntervalFormat extends UFormat {
251 private static final long serialVersionUID = 1;
254 * Used to save the information for a skeleton's best match skeleton.
255 * It is package accessible since it is used in DateIntervalInfo too.
257 static final class BestMatchInfo {
258 // the best match skeleton
259 final String bestMatchSkeleton;
260 // 0 means the best matched skeleton is the same as input skeleton
261 // 1 means the fields are the same, but field width are different
262 // 2 means the only difference between fields are v/z,
263 // -1 means there are other fields difference
264 final int bestMatchDistanceInfo;
265 BestMatchInfo(String bestSkeleton, int difference) {
266 bestMatchSkeleton = bestSkeleton;
267 bestMatchDistanceInfo = difference;
273 * Used to save the information on a skeleton and its best match.
275 private static final class SkeletonAndItsBestMatch {
276 final String skeleton;
277 final String bestMatchSkeleton;
278 SkeletonAndItsBestMatch(String skeleton, String bestMatch) {
279 this.skeleton = skeleton;
280 bestMatchSkeleton = bestMatch;
285 // Cache for the locale interval pattern
286 private static ICUCache<String, Map<String, PatternInfo>> LOCAL_PATTERN_CACHE =
287 new SimpleCache<String, Map<String, PatternInfo>>();
290 * The interval patterns for this locale.
292 private DateIntervalInfo fInfo;
295 * The DateFormat object used to format single pattern
297 private SimpleDateFormat fDateFormat;
300 * The 2 calendars with the from and to date.
301 * could re-use the calendar in fDateFormat,
302 * but keeping 2 calendars make it clear and clean.
304 private Calendar fFromCalendar;
305 private Calendar fToCalendar;
308 * Following are transient interval information
309 * relavent (locale) to this formatter.
311 private String fSkeleton = null;
314 * Needed for efficient deserialization. If set, it means we can use the
315 * cache to initialize fIntervalPatterns.
317 private boolean isDateIntervalInfoDefault;
320 * Interval patterns for this instance's locale.
322 private transient Map<String, PatternInfo> fIntervalPatterns = null;
326 * default constructor; private because we don't want anyone to use
328 @SuppressWarnings("unused")
329 private DateIntervalFormat() {
333 * Construct a DateIntervalFormat from DateFormat,
334 * a DateIntervalInfo, and skeleton.
335 * DateFormat provides the timezone, calendar,
336 * full pattern, and date format symbols information.
337 * It should be a SimpleDateFormat object which
338 * has a pattern in it.
339 * the DateIntervalInfo provides the interval patterns.
341 * @param skeleton the skeleton of the date formatter
342 * @param dtItvInfo the DateIntervalInfo object to be adopted.
343 * @param simpleDateFormat will be used for formatting
346 * @deprecated This API is ICU internal only.
348 public DateIntervalFormat(String skeleton, DateIntervalInfo dtItvInfo,
349 SimpleDateFormat simpleDateFormat)
351 fDateFormat = simpleDateFormat;
352 // freeze date interval info
354 fSkeleton = skeleton;
356 isDateIntervalInfoDefault = false;
357 fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
358 fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
359 initializePattern(null);
362 private DateIntervalFormat(String skeleton, ULocale locale,
363 SimpleDateFormat simpleDateFormat)
365 fDateFormat = simpleDateFormat;
366 fSkeleton = skeleton;
367 fInfo = new DateIntervalInfo(locale).freeze();
368 isDateIntervalInfoDefault = true;
369 fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
370 fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
371 initializePattern(LOCAL_PATTERN_CACHE);
376 * Construct a DateIntervalFormat from skeleton and the default <code>FORMAT</code> locale.
378 * This is a convenient override of
379 * getInstance(String skeleton, ULocale locale)
380 * with the value of locale as default <code>FORMAT</code> locale.
382 * @param skeleton the skeleton on which interval format based.
383 * @return a date time interval formatter.
384 * @see Category#FORMAT
387 public static final DateIntervalFormat
388 getInstance(String skeleton)
391 return getInstance(skeleton, ULocale.getDefault(Category.FORMAT));
396 * Construct a DateIntervalFormat from skeleton and a given locale.
398 * This is a convenient override of
399 * getInstance(String skeleton, ULocale locale)
401 * <p>Example code:{@.jcite com.ibm.icu.samples.text.dateintervalformat.DateIntervalFormatSample:---dtitvfmtPreDefinedExample}
402 * @param skeleton the skeleton on which interval format based.
403 * @param locale the given locale
404 * @return a date time interval formatter.
407 public static final DateIntervalFormat
408 getInstance(String skeleton, Locale locale)
410 return getInstance(skeleton, ULocale.forLocale(locale));
415 * Construct a DateIntervalFormat from skeleton and a given locale.
417 * In this factory method,
418 * the date interval pattern information is load from resource files.
419 * Users are encouraged to created date interval formatter this way and
420 * to use the pre-defined skeleton macros.
423 * There are pre-defined skeletons in DateFormat,
424 * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
426 * Those skeletons have pre-defined interval patterns in resource files.
427 * Users are encouraged to use them.
429 * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc);
431 * The given Locale provides the interval patterns.
432 * For example, for en_GB, if skeleton is YEAR_ABBR_MONTH_WEEKDAY_DAY,
433 * which is "yMMMEEEd",
434 * the interval patterns defined in resource file to above skeleton are:
435 * "EEE, d MMM, yyyy - EEE, d MMM, yyyy" for year differs,
436 * "EEE, d MMM - EEE, d MMM, yyyy" for month differs,
437 * "EEE, d - EEE, d MMM, yyyy" for day differs,
438 * @param skeleton the skeleton on which interval format based.
439 * @param locale the given locale
440 * @return a date time interval formatter.
443 public static final DateIntervalFormat
444 getInstance(String skeleton, ULocale locale)
446 DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
447 return new DateIntervalFormat(skeleton, locale, new SimpleDateFormat(generator.getBestPattern(skeleton), locale));
453 * Construct a DateIntervalFormat from skeleton
454 * DateIntervalInfo, and the default <code>FORMAT</code> locale.
456 * This is a convenient override of
457 * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
458 * with the locale value as default <code>FORMAT</code> locale.
460 * @param skeleton the skeleton on which interval format based.
461 * @param dtitvinf the DateIntervalInfo object to be adopted.
462 * @return a date time interval formatter.
463 * @see Category#FORMAT
466 public static final DateIntervalFormat getInstance(String skeleton,
467 DateIntervalInfo dtitvinf)
469 return getInstance(skeleton, ULocale.getDefault(Category.FORMAT), dtitvinf);
475 * Construct a DateIntervalFormat from skeleton
476 * a DateIntervalInfo, and the given locale.
478 * This is a convenient override of
479 * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
481 * <p>Example code:{@.jcite com.ibm.icu.samples.text.dateintervalformat.DateIntervalFormatSample:---dtitvfmtCustomizedExample}
482 * @param skeleton the skeleton on which interval format based.
483 * @param locale the given locale
484 * @param dtitvinf the DateIntervalInfo object to be adopted.
485 * @return a date time interval formatter.
488 public static final DateIntervalFormat getInstance(String skeleton,
490 DateIntervalInfo dtitvinf)
492 return getInstance(skeleton, ULocale.forLocale(locale), dtitvinf);
498 * Construct a DateIntervalFormat from skeleton
499 * a DateIntervalInfo, and the given locale.
502 * In this factory method, user provides its own date interval pattern
503 * information, instead of using those pre-defined data in resource file.
504 * This factory method is for powerful users who want to provide their own
508 * There are pre-defined skeleton in DateFormat,
509 * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
511 * Those skeletons have pre-defined interval patterns in resource files.
512 * Users are encouraged to use them.
514 * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc,itvinf);
516 * the DateIntervalInfo provides the interval patterns.
518 * User are encouraged to set default interval pattern in DateIntervalInfo
519 * as well, if they want to set other interval patterns ( instead of
520 * reading the interval patterns from resource files).
521 * When the corresponding interval pattern for a largest calendar different
522 * field is not found ( if user not set it ), interval format fallback to
523 * the default interval pattern.
524 * If user does not provide default interval pattern, it fallback to
525 * "{date0} - {date1}"
527 * @param skeleton the skeleton on which interval format based.
528 * @param locale the given locale
529 * @param dtitvinf the DateIntervalInfo object to be adopted.
530 * @return a date time interval formatter.
533 public static final DateIntervalFormat getInstance(String skeleton,
535 DateIntervalInfo dtitvinf)
537 // clone. If it is frozen, clone returns itself, otherwise, clone
539 dtitvinf = (DateIntervalInfo)dtitvinf.clone();
540 DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
541 return new DateIntervalFormat(skeleton, dtitvinf, new SimpleDateFormat(generator.getBestPattern(skeleton), locale));
546 * Clone this Format object polymorphically.
547 * @return A copy of the object.
550 public Object clone()
552 DateIntervalFormat other = (DateIntervalFormat) super.clone();
553 other.fDateFormat = (SimpleDateFormat) fDateFormat.clone();
554 other.fInfo = (DateIntervalInfo) fInfo.clone();
555 other.fFromCalendar = (Calendar) fFromCalendar.clone();
556 other.fToCalendar = (Calendar) fToCalendar.clone();
562 * Format an object to produce a string. This method handles Formattable
563 * objects with a DateInterval type.
564 * If a the Formattable object type is not a DateInterval,
565 * IllegalArgumentException is thrown.
567 * @param obj The object to format.
568 * Must be a DateInterval.
569 * @param appendTo Output parameter to receive result.
570 * Result is appended to existing contents.
571 * @param fieldPosition On input: an alignment field, if desired.
572 * On output: the offsets of the alignment field.
573 * @return Reference to 'appendTo' parameter.
574 * @throws IllegalArgumentException if the formatted object is not
575 * DateInterval object
578 public final StringBuffer
579 format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition)
581 if ( obj instanceof DateInterval ) {
582 return format( (DateInterval)obj, appendTo, fieldPosition);
585 throw new IllegalArgumentException("Cannot format given Object (" + obj.getClass().getName() + ") as a DateInterval");
590 * Format a DateInterval to produce a string.
592 * @param dtInterval DateInterval to be formatted.
593 * @param appendTo Output parameter to receive result.
594 * Result is appended to existing contents.
595 * @param fieldPosition On input: an alignment field, if desired.
596 * On output: the offsets of the alignment field.
597 * @return Reference to 'appendTo' parameter.
600 public final StringBuffer format(DateInterval dtInterval,
601 StringBuffer appendTo,
602 FieldPosition fieldPosition)
604 fFromCalendar.setTimeInMillis(dtInterval.getFromDate());
605 fToCalendar.setTimeInMillis(dtInterval.getToDate());
606 return format(fFromCalendar, fToCalendar, appendTo, fieldPosition);
611 * @deprecated This API is ICU internal only.
613 public String getPatterns(Calendar fromCalendar,
615 Output<String> part2) {
616 // First, find the largest different calendar field.
618 if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
619 field = Calendar.ERA;
620 } else if ( fromCalendar.get(Calendar.YEAR) !=
621 toCalendar.get(Calendar.YEAR) ) {
622 field = Calendar.YEAR;
623 } else if ( fromCalendar.get(Calendar.MONTH) !=
624 toCalendar.get(Calendar.MONTH) ) {
625 field = Calendar.MONTH;
626 } else if ( fromCalendar.get(Calendar.DATE) !=
627 toCalendar.get(Calendar.DATE) ) {
628 field = Calendar.DATE;
629 } else if ( fromCalendar.get(Calendar.AM_PM) !=
630 toCalendar.get(Calendar.AM_PM) ) {
631 field = Calendar.AM_PM;
632 } else if ( fromCalendar.get(Calendar.HOUR) !=
633 toCalendar.get(Calendar.HOUR) ) {
634 field = Calendar.HOUR;
635 } else if ( fromCalendar.get(Calendar.MINUTE) !=
636 toCalendar.get(Calendar.MINUTE) ) {
637 field = Calendar.MINUTE;
641 PatternInfo intervalPattern = fIntervalPatterns.get(
642 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
643 part2.value = intervalPattern.getSecondPart();
644 return intervalPattern.getFirstPart();
647 * Format 2 Calendars to produce a string.
649 * @param fromCalendar calendar set to the from date in date interval
650 * to be formatted into date interval string
651 * @param toCalendar calendar set to the to date in date interval
652 * to be formatted into date interval string
653 * @param appendTo Output parameter to receive result.
654 * Result is appended to existing contents.
655 * @param pos On input: an alignment field, if desired.
656 * On output: the offsets of the alignment field.
657 * @return Reference to 'appendTo' parameter.
658 * @throws IllegalArgumentException if the two calendars are not equivalent.
661 public final StringBuffer format(Calendar fromCalendar,
663 StringBuffer appendTo,
666 // not support different calendar types and time zones
667 if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
668 throw new IllegalArgumentException("can not format on two different calendars");
671 // First, find the largest different calendar field.
672 int field = -1; //init with an invalid value.
674 if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
675 field = Calendar.ERA;
676 } else if ( fromCalendar.get(Calendar.YEAR) !=
677 toCalendar.get(Calendar.YEAR) ) {
678 field = Calendar.YEAR;
679 } else if ( fromCalendar.get(Calendar.MONTH) !=
680 toCalendar.get(Calendar.MONTH) ) {
681 field = Calendar.MONTH;
682 } else if ( fromCalendar.get(Calendar.DATE) !=
683 toCalendar.get(Calendar.DATE) ) {
684 field = Calendar.DATE;
685 } else if ( fromCalendar.get(Calendar.AM_PM) !=
686 toCalendar.get(Calendar.AM_PM) ) {
687 field = Calendar.AM_PM;
688 } else if ( fromCalendar.get(Calendar.HOUR) !=
689 toCalendar.get(Calendar.HOUR) ) {
690 field = Calendar.HOUR;
691 } else if ( fromCalendar.get(Calendar.MINUTE) !=
692 toCalendar.get(Calendar.MINUTE) ) {
693 field = Calendar.MINUTE;
695 /* ignore the second/millisecond etc. small fields' difference.
696 * use single date when all the above are the same.
698 return fDateFormat.format(fromCalendar, appendTo, pos);
701 // get interval pattern
702 PatternInfo intervalPattern = fIntervalPatterns.get(
703 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
705 if ( intervalPattern == null ) {
706 if ( fDateFormat.isFieldUnitIgnored(field) ) {
707 /* the largest different calendar field is small than
708 * the smallest calendar field in pattern,
709 * return single date format.
711 return fDateFormat.format(fromCalendar, appendTo, pos);
714 return fallbackFormat(fromCalendar, toCalendar, appendTo, pos);
717 // If the first part in interval pattern is empty,
718 // the 2nd part of it saves the full-pattern used in fall-back.
719 // For a 'real' interval pattern, the first part will never be empty.
720 if ( intervalPattern.getFirstPart() == null ) {
722 return fallbackFormat(fromCalendar, toCalendar, appendTo, pos,
723 intervalPattern.getSecondPart());
727 if ( intervalPattern.firstDateInPtnIsLaterDate() ) {
728 firstCal = toCalendar;
729 secondCal = fromCalendar;
731 firstCal = fromCalendar;
732 secondCal = toCalendar;
734 // break the interval pattern into 2 parts
735 // first part should not be empty,
736 String originalPattern = fDateFormat.toPattern();
737 fDateFormat.applyPattern(intervalPattern.getFirstPart());
738 fDateFormat.format(firstCal, appendTo, pos);
739 if ( intervalPattern.getSecondPart() != null ) {
740 fDateFormat.applyPattern(intervalPattern.getSecondPart());
741 fDateFormat.format(secondCal, appendTo, pos);
743 fDateFormat.applyPattern(originalPattern);
749 * Format 2 Calendars to using fall-back interval pattern
751 * The full pattern used in this fall-back format is the
752 * full pattern of the date formatter.
754 * @param fromCalendar calendar set to the from date in date interval
755 * to be formatted into date interval string
756 * @param toCalendar calendar set to the to date in date interval
757 * to be formatted into date interval string
758 * @param appendTo Output parameter to receive result.
759 * Result is appended to existing contents.
760 * @param pos On input: an alignment field, if desired.
761 * On output: the offsets of the alignment field.
762 * @return Reference to 'appendTo' parameter.
764 private final StringBuffer fallbackFormat(Calendar fromCalendar,
766 StringBuffer appendTo,
769 StringBuffer earlierDate = new StringBuffer(64);
770 earlierDate = fDateFormat.format(fromCalendar, earlierDate, pos);
771 StringBuffer laterDate = new StringBuffer(64);
772 laterDate = fDateFormat.format(toCalendar, laterDate, pos);
773 String fallbackPattern = fInfo.getFallbackIntervalPattern();
774 String fallback = MessageFormat.format(fallbackPattern, new Object[]
775 {earlierDate.toString(), laterDate.toString()});
776 appendTo.append(fallback);
782 * Format 2 Calendars to using fall-back interval pattern
784 * This fall-back pattern is generated on a given full pattern,
785 * not the full pattern of the date formatter.
787 * @param fromCalendar calendar set to the from date in date interval
788 * to be formatted into date interval string
789 * @param toCalendar calendar set to the to date in date interval
790 * to be formatted into date interval string
791 * @param appendTo Output parameter to receive result.
792 * Result is appended to existing contents.
793 * @param pos On input: an alignment field, if desired.
794 * On output: the offsets of the alignment field.
795 * @param fullPattern the full pattern need to apply to date formatter
796 * @return Reference to 'appendTo' parameter.
798 private final StringBuffer fallbackFormat(Calendar fromCalendar,
800 StringBuffer appendTo,
802 String fullPattern) {
803 String originalPattern = fDateFormat.toPattern();
804 fDateFormat.applyPattern(fullPattern);
805 fallbackFormat(fromCalendar, toCalendar, appendTo, pos);
806 fDateFormat.applyPattern(originalPattern);
812 * Date interval parsing is not supported.
814 * This method should handle parsing of
815 * date time interval strings into Formattable objects with
816 * DateInterval type, which is a pair of UDate.
819 * Before calling, set parse_pos.index to the offset you want to start
820 * parsing at in the source. After calling, parse_pos.index is the end of
821 * the text you parsed. If error occurs, index is unchanged.
823 * When parsing, leading whitespace is discarded (with a successful parse),
824 * while trailing whitespace is left as is.
826 * See Format.parseObject() for more.
828 * @param source The string to be parsed into an object.
829 * @param parse_pos The position to start parsing at. Since no parsing
830 * is supported, upon return this param is unchanged.
831 * @return A newly created Formattable* object, or NULL
834 * @deprecated This API is ICU internal only.
836 public Object parseObject(String source, ParsePosition parse_pos)
838 throw new UnsupportedOperationException("parsing is not supported");
843 * Gets the date time interval patterns.
844 * @return a copy of the date time interval patterns associated with
845 * this date interval formatter.
848 public DateIntervalInfo getDateIntervalInfo()
850 return (DateIntervalInfo)fInfo.clone();
855 * Set the date time interval patterns.
856 * @param newItvPattern the given interval patterns to copy.
859 public void setDateIntervalInfo(DateIntervalInfo newItvPattern)
861 // clone it. If it is frozen, the clone returns itself.
862 // Otherwise, clone returns a copy
863 fInfo = (DateIntervalInfo)newItvPattern.clone();
864 this.isDateIntervalInfoDefault = false;
865 fInfo.freeze(); // freeze it
866 if ( fDateFormat != null ) {
867 initializePattern(null);
873 * Gets the date formatter
874 * @return a copy of the date formatter associated with
875 * this date interval formatter.
878 public DateFormat getDateFormat()
880 return (DateFormat)fDateFormat.clone();
885 * Below are for generating interval patterns locale to the formatter
889 * Initialize interval patterns locale to this formatter.
891 private void initializePattern(ICUCache<String, Map<String, PatternInfo>> cache) {
892 String fullPattern = fDateFormat.toPattern();
893 ULocale locale = fDateFormat.getLocale();
895 Map<String, PatternInfo> patterns = null;
897 if ( fSkeleton != null ) {
898 key = locale.toString() + "+" + fullPattern + "+" + fSkeleton;
900 key = locale.toString() + "+" + fullPattern;
902 patterns = cache.get(key);
904 if (patterns == null) {
905 Map<String, PatternInfo> intervalPatterns = initializeIntervalPattern(fullPattern, locale);
906 patterns = Collections.unmodifiableMap(intervalPatterns);
908 cache.put(key, patterns);
911 fIntervalPatterns = patterns;
917 * Initialize interval patterns locale to this formatter
919 * This code is a bit complicated since
920 * 1. the interval patterns saved in resource bundle files are interval
921 * patterns based on date or time only.
922 * It does not have interval patterns based on both date and time.
923 * Interval patterns on both date and time are algorithm generated.
925 * For example, it has interval patterns on skeleton "dMy" and "hm",
926 * but it does not have interval patterns on skeleton "dMyhm".
928 * The rule to generate interval patterns for both date and time skeleton are
929 * 1) when the year, month, or day differs, concatenate the two original
930 * expressions with a separator between,
931 * For example, interval pattern from "Jan 10, 2007 10:10 am"
932 * to "Jan 11, 2007 10:10am" is
933 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
935 * 2) otherwise, present the date followed by the range expression
937 * For example, interval pattern from "Jan 10, 2007 10:10 am"
938 * to "Jan 10, 2007 11:10am" is
939 * "Jan 10, 2007 10:10 am - 11:10am"
941 * 2. even a pattern does not request a certain calendar field,
942 * the interval pattern needs to include such field if such fields are
943 * different between 2 dates.
944 * For example, a pattern/skeleton is "hm", but the interval pattern
945 * includes year, month, and date when year, month, and date differs.
948 * @param fullPattern formatter's full pattern
949 * @param locale the given locale.
950 * @return interval patterns' hash map
952 private Map<String, PatternInfo> initializeIntervalPattern(String fullPattern, ULocale locale) {
953 DateTimePatternGenerator dtpng = DateTimePatternGenerator.getInstance(locale);
954 if ( fSkeleton == null ) {
955 // fSkeleton is already set by getDateIntervalInstance()
956 // or by getInstance(String skeleton, .... )
957 fSkeleton = dtpng.getSkeleton(fullPattern);
959 String skeleton = fSkeleton;
961 HashMap<String, PatternInfo> intervalPatterns = new HashMap<String, PatternInfo>();
963 /* Check whether the skeleton is a combination of date and time.
964 * For the complication reason 1 explained above.
966 StringBuilder date = new StringBuilder(skeleton.length());
967 StringBuilder normalizedDate = new StringBuilder(skeleton.length());
968 StringBuilder time = new StringBuilder(skeleton.length());
969 StringBuilder normalizedTime = new StringBuilder(skeleton.length());
971 /* the difference between time skeleton and normalizedTimeSkeleton are:
972 * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
973 * 2. 'a' is omitted in normalized time skeleton.
974 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized
977 * The difference between date skeleton and normalizedDateSkeleton are:
978 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
979 * 2. 'E' and 'EE' are normalized into 'EEE'
980 * 3. 'MM' is normalized into 'M'
982 getDateTimeSkeleton(skeleton, date, normalizedDate,
983 time, normalizedTime);
985 String dateSkeleton = date.toString();
986 String timeSkeleton = time.toString();
987 String normalizedDateSkeleton = normalizedDate.toString();
988 String normalizedTimeSkeleton = normalizedTime.toString();
990 boolean found = genSeparateDateTimePtn(normalizedDateSkeleton,
991 normalizedTimeSkeleton,
994 if ( found == false ) {
996 // TODO: if user asks "m", but "d" differ
997 //StringBuffer skeleton = new StringBuffer(skeleton);
998 if ( time.length() != 0 ) {
999 //genFallbackForNotFound(Calendar.MINUTE, skeleton);
1000 //genFallbackForNotFound(Calendar.HOUR, skeleton);
1001 //genFallbackForNotFound(Calendar.AM_PM, skeleton);
1002 if ( date.length() == 0 ) {
1004 timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
1005 String pattern =dtpng.getBestPattern(timeSkeleton);
1006 // for fall back interval patterns,
1007 // the first part of the pattern is empty,
1008 // the second part of the pattern is the full-pattern
1009 // should be used in fall-back.
1010 PatternInfo ptn = new PatternInfo(null, pattern,
1011 fInfo.getDefaultOrder());
1012 intervalPatterns.put(DateIntervalInfo.
1013 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
1014 // share interval pattern
1015 intervalPatterns.put(DateIntervalInfo.
1016 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
1017 // share interval pattern
1018 intervalPatterns.put(DateIntervalInfo.
1019 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
1021 //genFallbackForNotFound(Calendar.DATE, skeleton);
1022 //genFallbackForNotFound(Calendar.MONTH, skeleton);
1023 //genFallbackForNotFound(Calendar.YEAR, skeleton);
1026 //genFallbackForNotFound(Calendar.DATE, skeleton);
1027 //genFallbackForNotFound(Calendar.MONTH, skeleton);
1028 //genFallbackForNotFound(Calendar.YEAR, skeleton);
1030 return intervalPatterns;
1031 } // end of skeleton not found
1032 // interval patterns for skeleton are found in resource
1033 if ( time.length() == 0 ) {
1035 } else if ( date.length() == 0 ) {
1036 // need to set up patterns for y/M/d differ
1037 /* result from following looks confusing.
1038 * for example: 10 10:10 - 11 10:10, it is not
1039 * clear that the first 10 is the 10th day
1040 time.insert(0, 'd');
1041 genFallbackPattern(Calendar.DATE, time);
1042 time.insert(0, 'M');
1043 genFallbackPattern(Calendar.MONTH, time);
1044 time.insert(0, 'y');
1045 genFallbackPattern(Calendar.YEAR, time);
1048 timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
1049 String pattern =dtpng.getBestPattern(timeSkeleton);
1050 // for fall back interval patterns,
1051 // the first part of the pattern is empty,
1052 // the second part of the pattern is the full-pattern
1053 // should be used in fall-back.
1054 PatternInfo ptn = new PatternInfo(
1055 null, pattern, fInfo.getDefaultOrder());
1056 intervalPatterns.put(DateIntervalInfo.
1057 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
1058 intervalPatterns.put(DateIntervalInfo.
1059 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
1060 intervalPatterns.put(DateIntervalInfo.
1061 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
1064 * 1) when the year, month, or day differs,
1065 * concatenate the two original expressions with a separator between,
1066 * 2) otherwise, present the date followed by the
1067 * range expression for the time.
1070 * 1) when the year, month, or day differs,
1071 * concatenate the two original expressions with a separator between,
1073 // if field exists, use fall back
1074 if ( !fieldExistsInSkeleton(Calendar.DATE, dateSkeleton) ) {
1075 // prefix skeleton with 'd'
1076 skeleton = DateIntervalInfo.
1077 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE] + skeleton;
1078 genFallbackPattern(Calendar.DATE, skeleton, intervalPatterns, dtpng);
1080 if ( !fieldExistsInSkeleton(Calendar.MONTH, dateSkeleton) ) {
1081 // then prefix skeleton with 'M'
1082 skeleton = DateIntervalInfo.
1083 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH] + skeleton;
1084 genFallbackPattern(Calendar.MONTH, skeleton, intervalPatterns, dtpng);
1086 if ( !fieldExistsInSkeleton(Calendar.YEAR, dateSkeleton) ) {
1087 // then prefix skeleton with 'y'
1088 skeleton = DateIntervalInfo.
1089 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR] + skeleton;
1090 genFallbackPattern(Calendar.YEAR, skeleton, intervalPatterns, dtpng);
1094 * 2) otherwise, present the date followed by the
1095 * range expression for the time.
1097 // Need the Date/Time pattern for concatnation the date with
1098 // the time interval.
1099 // The date/time pattern ( such as {0} {1} ) is saved in
1100 // calendar, that is why need to get the CalendarData here.
1101 CalendarData calData = new CalendarData(locale, null);
1102 String[] patterns = calData.getDateTimePatterns();
1103 String datePattern =dtpng.getBestPattern(dateSkeleton);
1104 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.AM_PM, intervalPatterns);
1105 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.HOUR, intervalPatterns);
1106 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.MINUTE, intervalPatterns);
1109 return intervalPatterns;
1114 * Generate fall back interval pattern given a calendar field,
1115 * a skeleton, and a date time pattern generator
1116 * @param field the largest different calendar field
1117 * @param skeleton a skeleton
1118 * @param dtpng date time pattern generator
1119 * @param intervalPatterns interval patterns
1121 private void genFallbackPattern(int field, String skeleton,
1122 Map<String, PatternInfo> intervalPatterns,
1123 DateTimePatternGenerator dtpng) {
1124 String pattern = dtpng.getBestPattern(skeleton);
1125 // for fall back interval patterns,
1126 // the first part of the pattern is empty,
1127 // the second part of the pattern is the full-pattern
1128 // should be used in fall-back.
1129 PatternInfo ptn = new PatternInfo(
1130 null, pattern, fInfo.getDefaultOrder());
1131 intervalPatterns.put(
1132 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptn);
1138 private void genFallbackForNotFound(String field, StringBuffer skeleton) {
1139 if ( SimpleDateFormat.isFieldUnitIgnored(skeleton.toString(), field) ) {
1141 DateIntervalInfo.PatternInfo ptnInfo =
1142 new DateIntervalInfo.PatternInfo(null, fDateFormat.toPattern(),
1143 fInfo.getDefaultOrder());
1144 fIntervalPatterns.put(field, ptnInfo);
1146 } else if ( skeleton.indexOf(field) == -1 ) {
1147 skeleton.insert(0,field);
1148 genFallbackPattern(field, skeleton, dtpng);
1154 * get separated date and time skeleton from a combined skeleton.
1156 * The difference between date skeleton and normalizedDateSkeleton are:
1157 * 1. both 'y' and 'd' are appeared only once in normalizeDateSkeleton
1158 * 2. 'E' and 'EE' are normalized into 'EEE'
1159 * 3. 'MM' is normalized into 'M'
1161 ** the difference between time skeleton and normalizedTimeSkeleton are:
1162 * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton,
1163 * 2. 'a' is omitted in normalized time skeleton.
1164 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized time
1168 * @param skeleton given combined skeleton.
1169 * @param date Output parameter for date only skeleton.
1170 * @param normalizedDate Output parameter for normalized date only
1172 * @param time Output parameter for time only skeleton.
1173 * @param normalizedTime Output parameter for normalized time only
1176 private static void getDateTimeSkeleton(String skeleton,
1177 StringBuilder dateSkeleton,
1178 StringBuilder normalizedDateSkeleton,
1179 StringBuilder timeSkeleton,
1180 StringBuilder normalizedTimeSkeleton)
1182 // dateSkeleton follows the sequence of y*M*E*d*
1183 // timeSkeleton follows the sequence of hm*[v|z]?
1195 for (i = 0; i < skeleton.length(); ++i) {
1196 char ch = skeleton.charAt(i);
1199 dateSkeleton.append(ch);
1203 dateSkeleton.append(ch);
1207 dateSkeleton.append(ch);
1211 dateSkeleton.append(ch);
1228 normalizedDateSkeleton.append(ch);
1229 dateSkeleton.append(ch);
1232 // 'a' is implicitly handled
1233 timeSkeleton.append(ch);
1236 timeSkeleton.append(ch);
1240 timeSkeleton.append(ch);
1244 timeSkeleton.append(ch);
1249 timeSkeleton.append(ch);
1253 timeSkeleton.append(ch);
1263 timeSkeleton.append(ch);
1264 normalizedTimeSkeleton.append(ch);
1269 /* generate normalized form for date*/
1270 if ( yCount != 0 ) {
1271 for (i = 0; i < yCount; i++) {
1272 normalizedDateSkeleton.append('y');
1275 if ( MCount != 0 ) {
1277 normalizedDateSkeleton.append('M');
1279 for ( i = 0; i < MCount && i < 5; ++i ) {
1280 normalizedDateSkeleton.append('M');
1284 if ( ECount != 0 ) {
1285 if ( ECount <= 3 ) {
1286 normalizedDateSkeleton.append('E');
1288 for ( i = 0; i < ECount && i < 5; ++i ) {
1289 normalizedDateSkeleton.append('E');
1293 if ( dCount != 0 ) {
1294 normalizedDateSkeleton.append('d');
1297 /* generate normalized form for time */
1298 if ( HCount != 0 ) {
1299 normalizedTimeSkeleton.append('H');
1301 else if ( hCount != 0 ) {
1302 normalizedTimeSkeleton.append('h');
1304 if ( mCount != 0 ) {
1305 normalizedTimeSkeleton.append('m');
1307 if ( zCount != 0 ) {
1308 normalizedTimeSkeleton.append('z');
1310 if ( vCount != 0 ) {
1311 normalizedTimeSkeleton.append('v');
1318 * Generate date or time interval pattern from resource.
1320 * It needs to handle the following:
1321 * 1. need to adjust field width.
1322 * For example, the interval patterns saved in DateIntervalInfo
1323 * includes "dMMMy", but not "dMMMMy".
1324 * Need to get interval patterns for dMMMMy from dMMMy.
1325 * Another example, the interval patterns saved in DateIntervalInfo
1326 * includes "hmv", but not "hmz".
1327 * Need to get interval patterns for "hmz' from 'hmv'
1329 * 2. there might be no pattern for 'y' differ for skeleton "Md",
1330 * in order to get interval patterns for 'y' differ,
1331 * need to look for it from skeleton 'yMd'
1333 * @param dateSkeleton normalized date skeleton
1334 * @param timeSkeleton normalized time skeleton
1335 * @param intervalPatterns interval patterns
1336 * @return whether there is interval patterns for the skeleton.
1337 * true if there is, false otherwise
1339 private boolean genSeparateDateTimePtn(String dateSkeleton,
1340 String timeSkeleton,
1341 Map<String, PatternInfo> intervalPatterns)
1344 // if both date and time skeleton present,
1345 // the final interval pattern might include time interval patterns
1346 // ( when, am_pm, hour, minute differ ),
1347 // but not date interval patterns ( when year, month, day differ ).
1348 // For year/month/day differ, it falls back to fall-back pattern.
1349 if ( timeSkeleton.length() != 0 ) {
1350 skeleton = timeSkeleton;
1352 skeleton = dateSkeleton;
1355 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
1356 * are defined in resource,
1357 * interval patterns for skeleton "dMMMMy" are calculated by
1358 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
1359 * 2. get the interval patterns for "dMMMy",
1360 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
1361 * getBestSkeleton() is step 1.
1363 // best skeleton, and the difference information
1364 BestMatchInfo retValue = fInfo.getBestSkeleton(skeleton);
1365 String bestSkeleton = retValue.bestMatchSkeleton;
1366 int differenceInfo = retValue.bestMatchDistanceInfo;
1369 // 0 means the best matched skeleton is the same as input skeleton
1370 // 1 means the fields are the same, but field width are different
1371 // 2 means the only difference between fields are v/z,
1372 // -1 means there are other fields difference
1373 if ( differenceInfo == -1 ) {
1374 // skeleton has different fields, not only v/z difference
1378 if ( timeSkeleton.length() == 0 ) {
1379 // only has date skeleton
1380 genIntervalPattern(Calendar.DATE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1381 SkeletonAndItsBestMatch skeletons = genIntervalPattern(
1382 Calendar.MONTH, skeleton,
1383 bestSkeleton, differenceInfo,
1385 if ( skeletons != null ) {
1386 bestSkeleton = skeletons.skeleton;
1387 skeleton = skeletons.bestMatchSkeleton;
1389 genIntervalPattern(Calendar.YEAR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1391 genIntervalPattern(Calendar.MINUTE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1392 genIntervalPattern(Calendar.HOUR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1393 genIntervalPattern(Calendar.AM_PM, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
1402 * Generate interval pattern from existing resource
1404 * It not only save the interval patterns,
1405 * but also return the skeleton and its best match skeleton.
1407 * @param field largest different calendar field
1408 * @param skeleton skeleton
1409 * @param bestSkeleton the best match skeleton which has interval pattern
1410 * defined in resource
1411 * @param differenceInfo the difference between skeleton and best skeleton
1412 * 0 means the best matched skeleton is the same as input skeleton
1413 * 1 means the fields are the same, but field width are different
1414 * 2 means the only difference between fields are v/z,
1415 * -1 means there are other fields difference
1417 * @param intervalPatterns interval patterns
1419 * @return an extended skeleton or extended best skeleton if applicable.
1422 private SkeletonAndItsBestMatch genIntervalPattern(
1423 int field, String skeleton, String bestSkeleton,
1424 int differenceInfo, Map<String, PatternInfo> intervalPatterns) {
1425 SkeletonAndItsBestMatch retValue = null;
1426 PatternInfo pattern = fInfo.getIntervalPattern(
1427 bestSkeleton, field);
1428 if ( pattern == null ) {
1430 if ( SimpleDateFormat.isFieldUnitIgnored(bestSkeleton, field) ) {
1431 PatternInfo ptnInfo =
1432 new PatternInfo(fDateFormat.toPattern(),
1434 fInfo.getDefaultOrder());
1435 intervalPatterns.put(DateIntervalInfo.
1436 CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptnInfo);
1440 // for 24 hour system, interval patterns in resource file
1441 // might not include pattern when am_pm differ,
1442 // which should be the same as hour differ.
1443 // add it here for simplicity
1444 if ( field == Calendar.AM_PM ) {
1445 pattern = fInfo.getIntervalPattern(bestSkeleton,
1447 if ( pattern != null ) {
1449 intervalPatterns.put(DateIntervalInfo.
1450 CALENDAR_FIELD_TO_PATTERN_LETTER[field],
1455 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
1456 // first, get best match pattern "MMMd",
1457 // since there is no pattern for 'y' differs for skeleton 'MMMd',
1458 // need to look for it from skeleton 'yMMMd',
1459 // if found, adjust field width in interval pattern from
1461 String fieldLetter =
1462 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
1463 bestSkeleton = fieldLetter + bestSkeleton;
1464 skeleton = fieldLetter + skeleton;
1465 // for example, looking for patterns when 'y' differ for
1467 pattern = fInfo.getIntervalPattern(bestSkeleton, field);
1468 if ( pattern == null && differenceInfo == 0 ) {
1469 // if there is no skeleton "yMMMM" defined,
1470 // look for the best match skeleton, for example: "yMMM"
1471 BestMatchInfo tmpRetValue = fInfo.getBestSkeleton(skeleton);
1472 String tmpBestSkeleton = tmpRetValue.bestMatchSkeleton;
1473 differenceInfo = tmpRetValue.bestMatchDistanceInfo;
1474 if ( tmpBestSkeleton.length() != 0 && differenceInfo != -1 ) {
1475 pattern = fInfo.getIntervalPattern(tmpBestSkeleton, field);
1476 bestSkeleton = tmpBestSkeleton;
1479 if ( pattern != null ) {
1480 retValue = new SkeletonAndItsBestMatch(skeleton, bestSkeleton);
1483 if ( pattern != null ) {
1484 if ( differenceInfo != 0 ) {
1485 String part1 = adjustFieldWidth(skeleton, bestSkeleton,
1486 pattern.getFirstPart(), differenceInfo);
1487 String part2 = adjustFieldWidth(skeleton, bestSkeleton,
1488 pattern.getSecondPart(), differenceInfo);
1489 pattern = new PatternInfo(part1, part2,
1490 pattern.firstDateInPtnIsLaterDate());
1492 // pattern is immutable, no need to clone;
1493 // pattern = (PatternInfo)pattern.clone();
1495 intervalPatterns.put(
1496 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], pattern);
1502 * Adjust field width in best match interval pattern to match
1503 * the field width in input skeleton.
1505 * TODO (xji) make a general solution
1506 * The adjusting rule can be:
1509 * 3. default adjust, which means adjust according to the following rules
1510 * 3.1 always adjust string, such as MMM and MMMM
1511 * 3.2 never adjust between string and numeric, such as MM and MMM
1512 * 3.3 always adjust year
1513 * 3.4 do not adjust 'd', 'h', or 'm' if h presents
1514 * 3.5 do not adjust 'M' if it is numeric(?)
1516 * Since date interval format is well-formed format,
1517 * date and time skeletons are normalized previously,
1518 * till this stage, the adjust here is only "adjust strings, such as MMM
1519 * and MMMM, EEE and EEEE.
1521 * @param inputSkeleton the input skeleton
1522 * @param bestMatchSkeleton the best match skeleton
1523 * @param bestMatchIntervalpattern the best match interval pattern
1524 * @param differenceInfo the difference between 2 skeletons
1525 * 1 means only field width differs
1526 * 2 means v/z exchange
1527 * @return the adjusted interval pattern
1529 private static String adjustFieldWidth(String inputSkeleton,
1530 String bestMatchSkeleton,
1531 String bestMatchIntervalPattern,
1532 int differenceInfo ) {
1534 if ( bestMatchIntervalPattern == null ) {
1535 return null; // the 2nd part could be null
1537 int[] inputSkeletonFieldWidth = new int[58];
1538 int[] bestMatchSkeletonFieldWidth = new int[58];
1540 /* initialize as following
1542 // A B C D E F G H I J K L M N O
1543 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1544 // P Q R S T U V W X Y Z
1545 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1546 // a b c d e f g h i j k l m n o
1547 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1548 // p q r s t u v w x y z
1549 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1554 DateIntervalInfo.parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
1555 DateIntervalInfo.parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth);
1556 if ( differenceInfo == 2 ) {
1557 bestMatchIntervalPattern = bestMatchIntervalPattern.replace('v', 'z');
1560 StringBuilder adjustedPtn = new StringBuilder(bestMatchIntervalPattern);
1562 boolean inQuote = false;
1566 int PATTERN_CHAR_BASE = 0x41;
1568 // loop through the pattern string character by character
1569 int adjustedPtnLength = adjustedPtn.length();
1570 for (int i = 0; i < adjustedPtnLength; ++i) {
1571 char ch = adjustedPtn.charAt(i);
1572 if (ch != prevCh && count > 0) {
1573 // check the repeativeness of pattern letter
1574 char skeletonChar = prevCh;
1575 if ( skeletonChar == 'L' ) {
1576 // for skeleton "M+", the pattern is "...L..."
1579 int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
1580 int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
1581 if ( fieldCount == count && inputFieldCount > fieldCount ) {
1582 count = inputFieldCount - fieldCount;
1583 for ( int j = 0; j < count; ++j ) {
1584 adjustedPtn.insert(i, prevCh);
1587 adjustedPtnLength += count;
1592 // Consecutive single quotes are a single quote literal,
1593 // either outside of quotes or between quotes
1594 if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == '\'') {
1597 inQuote = ! inQuote;
1600 else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
1601 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
1602 // ch is a date-time pattern character
1609 // check the repeativeness of pattern letter
1610 char skeletonChar = prevCh;
1611 if ( skeletonChar == 'L' ) {
1612 // for skeleton "M+", the pattern is "...L..."
1615 int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
1616 int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
1617 if ( fieldCount == count && inputFieldCount > fieldCount ) {
1618 count = inputFieldCount - fieldCount;
1619 for ( int j = 0; j < count; ++j ) {
1620 adjustedPtn.append(prevCh);
1624 return adjustedPtn.toString();
1629 * Concat a single date pattern with a time interval pattern,
1630 * set it into the intervalPatterns, while field is time field.
1631 * This is used to handle time interval patterns on skeleton with
1632 * both time and date. Present the date followed by
1633 * the range expression for the time.
1634 * @param dtfmt date and time format
1635 * @param datePattern date pattern
1636 * @param field time calendar field: AM_PM, HOUR, MINUTE
1637 * @param intervalPatterns interval patterns
1639 private void concatSingleDate2TimeInterval(String dtfmt,
1642 Map<String, PatternInfo> intervalPatterns)
1645 PatternInfo timeItvPtnInfo =
1646 intervalPatterns.get(DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
1647 if ( timeItvPtnInfo != null ) {
1648 String timeIntervalPattern = timeItvPtnInfo.getFirstPart() +
1649 timeItvPtnInfo.getSecondPart();
1650 String pattern = MessageFormat.format(dtfmt, new Object[]
1651 {timeIntervalPattern, datePattern});
1652 timeItvPtnInfo = DateIntervalInfo.genPatternInfo(pattern,
1653 timeItvPtnInfo.firstDateInPtnIsLaterDate());
1654 intervalPatterns.put(
1655 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], timeItvPtnInfo);
1658 // it should not happen if the interval format defined is valid
1663 * check whether a calendar field present in a skeleton.
1664 * @param field calendar field need to check
1665 * @param skeleton given skeleton on which to check the calendar field
1666 * @return true if field present in a skeleton.
1668 private static boolean fieldExistsInSkeleton(int field, String skeleton)
1670 String fieldChar = DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
1671 return ( (skeleton.indexOf(fieldChar) == -1) ? false : true ) ;
1678 private void readObject(ObjectInputStream stream)
1679 throws IOException, ClassNotFoundException {
1680 stream.defaultReadObject();
1681 initializePattern(isDateIntervalInfoDefault ? LOCAL_PATTERN_CACHE : null);