2 * Copyright (C) 2008-2009, 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.Locale;
\r
13 import java.util.HashMap;
\r
14 import java.util.Map;
\r
15 import java.util.Collections;
\r
17 import com.ibm.icu.impl.ICUCache;
\r
18 import com.ibm.icu.impl.SimpleCache;
\r
19 import com.ibm.icu.impl.CalendarData;
\r
20 import com.ibm.icu.util.Calendar;
\r
21 import com.ibm.icu.util.ULocale;
\r
22 import com.ibm.icu.util.DateInterval;
\r
23 import com.ibm.icu.text.DateIntervalInfo;
\r
24 import com.ibm.icu.text.SimpleDateFormat;
\r
28 * DateIntervalFormat is a class for formatting and parsing date
\r
29 * intervals in a language-independent manner.
\r
30 * Date interval formatting is supported in Gregorian calendar only.
\r
31 * And only formatting is supported. Parsing is not supported.
\r
34 * Date interval means from one date to another date,
\r
35 * for example, from "Jan 11, 2008" to "Jan 18, 2008".
\r
36 * We introduced class DateInterval to represent it.
\r
37 * DateInterval is a pair of UDate, which is
\r
38 * the standard milliseconds since 24:00 GMT, Jan 1, 1970.
\r
41 * DateIntervalFormat formats a DateInterval into
\r
42 * text as compactly as possible.
\r
43 * For example, the date interval format from "Jan 11, 2008" to "Jan 18,. 2008"
\r
44 * is "Jan 11-18, 2008" for English.
\r
45 * And it parses text into DateInterval,
\r
46 * although initially, parsing is not supported.
\r
49 * There is no structural information in date time patterns.
\r
50 * For any punctuations and string literals inside a date time pattern,
\r
51 * we do not know whether it is just a separator, or a prefix, or a suffix.
\r
52 * Without such information, so, it is difficult to generate a sub-pattern
\r
53 * (or super-pattern) by algorithm.
\r
54 * So, formatting a DateInterval is pattern-driven. It is very
\r
55 * similar to formatting in SimpleDateFormat.
\r
56 * We introduce class DateIntervalInfo to save date interval
\r
57 * patterns, similar to date time pattern in SimpleDateFormat.
\r
60 * Logically, the interval patterns are mappings
\r
61 * from (skeleton, the_largest_different_calendar_field)
\r
62 * to (date_interval_pattern).
\r
68 * only keeps the field pattern letter and ignores all other parts
\r
69 * in a pattern, such as space, punctuations, and string literals.
\r
71 * hides the order of fields.
\r
73 * might hide a field's pattern letter length.
\r
75 * For those non-digit calendar fields, the pattern letter length is
\r
76 * important, such as MMM, MMMM, and MMMMM; EEE and EEEE,
\r
77 * and the field's pattern letter length is honored.
\r
79 * For the digit calendar fields, such as M or MM, d or dd, yy or yyyy,
\r
80 * the field pattern length is ignored and the best match, which is defined
\r
81 * in date time patterns, will be returned without honor the field pattern
\r
82 * letter length in skeleton.
\r
86 * The calendar fields we support for interval formatting are:
\r
87 * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.
\r
88 * Those calendar fields can be defined in the following order:
\r
89 * year > month > date > hour (in day) > minute
\r
91 * The largest different calendar fields between 2 calendars is the
\r
92 * first different calendar field in above order.
\r
94 * For example: the largest different calendar fields between "Jan 10, 2007"
\r
95 * and "Feb 20, 2008" is year.
\r
98 * For other calendar fields, the compact interval formatting is not
\r
99 * supported. And the interval format will be fall back to fall-back
\r
100 * patterns, which is mostly "{date0} - {date1}".
\r
103 * There is a set of pre-defined static skeleton strings in DateFormat,
\r
104 * There are pre-defined interval patterns for those pre-defined skeletons
\r
105 * in locales' resource files.
\r
106 * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is "yMMMd",
\r
107 * in en_US, if the largest different calendar field between date1 and date2
\r
108 * is "year", the date interval pattern is "MMM d, yyyy - MMM d, yyyy",
\r
109 * such as "Jan 10, 2007 - Jan 10, 2008".
\r
110 * If the largest different calendar field between date1 and date2 is "month",
\r
111 * the date interval pattern is "MMM d - MMM d, yyyy",
\r
112 * such as "Jan 10 - Feb 10, 2007".
\r
113 * If the largest different calendar field between date1 and date2 is "day",
\r
114 * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".
\r
116 * For date skeleton, the interval patterns when year, or month, or date is
\r
117 * different are defined in resource files.
\r
118 * For time skeleton, the interval patterns when am/pm, or hour, or minute is
\r
119 * different are defined in resource files.
\r
122 * If a skeleton is not found in a locale's DateIntervalInfo, which means
\r
123 * the interval patterns for the skeleton is not defined in resource file,
\r
124 * the interval pattern will falls back to the interval "fallback" pattern
\r
125 * defined in resource file.
\r
126 * If the interval "fallback" pattern is not defined, the default fall-back
\r
127 * is "{date0} - {data1}".
\r
130 * For the combination of date and time,
\r
131 * The rule to genearte interval patterns are:
\r
134 * when the year, month, or day differs, falls back to fall-back
\r
135 * interval pattern, which mostly is the concatenate the two original
\r
136 * expressions with a separator between,
\r
137 * For example, interval pattern from "Jan 10, 2007 10:10 am"
\r
138 * to "Jan 11, 2007 10:10am" is
\r
139 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
\r
141 * otherwise, present the date followed by the range expression
\r
143 * For example, interval pattern from "Jan 10, 2007 10:10 am"
\r
144 * to "Jan 10, 2007 11:10am" is "Jan 10, 2007 10:10 am - 11:10am"
\r
149 * If two dates are the same, the interval pattern is the single date pattern.
\r
150 * For example, interval pattern from "Jan 10, 2007" to "Jan 10, 2007" is
\r
153 * Or if the presenting fields between 2 dates have the exact same values,
\r
154 * the interval pattern is the single date pattern.
\r
155 * For example, if user only requests year and month,
\r
156 * the interval pattern from "Jan 10, 2007" to "Jan 20, 2007" is "Jan 2007".
\r
159 * DateIntervalFormat needs the following information for correct
\r
160 * formatting: time zone, calendar type, pattern, date format symbols,
\r
161 * and date interval patterns.
\r
162 * It can be instantiated in several ways:
\r
165 * create an instance using default or given locale plus given skeleton.
\r
166 * Users are encouraged to created date interval formatter this way and
\r
167 * to use the pre-defined skeleton macros, such as
\r
168 * YEAR_NUM_MONTH, which consists the calendar fields and
\r
169 * the format style.
\r
172 * create an instance using default or given locale plus given skeleton
\r
173 * plus a given DateIntervalInfo.
\r
174 * This factory method is for powerful users who want to provide their own
\r
175 * interval patterns.
\r
176 * Locale provides the timezone, calendar, and format symbols information.
\r
177 * Local plus skeleton provides full pattern information.
\r
178 * DateIntervalInfo provides the date interval patterns.
\r
183 * For the calendar field pattern letter, such as G, y, M, d, a, h, H, m, s etc.
\r
184 * DateIntervalFormat uses the same syntax as that of
\r
188 * Code Sample: general usage
\r
191 * // the date interval object which the DateIntervalFormat formats on
\r
192 * // and parses into
\r
193 * DateInterval dtInterval = new DateInterval(1000*3600*24L, 1000*3600*24*2L);
\r
194 * DateIntervalFormat dtIntervalFmt = DateIntervalFormat.getInstance(
\r
195 * YEAR_MONTH_DAY, Locale("en", "GB", ""));
\r
196 * StringBuffer str = new StringBuffer("");
\r
197 * FieldPosition pos = new FieldPosition(0);
\r
199 * dtIntervalFmt.format(dtInterval, dateIntervalString, pos);
\r
204 * Code Sample: for powerful users who wants to use their own interval pattern
\r
207 * import com.ibm.icu.text.DateIntervalInfo;
\r
208 * import com.ibm.icu.text.DateIntervalFormat;
\r
209 * ....................
\r
211 * // Get DateIntervalFormat instance using default locale
\r
212 * DateIntervalFormat dtitvfmt = DateIntervalFormat.getInstance(YEAR_MONTH_DAY);
\r
214 * // Create an empty DateIntervalInfo object, which does not have any interval patterns inside.
\r
215 * dtitvinf = new DateIntervalInfo();
\r
217 * // a series of set interval patterns.
\r
218 * // Only ERA, YEAR, MONTH, DATE, DAY_OF_MONTH, DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, and MINUTE are supported.
\r
219 * dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'");
\r
220 * dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d");
\r
221 * dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d");
\r
222 * dtitvinf.setIntervalPattern("yMMMd", Calendar.HOUR_OF_DAY, "yyyy MMM d HH:mm ~ HH:mm");
\r
224 * // Set fallback interval pattern. Fallback pattern is used when interval pattern is not found.
\r
225 * // If the fall-back pattern is not set, falls back to {date0} - {date1} if interval pattern is not found.
\r
226 * dtitvinf.setFallbackIntervalPattern("{0} - {1}");
\r
228 * // Set above DateIntervalInfo object as the interval patterns of date interval formatter
\r
229 * dtitvfmt.setDateIntervalInfo(dtitvinf);
\r
231 * // Prepare to format
\r
232 * pos = new FieldPosition(0);
\r
233 * str = new StringBuffer("");
\r
235 * // The 2 calendars should be equivalent, otherwise, IllegalArgumentException will be thrown by format()
\r
236 * Calendar fromCalendar = (Calendar) dtfmt.getCalendar().clone();
\r
237 * Calendar toCalendar = (Calendar) dtfmt.getCalendar().clone();
\r
238 * fromCalendar.setTimeInMillis(....);
\r
239 * toCalendar.setTimeInMillis(...);
\r
241 * //Formatting given 2 calendars
\r
242 * dtitvfmt.format(fromCalendar, toCalendar, str, pos);
\r
249 public class DateIntervalFormat extends UFormat {
\r
251 private static final long serialVersionUID = 1;
\r
254 * Used to save the information for a skeleton's best match skeleton.
\r
255 * It is package accessible since it is used in DateIntervalInfo too.
\r
257 static final class BestMatchInfo {
\r
258 // the best match skeleton
\r
259 final String bestMatchSkeleton;
\r
260 // 0 means the best matched skeleton is the same as input skeleton
\r
261 // 1 means the fields are the same, but field width are different
\r
262 // 2 means the only difference between fields are v/z,
\r
263 // -1 means there are other fields difference
\r
264 final int bestMatchDistanceInfo;
\r
265 BestMatchInfo(String bestSkeleton, int difference) {
\r
266 bestMatchSkeleton = bestSkeleton;
\r
267 bestMatchDistanceInfo = difference;
\r
273 * Used to save the information on a skeleton and its best match.
\r
275 private static final class SkeletonAndItsBestMatch {
\r
276 final String skeleton;
\r
277 final String bestMatchSkeleton;
\r
278 SkeletonAndItsBestMatch(String skeleton, String bestMatch) {
\r
279 this.skeleton = skeleton;
\r
280 bestMatchSkeleton = bestMatch;
\r
285 // Cache for the locale interval pattern
\r
286 private static ICUCache LOCAL_PATTERN_CACHE = new SimpleCache();
\r
289 * The interval patterns for this locale.
\r
291 private DateIntervalInfo fInfo;
\r
294 * The DateFormat object used to format single pattern
\r
296 private SimpleDateFormat fDateFormat;
\r
299 * The 2 calendars with the from and to date.
\r
300 * could re-use the calendar in fDateFormat,
\r
301 * but keeping 2 calendars make it clear and clean.
\r
303 private Calendar fFromCalendar;
\r
304 private Calendar fToCalendar;
\r
307 * Following are transient interval information
\r
308 * relavent (locale) to this formatter.
\r
310 private String fSkeleton = null;
\r
311 // HashMap<String, String> calendar_field -> interval pattern
\r
312 private transient Map fIntervalPatterns = null;
\r
316 * default constructor
\r
318 private DateIntervalFormat() {
\r
322 * Construct a DateIntervalFormat from DateFormat,
\r
323 * a DateIntervalInfo, and skeleton.
\r
324 * DateFormat provides the timezone, calendar,
\r
325 * full pattern, and date format symbols information.
\r
326 * It should be a SimpleDateFormat object which
\r
327 * has a pattern in it.
\r
328 * the DateIntervalInfo provides the interval patterns.
\r
330 * @param locale the locale of this date interval formatter.
\r
331 * @param dtitvinf the DateIntervalInfo object to be adopted.
\r
332 * @param skeleton the skeleton of the date formatter
\r
334 private DateIntervalFormat(ULocale locale, DateIntervalInfo dtItvInfo,
\r
337 // freeze date interval info
\r
338 dtItvInfo.freeze();
\r
339 fSkeleton = skeleton;
\r
342 DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
\r
343 final String bestPattern = generator.getBestPattern(skeleton);
\r
344 fDateFormat = new SimpleDateFormat(bestPattern, locale);
\r
345 fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
\r
346 fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
\r
347 initializePattern();
\r
352 * Construct a DateIntervalFormat from skeleton and the default locale.
\r
354 * This is a convenient override of
\r
355 * getInstance(String skeleton, ULocale locale)
\r
356 * with the value of locale as default locale.
\r
358 * @param skeleton the skeleton on which interval format based.
\r
359 * @return a date time interval formatter.
\r
362 public static final DateIntervalFormat
\r
363 getInstance(String skeleton)
\r
366 return getInstance(skeleton, ULocale.getDefault());
\r
371 * Construct a DateIntervalFormat from skeleton and a given locale.
\r
373 * This is a convenient override of
\r
374 * getInstance(String skeleton, ULocale locale)
\r
376 * @param skeleton the skeleton on which interval format based.
\r
377 * @param locale the given locale
\r
378 * @return a date time interval formatter.
\r
381 public static final DateIntervalFormat
\r
382 getInstance(String skeleton, Locale locale)
\r
384 return getInstance(skeleton, ULocale.forLocale(locale));
\r
389 * Construct a DateIntervalFormat from skeleton and a given locale.
\r
391 * In this factory method,
\r
392 * the date interval pattern information is load from resource files.
\r
393 * Users are encouraged to created date interval formatter this way and
\r
394 * to use the pre-defined skeleton macros.
\r
397 * There are pre-defined skeletons in DateFormat,
\r
398 * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
\r
400 * Those skeletons have pre-defined interval patterns in resource files.
\r
401 * Users are encouraged to use them.
\r
403 * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc);
\r
405 * The given Locale provides the interval patterns.
\r
406 * For example, for en_GB, if skeleton is YEAR_ABBR_MONTH_WEEKDAY_DAY,
\r
407 * which is "yMMMEEEd",
\r
408 * the interval patterns defined in resource file to above skeleton are:
\r
409 * "EEE, d MMM, yyyy - EEE, d MMM, yyyy" for year differs,
\r
410 * "EEE, d MMM - EEE, d MMM, yyyy" for month differs,
\r
411 * "EEE, d - EEE, d MMM, yyyy" for day differs,
\r
412 * @param skeleton the skeleton on which interval format based.
\r
413 * @param locale the given locale
\r
414 * @return a date time interval formatter.
\r
417 public static final DateIntervalFormat
\r
418 getInstance(String skeleton, ULocale locale)
\r
420 DateIntervalInfo dtitvinf = new DateIntervalInfo(locale);
\r
421 return new DateIntervalFormat(locale, dtitvinf, skeleton);
\r
427 * Construct a DateIntervalFormat from skeleton
\r
428 * DateIntervalInfo, and default locale.
\r
430 * This is a convenient override of
\r
431 * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
\r
432 * with the locale value as default locale.
\r
434 * @param skeleton the skeleton on which interval format based.
\r
435 * @param dtitvinf the DateIntervalInfo object to be adopted.
\r
436 * @return a date time interval formatter.
\r
439 public static final DateIntervalFormat getInstance(String skeleton,
\r
440 DateIntervalInfo dtitvinf)
\r
442 return getInstance(skeleton, ULocale.getDefault(), dtitvinf);
\r
448 * Construct a DateIntervalFormat from skeleton
\r
449 * a DateIntervalInfo, and the given locale.
\r
451 * This is a convenient override of
\r
452 * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
\r
454 * @param skeleton the skeleton on which interval format based.
\r
455 * @param locale the given locale
\r
456 * @param dtitvinf the DateIntervalInfo object to be adopted.
\r
457 * @return a date time interval formatter.
\r
460 public static final DateIntervalFormat getInstance(String skeleton,
\r
462 DateIntervalInfo dtitvinf)
\r
464 return getInstance(skeleton, ULocale.forLocale(locale), dtitvinf);
\r
470 * Construct a DateIntervalFormat from skeleton
\r
471 * a DateIntervalInfo, and the given locale.
\r
474 * In this factory method, user provides its own date interval pattern
\r
475 * information, instead of using those pre-defined data in resource file.
\r
476 * This factory method is for powerful users who want to provide their own
\r
477 * interval patterns.
\r
480 * There are pre-defined skeleton in DateFormat,
\r
481 * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc.
\r
483 * Those skeletons have pre-defined interval patterns in resource files.
\r
484 * Users are encouraged to use them.
\r
486 * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc,itvinf);
\r
488 * the DateIntervalInfo provides the interval patterns.
\r
490 * User are encouraged to set default interval pattern in DateIntervalInfo
\r
491 * as well, if they want to set other interval patterns ( instead of
\r
492 * reading the interval patterns from resource files).
\r
493 * When the corresponding interval pattern for a largest calendar different
\r
494 * field is not found ( if user not set it ), interval format fallback to
\r
495 * the default interval pattern.
\r
496 * If user does not provide default interval pattern, it fallback to
\r
497 * "{date0} - {date1}"
\r
499 * @param skeleton the skeleton on which interval format based.
\r
500 * @param locale the given locale
\r
501 * @param dtitvinf the DateIntervalInfo object to be adopted.
\r
502 * @return a date time interval formatter.
\r
505 public static final DateIntervalFormat getInstance(String skeleton,
\r
507 DateIntervalInfo dtitvinf)
\r
509 LOCAL_PATTERN_CACHE.clear();
\r
510 // clone. If it is frozen, clone returns itself, otherwise, clone
\r
512 dtitvinf = (DateIntervalInfo)dtitvinf.clone();
\r
513 return new DateIntervalFormat(locale, dtitvinf, skeleton);
\r
518 * Clone this Format object polymorphically.
\r
519 * @return A copy of the object.
\r
522 public Object clone()
\r
524 DateIntervalFormat other = (DateIntervalFormat) super.clone();
\r
525 other.fDateFormat = (SimpleDateFormat) fDateFormat.clone();
\r
526 other.fInfo = (DateIntervalInfo) fInfo.clone();
\r
527 other.fFromCalendar = (Calendar) fFromCalendar.clone();
\r
528 other.fToCalendar = (Calendar) fToCalendar.clone();
\r
529 other.fSkeleton = fSkeleton;
\r
530 other.fIntervalPatterns = fIntervalPatterns;
\r
536 * Format an object to produce a string. This method handles Formattable
\r
537 * objects with a DateInterval type.
\r
538 * If a the Formattable object type is not a DateInterval,
\r
539 * IllegalArgumentException is thrown.
\r
541 * @param obj The object to format.
\r
542 * Must be a DateInterval.
\r
543 * @param appendTo Output parameter to receive result.
\r
544 * Result is appended to existing contents.
\r
545 * @param fieldPosition On input: an alignment field, if desired.
\r
546 * On output: the offsets of the alignment field.
\r
547 * @return Reference to 'appendTo' parameter.
\r
548 * @throws IllegalArgumentException if the formatted object is not
\r
549 * DateInterval object
\r
552 public final StringBuffer
\r
553 format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition)
\r
555 if ( obj instanceof DateInterval ) {
\r
556 return format( (DateInterval)obj, appendTo, fieldPosition);
\r
559 throw new IllegalArgumentException("Cannot format given Object (" + obj.getClass().getName() + ") as a DateInterval");
\r
564 * Format a DateInterval to produce a string.
\r
566 * @param dtInterval DateInterval to be formatted.
\r
567 * @param appendTo Output parameter to receive result.
\r
568 * Result is appended to existing contents.
\r
569 * @param fieldPosition On input: an alignment field, if desired.
\r
570 * On output: the offsets of the alignment field.
\r
571 * @return Reference to 'appendTo' parameter.
\r
574 public final StringBuffer format(DateInterval dtInterval,
\r
575 StringBuffer appendTo,
\r
576 FieldPosition fieldPosition)
\r
578 fFromCalendar.setTimeInMillis(dtInterval.getFromDate());
\r
579 fToCalendar.setTimeInMillis(dtInterval.getToDate());
\r
580 return format(fFromCalendar, fToCalendar, appendTo, fieldPosition);
\r
585 * Format 2 Calendars to produce a string.
\r
587 * @param fromCalendar calendar set to the from date in date interval
\r
588 * to be formatted into date interval string
\r
589 * @param toCalendar calendar set to the to date in date interval
\r
590 * to be formatted into date interval string
\r
591 * @param appendTo Output parameter to receive result.
\r
592 * Result is appended to existing contents.
\r
593 * @param pos On input: an alignment field, if desired.
\r
594 * On output: the offsets of the alignment field.
\r
595 * @return Reference to 'appendTo' parameter.
\r
596 * @throws IllegalArgumentException if the two calendars are not equivalent, or the calendars are not Gregorian calendar.
\r
599 public final StringBuffer format(Calendar fromCalendar,
\r
600 Calendar toCalendar,
\r
601 StringBuffer appendTo,
\r
604 // not support different calendar types and time zones
\r
605 if ( !fromCalendar.isEquivalentTo(toCalendar) ||
\r
606 !fromCalendar.getType().equals("gregorian") ) {
\r
607 throw new IllegalArgumentException("can not format on two different calendars or non-Gregorian calendars");
\r
610 // First, find the largest different calendar field.
\r
611 int field = -1; //init with an invalid value.
\r
613 if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
\r
614 field = Calendar.ERA;
\r
615 } else if ( fromCalendar.get(Calendar.YEAR) !=
\r
616 toCalendar.get(Calendar.YEAR) ) {
\r
617 field = Calendar.YEAR;
\r
618 } else if ( fromCalendar.get(Calendar.MONTH) !=
\r
619 toCalendar.get(Calendar.MONTH) ) {
\r
620 field = Calendar.MONTH;
\r
621 } else if ( fromCalendar.get(Calendar.DATE) !=
\r
622 toCalendar.get(Calendar.DATE) ) {
\r
623 field = Calendar.DATE;
\r
624 } else if ( fromCalendar.get(Calendar.AM_PM) !=
\r
625 toCalendar.get(Calendar.AM_PM) ) {
\r
626 field = Calendar.AM_PM;
\r
627 } else if ( fromCalendar.get(Calendar.HOUR) !=
\r
628 toCalendar.get(Calendar.HOUR) ) {
\r
629 field = Calendar.HOUR;
\r
630 } else if ( fromCalendar.get(Calendar.MINUTE) !=
\r
631 toCalendar.get(Calendar.MINUTE) ) {
\r
632 field = Calendar.MINUTE;
\r
634 /* ignore the second/millisecond etc. small fields' difference.
\r
635 * use single date when all the above are the same.
\r
637 return fDateFormat.format(fromCalendar, appendTo, pos);
\r
640 // get interval pattern
\r
641 DateIntervalInfo.PatternInfo intervalPattern =
\r
642 (DateIntervalInfo.PatternInfo)fIntervalPatterns.get(
\r
643 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
\r
645 if ( intervalPattern == null ) {
\r
646 if ( fDateFormat.isFieldUnitIgnored(field) ) {
\r
647 /* the largest different calendar field is small than
\r
648 * the smallest calendar field in pattern,
\r
649 * return single date format.
\r
651 return fDateFormat.format(fromCalendar, appendTo, pos);
\r
654 return fallbackFormat(fromCalendar, toCalendar, appendTo, pos);
\r
657 // If the first part in interval pattern is empty,
\r
658 // the 2nd part of it saves the full-pattern used in fall-back.
\r
659 // For a 'real' interval pattern, the first part will never be empty.
\r
660 if ( intervalPattern.getFirstPart() == null ) {
\r
662 return fallbackFormat(fromCalendar, toCalendar, appendTo, pos,
\r
663 intervalPattern.getSecondPart());
\r
666 Calendar secondCal;
\r
667 if ( intervalPattern.firstDateInPtnIsLaterDate() ) {
\r
668 firstCal = toCalendar;
\r
669 secondCal = fromCalendar;
\r
671 firstCal = fromCalendar;
\r
672 secondCal = toCalendar;
\r
674 // break the interval pattern into 2 parts
\r
675 // first part should not be empty,
\r
676 String originalPattern = fDateFormat.toPattern();
\r
677 fDateFormat.applyPattern(intervalPattern.getFirstPart());
\r
678 fDateFormat.format(firstCal, appendTo, pos);
\r
679 if ( intervalPattern.getSecondPart() != null ) {
\r
680 fDateFormat.applyPattern(intervalPattern.getSecondPart());
\r
681 fDateFormat.format(secondCal, appendTo, pos);
\r
683 fDateFormat.applyPattern(originalPattern);
\r
689 * Format 2 Calendars to using fall-back interval pattern
\r
691 * The full pattern used in this fall-back format is the
\r
692 * full pattern of the date formatter.
\r
694 * @param fromCalendar calendar set to the from date in date interval
\r
695 * to be formatted into date interval string
\r
696 * @param toCalendar calendar set to the to date in date interval
\r
697 * to be formatted into date interval string
\r
698 * @param appendTo Output parameter to receive result.
\r
699 * Result is appended to existing contents.
\r
700 * @param pos On input: an alignment field, if desired.
\r
701 * On output: the offsets of the alignment field.
\r
702 * @return Reference to 'appendTo' parameter.
\r
704 private final StringBuffer fallbackFormat(Calendar fromCalendar,
\r
705 Calendar toCalendar,
\r
706 StringBuffer appendTo,
\r
707 FieldPosition pos) {
\r
709 StringBuffer earlierDate = new StringBuffer(64);
\r
710 earlierDate = fDateFormat.format(fromCalendar, earlierDate, pos);
\r
711 StringBuffer laterDate = new StringBuffer(64);
\r
712 laterDate = fDateFormat.format(toCalendar, laterDate, pos);
\r
713 String fallbackPattern = fInfo.getFallbackIntervalPattern();
\r
714 String fallback = MessageFormat.format(fallbackPattern, new Object[]
\r
715 {earlierDate.toString(), laterDate.toString()});
\r
716 appendTo.append(fallback);
\r
722 * Format 2 Calendars to using fall-back interval pattern
\r
724 * This fall-back pattern is generated on a given full pattern,
\r
725 * not the full pattern of the date formatter.
\r
727 * @param fromCalendar calendar set to the from date in date interval
\r
728 * to be formatted into date interval string
\r
729 * @param toCalendar calendar set to the to date in date interval
\r
730 * to be formatted into date interval string
\r
731 * @param appendTo Output parameter to receive result.
\r
732 * Result is appended to existing contents.
\r
733 * @param pos On input: an alignment field, if desired.
\r
734 * On output: the offsets of the alignment field.
\r
735 * @param fullPattern the full pattern need to apply to date formatter
\r
736 * @return Reference to 'appendTo' parameter.
\r
738 private final StringBuffer fallbackFormat(Calendar fromCalendar,
\r
739 Calendar toCalendar,
\r
740 StringBuffer appendTo,
\r
741 FieldPosition pos,
\r
742 String fullPattern) {
\r
743 String originalPattern = fDateFormat.toPattern();
\r
744 fDateFormat.applyPattern(fullPattern);
\r
745 fallbackFormat(fromCalendar, toCalendar, appendTo, pos);
\r
746 fDateFormat.applyPattern(originalPattern);
\r
752 * Date interval parsing is not supported.
\r
754 * This method should handle parsing of
\r
755 * date time interval strings into Formattable objects with
\r
756 * DateInterval type, which is a pair of UDate.
\r
759 * Before calling, set parse_pos.index to the offset you want to start
\r
760 * parsing at in the source. After calling, parse_pos.index is the end of
\r
761 * the text you parsed. If error occurs, index is unchanged.
\r
763 * When parsing, leading whitespace is discarded (with a successful parse),
\r
764 * while trailing whitespace is left as is.
\r
766 * See Format.parseObject() for more.
\r
768 * @param source The string to be parsed into an object.
\r
769 * @param parse_pos The position to start parsing at. Since no parsing
\r
770 * is supported, upon return this param is unchanged.
\r
771 * @return A newly created Formattable* object, or NULL
\r
773 * @internal ICU 4.0
\r
774 * @deprecated This API is ICU internal only.
\r
776 public Object parseObject(String source, ParsePosition parse_pos)
\r
778 throw new UnsupportedOperationException("parsing is not supported");
\r
783 * Gets the date time interval patterns.
\r
784 * @return a copy of the date time interval patterns associated with
\r
785 * this date interval formatter.
\r
788 public DateIntervalInfo getDateIntervalInfo()
\r
790 return (DateIntervalInfo)fInfo.clone();
\r
795 * Set the date time interval patterns.
\r
796 * @param newItvPattern the given interval patterns to copy.
\r
799 public void setDateIntervalInfo(DateIntervalInfo newItvPattern)
\r
801 // clone it. If it is frozen, the clone returns itself.
\r
802 // Otherwise, clone returns a copy
\r
803 fInfo = (DateIntervalInfo)newItvPattern.clone();
\r
804 fInfo.freeze(); // freeze it
\r
805 LOCAL_PATTERN_CACHE.clear();
\r
806 if ( fDateFormat != null ) {
\r
807 initializePattern();
\r
813 * Gets the date formatter
\r
814 * @return a copy of the date formatter associated with
\r
815 * this date interval formatter.
\r
818 public DateFormat getDateFormat()
\r
820 return (DateFormat)fDateFormat.clone();
\r
825 * Below are for generating interval patterns locale to the formatter
\r
829 * Initialize interval patterns locale to this formatter.
\r
831 private void initializePattern() {
\r
832 String fullPattern = ((SimpleDateFormat)fDateFormat).toPattern();
\r
833 ULocale locale = ((SimpleDateFormat)fDateFormat).getLocale();
\r
835 if ( fSkeleton != null ) {
\r
836 key = locale.toString() + "+" + fullPattern + "+" + fSkeleton;
\r
838 key = locale.toString() + "+" + fullPattern;
\r
840 Map patterns = (Map) LOCAL_PATTERN_CACHE.get(key);
\r
841 if ( patterns == null ) {
\r
842 HashMap intervalPatterns = initializeIntervalPattern(fullPattern, locale);
\r
843 patterns = Collections.unmodifiableMap(intervalPatterns);
\r
844 LOCAL_PATTERN_CACHE.put(key, patterns);
\r
846 fIntervalPatterns = patterns;
\r
852 * Initialize interval patterns locale to this formatter
\r
854 * This code is a bit complicated since
\r
855 * 1. the interval patterns saved in resource bundle files are interval
\r
856 * patterns based on date or time only.
\r
857 * It does not have interval patterns based on both date and time.
\r
858 * Interval patterns on both date and time are algorithm generated.
\r
860 * For example, it has interval patterns on skeleton "dMy" and "hm",
\r
861 * but it does not have interval patterns on skeleton "dMyhm".
\r
863 * The rule to generate interval patterns for both date and time skeleton are
\r
864 * 1) when the year, month, or day differs, concatenate the two original
\r
865 * expressions with a separator between,
\r
866 * For example, interval pattern from "Jan 10, 2007 10:10 am"
\r
867 * to "Jan 11, 2007 10:10am" is
\r
868 * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am"
\r
870 * 2) otherwise, present the date followed by the range expression
\r
872 * For example, interval pattern from "Jan 10, 2007 10:10 am"
\r
873 * to "Jan 10, 2007 11:10am" is
\r
874 * "Jan 10, 2007 10:10 am - 11:10am"
\r
876 * 2. even a pattern does not request a certain calendar field,
\r
877 * the interval pattern needs to include such field if such fields are
\r
878 * different between 2 dates.
\r
879 * For example, a pattern/skeleton is "hm", but the interval pattern
\r
880 * includes year, month, and date when year, month, and date differs.
\r
883 * @param fullPattern formatter's full pattern
\r
884 * @param locale the given locale.
\r
885 * @return interval patterns' hash map
\r
887 private HashMap initializeIntervalPattern(String fullPattern, ULocale locale) {
\r
888 DateTimePatternGenerator dtpng = DateTimePatternGenerator.getInstance(locale);
\r
889 if ( fSkeleton == null ) {
\r
890 // fSkeleton is already set by getDateIntervalInstance()
\r
891 // or by getInstance(String skeleton, .... )
\r
892 fSkeleton = dtpng.getSkeleton(fullPattern);
\r
894 String skeleton = fSkeleton;
\r
896 HashMap intervalPatterns = new HashMap();
\r
898 /* Check whether the skeleton is a combination of date and time.
\r
899 * For the complication reason 1 explained above.
\r
901 StringBuffer date = new StringBuffer(skeleton.length());
\r
902 StringBuffer normalizedDate = new StringBuffer(skeleton.length());
\r
903 StringBuffer time = new StringBuffer(skeleton.length());
\r
904 StringBuffer normalizedTime = new StringBuffer(skeleton.length());
\r
906 /* the difference between time skeleton and normalizedTimeSkeleton are:
\r
907 * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton,
\r
908 * 2. 'a' is omitted in normalized time skeleton.
\r
909 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized
\r
912 * The difference between date skeleton and normalizedDateSkeleton are:
\r
913 * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton
\r
914 * 2. 'E' and 'EE' are normalized into 'EEE'
\r
915 * 3. 'MM' is normalized into 'M'
\r
917 getDateTimeSkeleton(skeleton, date, normalizedDate,
\r
918 time, normalizedTime);
\r
920 String dateSkeleton = date.toString();
\r
921 String timeSkeleton = time.toString();
\r
922 String normalizedDateSkeleton = normalizedDate.toString();
\r
923 String normalizedTimeSkeleton = normalizedTime.toString();
\r
925 boolean found = genSeparateDateTimePtn(normalizedDateSkeleton,
\r
926 normalizedTimeSkeleton,
\r
929 if ( found == false ) {
\r
931 // TODO: if user asks "m", but "d" differ
\r
932 //StringBuffer skeleton = new StringBuffer(skeleton);
\r
933 if ( time.length() != 0 ) {
\r
934 //genFallbackForNotFound(Calendar.MINUTE, skeleton);
\r
935 //genFallbackForNotFound(Calendar.HOUR, skeleton);
\r
936 //genFallbackForNotFound(Calendar.AM_PM, skeleton);
\r
937 if ( date.length() == 0 ) {
\r
939 timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
\r
940 String pattern =dtpng.getBestPattern(timeSkeleton);
\r
941 // for fall back interval patterns,
\r
942 // the first part of the pattern is empty,
\r
943 // the second part of the pattern is the full-pattern
\r
944 // should be used in fall-back.
\r
945 DateIntervalInfo.PatternInfo ptn =
\r
946 new DateIntervalInfo.PatternInfo(null, pattern,
\r
947 fInfo.getDefaultOrder());
\r
948 intervalPatterns.put(DateIntervalInfo.
\r
949 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
\r
950 // share interval pattern
\r
951 intervalPatterns.put(DateIntervalInfo.
\r
952 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
\r
953 // share interval pattern
\r
954 intervalPatterns.put(DateIntervalInfo.
\r
955 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
\r
957 //genFallbackForNotFound(Calendar.DATE, skeleton);
\r
958 //genFallbackForNotFound(Calendar.MONTH, skeleton);
\r
959 //genFallbackForNotFound(Calendar.YEAR, skeleton);
\r
962 //genFallbackForNotFound(Calendar.DATE, skeleton);
\r
963 //genFallbackForNotFound(Calendar.MONTH, skeleton);
\r
964 //genFallbackForNotFound(Calendar.YEAR, skeleton);
\r
966 return intervalPatterns;
\r
967 } // end of skeleton not found
\r
968 // interval patterns for skeleton are found in resource
\r
969 if ( time.length() == 0 ) {
\r
971 } else if ( date.length() == 0 ) {
\r
972 // need to set up patterns for y/M/d differ
\r
973 /* result from following looks confusing.
\r
974 * for example: 10 10:10 - 11 10:10, it is not
\r
975 * clear that the first 10 is the 10th day
\r
976 time.insert(0, 'd');
\r
977 genFallbackPattern(Calendar.DATE, time);
\r
978 time.insert(0, 'M');
\r
979 genFallbackPattern(Calendar.MONTH, time);
\r
980 time.insert(0, 'y');
\r
981 genFallbackPattern(Calendar.YEAR, time);
\r
984 timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton;
\r
985 String pattern =dtpng.getBestPattern(timeSkeleton);
\r
986 // for fall back interval patterns,
\r
987 // the first part of the pattern is empty,
\r
988 // the second part of the pattern is the full-pattern
\r
989 // should be used in fall-back.
\r
990 DateIntervalInfo.PatternInfo ptn = new DateIntervalInfo.PatternInfo(
\r
991 null, pattern, fInfo.getDefaultOrder());
\r
992 intervalPatterns.put(DateIntervalInfo.
\r
993 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn);
\r
994 intervalPatterns.put(DateIntervalInfo.
\r
995 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn);
\r
996 intervalPatterns.put(DateIntervalInfo.
\r
997 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn);
\r
999 /* if both present,
\r
1000 * 1) when the year, month, or day differs,
\r
1001 * concatenate the two original expressions with a separator between,
\r
1002 * 2) otherwise, present the date followed by the
\r
1003 * range expression for the time.
\r
1006 * 1) when the year, month, or day differs,
\r
1007 * concatenate the two original expressions with a separator between,
\r
1009 // if field exists, use fall back
\r
1010 if ( !fieldExistsInSkeleton(Calendar.DATE, dateSkeleton) ) {
\r
1011 // prefix skeleton with 'd'
\r
1012 skeleton = DateIntervalInfo.
\r
1013 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE] + skeleton;
\r
1014 genFallbackPattern(Calendar.DATE, skeleton, intervalPatterns, dtpng);
\r
1016 if ( !fieldExistsInSkeleton(Calendar.MONTH, dateSkeleton) ) {
\r
1017 // then prefix skeleton with 'M'
\r
1018 skeleton = DateIntervalInfo.
\r
1019 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH] + skeleton;
\r
1020 genFallbackPattern(Calendar.MONTH, skeleton, intervalPatterns, dtpng);
\r
1022 if ( !fieldExistsInSkeleton(Calendar.YEAR, dateSkeleton) ) {
\r
1023 // then prefix skeleton with 'y'
\r
1024 skeleton = DateIntervalInfo.
\r
1025 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR] + skeleton;
\r
1026 genFallbackPattern(Calendar.YEAR, skeleton, intervalPatterns, dtpng);
\r
1030 * 2) otherwise, present the date followed by the
\r
1031 * range expression for the time.
\r
1033 // Need the Date/Time pattern for concatnation the date with
\r
1034 // the time interval.
\r
1035 // The date/time pattern ( such as {0} {1} ) is saved in
\r
1036 // calendar, that is why need to get the CalendarData here.
\r
1037 CalendarData calData = new CalendarData(locale, null);
\r
1038 String[] patterns = calData.getDateTimePatterns();
\r
1039 String datePattern =dtpng.getBestPattern(dateSkeleton);
\r
1040 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.AM_PM, intervalPatterns);
\r
1041 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.HOUR, intervalPatterns);
\r
1042 concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.MINUTE, intervalPatterns);
\r
1045 return intervalPatterns;
\r
1050 * Generate fall back interval pattern given a calendar field,
\r
1051 * a skeleton, and a date time pattern generator
\r
1052 * @param field the largest different calendar field
\r
1053 * @param skeleton a skeleton
\r
1054 * @param dtpng date time pattern generator
\r
1055 * @param intervalPatterns interval patterns
\r
1057 private void genFallbackPattern(int field, String skeleton,
\r
1058 HashMap intervalPatterns,
\r
1059 DateTimePatternGenerator dtpng) {
\r
1060 String pattern = dtpng.getBestPattern(skeleton);
\r
1061 // for fall back interval patterns,
\r
1062 // the first part of the pattern is empty,
\r
1063 // the second part of the pattern is the full-pattern
\r
1064 // should be used in fall-back.
\r
1065 DateIntervalInfo.PatternInfo ptn = new DateIntervalInfo.PatternInfo(
\r
1066 null, pattern, fInfo.getDefaultOrder());
\r
1067 intervalPatterns.put(
\r
1068 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptn);
\r
1074 private void genFallbackForNotFound(String field, StringBuffer skeleton) {
\r
1075 if ( SimpleDateFormat.isFieldUnitIgnored(skeleton.toString(), field) ) {
\r
1077 DateIntervalInfo.PatternInfo ptnInfo =
\r
1078 new DateIntervalInfo.PatternInfo(null, fDateFormat.toPattern(),
\r
1079 fInfo.getDefaultOrder());
\r
1080 fIntervalPatterns.put(field, ptnInfo);
\r
1082 } else if ( skeleton.indexOf(field) == -1 ) {
\r
1083 skeleton.insert(0,field);
\r
1084 genFallbackPattern(field, skeleton, dtpng);
\r
1090 * get separated date and time skeleton from a combined skeleton.
\r
1092 * The difference between date skeleton and normalizedDateSkeleton are:
\r
1093 * 1. both 'y' and 'd' are appeared only once in normalizeDateSkeleton
\r
1094 * 2. 'E' and 'EE' are normalized into 'EEE'
\r
1095 * 3. 'MM' is normalized into 'M'
\r
1097 ** the difference between time skeleton and normalizedTimeSkeleton are:
\r
1098 * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton,
\r
1099 * 2. 'a' is omitted in normalized time skeleton.
\r
1100 * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized time
\r
1104 * @param skeleton given combined skeleton.
\r
1105 * @param date Output parameter for date only skeleton.
\r
1106 * @param normalizedDate Output parameter for normalized date only
\r
1108 * @param time Output parameter for time only skeleton.
\r
1109 * @param normalizedTime Output parameter for normalized time only
\r
1112 private static void getDateTimeSkeleton(String skeleton,
\r
1113 StringBuffer dateSkeleton,
\r
1114 StringBuffer normalizedDateSkeleton,
\r
1115 StringBuffer timeSkeleton,
\r
1116 StringBuffer normalizedTimeSkeleton)
\r
1118 // dateSkeleton follows the sequence of y*M*E*d*
\r
1119 // timeSkeleton follows the sequence of hm*[v|z]?
\r
1130 for (i = 0; i < skeleton.length(); ++i) {
\r
1131 char ch = skeleton.charAt(i);
\r
1134 dateSkeleton.append(ch);
\r
1138 dateSkeleton.append(ch);
\r
1142 dateSkeleton.append(ch);
\r
1146 dateSkeleton.append(ch);
\r
1163 normalizedDateSkeleton.append(ch);
\r
1164 dateSkeleton.append(ch);
\r
1167 // 'a' is implicitly handled
\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 if ( mCount != 0 ) {
\r
1232 normalizedTimeSkeleton.append('m');
\r
1234 if ( zCount != 0 ) {
\r
1235 normalizedTimeSkeleton.append('z');
\r
1237 if ( vCount != 0 ) {
\r
1238 normalizedTimeSkeleton.append('v');
\r
1245 * Generate date or time interval pattern from resource.
\r
1247 * It needs to handle the following:
\r
1248 * 1. need to adjust field width.
\r
1249 * For example, the interval patterns saved in DateIntervalInfo
\r
1250 * includes "dMMMy", but not "dMMMMy".
\r
1251 * Need to get interval patterns for dMMMMy from dMMMy.
\r
1252 * Another example, the interval patterns saved in DateIntervalInfo
\r
1253 * includes "hmv", but not "hmz".
\r
1254 * Need to get interval patterns for "hmz' from 'hmv'
\r
1256 * 2. there might be no pattern for 'y' differ for skeleton "Md",
\r
1257 * in order to get interval patterns for 'y' differ,
\r
1258 * need to look for it from skeleton 'yMd'
\r
1260 * @param dateSkeleton normalized date skeleton
\r
1261 * @param timeSkeleton normalized time skeleton
\r
1262 * @param intervalPatterns interval patterns
\r
1263 * @return whether there is interval patterns for the skeleton.
\r
1264 * true if there is, false otherwise
\r
1266 private boolean genSeparateDateTimePtn(String dateSkeleton,
\r
1267 String timeSkeleton,
\r
1268 HashMap intervalPatterns)
\r
1271 // if both date and time skeleton present,
\r
1272 // the final interval pattern might include time interval patterns
\r
1273 // ( when, am_pm, hour, minute differ ),
\r
1274 // but not date interval patterns ( when year, month, day differ ).
\r
1275 // For year/month/day differ, it falls back to fall-back pattern.
\r
1276 if ( timeSkeleton.length() != 0 ) {
\r
1277 skeleton = timeSkeleton;
\r
1279 skeleton = dateSkeleton;
\r
1282 /* interval patterns for skeleton "dMMMy" (but not "dMMMMy")
\r
1283 * are defined in resource,
\r
1284 * interval patterns for skeleton "dMMMMy" are calculated by
\r
1285 * 1. get the best match skeleton for "dMMMMy", which is "dMMMy"
\r
1286 * 2. get the interval patterns for "dMMMy",
\r
1287 * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy"
\r
1288 * getBestSkeleton() is step 1.
\r
1290 // best skeleton, and the difference information
\r
1291 BestMatchInfo retValue = fInfo.getBestSkeleton(skeleton);
\r
1292 String bestSkeleton = retValue.bestMatchSkeleton;
\r
1293 int differenceInfo = retValue.bestMatchDistanceInfo;
\r
1296 // 0 means the best matched skeleton is the same as input skeleton
\r
1297 // 1 means the fields are the same, but field width are different
\r
1298 // 2 means the only difference between fields are v/z,
\r
1299 // -1 means there are other fields difference
\r
1300 if ( differenceInfo == -1 ) {
\r
1301 // skeleton has different fields, not only v/z difference
\r
1305 if ( timeSkeleton.length() == 0 ) {
\r
1306 // only has date skeleton
\r
1307 genIntervalPattern(Calendar.DATE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1308 SkeletonAndItsBestMatch skeletons = genIntervalPattern(
\r
1309 Calendar.MONTH, skeleton,
\r
1310 bestSkeleton, differenceInfo,
\r
1311 intervalPatterns);
\r
1312 if ( skeletons != null ) {
\r
1313 bestSkeleton = skeletons.skeleton;
\r
1314 skeleton = skeletons.bestMatchSkeleton;
\r
1316 genIntervalPattern(Calendar.YEAR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1318 genIntervalPattern(Calendar.MINUTE, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1319 genIntervalPattern(Calendar.HOUR, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1320 genIntervalPattern(Calendar.AM_PM, skeleton, bestSkeleton, differenceInfo, intervalPatterns);
\r
1329 * Generate interval pattern from existing resource
\r
1331 * It not only save the interval patterns,
\r
1332 * but also return the skeleton and its best match skeleton.
\r
1334 * @param field largest different calendar field
\r
1335 * @param skeleton skeleton
\r
1336 * @param bestSkeleton the best match skeleton which has interval pattern
\r
1337 * defined in resource
\r
1338 * @param differenceInfo the difference between skeleton and best skeleton
\r
1339 * 0 means the best matched skeleton is the same as input skeleton
\r
1340 * 1 means the fields are the same, but field width are different
\r
1341 * 2 means the only difference between fields are v/z,
\r
1342 * -1 means there are other fields difference
\r
1344 * @param intervalPatterns interval patterns
\r
1346 * @return an extended skeleton or extended best skeleton if applicable.
\r
1349 private SkeletonAndItsBestMatch genIntervalPattern(
\r
1350 int field, String skeleton, String bestSkeleton,
\r
1351 int differenceInfo, HashMap intervalPatterns) {
\r
1352 SkeletonAndItsBestMatch retValue = null;
\r
1353 DateIntervalInfo.PatternInfo pattern = fInfo.getIntervalPattern(
\r
1354 bestSkeleton, field);
\r
1355 if ( pattern == null ) {
\r
1357 if ( SimpleDateFormat.isFieldUnitIgnored(bestSkeleton, field) ) {
\r
1358 DateIntervalInfo.PatternInfo ptnInfo =
\r
1359 new DateIntervalInfo.PatternInfo(fDateFormat.toPattern(),
\r
1361 fInfo.getDefaultOrder());
\r
1362 intervalPatterns.put(DateIntervalInfo.
\r
1363 CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptnInfo);
\r
1367 // for 24 hour system, interval patterns in resource file
\r
1368 // might not include pattern when am_pm differ,
\r
1369 // which should be the same as hour differ.
\r
1370 // add it here for simplicity
\r
1371 if ( field == Calendar.AM_PM ) {
\r
1372 pattern = fInfo.getIntervalPattern(bestSkeleton,
\r
1374 if ( pattern != null ) {
\r
1376 intervalPatterns.put(DateIntervalInfo.
\r
1377 CALENDAR_FIELD_TO_PATTERN_LETTER[field],
\r
1382 // else, looking for pattern when 'y' differ for 'dMMMM' skeleton,
\r
1383 // first, get best match pattern "MMMd",
\r
1384 // since there is no pattern for 'y' differs for skeleton 'MMMd',
\r
1385 // need to look for it from skeleton 'yMMMd',
\r
1386 // if found, adjust field width in interval pattern from
\r
1387 // "MMM" to "MMMM".
\r
1388 String fieldLetter =
\r
1389 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
\r
1390 bestSkeleton = fieldLetter + bestSkeleton;
\r
1391 skeleton = fieldLetter + skeleton;
\r
1392 // for example, looking for patterns when 'y' differ for
\r
1393 // skeleton "MMMM".
\r
1394 pattern = fInfo.getIntervalPattern(bestSkeleton, field);
\r
1395 if ( pattern == null && differenceInfo == 0 ) {
\r
1396 // if there is no skeleton "yMMMM" defined,
\r
1397 // look for the best match skeleton, for example: "yMMM"
\r
1398 BestMatchInfo tmpRetValue = fInfo.getBestSkeleton(skeleton);
\r
1399 String tmpBestSkeleton = tmpRetValue.bestMatchSkeleton;
\r
1400 differenceInfo = tmpRetValue.bestMatchDistanceInfo;
\r
1401 if ( tmpBestSkeleton.length() != 0 && differenceInfo != -1 ) {
\r
1402 pattern = fInfo.getIntervalPattern(tmpBestSkeleton, field);
\r
1403 bestSkeleton = tmpBestSkeleton;
\r
1406 if ( pattern != null ) {
\r
1407 retValue = new SkeletonAndItsBestMatch(skeleton, bestSkeleton);
\r
1410 if ( pattern != null ) {
\r
1411 if ( differenceInfo != 0 ) {
\r
1412 String part1 = adjustFieldWidth(skeleton, bestSkeleton,
\r
1413 pattern.getFirstPart(), differenceInfo);
\r
1414 String part2 = adjustFieldWidth(skeleton, bestSkeleton,
\r
1415 pattern.getSecondPart(), differenceInfo);
\r
1416 pattern = new DateIntervalInfo.PatternInfo(part1, part2,
\r
1417 pattern.firstDateInPtnIsLaterDate());
\r
1419 // pattern is immutable, no need to clone;
\r
1420 // pattern = (DateIntervalInfo.PatternInfo)pattern.clone();
\r
1422 intervalPatterns.put(
\r
1423 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], pattern);
\r
1429 * Adjust field width in best match interval pattern to match
\r
1430 * the field width in input skeleton.
\r
1432 * TODO (xji) make a general solution
\r
1433 * The adjusting rule can be:
\r
1434 * 1. always adjust
\r
1436 * 3. default adjust, which means adjust according to the following rules
\r
1437 * 3.1 always adjust string, such as MMM and MMMM
\r
1438 * 3.2 never adjust between string and numeric, such as MM and MMM
\r
1439 * 3.3 always adjust year
\r
1440 * 3.4 do not adjust 'd', 'h', or 'm' if h presents
\r
1441 * 3.5 do not adjust 'M' if it is numeric(?)
\r
1443 * Since date interval format is well-formed format,
\r
1444 * date and time skeletons are normalized previously,
\r
1445 * till this stage, the adjust here is only "adjust strings, such as MMM
\r
1446 * and MMMM, EEE and EEEE.
\r
1448 * @param inputSkeleton the input skeleton
\r
1449 * @param bestMatchSkeleton the best match skeleton
\r
1450 * @param bestMatchIntervalpattern the best match interval pattern
\r
1451 * @param differenceInfo the difference between 2 skeletons
\r
1452 * 1 means only field width differs
\r
1453 * 2 means v/z exchange
\r
1454 * @return the adjusted interval pattern
\r
1456 private static String adjustFieldWidth(String inputSkeleton,
\r
1457 String bestMatchSkeleton,
\r
1458 String bestMatchIntervalPattern,
\r
1459 int differenceInfo ) {
\r
1461 if ( bestMatchIntervalPattern == null ) {
\r
1462 return null; // the 2nd part could be null
\r
1464 int[] inputSkeletonFieldWidth = new int[58];
\r
1465 int[] bestMatchSkeletonFieldWidth = new int[58];
\r
1467 /* initialize as following
\r
1469 // A B C D E F G H I J K L M N O
\r
1470 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
1471 // P Q R S T U V W X Y Z
\r
1472 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
1473 // a b c d e f g h i j k l m n o
\r
1474 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
1475 // p q r s t u v w x y z
\r
1476 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
\r
1481 DateIntervalInfo.parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
\r
1482 DateIntervalInfo.parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth);
\r
1483 if ( differenceInfo == 2 ) {
\r
1484 bestMatchIntervalPattern = bestMatchIntervalPattern.replace('v', 'z');
\r
1487 StringBuffer adjustedPtn = new StringBuffer(bestMatchIntervalPattern);
\r
1489 boolean inQuote = false;
\r
1493 int PATTERN_CHAR_BASE = 0x41;
\r
1495 // loop through the pattern string character by character
\r
1496 int adjustedPtnLength = adjustedPtn.length();
\r
1497 for (int i = 0; i < adjustedPtnLength; ++i) {
\r
1498 char ch = adjustedPtn.charAt(i);
\r
1499 if (ch != prevCh && count > 0) {
\r
1500 // check the repeativeness of pattern letter
\r
1501 char skeletonChar = prevCh;
\r
1502 if ( skeletonChar == 'L' ) {
\r
1503 // for skeleton "M+", the pattern is "...L..."
\r
1504 skeletonChar = 'M';
\r
1506 int fieldCount = bestMatchSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
\r
1507 int inputFieldCount = inputSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
\r
1508 if ( fieldCount == count && inputFieldCount > fieldCount ) {
\r
1509 count = inputFieldCount - fieldCount;
\r
1510 for ( int j = 0; j < count; ++j ) {
\r
1511 adjustedPtn.insert(i, prevCh);
\r
1514 adjustedPtnLength += count;
\r
1519 // Consecutive single quotes are a single quote literal,
\r
1520 // either outside of quotes or between quotes
\r
1521 if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == '\'') {
\r
1524 inQuote = ! inQuote;
\r
1527 else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
\r
1528 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
\r
1529 // ch is a date-time pattern character
\r
1534 if ( count > 0 ) {
\r
1536 // check the repeativeness of pattern letter
\r
1537 char skeletonChar = prevCh;
\r
1538 if ( skeletonChar == 'L' ) {
\r
1539 // for skeleton "M+", the pattern is "...L..."
\r
1540 skeletonChar = 'M';
\r
1542 int fieldCount = bestMatchSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
\r
1543 int inputFieldCount = inputSkeletonFieldWidth[(int)(skeletonChar - PATTERN_CHAR_BASE)];
\r
1544 if ( fieldCount == count && inputFieldCount > fieldCount ) {
\r
1545 count = inputFieldCount - fieldCount;
\r
1546 for ( int j = 0; j < count; ++j ) {
\r
1547 adjustedPtn.append(prevCh);
\r
1551 return adjustedPtn.toString();
\r
1556 * Concat a single date pattern with a time interval pattern,
\r
1557 * set it into the intervalPatterns, while field is time field.
\r
1558 * This is used to handle time interval patterns on skeleton with
\r
1559 * both time and date. Present the date followed by
\r
1560 * the range expression for the time.
\r
1561 * @param dtfmt date and time format
\r
1562 * @param datePattern date pattern
\r
1563 * @param field time calendar field: AM_PM, HOUR, MINUTE
\r
1564 * @param intervalPatterns interval patterns
\r
1566 private void concatSingleDate2TimeInterval(String dtfmt,
\r
1567 String datePattern,
\r
1569 HashMap intervalPatterns)
\r
1572 DateIntervalInfo.PatternInfo timeItvPtnInfo =
\r
1573 (DateIntervalInfo.PatternInfo)intervalPatterns.get(
\r
1574 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
\r
1575 if ( timeItvPtnInfo != null ) {
\r
1576 String timeIntervalPattern = timeItvPtnInfo.getFirstPart() +
\r
1577 timeItvPtnInfo.getSecondPart();
\r
1578 String pattern = MessageFormat.format(dtfmt, new Object[]
\r
1579 {timeIntervalPattern, datePattern});
\r
1580 timeItvPtnInfo = DateIntervalInfo.genPatternInfo(pattern,
\r
1581 timeItvPtnInfo.firstDateInPtnIsLaterDate());
\r
1582 intervalPatterns.put(
\r
1583 DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], timeItvPtnInfo);
\r
1585 // else: fall back
\r
1586 // it should not happen if the interval format defined is valid
\r
1591 * check whether a calendar field present in a skeleton.
\r
1592 * @param field calendar field need to check
\r
1593 * @param skeleton given skeleton on which to check the calendar field
\r
1594 * @return true if field present in a skeleton.
\r
1596 private static boolean fieldExistsInSkeleton(int field, String skeleton)
\r
1598 String fieldChar = DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field];
\r
1599 return ( (skeleton.indexOf(fieldChar) == -1) ? false : true ) ;
\r
1606 private void readObject(ObjectInputStream stream)
\r
1607 throws IOException, ClassNotFoundException {
\r
1608 stream.defaultReadObject();
\r
1609 initializePattern();
\r