2 * Copyright (C) 2008-2010, International Business Machines
\r
3 * Corporation and others. All Rights Reserved.
\r
6 package com.ibm.icu.text;
\r
8 import java.io.IOException;
\r
9 import java.io.ObjectInputStream;
\r
10 import java.text.FieldPosition;
\r
11 import java.text.ParsePosition;
\r
12 import java.util.Collections;
\r
13 import java.util.HashMap;
\r
14 import java.util.Locale;
\r
15 import java.util.Map;
\r
17 import com.ibm.icu.impl.CalendarData;
\r
18 import com.ibm.icu.impl.ICUCache;
\r
19 import com.ibm.icu.impl.SimpleCache;
\r
20 import com.ibm.icu.text.DateIntervalInfo.PatternInfo;
\r
21 import com.ibm.icu.util.Calendar;
\r
22 import com.ibm.icu.util.DateInterval;
\r
23 import com.ibm.icu.util.ULocale;
\r
27 * DateIntervalFormat is a class for formatting and parsing date
\r
28 * intervals in a language-independent manner.
\r
29 * Only formatting is supported. Parsing is not supported.
\r
32 * Date interval means from one date to another date,
\r
33 * for example, from "Jan 11, 2008" to "Jan 18, 2008".
\r
34 * We introduced class DateInterval to represent it.
\r
35 * DateInterval is a pair of UDate, which is
\r
36 * the standard milliseconds since 24:00 GMT, Jan 1, 1970.
\r
39 * DateIntervalFormat formats a DateInterval into
\r
40 * text as compactly as possible.
\r
41 * For example, the date interval format from "Jan 11, 2008" to "Jan 18,. 2008"
\r
42 * is "Jan 11-18, 2008" for English.
\r
43 * And it parses text into DateInterval,
\r
44 * although initially, parsing is not supported.
\r
47 * There is no structural information in date time patterns.
\r
48 * For any punctuations and string literals inside a date time pattern,
\r
49 * we do not know whether it is just a separator, or a prefix, or a suffix.
\r
50 * Without such information, so, it is difficult to generate a sub-pattern
\r
51 * (or super-pattern) by algorithm.
\r
52 * So, formatting a DateInterval is pattern-driven. It is very
\r
53 * similar to formatting in SimpleDateFormat.
\r
54 * We introduce class DateIntervalInfo to save date interval
\r
55 * patterns, similar to date time pattern in SimpleDateFormat.
\r
58 * Logically, the interval patterns are mappings
\r
59 * from (skeleton, the_largest_different_calendar_field)
\r
60 * to (date_interval_pattern).
\r
66 * only keeps the field pattern letter and ignores all other parts
\r
67 * in a pattern, such as space, punctuations, and string literals.
\r
69 * hides the order of fields.
\r
71 * might hide a field's pattern letter length.
\r
73 * For those non-digit calendar fields, the pattern letter length is
\r
74 * important, such as MMM, MMMM, and MMMMM; EEE and EEEE,
\r
75 * and the field's pattern letter length is honored.
\r
77 * For the digit calendar fields, such as M or MM, d or dd, yy or yyyy,
\r
78 * the field pattern length is ignored and the best match, which is defined
\r
79 * in date time patterns, will be returned without honor the field pattern
\r
80 * letter length in skeleton.
\r
84 * The calendar fields we support for interval formatting are:
\r
85 * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.
\r
86 * Those calendar fields can be defined in the following order:
\r
87 * year > month > date > hour (in day) > minute
\r
89 * The largest different calendar fields between 2 calendars is the
\r
90 * first different calendar field in above order.
\r
92 * For example: the largest different calendar fields between "Jan 10, 2007"
\r
93 * and "Feb 20, 2008" is year.
\r
96 * For other calendar fields, the compact interval formatting is not
\r
97 * supported. And the interval format will be fall back to fall-back
\r
98 * patterns, which is mostly "{date0} - {date1}".
\r
101 * There is a set of pre-defined static skeleton strings in DateFormat,
\r
102 * There are pre-defined interval patterns for those pre-defined skeletons
\r
103 * in locales' resource files.
\r
104 * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is "yMMMd",
\r
105 * in en_US, if the largest different calendar field between date1 and date2
\r
106 * is "year", the date interval pattern is "MMM d, yyyy - MMM d, yyyy",
\r
107 * such as "Jan 10, 2007 - Jan 10, 2008".
\r
108 * If the largest different calendar field between date1 and date2 is "month",
\r
109 * the date interval pattern is "MMM d - MMM d, yyyy",
\r
110 * such as "Jan 10 - Feb 10, 2007".
\r
111 * If the largest different calendar field between date1 and date2 is "day",
\r
112 * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".
\r
114 * For date skeleton, the interval patterns when year, or month, or date is
\r
115 * different are defined in resource files.
\r
116 * For time skeleton, the interval patterns when am/pm, or hour, or minute is
\r
117 * different are defined in resource files.
\r
120 * If a skeleton is not found in a locale's DateIntervalInfo, which means
\r
121 * the interval patterns for the skeleton is not defined in resource file,
\r
122 * the interval pattern will falls back to the interval "fallback" pattern
\r
123 * defined in resource file.
\r
124 * If the interval "fallback" pattern is not defined, the default fall-back
\r
125 * is "{date0} - {data1}".
\r
128 * For the combination of date and time,
\r
129 * The rule to genearte interval patterns are:
\r
132 * when the year, month, or day differs, falls back to fall-back
\r
133 * interval pattern, which mostly is the concatenate the two original
\r
134 * expressions with a separator between,
\r
135 * For example, interval pattern from "Jan 10, 2007 10:10 am"
\r
136 * to "Jan 11, 2007 10:10am" is
\r
137 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
\r
139 * otherwise, present the date followed by the range expression
\r
141 * For example, interval pattern from "Jan 10, 2007 10:10 am"
\r
142 * to "Jan 10, 2007 11:10am" is "Jan 10, 2007 10:10 am - 11:10am"
\r
147 * If two dates are the same, the interval pattern is the single date pattern.
\r
148 * For example, interval pattern from "Jan 10, 2007" to "Jan 10, 2007" is
\r
151 * Or if the presenting fields between 2 dates have the exact same values,
\r
152 * the interval pattern is the single date pattern.
\r
153 * For example, if user only requests year and month,
\r
154 * the interval pattern from "Jan 10, 2007" to "Jan 20, 2007" is "Jan 2007".
\r
157 * DateIntervalFormat needs the following information for correct
\r
158 * formatting: time zone, calendar type, pattern, date format symbols,
\r
159 * and date interval patterns.
\r
160 * It can be instantiated in several ways:
\r
163 * create an instance using default or given locale plus given skeleton.
\r
164 * Users are encouraged to created date interval formatter this way and
\r
165 * to use the pre-defined skeleton macros, such as
\r
166 * YEAR_NUM_MONTH, which consists the calendar fields and
\r
167 * the format style.
\r
170 * create an instance using default or given locale plus given skeleton
\r
171 * plus a given DateIntervalInfo.
\r
172 * This factory method is for powerful users who want to provide their own
\r
173 * interval patterns.
\r
174 * Locale provides the timezone, calendar, and format symbols information.
\r
175 * Local plus skeleton provides full pattern information.
\r
176 * DateIntervalInfo provides the date interval patterns.
\r
181 * For the calendar field pattern letter, such as G, y, M, d, a, h, H, m, s etc.
\r
182 * DateIntervalFormat uses the same syntax as that of
\r
186 * Code Sample: general usage
\r
189 * // the date interval object which the DateIntervalFormat formats on
\r
190 * // and parses into
\r
191 * DateInterval dtInterval = new DateInterval(1000*3600*24L, 1000*3600*24*2L);
\r
192 * DateIntervalFormat dtIntervalFmt = DateIntervalFormat.getInstance(
\r
193 * YEAR_MONTH_DAY, Locale("en", "GB", ""));
\r
194 * StringBuffer str = new StringBuffer("");
\r
195 * FieldPosition pos = new FieldPosition(0);
\r
197 * dtIntervalFmt.format(dtInterval, dateIntervalString, pos);
\r
202 * Code Sample: for powerful users who wants to use their own interval pattern
\r
205 * import com.ibm.icu.text.DateIntervalInfo;
\r
206 * import com.ibm.icu.text.DateIntervalFormat;
\r
207 * ....................
\r
209 * // Get DateIntervalFormat instance using default locale
\r
210 * DateIntervalFormat dtitvfmt = DateIntervalFormat.getInstance(YEAR_MONTH_DAY);
\r
212 * // Create an empty DateIntervalInfo object, which does not have any interval patterns inside.
\r
213 * dtitvinf = new DateIntervalInfo();
\r
215 * // a series of set interval patterns.
\r
216 * // Only ERA, YEAR, MONTH, DATE, DAY_OF_MONTH, DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, and MINUTE are supported.
\r
217 * dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'");
\r
218 * dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d");
\r
219 * dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d");
\r
220 * dtitvinf.setIntervalPattern("yMMMd", Calendar.HOUR_OF_DAY, "yyyy MMM d HH:mm ~ HH:mm");
\r
222 * // Set fallback interval pattern. Fallback pattern is used when interval pattern is not found.
\r
223 * // If the fall-back pattern is not set, falls back to {date0} - {date1} if interval pattern is not found.
\r
224 * dtitvinf.setFallbackIntervalPattern("{0} - {1}");
\r
226 * // Set above DateIntervalInfo object as the interval patterns of date interval formatter
\r
227 * dtitvfmt.setDateIntervalInfo(dtitvinf);
\r
229 * // Prepare to format
\r
230 * pos = new FieldPosition(0);
\r
231 * str = new StringBuffer("");
\r
233 * // The 2 calendars should be equivalent, otherwise, IllegalArgumentException will be thrown by format()
\r
234 * Calendar fromCalendar = (Calendar) dtfmt.getCalendar().clone();
\r
235 * Calendar toCalendar = (Calendar) dtfmt.getCalendar().clone();
\r
236 * fromCalendar.setTimeInMillis(....);
\r
237 * toCalendar.setTimeInMillis(...);
\r
239 * //Formatting given 2 calendars
\r
240 * dtitvfmt.format(fromCalendar, toCalendar, str, pos);
\r
247 public class DateIntervalFormat extends UFormat {
\r
249 private static final long serialVersionUID = 1;
\r
252 * Used to save the information for a skeleton's best match skeleton.
\r
253 * It is package accessible since it is used in DateIntervalInfo too.
\r
255 static final class BestMatchInfo {
\r
256 // the best match skeleton
\r
257 final String bestMatchSkeleton;
\r
258 // 0 means the best matched skeleton is the same as input skeleton
\r
259 // 1 means the fields are the same, but field width are different
\r
260 // 2 means the only difference between fields are v/z,
\r
261 // -1 means there are other fields difference
\r
262 final int bestMatchDistanceInfo;
\r
263 BestMatchInfo(String bestSkeleton, int difference) {
\r
264 bestMatchSkeleton = bestSkeleton;
\r
265 bestMatchDistanceInfo = difference;
\r
271 * Used to save the information on a skeleton and its best match.
\r
273 private static final class SkeletonAndItsBestMatch {
\r
274 final String skeleton;
\r
275 final String bestMatchSkeleton;
\r
276 SkeletonAndItsBestMatch(String skeleton, String bestMatch) {
\r
277 this.skeleton = skeleton;
\r
278 bestMatchSkeleton = bestMatch;
\r
283 // Cache for the locale interval pattern
\r
284 private static ICUCache<String, Map<String, PatternInfo>> LOCAL_PATTERN_CACHE =
\r
285 new SimpleCache<String, Map<String, PatternInfo>>();
\r
288 * The interval patterns for this locale.
\r
290 private DateIntervalInfo fInfo;
\r
293 * The DateFormat object used to format single pattern
\r
295 private SimpleDateFormat fDateFormat;
\r
298 * The 2 calendars with the from and to date.
\r
299 * could re-use the calendar in fDateFormat,
\r
300 * but keeping 2 calendars make it clear and clean.
\r
302 private Calendar fFromCalendar;
\r
303 private Calendar fToCalendar;
\r
306 * Following are transient interval information
\r
307 * relavent (locale) to this formatter.
\r
309 private String fSkeleton = null;
\r
311 private transient Map<String, PatternInfo> fIntervalPatterns = null;
\r
315 * default constructor
\r
317 private DateIntervalFormat() {
\r
321 * Construct a DateIntervalFormat from DateFormat,
\r
322 * a DateIntervalInfo, and skeleton.
\r
323 * DateFormat provides the timezone, calendar,
\r
324 * full pattern, and date format symbols information.
\r
325 * It should be a SimpleDateFormat object which
\r
326 * has a pattern in it.
\r
327 * the DateIntervalInfo provides the interval patterns.
\r
329 * @param locale the locale of this date interval formatter.
\r
330 * @param dtitvinf the DateIntervalInfo object to be adopted.
\r
331 * @param skeleton the skeleton of the date formatter
\r
333 private DateIntervalFormat(ULocale locale, DateIntervalInfo dtItvInfo,
\r
336 // freeze date interval info
\r
337 dtItvInfo.freeze();
\r
338 fSkeleton = skeleton;
\r
341 DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
\r
342 final String bestPattern = generator.getBestPattern(skeleton);
\r
343 fDateFormat = new SimpleDateFormat(bestPattern, locale);
\r
344 fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
\r
345 fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
\r
346 initializePattern();
\r
351 * Construct a DateIntervalFormat from skeleton and the default locale.
\r
353 * This is a convenient override of
\r
354 * getInstance(String skeleton, ULocale locale)
\r
355 * with the value of locale as default locale.
\r
357 * @param skeleton the skeleton on which interval format based.
\r
358 * @return a date time interval formatter.
\r
361 public static final DateIntervalFormat
\r
362 getInstance(String skeleton)
\r
365 return getInstance(skeleton, ULocale.getDefault());
\r
370 * Construct a DateIntervalFormat from skeleton and a given locale.
\r
372 * This is a convenient override of
\r
373 * getInstance(String skeleton, ULocale locale)
\r
375 * @param skeleton the skeleton on which interval format based.
\r
376 * @param locale the given locale
\r
377 * @return a date time interval formatter.
\r
380 public static final DateIntervalFormat
\r
381 getInstance(String skeleton, Locale locale)
\r
383 return getInstance(skeleton, ULocale.forLocale(locale));
\r
388 * Construct a DateIntervalFormat from skeleton and a given locale.
\r
390 * In this factory method,
\r
391 * the date interval pattern information is load from resource files.
\r
392 * Users are encouraged to created date interval formatter this way and
\r
393 * to use the pre-defined skeleton macros.
\r
396 * There are pre-defined skeletons in DateFormat,
\r
397 * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
\r
399 * Those skeletons have pre-defined interval patterns in resource files.
\r
400 * Users are encouraged to use them.
\r
402 * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc);
\r
404 * The given Locale provides the interval patterns.
\r
405 * For example, for en_GB, if skeleton is YEAR_ABBR_MONTH_WEEKDAY_DAY,
\r
406 * which is "yMMMEEEd",
\r
407 * the interval patterns defined in resource file to above skeleton are:
\r
408 * "EEE, d MMM, yyyy - EEE, d MMM, yyyy" for year differs,
\r
409 * "EEE, d MMM - EEE, d MMM, yyyy" for month differs,
\r
410 * "EEE, d - EEE, d MMM, yyyy" for day differs,
\r
411 * @param skeleton the skeleton on which interval format based.
\r
412 * @param locale the given locale
\r
413 * @return a date time interval formatter.
\r
416 public static final DateIntervalFormat
\r
417 getInstance(String skeleton, ULocale locale)
\r
419 DateIntervalInfo dtitvinf = new DateIntervalInfo(locale);
\r
420 return new DateIntervalFormat(locale, dtitvinf, skeleton);
\r
426 * Construct a DateIntervalFormat from skeleton
\r
427 * DateIntervalInfo, and default locale.
\r
429 * This is a convenient override of
\r
430 * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
\r
431 * with the locale value as default locale.
\r
433 * @param skeleton the skeleton on which interval format based.
\r
434 * @param dtitvinf the DateIntervalInfo object to be adopted.
\r
435 * @return a date time interval formatter.
\r
438 public static final DateIntervalFormat getInstance(String skeleton,
\r
439 DateIntervalInfo dtitvinf)
\r
441 return getInstance(skeleton, ULocale.getDefault(), dtitvinf);
\r
447 * Construct a DateIntervalFormat from skeleton
\r
448 * a DateIntervalInfo, and the given locale.
\r
450 * This is a convenient override of
\r
451 * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
\r
453 * @param skeleton the skeleton on which interval format based.
\r
454 * @param locale the given locale
\r
455 * @param dtitvinf the DateIntervalInfo object to be adopted.
\r
456 * @return a date time interval formatter.
\r
459 public static final DateIntervalFormat getInstance(String skeleton,
\r
461 DateIntervalInfo dtitvinf)
\r
463 return getInstance(skeleton, ULocale.forLocale(locale), dtitvinf);
\r
469 * Construct a DateIntervalFormat from skeleton
\r
470 * a DateIntervalInfo, and the given locale.
\r
473 * In this factory method, user provides its own date interval pattern
\r
474 * information, instead of using those pre-defined data in resource file.
\r
475 * This factory method is for powerful users who want to provide their own
\r
476 * interval patterns.
\r
479 * There are pre-defined skeleton in DateFormat,
\r
480 * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
\r
482 * Those skeletons have pre-defined interval patterns in resource files.
\r
483 * Users are encouraged to use them.
\r
485 * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc,itvinf);
\r
487 * the DateIntervalInfo provides the interval patterns.
\r
489 * User are encouraged to set default interval pattern in DateIntervalInfo
\r
490 * as well, if they want to set other interval patterns ( instead of
\r
491 * reading the interval patterns from resource files).
\r
492 * When the corresponding interval pattern for a largest calendar different
\r
493 * field is not found ( if user not set it ), interval format fallback to
\r
494 * the default interval pattern.
\r
495 * If user does not provide default interval pattern, it fallback to
\r
496 * "{date0} - {date1}"
\r
498 * @param skeleton the skeleton on which interval format based.
\r
499 * @param locale the given locale
\r
500 * @param dtitvinf the DateIntervalInfo object to be adopted.
\r
501 * @return a date time interval formatter.
\r
504 public static final DateIntervalFormat getInstance(String skeleton,
\r
506 DateIntervalInfo dtitvinf)
\r
508 LOCAL_PATTERN_CACHE.clear();
\r
509 // clone. If it is frozen, clone returns itself, otherwise, clone
\r
511 dtitvinf = (DateIntervalInfo)dtitvinf.clone();
\r
512 return new DateIntervalFormat(locale, dtitvinf, skeleton);
\r
517 * Clone this Format object polymorphically.
\r
518 * @return A copy of the object.
\r
521 public Object clone()
\r
523 DateIntervalFormat other = (DateIntervalFormat) super.clone();
\r
524 other.fDateFormat = (SimpleDateFormat) fDateFormat.clone();
\r
525 other.fInfo = (DateIntervalInfo) fInfo.clone();
\r
526 other.fFromCalendar = (Calendar) fFromCalendar.clone();
\r
527 other.fToCalendar = (Calendar) fToCalendar.clone();
\r
528 other.fSkeleton = fSkeleton;
\r
529 other.fIntervalPatterns = fIntervalPatterns;
\r
535 * Format an object to produce a string. This method handles Formattable
\r
536 * objects with a DateInterval type.
\r
537 * If a the Formattable object type is not a DateInterval,
\r
538 * IllegalArgumentException is thrown.
\r
540 * @param obj The object to format.
\r
541 * Must be a DateInterval.
\r
542 * @param appendTo Output parameter to receive result.
\r
543 * Result is appended to existing contents.
\r
544 * @param fieldPosition On input: an alignment field, if desired.
\r
545 * On output: the offsets of the alignment field.
\r
546 * @return Reference to 'appendTo' parameter.
\r
547 * @throws IllegalArgumentException if the formatted object is not
\r
548 * DateInterval object
\r
551 public final StringBuffer
\r
552 format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition)
\r
554 if ( obj instanceof DateInterval ) {
\r
555 return format( (DateInterval)obj, appendTo, fieldPosition);
\r
558 throw new IllegalArgumentException("Cannot format given Object (" + obj.getClass().getName() + ") as a DateInterval");
\r
563 * Format a DateInterval to produce a string.
\r
565 * @param dtInterval DateInterval to be formatted.
\r
566 * @param appendTo Output parameter to receive result.
\r
567 * Result is appended to existing contents.
\r
568 * @param fieldPosition On input: an alignment field, if desired.
\r
569 * On output: the offsets of the alignment field.
\r
570 * @return Reference to 'appendTo' parameter.
\r
573 public final StringBuffer format(DateInterval dtInterval,
\r
574 StringBuffer appendTo,
\r
575 FieldPosition fieldPosition)
\r
577 fFromCalendar.setTimeInMillis(dtInterval.getFromDate());
\r
578 fToCalendar.setTimeInMillis(dtInterval.getToDate());
\r
579 return format(fFromCalendar, fToCalendar, appendTo, fieldPosition);
\r
584 * Format 2 Calendars to produce a string.
\r
586 * @param fromCalendar calendar set to the from date in date interval
\r
587 * to be formatted into date interval string
\r
588 * @param toCalendar calendar set to the to date in date interval
\r
589 * to be formatted into date interval string
\r
590 * @param appendTo Output parameter to receive result.
\r
591 * Result is appended to existing contents.
\r
592 * @param pos On input: an alignment field, if desired.
\r
593 * On output: the offsets of the alignment field.
\r
594 * @return Reference to 'appendTo' parameter.
\r
595 * @throws IllegalArgumentException if the two calendars are not equivalent.
\r
598 public final StringBuffer format(Calendar fromCalendar,
\r
599 Calendar toCalendar,
\r
600 StringBuffer appendTo,
\r
603 // not support different calendar types and time zones
\r
604 if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
\r
605 throw new IllegalArgumentException("can not format on two different calendars");
\r
608 // First, find the largest different calendar field.
\r
609 int field = -1; //init with an invalid value.
\r
611 if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
\r
612 field = Calendar.ERA;
\r
613 } else if ( fromCalendar.get(Calendar.YEAR) !=
\r
614 toCalendar.get(Calendar.YEAR) ) {
\r
615 field = Calendar.YEAR;
\r
616 } else if ( fromCalendar.get(Calendar.MONTH) !=
\r
617 toCalendar.get(Calendar.MONTH) ) {
\r
618 field = Calendar.MONTH;
\r
619 } else if ( fromCalendar.get(Calendar.DATE) !=
\r
620 toCalendar.get(Calendar.DATE) ) {
\r
621 field = Calendar.DATE;
\r
622 } else if ( fromCalendar.get(Calendar.AM_PM) !=
\r
623 toCalendar.get(Calendar.AM_PM) ) {
\r
624 field = Calendar.AM_PM;
\r
625 } else if ( fromCalendar.get(Calendar.HOUR) !=
\r
626 toCalendar.get(Calendar.HOUR) ) {
\r
627 field = Calendar.HOUR;
\r
628 } else if ( fromCalendar.get(Calendar.MINUTE) !=
\r
629 toCalendar.get(Calendar.MINUTE) ) {
\r
630 field = Calendar.MINUTE;
\r
632 /* ignore the second/millisecond etc. small fields' difference.
\r
633 * use single date when all the above are the same.
\r
635 return fDateFormat.format(fromCalendar, appendTo, pos);
\r
638 // get interval pattern
\r
639 PatternInfo intervalPattern = fIntervalPatterns.get(
\r
640 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
\r
642 if ( intervalPattern == null ) {
\r
643 if ( fDateFormat.isFieldUnitIgnored(field) ) {
\r
644 /* the largest different calendar field is small than
\r
645 * the smallest calendar field in pattern,
\r
646 * return single date format.
\r
648 return fDateFormat.format(fromCalendar, appendTo, pos);
\r
651 return fallbackFormat(fromCalendar, toCalendar, appendTo, pos);
\r
654 // If the first part in interval pattern is empty,
\r
655 // the 2nd part of it saves the full-pattern used in fall-back.
\r
656 // For a 'real' interval pattern, the first part will never be empty.
\r
657 if ( intervalPattern.getFirstPart() == null ) {
\r
659 return fallbackFormat(fromCalendar, toCalendar, appendTo, pos,
\r
660 intervalPattern.getSecondPart());
\r
663 Calendar secondCal;
\r
664 if ( intervalPattern.firstDateInPtnIsLaterDate() ) {
\r
665 firstCal = toCalendar;
\r
666 secondCal = fromCalendar;
\r
668 firstCal = fromCalendar;
\r
669 secondCal = toCalendar;
\r
671 // break the interval pattern into 2 parts
\r
672 // first part should not be empty,
\r
673 String originalPattern = fDateFormat.toPattern();
\r
674 fDateFormat.applyPattern(intervalPattern.getFirstPart());
\r
675 fDateFormat.format(firstCal, appendTo, pos);
\r
676 if ( intervalPattern.getSecondPart() != null ) {
\r
677 fDateFormat.applyPattern(intervalPattern.getSecondPart());
\r
678 fDateFormat.format(secondCal, appendTo, pos);
\r
680 fDateFormat.applyPattern(originalPattern);
\r
686 * Format 2 Calendars to using fall-back interval pattern
\r
688 * The full pattern used in this fall-back format is the
\r
689 * full pattern of the date formatter.
\r
691 * @param fromCalendar calendar set to the from date in date interval
\r
692 * to be formatted into date interval string
\r
693 * @param toCalendar calendar set to the to date in date interval
\r
694 * to be formatted into date interval string
\r
695 * @param appendTo Output parameter to receive result.
\r
696 * Result is appended to existing contents.
\r
697 * @param pos On input: an alignment field, if desired.
\r
698 * On output: the offsets of the alignment field.
\r
699 * @return Reference to 'appendTo' parameter.
\r
701 private final StringBuffer fallbackFormat(Calendar fromCalendar,
\r
702 Calendar toCalendar,
\r
703 StringBuffer appendTo,
\r
704 FieldPosition pos) {
\r
706 StringBuffer earlierDate = new StringBuffer(64);
\r
707 earlierDate = fDateFormat.format(fromCalendar, earlierDate, pos);
\r
708 StringBuffer laterDate = new StringBuffer(64);
\r
709 laterDate = fDateFormat.format(toCalendar, laterDate, pos);
\r
710 String fallbackPattern = fInfo.getFallbackIntervalPattern();
\r
711 String fallback = MessageFormat.format(fallbackPattern, new Object[]
\r
712 {earlierDate.toString(), laterDate.toString()});
\r
713 appendTo.append(fallback);
\r
719 * Format 2 Calendars to using fall-back interval pattern
\r
721 * This fall-back pattern is generated on a given full pattern,
\r
722 * not the full pattern of the date formatter.
\r
724 * @param fromCalendar calendar set to the from date in date interval
\r
725 * to be formatted into date interval string
\r
726 * @param toCalendar calendar set to the to date in date interval
\r
727 * to be formatted into date interval string
\r
728 * @param appendTo Output parameter to receive result.
\r
729 * Result is appended to existing contents.
\r
730 * @param pos On input: an alignment field, if desired.
\r
731 * On output: the offsets of the alignment field.
\r
732 * @param fullPattern the full pattern need to apply to date formatter
\r
733 * @return Reference to 'appendTo' parameter.
\r
735 private final StringBuffer fallbackFormat(Calendar fromCalendar,
\r
736 Calendar toCalendar,
\r
737 StringBuffer appendTo,
\r
738 FieldPosition pos,
\r
739 String fullPattern) {
\r
740 String originalPattern = fDateFormat.toPattern();
\r
741 fDateFormat.applyPattern(fullPattern);
\r
742 fallbackFormat(fromCalendar, toCalendar, appendTo, pos);
\r
743 fDateFormat.applyPattern(originalPattern);
\r
749 * Date interval parsing is not supported.
\r
751 * This method should handle parsing of
\r
752 * date time interval strings into Formattable objects with
\r
753 * DateInterval type, which is a pair of UDate.
\r
756 * Before calling, set parse_pos.index to the offset you want to start
\r
757 * parsing at in the source. After calling, parse_pos.index is the end of
\r
758 * the text you parsed. If error occurs, index is unchanged.
\r
760 * When parsing, leading whitespace is discarded (with a successful parse),
\r
761 * while trailing whitespace is left as is.
\r
763 * See Format.parseObject() for more.
\r
765 * @param source The string to be parsed into an object.
\r
766 * @param parse_pos The position to start parsing at. Since no parsing
\r
767 * is supported, upon return this param is unchanged.
\r
768 * @return A newly created Formattable* object, or NULL
\r
771 * @deprecated This API is ICU internal only.
\r
773 public Object parseObject(String source, ParsePosition parse_pos)
\r
775 throw new UnsupportedOperationException("parsing is not supported");
\r
780 * Gets the date time interval patterns.
\r
781 * @return a copy of the date time interval patterns associated with
\r
782 * this date interval formatter.
\r
785 public DateIntervalInfo getDateIntervalInfo()
\r
787 return (DateIntervalInfo)fInfo.clone();
\r
792 * Set the date time interval patterns.
\r
793 * @param newItvPattern the given interval patterns to copy.
\r
796 public void setDateIntervalInfo(DateIntervalInfo newItvPattern)
\r
798 // clone it. If it is frozen, the clone returns itself.
\r
799 // Otherwise, clone returns a copy
\r
800 fInfo = (DateIntervalInfo)newItvPattern.clone();
\r
801 fInfo.freeze(); // freeze it
\r
802 LOCAL_PATTERN_CACHE.clear();
\r
803 if ( fDateFormat != null ) {
\r
804 initializePattern();
\r
810 * Gets the date formatter
\r
811 * @return a copy of the date formatter associated with
\r
812 * this date interval formatter.
\r
815 public DateFormat getDateFormat()
\r
817 return (DateFormat)fDateFormat.clone();
\r
822 * Below are for generating interval patterns locale to the formatter
\r
826 * Initialize interval patterns locale to this formatter.
\r
828 private void initializePattern() {
\r
829 String fullPattern = fDateFormat.toPattern();
\r
830 ULocale locale = fDateFormat.getLocale();
\r
832 if ( fSkeleton != null ) {
\r
833 key = locale.toString() + "+" + fullPattern + "+" + fSkeleton;
\r
835 key = locale.toString() + "+" + fullPattern;
\r
837 Map<String, PatternInfo> patterns = LOCAL_PATTERN_CACHE.get(key);
\r
838 if ( patterns == null ) {
\r
839 Map<String, PatternInfo> intervalPatterns = initializeIntervalPattern(fullPattern, locale);
\r
840 patterns = Collections.unmodifiableMap(intervalPatterns);
\r
841 LOCAL_PATTERN_CACHE.put(key, patterns);
\r
843 fIntervalPatterns = patterns;
\r
849 * Initialize interval patterns locale to this formatter
\r
851 * This code is a bit complicated since
\r
852 * 1. the interval patterns saved in resource bundle files are interval
\r
853 * patterns based on date or time only.
\r
854 * It does not have interval patterns based on both date and time.
\r
855 * Interval patterns on both date and time are algorithm generated.
\r
857 * For example, it has interval patterns on skeleton "dMy" and "hm",
\r
858 * but it does not have interval patterns on skeleton "dMyhm".
\r
860 * The rule to generate interval patterns for both date and time skeleton are
\r
861 * 1) when the year, month, or day differs, concatenate the two original
\r
862 * expressions with a separator between,
\r
863 * For example, interval pattern from "Jan 10, 2007 10:10 am"
\r
864 * to "Jan 11, 2007 10:10am" is
\r
865 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
\r
867 * 2) otherwise, present the date followed by the range expression
\r
869 * For example, interval pattern from "Jan 10, 2007 10:10 am"
\r
870 * to "Jan 10, 2007 11:10am" is
\r
871 * "Jan 10, 2007 10:10 am - 11:10am"
\r
873 * 2. even a pattern does not request a certain calendar field,
\r
874 * the interval pattern needs to include such field if such fields are
\r
875 * different between 2 dates.
\r
876 * For example, a pattern/skeleton is "hm", but the interval pattern
\r
877 * includes year, month, and date when year, month, and date differs.
\r
880 * @param fullPattern formatter's full pattern
\r
881 * @param locale the given locale.
\r
882 * @return interval patterns' hash map
\r
884 private Map<String, PatternInfo> initializeIntervalPattern(String fullPattern, ULocale locale) {
\r
885 DateTimePatternGenerator dtpng = DateTimePatternGenerator.getInstance(locale);
\r
886 if ( fSkeleton == null ) {
\r
887 // fSkeleton is already set by getDateIntervalInstance()
\r
888 // or by getInstance(String skeleton, .... )
\r
889 fSkeleton = dtpng.getSkeleton(fullPattern);
\r
891 String skeleton = fSkeleton;
\r
893 HashMap<String, PatternInfo> intervalPatterns = new HashMap<String, PatternInfo>();
\r
895 /* Check whether the skeleton is a combination of date and time.
\r
896 * For the complication reason 1 explained above.
\r
898 StringBuilder date = new StringBuilder(skeleton.length());
\r
899 StringBuilder normalizedDate = new StringBuilder(skeleton.length());
\r
900 StringBuilder time = new StringBuilder(skeleton.length());
\r
901 StringBuilder normalizedTime = new StringBuilder(skeleton.length());
\r
903 /* the difference between time skeleton and normalizedTimeSkeleton are:
\r
904 * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true)
\r
905 * 2. 'a' is omitted in normalized time skeleton.
\r
906 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized
\r
909 * The difference between date skeleton and normalizedDateSkeleton are:
\r
910 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
\r
911 * 2. 'E' and 'EE' are normalized into 'EEE'
\r
912 * 3. 'MM' is normalized into 'M'
\r
914 getDateTimeSkeleton(skeleton, date, normalizedDate,
\r
915 time, normalizedTime);
\r
917 String dateSkeleton = date.toString();
\r
918 String timeSkeleton = time.toString();
\r
919 String normalizedDateSkeleton = normalizedDate.toString();
\r
920 String normalizedTimeSkeleton = normalizedTime.toString();
\r
922 boolean found = genSeparateDateTimePtn(normalizedDateSkeleton,
\r
923 normalizedTimeSkeleton,
\r
926 if ( found == false ) {
\r
928 // TODO: if user asks "m", but "d" differ
\r
929 //StringBuffer skeleton = new StringBuffer(skeleton);
\r
930 if ( time.length() != 0 ) {
\r
931 //genFallbackForNotFound(Calendar.MINUTE, skeleton);
\r
932 //genFallbackForNotFound(Calendar.HOUR, skeleton);
\r
933 //genFallbackForNotFound(Calendar.AM_PM, skeleton);
\r
934 if ( date.length() == 0 ) {
\r
936 timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
\r
937 String pattern =dtpng.getBestPattern(timeSkeleton);
\r
938 // for fall back interval patterns,
\r
939 // the first part of the pattern is empty,
\r
940 // the second part of the pattern is the full-pattern
\r
941 // should be used in fall-back.
\r
942 PatternInfo ptn = new PatternInfo(null, pattern,
\r
943 fInfo.getDefaultOrder());
\r
944 intervalPatterns.put(DateIntervalInfo.
\r
945 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
\r
946 // share interval pattern
\r
947 intervalPatterns.put(DateIntervalInfo.
\r
948 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
\r
949 // share interval pattern
\r
950 intervalPatterns.put(DateIntervalInfo.
\r
951 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
\r
953 //genFallbackForNotFound(Calendar.DATE, skeleton);
\r
954 //genFallbackForNotFound(Calendar.MONTH, skeleton);
\r
955 //genFallbackForNotFound(Calendar.YEAR, skeleton);
\r
958 //genFallbackForNotFound(Calendar.DATE, skeleton);
\r
959 //genFallbackForNotFound(Calendar.MONTH, skeleton);
\r
960 //genFallbackForNotFound(Calendar.YEAR, skeleton);
\r
962 return intervalPatterns;
\r
963 } // end of skeleton not found
\r
964 // interval patterns for skeleton are found in resource
\r
965 if ( time.length() == 0 ) {
\r
967 } else if ( date.length() == 0 ) {
\r
968 // need to set up patterns for y/M/d differ
\r
969 /* result from following looks confusing.
\r
970 * for example: 10 10:10 - 11 10:10, it is not
\r
971 * clear that the first 10 is the 10th day
\r
972 time.insert(0, 'd');
\r
973 genFallbackPattern(Calendar.DATE, time);
\r
974 time.insert(0, 'M');
\r
975 genFallbackPattern(Calendar.MONTH, time);
\r
976 time.insert(0, 'y');
\r
977 genFallbackPattern(Calendar.YEAR, time);
\r
980 timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
\r
981 String pattern =dtpng.getBestPattern(timeSkeleton);
\r
982 // for fall back interval patterns,
\r
983 // the first part of the pattern is empty,
\r
984 // the second part of the pattern is the full-pattern
\r
985 // should be used in fall-back.
\r
986 PatternInfo ptn = new PatternInfo(
\r
987 null, pattern, fInfo.getDefaultOrder());
\r
988 intervalPatterns.put(DateIntervalInfo.
\r
989 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
\r
990 intervalPatterns.put(DateIntervalInfo.
\r
991 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
\r
992 intervalPatterns.put(DateIntervalInfo.
\r
993 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
\r
995 /* if both present,
\r
996 * 1) when the year, month, or day differs,
\r
997 * concatenate the two original expressions with a separator between,
\r
998 * 2) otherwise, present the date followed by the
\r
999 * range expression for the time.
\r
1002 * 1) when the year, month, or day differs,
\r
1003 * concatenate the two original expressions with a separator between,
\r
1005 // if field exists, use fall back
\r
1006 if ( !fieldExistsInSkeleton(Calendar.DATE, dateSkeleton) ) {
\r
1007 // prefix skeleton with 'd'
\r
1008 skeleton = DateIntervalInfo.
\r
1009 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE] + skeleton;
\r
1010 genFallbackPattern(Calendar.DATE, skeleton, intervalPatterns, dtpng);
\r
1012 if ( !fieldExistsInSkeleton(Calendar.MONTH, dateSkeleton) ) {
\r
1013 // then prefix skeleton with 'M'
\r
1014 skeleton = DateIntervalInfo.
\r
1015 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH] + skeleton;
\r
1016 genFallbackPattern(Calendar.MONTH, skeleton, intervalPatterns, dtpng);
\r
1018 if ( !fieldExistsInSkeleton(Calendar.YEAR, dateSkeleton) ) {
\r
1019 // then prefix skeleton with 'y'
\r
1020 skeleton = DateIntervalInfo.
\r
1021 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR] + skeleton;
\r
1022 genFallbackPattern(Calendar.YEAR, skeleton, intervalPatterns, dtpng);
\r
1026 * 2) otherwise, present the date followed by the
\r
1027 * range expression for the time.
\r
1029 // Need the Date/Time pattern for concatnation the date with
\r
1030 // the time interval.
\r
1031 // The date/time pattern ( such as {0} {1} ) is saved in
\r
1032 // calendar, that is why need to get the CalendarData here.
\r
1033 CalendarData calData = new CalendarData(locale, null);
\r
1034 String[] patterns = calData.getDateTimePatterns();
\r
1035 String datePattern =dtpng.getBestPattern(dateSkeleton);
\r
1036 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.AM_PM, intervalPatterns);
\r
1037 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.HOUR, intervalPatterns);
\r
1038 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.MINUTE, intervalPatterns);
\r
1041 return intervalPatterns;
\r
1046 * Generate fall back interval pattern given a calendar field,
\r
1047 * a skeleton, and a date time pattern generator
\r
1048 * @param field the largest different calendar field
\r
1049 * @param skeleton a skeleton
\r
1050 * @param dtpng date time pattern generator
\r
1051 * @param intervalPatterns interval patterns
\r
1053 private void genFallbackPattern(int field, String skeleton,
\r
1054 Map<String, PatternInfo> intervalPatterns,
\r
1055 DateTimePatternGenerator dtpng) {
\r
1056 String pattern = dtpng.getBestPattern(skeleton);
\r
1057 // for fall back interval patterns,
\r
1058 // the first part of the pattern is empty,
\r
1059 // the second part of the pattern is the full-pattern
\r
1060 // should be used in fall-back.
\r
1061 PatternInfo ptn = new PatternInfo(
\r
1062 null, pattern, fInfo.getDefaultOrder());
\r
1063 intervalPatterns.put(
\r
1064 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptn);
\r
1070 private void genFallbackForNotFound(String field, StringBuffer skeleton) {
\r
1071 if ( SimpleDateFormat.isFieldUnitIgnored(skeleton.toString(), field) ) {
\r
1073 DateIntervalInfo.PatternInfo ptnInfo =
\r
1074 new DateIntervalInfo.PatternInfo(null, fDateFormat.toPattern(),
\r
1075 fInfo.getDefaultOrder());
\r
1076 fIntervalPatterns.put(field, ptnInfo);
\r
1078 } else if ( skeleton.indexOf(field) == -1 ) {
\r
1079 skeleton.insert(0,field);
\r
1080 genFallbackPattern(field, skeleton, dtpng);
\r
1086 * get separated date and time skeleton from a combined skeleton.
\r
1088 * The difference between date skeleton and normalizedDateSkeleton are:
\r
1089 * 1. both 'y' and 'd' are appeared only once in normalizeDateSkeleton
\r
1090 * 2. 'E' and 'EE' are normalized into 'EEE'
\r
1091 * 3. 'MM' is normalized into 'M'
\r
1093 ** the difference between time skeleton and normalizedTimeSkeleton are:
\r
1094 * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton,
\r
1095 * 2. 'a' is omitted in normalized time skeleton.
\r
1096 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized time
\r
1100 * @param skeleton given combined skeleton.
\r
1101 * @param date Output parameter for date only skeleton.
\r
1102 * @param normalizedDate Output parameter for normalized date only
\r
1104 * @param time Output parameter for time only skeleton.
\r
1105 * @param normalizedTime Output parameter for normalized time only
\r
1108 private static void getDateTimeSkeleton(String skeleton,
\r
1109 StringBuilder dateSkeleton,
\r
1110 StringBuilder normalizedDateSkeleton,
\r
1111 StringBuilder timeSkeleton,
\r
1112 StringBuilder normalizedTimeSkeleton)
\r
1114 // dateSkeleton follows the sequence of y*M*E*d*
\r
1115 // timeSkeleton follows the sequence of hm*[v|z]?
\r
1127 for (i = 0; i < skeleton.length(); ++i) {
\r
1128 char ch = skeleton.charAt(i);
\r
1131 dateSkeleton.append(ch);
\r
1135 dateSkeleton.append(ch);
\r
1139 dateSkeleton.append(ch);
\r
1143 dateSkeleton.append(ch);
\r
1160 normalizedDateSkeleton.append(ch);
\r
1161 dateSkeleton.append(ch);
\r
1164 // 'a' is implicitly handled
\r
1165 timeSkeleton.append(ch);
\r
1168 timeSkeleton.append(ch);
\r
1172 timeSkeleton.append(ch);
\r
1176 timeSkeleton.append(ch);
\r
1181 timeSkeleton.append(ch);
\r
1185 timeSkeleton.append(ch);
\r
1195 timeSkeleton.append(ch);
\r
1196 normalizedTimeSkeleton.append(ch);
\r
1201 /* generate normalized form for date*/
\r
1202 if ( yCount != 0 ) {
\r
1203 normalizedDateSkeleton.append('y');
\r
1205 if ( MCount != 0 ) {
\r
1206 if ( MCount < 3 ) {
\r
1207 normalizedDateSkeleton.append('M');
\r
1209 for ( i = 0; i < MCount && i < 5; ++i ) {
\r
1210 normalizedDateSkeleton.append('M');
\r
1214 if ( ECount != 0 ) {
\r
1215 if ( ECount <= 3 ) {
\r
1216 normalizedDateSkeleton.append('E');
\r
1218 for ( i = 0; i < ECount && i < 5; ++i ) {
\r
1219 normalizedDateSkeleton.append('E');
\r
1223 if ( dCount != 0 ) {
\r
1224 normalizedDateSkeleton.append('d');
\r
1227 /* generate normalized form for time */
\r
1228 if ( HCount != 0 ) {
\r
1229 normalizedTimeSkeleton.append('H');
\r
1231 else if ( hCount != 0 ) {
\r
1232 normalizedTimeSkeleton.append('h');
\r
1234 if ( mCount != 0 ) {
\r
1235 normalizedTimeSkeleton.append('m');
\r
1237 if ( zCount != 0 ) {
\r
1238 normalizedTimeSkeleton.append('z');
\r
1240 if ( vCount != 0 ) {
\r
1241 normalizedTimeSkeleton.append('v');
\r
1248 * Generate date or time interval pattern from resource.
\r
1250 * It needs to handle the following:
\r
1251 * 1. need to adjust field width.
\r
1252 * For example, the interval patterns saved in DateIntervalInfo
\r
1253 * includes "dMMMy", but not "dMMMMy".
\r
1254 * Need to get interval patterns for dMMMMy from dMMMy.
\r
1255 * Another example, the interval patterns saved in DateIntervalInfo
\r
1256 * includes "hmv", but not "hmz".
\r
1257 * Need to get interval patterns for "hmz' from 'hmv'
\r
1259 * 2. there might be no pattern for 'y' differ for skeleton "Md",
\r
1260 * in order to get interval patterns for 'y' differ,
\r
1261 * need to look for it from skeleton 'yMd'
\r
1263 * @param dateSkeleton normalized date skeleton
\r
1264 * @param timeSkeleton normalized time skeleton
\r
1265 * @param intervalPatterns interval patterns
\r
1266 * @return whether there is interval patterns for the skeleton.
\r
1267 * true if there is, false otherwise
\r
1269 private boolean genSeparateDateTimePtn(String dateSkeleton,
\r
1270 String timeSkeleton,
\r
1271 Map<String, PatternInfo> intervalPatterns)
\r
1274 // if both date and time skeleton present,
\r
1275 // the final interval pattern might include time interval patterns
\r
1276 // ( when, am_pm, hour, minute differ ),
\r
1277 // but not date interval patterns ( when year, month, day differ ).
\r
1278 // For year/month/day differ, it falls back to fall-back pattern.
\r
1279 if ( timeSkeleton.length() != 0 ) {
\r
1280 skeleton = timeSkeleton;
\r
1282 skeleton = dateSkeleton;
\r
1285 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
\r
1286 * are defined in resource,
\r
1287 * interval patterns for skeleton "dMMMMy" are calculated by
\r
1288 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
\r
1289 * 2. get the interval patterns for "dMMMy",
\r
1290 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
\r
1291 * getBestSkeleton() is step 1.
\r
1293 // best skeleton, and the difference information
\r
1294 BestMatchInfo retValue = fInfo.getBestSkeleton(skeleton);
\r
1295 String bestSkeleton = retValue.bestMatchSkeleton;
\r
1296 int differenceInfo = retValue.bestMatchDistanceInfo;
\r
1299 // 0 means the best matched skeleton is the same as input skeleton
\r
1300 // 1 means the fields are the same, but field width are different
\r
1301 // 2 means the only difference between fields are v/z,
\r
1302 // -1 means there are other fields difference
\r
1303 if ( differenceInfo == -1 ) {
\r
1304 // skeleton has different fields, not only v/z difference
\r
1308 if ( timeSkeleton.length() == 0 ) {
\r
1309 // only has date skeleton
\r
1310 genIntervalPattern(Calendar.DATE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1311 SkeletonAndItsBestMatch skeletons = genIntervalPattern(
\r
1312 Calendar.MONTH, skeleton,
\r
1313 bestSkeleton, differenceInfo,
\r
1314 intervalPatterns);
\r
1315 if ( skeletons != null ) {
\r
1316 bestSkeleton = skeletons.skeleton;
\r
1317 skeleton = skeletons.bestMatchSkeleton;
\r
1319 genIntervalPattern(Calendar.YEAR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1321 genIntervalPattern(Calendar.MINUTE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1322 genIntervalPattern(Calendar.HOUR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1323 genIntervalPattern(Calendar.AM_PM, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1332 * Generate interval pattern from existing resource
\r
1334 * It not only save the interval patterns,
\r
1335 * but also return the skeleton and its best match skeleton.
\r
1337 * @param field largest different calendar field
\r
1338 * @param skeleton skeleton
\r
1339 * @param bestSkeleton the best match skeleton which has interval pattern
\r
1340 * defined in resource
\r
1341 * @param differenceInfo the difference between skeleton and best skeleton
\r
1342 * 0 means the best matched skeleton is the same as input skeleton
\r
1343 * 1 means the fields are the same, but field width are different
\r
1344 * 2 means the only difference between fields are v/z,
\r
1345 * -1 means there are other fields difference
\r
1347 * @param intervalPatterns interval patterns
\r
1349 * @return an extended skeleton or extended best skeleton if applicable.
\r
1352 private SkeletonAndItsBestMatch genIntervalPattern(
\r
1353 int field, String skeleton, String bestSkeleton,
\r
1354 int differenceInfo, Map<String, PatternInfo> intervalPatterns) {
\r
1355 SkeletonAndItsBestMatch retValue = null;
\r
1356 PatternInfo pattern = fInfo.getIntervalPattern(
\r
1357 bestSkeleton, field);
\r
1358 if ( pattern == null ) {
\r
1360 if ( SimpleDateFormat.isFieldUnitIgnored(bestSkeleton, field) ) {
\r
1361 PatternInfo ptnInfo =
\r
1362 new PatternInfo(fDateFormat.toPattern(),
\r
1364 fInfo.getDefaultOrder());
\r
1365 intervalPatterns.put(DateIntervalInfo.
\r
1366 CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptnInfo);
\r
1370 // for 24 hour system, interval patterns in resource file
\r
1371 // might not include pattern when am_pm differ,
\r
1372 // which should be the same as hour differ.
\r
1373 // add it here for simplicity
\r
1374 if ( field == Calendar.AM_PM ) {
\r
1375 pattern = fInfo.getIntervalPattern(bestSkeleton,
\r
1377 if ( pattern != null ) {
\r
1379 intervalPatterns.put(DateIntervalInfo.
\r
1380 CALENDAR_FIELD_TO_PATTERN_LETTER[field],
\r
1385 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
\r
1386 // first, get best match pattern "MMMd",
\r
1387 // since there is no pattern for 'y' differs for skeleton 'MMMd',
\r
1388 // need to look for it from skeleton 'yMMMd',
\r
1389 // if found, adjust field width in interval pattern from
\r
1390 // "MMM" to "MMMM".
\r
1391 String fieldLetter =
\r
1392 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
\r
1393 bestSkeleton = fieldLetter + bestSkeleton;
\r
1394 skeleton = fieldLetter + skeleton;
\r
1395 // for example, looking for patterns when 'y' differ for
\r
1396 // skeleton "MMMM".
\r
1397 pattern = fInfo.getIntervalPattern(bestSkeleton, field);
\r
1398 if ( pattern == null && differenceInfo == 0 ) {
\r
1399 // if there is no skeleton "yMMMM" defined,
\r
1400 // look for the best match skeleton, for example: "yMMM"
\r
1401 BestMatchInfo tmpRetValue = fInfo.getBestSkeleton(skeleton);
\r
1402 String tmpBestSkeleton = tmpRetValue.bestMatchSkeleton;
\r
1403 differenceInfo = tmpRetValue.bestMatchDistanceInfo;
\r
1404 if ( tmpBestSkeleton.length() != 0 && differenceInfo != -1 ) {
\r
1405 pattern = fInfo.getIntervalPattern(tmpBestSkeleton, field);
\r
1406 bestSkeleton = tmpBestSkeleton;
\r
1409 if ( pattern != null ) {
\r
1410 retValue = new SkeletonAndItsBestMatch(skeleton, bestSkeleton);
\r
1413 if ( pattern != null ) {
\r
1414 if ( differenceInfo != 0 ) {
\r
1415 String part1 = adjustFieldWidth(skeleton, bestSkeleton,
\r
1416 pattern.getFirstPart(), differenceInfo);
\r
1417 String part2 = adjustFieldWidth(skeleton, bestSkeleton,
\r
1418 pattern.getSecondPart(), differenceInfo);
\r
1419 pattern = new PatternInfo(part1, part2,
\r
1420 pattern.firstDateInPtnIsLaterDate());
\r
1422 // pattern is immutable, no need to clone;
\r
1423 // pattern = (PatternInfo)pattern.clone();
\r
1425 intervalPatterns.put(
\r
1426 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], pattern);
\r
1432 * Adjust field width in best match interval pattern to match
\r
1433 * the field width in input skeleton.
\r
1435 * TODO (xji) make a general solution
\r
1436 * The adjusting rule can be:
\r
1437 * 1. always adjust
\r
1439 * 3. default adjust, which means adjust according to the following rules
\r
1440 * 3.1 always adjust string, such as MMM and MMMM
\r
1441 * 3.2 never adjust between string and numeric, such as MM and MMM
\r
1442 * 3.3 always adjust year
\r
1443 * 3.4 do not adjust 'd', 'h', or 'm' if h presents
\r
1444 * 3.5 do not adjust 'M' if it is numeric(?)
\r
1446 * Since date interval format is well-formed format,
\r
1447 * date and time skeletons are normalized previously,
\r
1448 * till this stage, the adjust here is only "adjust strings, such as MMM
\r
1449 * and MMMM, EEE and EEEE.
\r
1451 * @param inputSkeleton the input skeleton
\r
1452 * @param bestMatchSkeleton the best match skeleton
\r
1453 * @param bestMatchIntervalpattern the best match interval pattern
\r
1454 * @param differenceInfo the difference between 2 skeletons
\r
1455 * 1 means only field width differs
\r
1456 * 2 means v/z exchange
\r
1457 * @return the adjusted interval pattern
\r
1459 private static String adjustFieldWidth(String inputSkeleton,
\r
1460 String bestMatchSkeleton,
\r
1461 String bestMatchIntervalPattern,
\r
1462 int differenceInfo ) {
\r
1464 if ( bestMatchIntervalPattern == null ) {
\r
1465 return null; // the 2nd part could be null
\r
1467 int[] inputSkeletonFieldWidth = new int[58];
\r
1468 int[] bestMatchSkeletonFieldWidth = new int[58];
\r
1470 /* initialize as following
\r
1472 // A B C D E F G H I J K L M N O
\r
1473 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
1474 // P Q R S T U V W X Y Z
\r
1475 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
1476 // a b c d e f g h i j k l m n o
\r
1477 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
1478 // p q r s t u v w x y z
\r
1479 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
1484 DateIntervalInfo.parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
\r
1485 DateIntervalInfo.parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth);
\r
1486 if ( differenceInfo == 2 ) {
\r
1487 bestMatchIntervalPattern = bestMatchIntervalPattern.replace('v', 'z');
\r
1490 StringBuilder adjustedPtn = new StringBuilder(bestMatchIntervalPattern);
\r
1492 boolean inQuote = false;
\r
1496 int PATTERN_CHAR_BASE = 0x41;
\r
1498 // loop through the pattern string character by character
\r
1499 int adjustedPtnLength = adjustedPtn.length();
\r
1500 for (int i = 0; i < adjustedPtnLength; ++i) {
\r
1501 char ch = adjustedPtn.charAt(i);
\r
1502 if (ch != prevCh && count > 0) {
\r
1503 // check the repeativeness of pattern letter
\r
1504 char skeletonChar = prevCh;
\r
1505 if ( skeletonChar == 'L' ) {
\r
1506 // for skeleton "M+", the pattern is "...L..."
\r
1507 skeletonChar = 'M';
\r
1509 int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
\r
1510 int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
\r
1511 if ( fieldCount == count && inputFieldCount > fieldCount ) {
\r
1512 count = inputFieldCount - fieldCount;
\r
1513 for ( int j = 0; j < count; ++j ) {
\r
1514 adjustedPtn.insert(i, prevCh);
\r
1517 adjustedPtnLength += count;
\r
1522 // Consecutive single quotes are a single quote literal,
\r
1523 // either outside of quotes or between quotes
\r
1524 if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == '\'') {
\r
1527 inQuote = ! inQuote;
\r
1530 else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
\r
1531 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
\r
1532 // ch is a date-time pattern character
\r
1537 if ( count > 0 ) {
\r
1539 // check the repeativeness of pattern letter
\r
1540 char skeletonChar = prevCh;
\r
1541 if ( skeletonChar == 'L' ) {
\r
1542 // for skeleton "M+", the pattern is "...L..."
\r
1543 skeletonChar = 'M';
\r
1545 int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
\r
1546 int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE];
\r
1547 if ( fieldCount == count && inputFieldCount > fieldCount ) {
\r
1548 count = inputFieldCount - fieldCount;
\r
1549 for ( int j = 0; j < count; ++j ) {
\r
1550 adjustedPtn.append(prevCh);
\r
1554 return adjustedPtn.toString();
\r
1559 * Concat a single date pattern with a time interval pattern,
\r
1560 * set it into the intervalPatterns, while field is time field.
\r
1561 * This is used to handle time interval patterns on skeleton with
\r
1562 * both time and date. Present the date followed by
\r
1563 * the range expression for the time.
\r
1564 * @param dtfmt date and time format
\r
1565 * @param datePattern date pattern
\r
1566 * @param field time calendar field: AM_PM, HOUR, MINUTE
\r
1567 * @param intervalPatterns interval patterns
\r
1569 private void concatSingleDate2TimeInterval(String dtfmt,
\r
1570 String datePattern,
\r
1572 Map<String, PatternInfo> intervalPatterns)
\r
1575 PatternInfo timeItvPtnInfo =
\r
1576 intervalPatterns.get(DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
\r
1577 if ( timeItvPtnInfo != null ) {
\r
1578 String timeIntervalPattern = timeItvPtnInfo.getFirstPart() +
\r
1579 timeItvPtnInfo.getSecondPart();
\r
1580 String pattern = MessageFormat.format(dtfmt, new Object[]
\r
1581 {timeIntervalPattern, datePattern});
\r
1582 timeItvPtnInfo = DateIntervalInfo.genPatternInfo(pattern,
\r
1583 timeItvPtnInfo.firstDateInPtnIsLaterDate());
\r
1584 intervalPatterns.put(
\r
1585 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], timeItvPtnInfo);
\r
1587 // else: fall back
\r
1588 // it should not happen if the interval format defined is valid
\r
1593 * check whether a calendar field present in a skeleton.
\r
1594 * @param field calendar field need to check
\r
1595 * @param skeleton given skeleton on which to check the calendar field
\r
1596 * @return true if field present in a skeleton.
\r
1598 private static boolean fieldExistsInSkeleton(int field, String skeleton)
\r
1600 String fieldChar = DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
\r
1601 return ( (skeleton.indexOf(fieldChar) == -1) ? false : true ) ;
\r
1608 private void readObject(ObjectInputStream stream)
\r
1609 throws IOException, ClassNotFoundException {
\r
1610 stream.defaultReadObject();
\r
1611 initializePattern();
\r