2 *******************************************************************************
\r
3 * Copyright (C) 2008-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
8 package com.ibm.icu.text;
\r
10 import java.io.Serializable;
\r
11 import java.util.HashMap;
\r
12 import java.util.HashSet;
\r
13 import java.util.Map;
\r
14 import java.util.MissingResourceException;
\r
16 import com.ibm.icu.impl.ICUCache;
\r
17 import com.ibm.icu.impl.ICUResourceBundle;
\r
18 import com.ibm.icu.impl.SimpleCache;
\r
19 import com.ibm.icu.impl.Utility;
\r
20 import com.ibm.icu.util.Calendar;
\r
21 import com.ibm.icu.util.Freezable;
\r
22 import com.ibm.icu.util.ULocale;
\r
23 import com.ibm.icu.util.UResourceBundle;
\r
27 * DateIntervalInfo is a public class for encapsulating localizable
\r
28 * date time interval patterns. It is used by DateIntervalFormat.
\r
31 * For most users, ordinary use of DateIntervalFormat does not need to create
\r
32 * DateIntervalInfo object directly.
\r
33 * DateIntervalFormat will take care of it when creating a date interval
\r
34 * formatter when user pass in skeleton and locale.
\r
37 * For power users, who want to create their own date interval patterns,
\r
38 * or want to re-set date interval patterns, they could do so by
\r
39 * directly creating DateIntervalInfo and manupulating it.
\r
42 * Logically, the interval patterns are mappings
\r
43 * from (skeleton, the_largest_different_calendar_field)
\r
44 * to (date_interval_pattern).
\r
50 * only keeps the field pattern letter and ignores all other parts
\r
51 * in a pattern, such as space, punctuations, and string literals.
\r
53 * hides the order of fields.
\r
55 * might hide a field's pattern letter length.
\r
57 * For those non-digit calendar fields, the pattern letter length is
\r
58 * important, such as MMM, MMMM, and MMMMM; EEE and EEEE,
\r
59 * and the field's pattern letter length is honored.
\r
61 * For the digit calendar fields, such as M or MM, d or dd, yy or yyyy,
\r
62 * the field pattern length is ignored and the best match, which is defined
\r
63 * in date time patterns, will be returned without honor the field pattern
\r
64 * letter length in skeleton.
\r
68 * The calendar fields we support for interval formatting are:
\r
69 * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.
\r
70 * Those calendar fields can be defined in the following order:
\r
71 * year > month > date > am-pm > hour > minute
\r
73 * The largest different calendar fields between 2 calendars is the
\r
74 * first different calendar field in above order.
\r
76 * For example: the largest different calendar fields between "Jan 10, 2007"
\r
77 * and "Feb 20, 2008" is year.
\r
80 * There is a set of pre-defined static skeleton strings.
\r
81 * There are pre-defined interval patterns for those pre-defined skeletons
\r
82 * in locales' resource files.
\r
83 * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is "yMMMd",
\r
84 * in en_US, if the largest different calendar field between date1 and date2
\r
85 * is "year", the date interval pattern is "MMM d, yyyy - MMM d, yyyy",
\r
86 * such as "Jan 10, 2007 - Jan 10, 2008".
\r
87 * If the largest different calendar field between date1 and date2 is "month",
\r
88 * the date interval pattern is "MMM d - MMM d, yyyy",
\r
89 * such as "Jan 10 - Feb 10, 2007".
\r
90 * If the largest different calendar field between date1 and date2 is "day",
\r
91 * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".
\r
93 * For date skeleton, the interval patterns when year, or month, or date is
\r
94 * different are defined in resource files.
\r
95 * For time skeleton, the interval patterns when am/pm, or hour, or minute is
\r
96 * different are defined in resource files.
\r
100 * There are 2 dates in interval pattern. For most locales, the first date
\r
101 * in an interval pattern is the earlier date. There might be a locale in which
\r
102 * the first date in an interval pattern is the later date.
\r
103 * We use fallback format for the default order for the locale.
\r
104 * For example, if the fallback format is "{0} - {1}", it means
\r
105 * the first date in the interval pattern for this locale is earlier date.
\r
106 * If the fallback format is "{1} - {0}", it means the first date is the
\r
108 * For a particular interval pattern, the default order can be overriden
\r
109 * by prefixing "latestFirst:" or "earliestFirst:" to the interval pattern.
\r
110 * For example, if the fallback format is "{0}-{1}",
\r
111 * but for skeleton "yMMMd", the interval pattern when day is different is
\r
112 * "latestFirst:d-d MMM yy", it means by default, the first date in interval
\r
113 * pattern is the earlier date. But for skeleton "yMMMd", when day is different,
\r
114 * the first date in "d-d MMM yy" is the later date.
\r
117 * The recommended way to create a DateIntervalFormat object is to pass in
\r
119 * By using a Locale parameter, the DateIntervalFormat object is
\r
120 * initialized with the pre-defined interval patterns for a given or
\r
123 * Users can also create DateIntervalFormat object
\r
124 * by supplying their own interval patterns.
\r
125 * It provides flexibility for power usage.
\r
128 * After a DateIntervalInfo object is created, clients may modify
\r
129 * the interval patterns using setIntervalPattern function as so desired.
\r
130 * Currently, users can only set interval patterns when the following
\r
131 * calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH,
\r
132 * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, and MINUTE.
\r
133 * Interval patterns when other calendar fields are different is not supported.
\r
135 * DateIntervalInfo objects are cloneable.
\r
136 * When clients obtain a DateIntervalInfo object,
\r
137 * they can feel free to modify it as necessary.
\r
139 * DateIntervalInfo are not expected to be subclassed.
\r
140 * Data for a calendar is loaded out of resource bundles.
\r
141 * Through ICU 4.4, date interval patterns are only supported in the Gregoria
\r
142 * calendar; non-Gregorian calendars are supported from ICU 4.4.1.
\r
147 public class DateIntervalInfo implements Cloneable, Freezable<DateIntervalInfo>, Serializable {
\r
148 /* Save the interval pattern information.
\r
149 * Interval pattern consists of 2 single date patterns and the separator.
\r
150 * For example, interval pattern "MMM d - MMM d, yyyy" consists
\r
151 * a single date pattern "MMM d", another single date pattern "MMM d, yyyy",
\r
152 * and a separator "-".
\r
153 * Also, the first date appears in an interval pattern could be
\r
154 * the earlier date or the later date.
\r
155 * And such information is saved in the interval pattern as well.
\r
157 static final int currentSerialVersion = 1;
\r
160 * PatternInfo class saves the first and second part of interval pattern,
\r
161 * and whether the interval pattern is earlier date first.
\r
164 public static final class PatternInfo implements Cloneable, Serializable {
\r
165 static final int currentSerialVersion = 1;
\r
166 private static final long serialVersionUID = 1;
\r
167 private final String fIntervalPatternFirstPart;
\r
168 private final String fIntervalPatternSecondPart;
\r
170 * Whether the first date in interval pattern is later date or not.
\r
171 * Fallback format set the default ordering.
\r
172 * And for a particular interval pattern, the order can be
\r
173 * overriden by prefixing the interval pattern with "latestFirst:" or
\r
175 * For example, given 2 date, Jan 10, 2007 to Feb 10, 2007.
\r
176 * if the fallback format is "{0} - {1}",
\r
177 * and the pattern is "d MMM - d MMM yyyy", the interval format is
\r
178 * "10 Jan - 10 Feb, 2007".
\r
179 * If the pattern is "latestFirst:d MMM - d MMM yyyy",
\r
180 * the interval format is "10 Feb - 10 Jan, 2007"
\r
182 private final boolean fFirstDateInPtnIsLaterDate;
\r
188 public PatternInfo(String firstPart, String secondPart,
\r
189 boolean firstDateInPtnIsLaterDate) {
\r
190 fIntervalPatternFirstPart = firstPart;
\r
191 fIntervalPatternSecondPart = secondPart;
\r
192 fFirstDateInPtnIsLaterDate = firstDateInPtnIsLaterDate;
\r
199 public String getFirstPart() {
\r
200 return fIntervalPatternFirstPart;
\r
207 public String getSecondPart() {
\r
208 return fIntervalPatternSecondPart;
\r
215 public boolean firstDateInPtnIsLaterDate() {
\r
216 return fFirstDateInPtnIsLaterDate;
\r
223 public boolean equals(Object a) {
\r
224 if ( a instanceof PatternInfo ) {
\r
225 PatternInfo patternInfo = (PatternInfo)a;
\r
226 return Utility.objectEquals(fIntervalPatternFirstPart, patternInfo.fIntervalPatternFirstPart) &&
\r
227 Utility.objectEquals(fIntervalPatternSecondPart, fIntervalPatternSecondPart) &&
\r
228 fFirstDateInPtnIsLaterDate == patternInfo.fFirstDateInPtnIsLaterDate;
\r
234 * Override hashcode
\r
237 public int hashCode() {
\r
238 int hash = fIntervalPatternFirstPart != null ? fIntervalPatternFirstPart.hashCode() : 0;
\r
239 if (fIntervalPatternSecondPart != null) {
\r
240 hash ^= fIntervalPatternSecondPart.hashCode();
\r
242 if (fFirstDateInPtnIsLaterDate) {
\r
249 // Following is package protected since
\r
250 // it is shared with DateIntervalFormat.
\r
251 static final String[] CALENDAR_FIELD_TO_PATTERN_LETTER =
\r
261 private static final long serialVersionUID = 1;
\r
262 private static final int MINIMUM_SUPPORTED_CALENDAR_FIELD =
\r
264 //private static boolean DEBUG = true;
\r
266 private static String FALLBACK_STRING = "fallback";
\r
267 private static String LATEST_FIRST_PREFIX = "latestFirst:";
\r
268 private static String EARLIEST_FIRST_PREFIX = "earliestFirst:";
\r
270 // DateIntervalInfo cache
\r
271 private final static ICUCache<String, DateIntervalInfo> DIICACHE = new SimpleCache<String, DateIntervalInfo>();
\r
273 // default interval pattern on the skeleton, {0} - {1}
\r
274 private String fFallbackIntervalPattern;
\r
276 private boolean fFirstDateInPtnIsLaterDate = false;
\r
278 // HashMap( skeleton, HashMap(largest_different_field, pattern) )
\r
279 private Map<String, Map<String, PatternInfo>> fIntervalPatterns = null;
\r
281 private transient boolean frozen = false;
\r
285 * Create empty instance.
\r
286 * It does not initialize any interval patterns except
\r
287 * that it initialize default fall-back pattern as "{0} - {1}",
\r
288 * which can be reset by setFallbackIntervalPattern().
\r
290 * It should be followed by setFallbackIntervalPattern() and
\r
291 * setIntervalPattern(),
\r
292 * and is recommended to be used only for power users who
\r
293 * wants to create their own interval patterns and use them to create
\r
294 * date interval formatter.
\r
296 * @deprecated This API is ICU internal only.
\r
298 public DateIntervalInfo()
\r
300 fIntervalPatterns = new HashMap<String, Map<String, PatternInfo>>();
\r
301 fFallbackIntervalPattern = "{0} \u2013 {1}";
\r
306 * Construct DateIntervalInfo for the given locale,
\r
307 * @param locale the interval patterns are loaded from the appropriate
\r
308 * calendar data (specified calendar or default calendar)
\r
312 public DateIntervalInfo(ULocale locale)
\r
314 initializeData(locale);
\r
319 * Initialize the DateIntervalInfo from locale
\r
320 * @param locale the given locale.
\r
322 private void initializeData(ULocale locale)
\r
324 String key = locale.toString();
\r
325 DateIntervalInfo dii = DIICACHE.get(key);
\r
326 if ( dii == null ) {
\r
327 // initialize data from scratch
\r
329 // TODO: should put a clone in cache?
\r
330 // or put itself in cache?
\r
331 // DIICACHE.put(key, this);
\r
332 dii = (DateIntervalInfo)this.clone();
\r
333 DIICACHE.put(key, dii);
\r
335 initializeData(dii);
\r
342 * Initialize DateIntervalInfo from another instance
\r
343 * @param dii an DateIntervalInfo instance
\r
345 private void initializeData(DateIntervalInfo dii) {
\r
346 fFallbackIntervalPattern = dii.fFallbackIntervalPattern;
\r
347 fFirstDateInPtnIsLaterDate = dii.fFirstDateInPtnIsLaterDate;
\r
348 fIntervalPatterns = dii.fIntervalPatterns;
\r
353 * Initialize DateIntervalInfo from calendar data
\r
354 * @param calData calendar data
\r
356 private void setup(ULocale locale) {
\r
357 int DEFAULT_HASH_SIZE = 19;
\r
358 fIntervalPatterns = new HashMap<String, Map<String, PatternInfo>>(DEFAULT_HASH_SIZE);
\r
359 // initialize to guard if there is no interval date format defined in
\r
361 fFallbackIntervalPattern = "{0} \u2013 {1}";
\r
362 HashSet<String> skeletonSet = new HashSet<String>();
\r
364 // loop through all locales to get all available skeletons'
\r
366 ULocale parentLocale = locale;
\r
367 // Get the correct calendar type
\r
368 String calendarTypeToUse = locale.getKeywordValue("calendar");
\r
369 if ( calendarTypeToUse == null ) {
\r
370 String[] preferredCalendarTypes = Calendar.getKeywordValuesForLocale("calendar", locale, true);
\r
371 calendarTypeToUse = preferredCalendarTypes[0]; // the most preferred calendar
\r
373 if ( calendarTypeToUse == null ) {
\r
374 calendarTypeToUse = "gregorian"; // fallback
\r
377 String name = parentLocale.getName();
\r
378 if ( name.length() == 0 ) {
\r
382 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.
\r
383 getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);
\r
384 rb = rb.getWithFallback("calendar");
\r
385 ICUResourceBundle calTypeBundle = rb.getWithFallback(
\r
386 calendarTypeToUse);
\r
387 ICUResourceBundle itvDtPtnResource =calTypeBundle.
\r
388 getWithFallback("intervalFormats");
\r
389 // look for fallback first, since it establishes the default order
\r
390 String fallback = itvDtPtnResource.getStringWithFallback(
\r
392 setFallbackIntervalPattern(fallback);
\r
393 int size = itvDtPtnResource.getSize();
\r
394 for ( int index = 0; index < size; ++index ) {
\r
395 String skeleton = itvDtPtnResource.get(index).getKey();
\r
396 if ( skeletonSet.contains(skeleton) ) {
\r
399 skeletonSet.add(skeleton);
\r
400 if ( skeleton.compareTo(FALLBACK_STRING) == 0 ) {
\r
403 ICUResourceBundle intervalPatterns =
\r
404 itvDtPtnResource.getWithFallback(skeleton);
\r
405 int ptnNum = intervalPatterns.getSize();
\r
406 for ( int ptnIndex = 0; ptnIndex < ptnNum; ++ptnIndex) {
\r
407 String key = intervalPatterns.get(ptnIndex).getKey();
\r
408 String pattern = intervalPatterns.get(ptnIndex).getString();
\r
410 int calendarField = -1; // initialize with an invalid value.
\r
411 if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR]) == 0 ) {
\r
412 calendarField = Calendar.YEAR;
\r
413 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH]) == 0 ) {
\r
414 calendarField = Calendar.MONTH;
\r
415 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE]) == 0 ) {
\r
416 calendarField = Calendar.DATE;
\r
417 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM]) == 0 ) {
\r
418 calendarField = Calendar.AM_PM;
\r
419 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR]) == 0 ) {
\r
420 calendarField = Calendar.HOUR;
\r
421 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MINUTE]) == 0 ) {
\r
422 calendarField = Calendar.MINUTE;
\r
425 if ( calendarField != -1 ) {
\r
426 setIntervalPatternInternally(skeleton, key, pattern);
\r
430 parentLocale = parentLocale.getFallback();
\r
431 } while (parentLocale != null && !parentLocale.equals(ULocale.ROOT));
\r
432 } catch ( MissingResourceException e) {
\r
433 // ok, will fallback to {data0} - {date1}
\r
439 * Split interval patterns into 2 part.
\r
440 * @param intervalPattern interval pattern
\r
441 * @return the index in interval pattern which split the pattern into 2 part
\r
443 private static int splitPatternInto2Part(String intervalPattern) {
\r
444 boolean inQuote = false;
\r
448 /* repeatedPattern used to record whether a pattern has already seen.
\r
449 It is a pattern applies to first calendar if it is first time seen,
\r
450 otherwise, it is a pattern applies to the second calendar
\r
452 int[] patternRepeated = new int[58];
\r
454 int PATTERN_CHAR_BASE = 0x41;
\r
456 /* loop through the pattern string character by character looking for
\r
457 * the first repeated pattern letter, which breaks the interval pattern
\r
461 boolean foundRepetition = false;
\r
462 for (i = 0; i < intervalPattern.length(); ++i) {
\r
463 char ch = intervalPattern.charAt(i);
\r
465 if (ch != prevCh && count > 0) {
\r
466 // check the repeativeness of pattern letter
\r
467 int repeated = patternRepeated[prevCh - PATTERN_CHAR_BASE];
\r
468 if ( repeated == 0 ) {
\r
469 patternRepeated[prevCh - PATTERN_CHAR_BASE] = 1;
\r
471 foundRepetition = true;
\r
477 // Consecutive single quotes are a single quote literal,
\r
478 // either outside of quotes or between quotes
\r
479 if ((i+1) < intervalPattern.length() &&
\r
480 intervalPattern.charAt(i+1) == '\'') {
\r
483 inQuote = ! inQuote;
\r
486 else if (!inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
\r
487 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
\r
488 // ch is a date-time pattern character
\r
493 // check last pattern char, distinguish
\r
494 // "dd MM" ( no repetition ),
\r
495 // "d-d"(last char repeated ), and
\r
496 // "d-d MM" ( repetition found )
\r
497 if ( count > 0 && foundRepetition == false ) {
\r
498 if ( patternRepeated[prevCh - PATTERN_CHAR_BASE] == 0 ) {
\r
502 return (i - count);
\r
507 * Provides a way for client to build interval patterns.
\r
508 * User could construct DateIntervalInfo by providing
\r
509 * a list of skeletons and their patterns.
\r
513 * DateIntervalInfo dIntervalInfo = new DateIntervalInfo();
\r
514 * dIntervalInfo.setIntervalPattern("yMd", Calendar.YEAR, "'from' yyyy-M-d 'to' yyyy-M-d");
\r
515 * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.MONTH, "'from' yyyy MMM d 'to' MMM d");
\r
516 * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.DAY, "yyyy MMM d-d");
\r
517 * dIntervalInfo.setFallbackIntervalPattern("{0} ~ {1}");
\r
521 * Currently, users can only set interval patterns when the following
\r
522 * calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH,
\r
523 * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, and MINUTE.
\r
524 * Interval patterns when other calendar fields are different are
\r
527 * @param skeleton the skeleton on which interval pattern based
\r
528 * @param lrgDiffCalUnit the largest different calendar unit.
\r
529 * @param intervalPattern the interval pattern on the largest different
\r
531 * For example, if lrgDiffCalUnit is
\r
532 * "year", the interval pattern for en_US when year
\r
533 * is different could be "'from' yyyy 'to' yyyy".
\r
534 * @throws IllegalArgumentException if setting interval pattern on
\r
535 * a calendar field that is smaller
\r
536 * than the MINIMUM_SUPPORTED_CALENDAR_FIELD
\r
537 * @throws UnsupportedOperationException if the object is frozen
\r
540 public void setIntervalPattern(String skeleton,
\r
541 int lrgDiffCalUnit,
\r
542 String intervalPattern)
\r
545 throw new UnsupportedOperationException("no modification is allowed after DII is frozen");
\r
547 if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
\r
548 throw new IllegalArgumentException("calendar field is larger than MINIMUM_SUPPORTED_CALENDAR_FIELD");
\r
551 PatternInfo ptnInfo = setIntervalPatternInternally(skeleton,
\r
552 CALENDAR_FIELD_TO_PATTERN_LETTER[lrgDiffCalUnit],
\r
554 if ( lrgDiffCalUnit == Calendar.HOUR_OF_DAY ) {
\r
555 setIntervalPattern(skeleton,
\r
556 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM],
\r
558 setIntervalPattern(skeleton,
\r
559 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR],
\r
561 } else if ( lrgDiffCalUnit == Calendar.DAY_OF_MONTH ||
\r
562 lrgDiffCalUnit == Calendar.DAY_OF_WEEK ) {
\r
563 setIntervalPattern(skeleton,
\r
564 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE],
\r
570 /* Set Interval pattern.
\r
572 * It generates the interval pattern info,
\r
573 * afer which, not only sets the interval pattern info into the hash map,
\r
574 * but also returns the interval pattern info to the caller
\r
575 * so that caller can re-use it.
\r
577 * @param skeleton skeleton on which the interval pattern based
\r
578 * @param lrgDiffCalUnit the largest different calendar unit.
\r
579 * @param intervalPattern the interval pattern on the largest different
\r
581 * @return the interval pattern pattern information
\r
583 private PatternInfo setIntervalPatternInternally(String skeleton,
\r
584 String lrgDiffCalUnit,
\r
585 String intervalPattern) {
\r
586 Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
\r
587 boolean emptyHash = false;
\r
588 if (patternsOfOneSkeleton == null) {
\r
589 patternsOfOneSkeleton = new HashMap<String, PatternInfo>();
\r
592 boolean order = fFirstDateInPtnIsLaterDate;
\r
593 // check for "latestFirst:" or "earliestFirst:" prefix
\r
594 if ( intervalPattern.startsWith(LATEST_FIRST_PREFIX) ) {
\r
596 int prefixLength = LATEST_FIRST_PREFIX.length();
\r
597 intervalPattern = intervalPattern.substring(prefixLength, intervalPattern.length());
\r
598 } else if ( intervalPattern.startsWith(EARLIEST_FIRST_PREFIX) ) {
\r
600 int earliestFirstLength = EARLIEST_FIRST_PREFIX.length();
\r
601 intervalPattern = intervalPattern.substring(earliestFirstLength, intervalPattern.length());
\r
603 PatternInfo itvPtnInfo = genPatternInfo(intervalPattern, order);
\r
605 patternsOfOneSkeleton.put(lrgDiffCalUnit, itvPtnInfo);
\r
606 if ( emptyHash == true ) {
\r
607 fIntervalPatterns.put(skeleton, patternsOfOneSkeleton);
\r
614 /* Set Interval pattern.
\r
616 * @param skeleton skeleton on which the interval pattern based
\r
617 * @param lrgDiffCalUnit the largest different calendar unit.
\r
618 * @param ptnInfo interval pattern infomration
\r
620 private void setIntervalPattern(String skeleton,
\r
621 String lrgDiffCalUnit,
\r
622 PatternInfo ptnInfo) {
\r
623 Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
\r
624 patternsOfOneSkeleton.put(lrgDiffCalUnit, ptnInfo);
\r
629 * Break interval patterns as 2 part and save them into pattern info.
\r
630 * @param intervalPattern interval pattern
\r
631 * @param laterDateFirst whether the first date in intervalPattern
\r
632 * is earlier date or later date
\r
633 * @return pattern info object
\r
635 static PatternInfo genPatternInfo(String intervalPattern,
\r
636 boolean laterDateFirst) {
\r
637 int splitPoint = splitPatternInto2Part(intervalPattern);
\r
639 String firstPart = intervalPattern.substring(0, splitPoint);
\r
640 String secondPart = null;
\r
641 if ( splitPoint < intervalPattern.length() ) {
\r
642 secondPart = intervalPattern.substring(splitPoint, intervalPattern.length());
\r
645 return new PatternInfo(firstPart, secondPart, laterDateFirst);
\r
650 * Get the interval pattern given the largest different calendar field.
\r
651 * @param skeleton the skeleton
\r
652 * @param field the largest different calendar field
\r
653 * @return interval pattern return null if interval pattern is not found.
\r
654 * @throws IllegalArgumentException if getting interval pattern on
\r
655 * a calendar field that is smaller
\r
656 * than the MINIMUM_SUPPORTED_CALENDAR_FIELD
\r
659 public PatternInfo getIntervalPattern(String skeleton, int field)
\r
661 if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
\r
662 throw new IllegalArgumentException("no support for field less than MINUTE");
\r
664 Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
\r
665 if ( patternsOfOneSkeleton != null ) {
\r
666 PatternInfo intervalPattern = patternsOfOneSkeleton.
\r
667 get(CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
\r
668 if ( intervalPattern != null ) {
\r
669 return intervalPattern;
\r
678 * Get the fallback interval pattern.
\r
679 * @return fallback interval pattern
\r
682 public String getFallbackIntervalPattern()
\r
684 return fFallbackIntervalPattern;
\r
689 * Re-set the fallback interval pattern.
\r
691 * In construction, default fallback pattern is set as "{0} - {1}".
\r
692 * And constructor taking locale as parameter will set the
\r
693 * fallback pattern as what defined in the locale resource file.
\r
695 * This method provides a way for user to replace the fallback pattern.
\r
697 * @param fallbackPattern fall-back interval pattern.
\r
698 * @throws UnsupportedOperationException if the object is frozen
\r
699 * @throws IllegalArgumentException if there is no pattern {0} or
\r
700 * pattern {1} in fallbakckPattern
\r
704 public void setFallbackIntervalPattern(String fallbackPattern)
\r
707 throw new UnsupportedOperationException("no modification is allowed after DII is frozen");
\r
709 int firstPatternIndex = fallbackPattern.indexOf("{0}");
\r
710 int secondPatternIndex = fallbackPattern.indexOf("{1}");
\r
711 if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) {
\r
712 throw new IllegalArgumentException("no pattern {0} or pattern {1} in fallbackPattern");
\r
714 if ( firstPatternIndex > secondPatternIndex ) {
\r
715 fFirstDateInPtnIsLaterDate = true;
\r
717 fFallbackIntervalPattern = fallbackPattern;
\r
722 * Get default order -- whether the first date in pattern is later date
\r
725 * return default date ordering in interval pattern. TRUE if the first date
\r
726 * in pattern is later date, FALSE otherwise.
\r
729 public boolean getDefaultOrder()
\r
731 return fFirstDateInPtnIsLaterDate;
\r
736 * Boilerplate. Clone this object.
\r
737 * @return a copy of the object
\r
740 public Object clone()
\r
745 return cloneUnfrozenDII();
\r
750 * Clone an unfrozen DateIntervalInfo object.
\r
751 * @return a copy of the object
\r
753 private Object cloneUnfrozenDII() //throws IllegalStateException
\r
756 DateIntervalInfo other = (DateIntervalInfo) super.clone();
\r
757 other.fFallbackIntervalPattern=fFallbackIntervalPattern;
\r
758 other.fFirstDateInPtnIsLaterDate = fFirstDateInPtnIsLaterDate;
\r
759 other.fIntervalPatterns = new HashMap<String, Map<String, PatternInfo>>();
\r
760 for (String skeleton : fIntervalPatterns.keySet()) {
\r
761 Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
\r
762 Map<String, PatternInfo> oneSetPtn = new HashMap<String, PatternInfo>();
\r
763 for (String calField : patternsOfOneSkeleton.keySet()) {
\r
764 PatternInfo value = patternsOfOneSkeleton.get(calField);
\r
765 oneSetPtn.put(calField, value);
\r
767 other.fIntervalPatterns.put(skeleton, oneSetPtn);
\r
769 other.frozen = false;
\r
771 } catch ( CloneNotSupportedException e ) {
\r
773 throw new IllegalStateException("clone is not supported");
\r
780 * Boilerplate for Freezable
\r
783 public boolean isFrozen() {
\r
788 * Boilerplate for Freezable
\r
791 public DateIntervalInfo freeze() {
\r
797 * Boilerplate for Freezable
\r
800 public DateIntervalInfo cloneAsThawed() {
\r
801 DateIntervalInfo result = (DateIntervalInfo) (this.cloneUnfrozenDII());
\r
807 * Parse skeleton, save each field's width.
\r
808 * It is used for looking for best match skeleton,
\r
809 * and adjust pattern field width.
\r
810 * @param skeleton skeleton to be parsed
\r
811 * @param skeletonFieldWidth parsed skeleton field width
\r
813 static void parseSkeleton(String skeleton, int[] skeletonFieldWidth) {
\r
814 int PATTERN_CHAR_BASE = 0x41;
\r
815 for ( int i = 0; i < skeleton.length(); ++i ) {
\r
816 ++skeletonFieldWidth[skeleton.charAt(i) - PATTERN_CHAR_BASE];
\r
823 * Check whether one field width is numeric while the other is string.
\r
825 * TODO (xji): make it general
\r
827 * @param fieldWidth one field width
\r
828 * @param anotherFieldWidth another field width
\r
829 * @param patternLetter pattern letter char
\r
830 * @return true if one field width is numeric and the other is string,
\r
833 private static boolean stringNumeric(int fieldWidth,
\r
834 int anotherFieldWidth,
\r
835 char patternLetter) {
\r
836 if ( patternLetter == 'M' ) {
\r
837 if ( fieldWidth <= 2 && anotherFieldWidth > 2 ||
\r
838 fieldWidth > 2 && anotherFieldWidth <= 2 ) {
\r
847 * given an input skeleton, get the best match skeleton
\r
848 * which has pre-defined interval pattern in resource file.
\r
850 * TODO (xji): set field weight or
\r
851 * isolate the funtionality in DateTimePatternGenerator
\r
852 * @param inputSkeleton input skeleton
\r
853 * @return 0, if there is exact match for input skeleton
\r
854 * 1, if there is only field width difference between
\r
855 * the best match and the input skeleton
\r
856 * 2, the only field difference is 'v' and 'z'
\r
857 * -1, if there is calendar field difference between
\r
858 * the best match and the input skeleton
\r
860 DateIntervalFormat.BestMatchInfo getBestSkeleton(String inputSkeleton) {
\r
861 String bestSkeleton = inputSkeleton;
\r
862 int[] inputSkeletonFieldWidth = new int[58];
\r
863 int[] skeletonFieldWidth = new int[58];
\r
865 final int DIFFERENT_FIELD = 0x1000;
\r
866 final int STRING_NUMERIC_DIFFERENCE = 0x100;
\r
867 final int BASE = 0x41;
\r
869 // TODO: this is a hack for 'v' and 'z'
\r
870 // resource bundle only have time skeletons ending with 'v',
\r
871 // but not for time skeletons ending with 'z'.
\r
872 boolean replaceZWithV = false;
\r
873 if ( inputSkeleton.indexOf('z') != -1 ) {
\r
874 inputSkeleton = inputSkeleton.replace('z', 'v');
\r
875 replaceZWithV = true;
\r
878 parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
\r
879 int bestDistance = Integer.MAX_VALUE;
\r
880 // 0 means exact the same skeletons;
\r
881 // 1 means having the same field, but with different length,
\r
882 // 2 means only z/v differs
\r
883 // -1 means having different field.
\r
884 int bestFieldDifference = 0;
\r
885 for (String skeleton : fIntervalPatterns.keySet()) {
\r
886 // clear skeleton field width
\r
887 for ( int i = 0; i < skeletonFieldWidth.length; ++i ) {
\r
888 skeletonFieldWidth[i] = 0;
\r
890 parseSkeleton(skeleton, skeletonFieldWidth);
\r
891 // calculate distance
\r
893 int fieldDifference = 1;
\r
894 for ( int i = 0; i < inputSkeletonFieldWidth.length; ++i ) {
\r
895 int inputFieldWidth = inputSkeletonFieldWidth[i];
\r
896 int fieldWidth = skeletonFieldWidth[i];
\r
897 if ( inputFieldWidth == fieldWidth ) {
\r
900 if ( inputFieldWidth == 0 ) {
\r
901 fieldDifference = -1;
\r
902 distance += DIFFERENT_FIELD;
\r
903 } else if ( fieldWidth == 0 ) {
\r
904 fieldDifference = -1;
\r
905 distance += DIFFERENT_FIELD;
\r
906 } else if (stringNumeric(inputFieldWidth, fieldWidth,
\r
907 (char)(i+BASE) ) ) {
\r
908 distance += STRING_NUMERIC_DIFFERENCE;
\r
910 distance += Math.abs(inputFieldWidth - fieldWidth);
\r
913 if ( distance < bestDistance ) {
\r
914 bestSkeleton = skeleton;
\r
915 bestDistance = distance;
\r
916 bestFieldDifference = fieldDifference;
\r
918 if ( distance == 0 ) {
\r
919 bestFieldDifference = 0;
\r
923 if ( replaceZWithV && bestFieldDifference != -1 ) {
\r
924 bestFieldDifference = 2;
\r
926 return new DateIntervalFormat.BestMatchInfo(bestSkeleton, bestFieldDifference);
\r
933 public boolean equals(Object a) {
\r
934 if ( a instanceof DateIntervalInfo ) {
\r
935 DateIntervalInfo dtInfo = (DateIntervalInfo)a;
\r
936 return fIntervalPatterns.equals(dtInfo.fIntervalPatterns);
\r
942 * Override hashcode
\r
945 public int hashCode() {
\r
946 return fIntervalPatterns.hashCode();
\r
948 }// end class DateIntervalInfo
\r