2 *******************************************************************************
\r
3 * Copyright (C) 2008-2009, 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
12 import java.util.MissingResourceException;
\r
13 import java.util.Iterator;
\r
14 import java.util.HashMap;
\r
15 import java.util.HashSet;
\r
17 import com.ibm.icu.impl.ICUResourceBundle;
\r
18 import com.ibm.icu.impl.ICUCache;
\r
19 import com.ibm.icu.impl.SimpleCache;
\r
20 import com.ibm.icu.impl.Utility;
\r
21 import com.ibm.icu.util.Calendar;
\r
22 import com.ibm.icu.util.ULocale;
\r
23 import com.ibm.icu.util.Freezable;
\r
24 import com.ibm.icu.util.UResourceBundle;
\r
28 * DateIntervalInfo is a public class for encapsulating localizable
\r
29 * date time interval patterns. It is used by DateIntervalFormat.
\r
32 * For most users, ordinary use of DateIntervalFormat does not need to create
\r
33 * DateIntervalInfo object directly.
\r
34 * DateIntervalFormat will take care of it when creating a date interval
\r
35 * formatter when user pass in skeleton and locale.
\r
38 * For power users, who want to create their own date interval patterns,
\r
39 * or want to re-set date interval patterns, they could do so by
\r
40 * directly creating DateIntervalInfo and manupulating it.
\r
43 * Logically, the interval patterns are mappings
\r
44 * from (skeleton, the_largest_different_calendar_field)
\r
45 * to (date_interval_pattern).
\r
51 * only keeps the field pattern letter and ignores all other parts
\r
52 * in a pattern, such as space, punctuations, and string literals.
\r
54 * hides the order of fields.
\r
56 * might hide a field's pattern letter length.
\r
58 * For those non-digit calendar fields, the pattern letter length is
\r
59 * important, such as MMM, MMMM, and MMMMM; EEE and EEEE,
\r
60 * and the field's pattern letter length is honored.
\r
62 * For the digit calendar fields, such as M or MM, d or dd, yy or yyyy,
\r
63 * the field pattern length is ignored and the best match, which is defined
\r
64 * in date time patterns, will be returned without honor the field pattern
\r
65 * letter length in skeleton.
\r
69 * The calendar fields we support for interval formatting are:
\r
70 * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute.
\r
71 * Those calendar fields can be defined in the following order:
\r
72 * year > month > date > am-pm > hour > minute
\r
74 * The largest different calendar fields between 2 calendars is the
\r
75 * first different calendar field in above order.
\r
77 * For example: the largest different calendar fields between "Jan 10, 2007"
\r
78 * and "Feb 20, 2008" is year.
\r
81 * There is a set of pre-defined static skeleton strings.
\r
82 * There are pre-defined interval patterns for those pre-defined skeletons
\r
83 * in locales' resource files.
\r
84 * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is "yMMMd",
\r
85 * in en_US, if the largest different calendar field between date1 and date2
\r
86 * is "year", the date interval pattern is "MMM d, yyyy - MMM d, yyyy",
\r
87 * such as "Jan 10, 2007 - Jan 10, 2008".
\r
88 * If the largest different calendar field between date1 and date2 is "month",
\r
89 * the date interval pattern is "MMM d - MMM d, yyyy",
\r
90 * such as "Jan 10 - Feb 10, 2007".
\r
91 * If the largest different calendar field between date1 and date2 is "day",
\r
92 * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007".
\r
94 * For date skeleton, the interval patterns when year, or month, or date is
\r
95 * different are defined in resource files.
\r
96 * For time skeleton, the interval patterns when am/pm, or hour, or minute is
\r
97 * different are defined in resource files.
\r
101 * There are 2 dates in interval pattern. For most locales, the first date
\r
102 * in an interval pattern is the earlier date. There might be a locale in which
\r
103 * the first date in an interval pattern is the later date.
\r
104 * We use fallback format for the default order for the locale.
\r
105 * For example, if the fallback format is "{0} - {1}", it means
\r
106 * the first date in the interval pattern for this locale is earlier date.
\r
107 * If the fallback format is "{1} - {0}", it means the first date is the
\r
109 * For a particular interval pattern, the default order can be overriden
\r
110 * by prefixing "latestFirst:" or "earliestFirst:" to the interval pattern.
\r
111 * For example, if the fallback format is "{0}-{1}",
\r
112 * but for skeleton "yMMMd", the interval pattern when day is different is
\r
113 * "latestFirst:d-d MMM yy", it means by default, the first date in interval
\r
114 * pattern is the earlier date. But for skeleton "yMMMd", when day is different,
\r
115 * the first date in "d-d MMM yy" is the later date.
\r
118 * The recommended way to create a DateIntervalFormat object is to pass in
\r
120 * By using a Locale parameter, the DateIntervalFormat object is
\r
121 * initialized with the pre-defined interval patterns for a given or
\r
124 * Users can also create DateIntervalFormat object
\r
125 * by supplying their own interval patterns.
\r
126 * It provides flexibility for power usage.
\r
129 * After a DateIntervalInfo object is created, clients may modify
\r
130 * the interval patterns using setIntervalPattern function as so desired.
\r
131 * Currently, users can only set interval patterns when the following
\r
132 * calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH,
\r
133 * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, and MINUTE.
\r
134 * Interval patterns when other calendar fields are different is not supported.
\r
136 * DateIntervalInfo objects are cloneable.
\r
137 * When clients obtain a DateIntervalInfo object,
\r
138 * they can feel free to modify it as necessary.
\r
140 * DateIntervalInfo are not expected to be subclassed.
\r
141 * Data for a calendar is loaded out of resource bundles.
\r
142 * To ICU 4.0, date interval patterns are only supported in Gregorian calendar.
\r
147 public class DateIntervalInfo implements Cloneable, Freezable, 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 DIICACHE = new SimpleCache();
\r
273 // default interval pattern on the skeleton, {0} - {1}
\r
274 private String fFallbackIntervalPattern;
\r
276 private boolean fFirstDateInPtnIsLaterDate = false;
\r
278 // HashMap<String, HashMap<String, PatternInfo> >
\r
279 // HashMap( skeleton, HashMap(largest_different_field, pattern) )
\r
280 private HashMap fIntervalPatterns = null;
\r
282 private transient boolean frozen = false;
\r
286 * Create empty instance.
\r
287 * It does not initialize any interval patterns except
\r
288 * that it initialize default fall-back pattern as "{0} - {1}",
\r
289 * which can be reset by setFallbackIntervalPattern().
\r
291 * It should be followed by setFallbackIntervalPattern() and
\r
292 * setIntervalPattern(),
\r
293 * and is recommended to be used only for power users who
\r
294 * wants to create their own interval patterns and use them to create
\r
295 * date interval formatter.
\r
296 * @internal ICU 4.0
\r
297 * @deprecated This API is ICU internal only.
\r
299 public DateIntervalInfo()
\r
301 fIntervalPatterns = new HashMap();
\r
302 fFallbackIntervalPattern = "{0} \u2013 {1}";
\r
307 * Construct DateIntervalInfo for the given locale,
\r
308 * @param locale the interval patterns are loaded from the Gregorian
\r
309 * calendar data in this locale.
\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 = (DateIntervalInfo) 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 if ( locale == null ) {
\r
361 int DEFAULT_HASH_SIZE = 19;
\r
362 fIntervalPatterns = new HashMap(DEFAULT_HASH_SIZE);
\r
363 // initialize to guard if there is no interval date format defined in
\r
365 fFallbackIntervalPattern = "{0} \u2013 {1}";
\r
366 HashSet skeletonSet = new HashSet();
\r
368 // loop through all locales to get all available skeletons'
\r
370 ULocale parentLocale = locale;
\r
372 String name = parentLocale.getName();
\r
373 if ( name.length() == 0 ) {
\r
377 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.
\r
378 getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);
\r
379 rb = rb.getWithFallback("calendar");
\r
380 ICUResourceBundle gregorianBundle = rb.getWithFallback(
\r
382 ICUResourceBundle itvDtPtnResource =gregorianBundle.
\r
383 getWithFallback("intervalFormats");
\r
384 // look for fallback first, since it establishes the default order
\r
385 String fallback = itvDtPtnResource.getStringWithFallback(
\r
387 setFallbackIntervalPattern(fallback);
\r
388 int size = itvDtPtnResource.getSize();
\r
389 for ( int index = 0; index < size; ++index ) {
\r
390 String skeleton = itvDtPtnResource.get(index).getKey();
\r
391 if ( skeletonSet.contains(skeleton) ) {
\r
394 skeletonSet.add(skeleton);
\r
395 if ( skeleton.compareTo(FALLBACK_STRING) == 0 ) {
\r
398 ICUResourceBundle intervalPatterns =
\r
399 itvDtPtnResource.getWithFallback(skeleton);
\r
400 int ptnNum = intervalPatterns.getSize();
\r
401 for ( int ptnIndex = 0; ptnIndex < ptnNum; ++ptnIndex) {
\r
402 String key = intervalPatterns.get(ptnIndex).getKey();
\r
403 String pattern = intervalPatterns.get(ptnIndex).getString();
\r
405 int calendarField = -1; // initialize with an invalid value.
\r
406 if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR]) == 0 ) {
\r
407 calendarField = Calendar.YEAR;
\r
408 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH]) == 0 ) {
\r
409 calendarField = Calendar.MONTH;
\r
410 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE]) == 0 ) {
\r
411 calendarField = Calendar.DATE;
\r
412 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM]) == 0 ) {
\r
413 calendarField = Calendar.AM_PM;
\r
414 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR]) == 0 ) {
\r
415 calendarField = Calendar.HOUR;
\r
416 } else if ( key.compareTo(CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MINUTE]) == 0 ) {
\r
417 calendarField = Calendar.MINUTE;
\r
420 if ( calendarField != -1 ) {
\r
421 setIntervalPatternInternally(skeleton, key, pattern);
\r
425 parentLocale = parentLocale.getFallback();
\r
426 } while (parentLocale != null && !parentLocale.equals(ULocale.ROOT));
\r
427 } catch ( MissingResourceException e) {
\r
428 // ok, will fallback to {data0} - {date1}
\r
434 * Split interval patterns into 2 part.
\r
435 * @param intervalPattern interval pattern
\r
436 * @return the index in interval pattern which split the pattern into 2 part
\r
438 private static int splitPatternInto2Part(String intervalPattern) {
\r
439 boolean inQuote = false;
\r
443 /* repeatedPattern used to record whether a pattern has already seen.
\r
444 It is a pattern applies to first calendar if it is first time seen,
\r
445 otherwise, it is a pattern applies to the second calendar
\r
447 int[] patternRepeated = new int[58];
\r
449 int PATTERN_CHAR_BASE = 0x41;
\r
451 /* loop through the pattern string character by character looking for
\r
452 * the first repeated pattern letter, which breaks the interval pattern
\r
456 boolean foundRepetition = false;
\r
457 for (i = 0; i < intervalPattern.length(); ++i) {
\r
458 char ch = intervalPattern.charAt(i);
\r
460 if (ch != prevCh && count > 0) {
\r
461 // check the repeativeness of pattern letter
\r
462 int repeated = patternRepeated[(int)(prevCh - PATTERN_CHAR_BASE)];
\r
463 if ( repeated == 0 ) {
\r
464 patternRepeated[prevCh - PATTERN_CHAR_BASE] = 1;
\r
466 foundRepetition = true;
\r
472 // Consecutive single quotes are a single quote literal,
\r
473 // either outside of quotes or between quotes
\r
474 if ((i+1) < intervalPattern.length() &&
\r
475 intervalPattern.charAt(i+1) == '\'') {
\r
478 inQuote = ! inQuote;
\r
481 else if (!inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
\r
482 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
\r
483 // ch is a date-time pattern character
\r
488 // check last pattern char, distinguish
\r
489 // "dd MM" ( no repetition ),
\r
490 // "d-d"(last char repeated ), and
\r
491 // "d-d MM" ( repetition found )
\r
492 if ( count > 0 && foundRepetition == false ) {
\r
493 if ( patternRepeated[(int)(prevCh - PATTERN_CHAR_BASE)] == 0 ) {
\r
497 return (i - count);
\r
502 * Provides a way for client to build interval patterns.
\r
503 * User could construct DateIntervalInfo by providing
\r
504 * a list of skeletons and their patterns.
\r
508 * DateIntervalInfo dIntervalInfo = new DateIntervalInfo();
\r
509 * dIntervalInfo.setIntervalPattern("yMd", Calendar.YEAR, "'from' yyyy-M-d 'to' yyyy-M-d");
\r
510 * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.MONTH, "'from' yyyy MMM d 'to' MMM d");
\r
511 * dIntervalInfo.setIntervalPattern("yMMMd", Calendar.DAY, "yyyy MMM d-d");
\r
512 * dIntervalInfo.setFallbackIntervalPattern("{0} ~ {1}");
\r
516 * Currently, users can only set interval patterns when the following
\r
517 * calendar fields are different: ERA, YEAR, MONTH, DATE, DAY_OF_MONTH,
\r
518 * DAY_OF_WEEK, AM_PM, HOUR, HOUR_OF_DAY, and MINUTE.
\r
519 * Interval patterns when other calendar fields are different are
\r
522 * @param skeleton the skeleton on which interval pattern based
\r
523 * @param lrgDiffCalUnit the largest different calendar unit.
\r
524 * @param intervalPattern the interval pattern on the largest different
\r
526 * For example, if lrgDiffCalUnit is
\r
527 * "year", the interval pattern for en_US when year
\r
528 * is different could be "'from' yyyy 'to' yyyy".
\r
529 * @throws IllegalArgumentException if setting interval pattern on
\r
530 * a calendar field that is smaller
\r
531 * than the MINIMUM_SUPPORTED_CALENDAR_FIELD
\r
532 * @throws UnsupportedOperationException if the object is frozen
\r
535 public void setIntervalPattern(String skeleton,
\r
536 int lrgDiffCalUnit,
\r
537 String intervalPattern)
\r
540 throw new UnsupportedOperationException("no modification is allowed after DII is frozen");
\r
542 if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
\r
543 throw new IllegalArgumentException("calendar field is larger than MINIMUM_SUPPORTED_CALENDAR_FIELD");
\r
546 PatternInfo ptnInfo = setIntervalPatternInternally(skeleton,
\r
547 CALENDAR_FIELD_TO_PATTERN_LETTER[lrgDiffCalUnit],
\r
549 if ( lrgDiffCalUnit == Calendar.HOUR_OF_DAY ) {
\r
550 setIntervalPattern(skeleton,
\r
551 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.AM_PM],
\r
553 setIntervalPattern(skeleton,
\r
554 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.HOUR],
\r
556 } else if ( lrgDiffCalUnit == Calendar.DAY_OF_MONTH ||
\r
557 lrgDiffCalUnit == Calendar.DAY_OF_WEEK ) {
\r
558 setIntervalPattern(skeleton,
\r
559 CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE],
\r
565 /* Set Interval pattern.
\r
567 * It generates the interval pattern info,
\r
568 * afer which, not only sets the interval pattern info into the hash map,
\r
569 * but also returns the interval pattern info to the caller
\r
570 * so that caller can re-use it.
\r
572 * @param skeleton skeleton on which the interval pattern based
\r
573 * @param lrgDiffCalUnit the largest different calendar unit.
\r
574 * @param intervalPattern the interval pattern on the largest different
\r
576 * @return the interval pattern pattern information
\r
578 private PatternInfo setIntervalPatternInternally(String skeleton,
\r
579 String lrgDiffCalUnit,
\r
580 String intervalPattern) {
\r
581 HashMap patternsOfOneSkeleton = (HashMap)fIntervalPatterns.get(skeleton);
\r
582 boolean emptyHash = false;
\r
583 if ( patternsOfOneSkeleton == null ) {
\r
584 patternsOfOneSkeleton = new HashMap();
\r
587 boolean order = fFirstDateInPtnIsLaterDate;
\r
588 // check for "latestFirst:" or "earliestFirst:" prefix
\r
589 if ( intervalPattern.startsWith(LATEST_FIRST_PREFIX) ) {
\r
591 int prefixLength = LATEST_FIRST_PREFIX.length();
\r
592 intervalPattern = intervalPattern.substring(prefixLength, intervalPattern.length());
\r
593 } else if ( intervalPattern.startsWith(EARLIEST_FIRST_PREFIX) ) {
\r
595 int earliestFirstLength = EARLIEST_FIRST_PREFIX.length();
\r
596 intervalPattern = intervalPattern.substring(earliestFirstLength, intervalPattern.length());
\r
598 PatternInfo itvPtnInfo = genPatternInfo(intervalPattern, order);
\r
600 patternsOfOneSkeleton.put(lrgDiffCalUnit, itvPtnInfo);
\r
601 if ( emptyHash == true ) {
\r
602 fIntervalPatterns.put(skeleton, patternsOfOneSkeleton);
\r
609 /* Set Interval pattern.
\r
611 * @param skeleton skeleton on which the interval pattern based
\r
612 * @param lrgDiffCalUnit the largest different calendar unit.
\r
613 * @param ptnInfo interval pattern infomration
\r
615 private void setIntervalPattern(String skeleton,
\r
616 String lrgDiffCalUnit,
\r
617 PatternInfo ptnInfo) {
\r
618 HashMap patternsOfOneSkeleton = (HashMap)fIntervalPatterns.get(skeleton);
\r
619 patternsOfOneSkeleton.put(lrgDiffCalUnit, ptnInfo);
\r
624 * Break interval patterns as 2 part and save them into pattern info.
\r
625 * @param intervalPattern interval pattern
\r
626 * @param laterDateFirst whether the first date in intervalPattern
\r
627 * is earlier date or later date
\r
628 * @return pattern info object
\r
629 * @internal ICU 4.0
\r
630 * @deprecated This API is ICU internal only.
\r
632 static PatternInfo genPatternInfo(String intervalPattern,
\r
633 boolean laterDateFirst) {
\r
634 int splitPoint = splitPatternInto2Part(intervalPattern);
\r
636 String firstPart = intervalPattern.substring(0, splitPoint);
\r
637 String secondPart = null;
\r
638 if ( splitPoint < intervalPattern.length() ) {
\r
639 secondPart = intervalPattern.substring(splitPoint, intervalPattern.length());
\r
642 return new PatternInfo(firstPart, secondPart, laterDateFirst);
\r
647 * Get the interval pattern given the largest different calendar field.
\r
648 * @param skeleton the skeleton
\r
649 * @param field the largest different calendar field
\r
650 * @return interval pattern return null if interval pattern is not found.
\r
651 * @throws IllegalArgumentException if getting interval pattern on
\r
652 * a calendar field that is smaller
\r
653 * than the MINIMUM_SUPPORTED_CALENDAR_FIELD
\r
656 public PatternInfo getIntervalPattern(String skeleton, int field)
\r
658 if ( field > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
\r
659 throw new IllegalArgumentException("no support for field less than MINUTE");
\r
661 HashMap patternsOfOneSkeleton = (HashMap) fIntervalPatterns.get(skeleton);
\r
662 if ( patternsOfOneSkeleton != null ) {
\r
663 PatternInfo intervalPattern = (PatternInfo) patternsOfOneSkeleton.
\r
664 get(CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
\r
665 if ( intervalPattern != null ) {
\r
666 return intervalPattern;
\r
675 * Get the fallback interval pattern.
\r
676 * @return fallback interval pattern
\r
679 public String getFallbackIntervalPattern()
\r
681 return fFallbackIntervalPattern;
\r
686 * Re-set the fallback interval pattern.
\r
688 * In construction, default fallback pattern is set as "{0} - {1}".
\r
689 * And constructor taking locale as parameter will set the
\r
690 * fallback pattern as what defined in the locale resource file.
\r
692 * This method provides a way for user to replace the fallback pattern.
\r
694 * @param fallbackPattern fall-back interval pattern.
\r
695 * @throws UnsupportedOperationException if the object is frozen
\r
696 * @throws IllegalArgumentException if there is no pattern {0} or
\r
697 * pattern {1} in fallbakckPattern
\r
701 public void setFallbackIntervalPattern(String fallbackPattern)
\r
704 throw new UnsupportedOperationException("no modification is allowed after DII is frozen");
\r
706 int firstPatternIndex = fallbackPattern.indexOf("{0}");
\r
707 int secondPatternIndex = fallbackPattern.indexOf("{1}");
\r
708 if ( firstPatternIndex == -1 || secondPatternIndex == -1 ) {
\r
709 throw new IllegalArgumentException("no pattern {0} or pattern {1} in fallbackPattern");
\r
711 if ( firstPatternIndex > secondPatternIndex ) {
\r
712 fFirstDateInPtnIsLaterDate = true;
\r
714 fFallbackIntervalPattern = fallbackPattern;
\r
719 * Get default order -- whether the first date in pattern is later date
\r
722 * return default date ordering in interval pattern. TRUE if the first date
\r
723 * in pattern is later date, FALSE otherwise.
\r
726 public boolean getDefaultOrder()
\r
728 return fFirstDateInPtnIsLaterDate;
\r
733 * Boilerplate. Clone this object.
\r
734 * @return a copy of the object
\r
737 public Object clone()
\r
742 return cloneUnfrozenDII();
\r
747 * Clone an unfrozen DateIntervalInfo object.
\r
748 * @return a copy of the object
\r
750 private Object cloneUnfrozenDII() //throws IllegalStateException
\r
753 DateIntervalInfo other = (DateIntervalInfo) super.clone();
\r
754 other.fFallbackIntervalPattern=fFallbackIntervalPattern;
\r
755 other.fFirstDateInPtnIsLaterDate = fFirstDateInPtnIsLaterDate;
\r
756 other.fIntervalPatterns = new HashMap();
\r
757 Iterator iter = fIntervalPatterns.keySet().iterator();
\r
758 while ( iter.hasNext() ) {
\r
759 String skeleton = (String) iter.next();
\r
760 HashMap patternsOfOneSkeleton = (HashMap)fIntervalPatterns.get(skeleton);
\r
761 HashMap oneSetPtn = new HashMap();
\r
762 Iterator patternIter = patternsOfOneSkeleton.keySet().iterator();
\r
763 while ( patternIter.hasNext() ) {
\r
764 String calField = (String) patternIter.next();
\r
765 PatternInfo value = (PatternInfo) patternsOfOneSkeleton.get(calField);
\r
766 oneSetPtn.put(calField, value);
\r
768 other.fIntervalPatterns.put(skeleton, oneSetPtn);
\r
770 other.frozen = false;
\r
772 } catch ( CloneNotSupportedException e ) {
\r
773 throw new IllegalStateException("clone is not supported");
\r
779 * Boilerplate for Freezable
\r
782 public boolean isFrozen() {
\r
787 * Boilerplate for Freezable
\r
790 public Object freeze() {
\r
796 * Boilerplate for Freezable
\r
799 public Object cloneAsThawed() {
\r
800 DateIntervalInfo result = (DateIntervalInfo) (this.cloneUnfrozenDII());
\r
806 * Parse skeleton, save each field's width.
\r
807 * It is used for looking for best match skeleton,
\r
808 * and adjust pattern field width.
\r
809 * @param skeleton skeleton to be parsed
\r
810 * @param skeletonFieldWidth parsed skeleton field width
\r
811 * @internal ICU 4.0
\r
812 * @deprecated This API is ICU internal only.
\r
814 static void parseSkeleton(String skeleton, int[] skeletonFieldWidth) {
\r
815 int PATTERN_CHAR_BASE = 0x41;
\r
816 for ( int i = 0; i < skeleton.length(); ++i ) {
\r
817 ++skeletonFieldWidth[(int)(skeleton.charAt(i) - PATTERN_CHAR_BASE)];
\r
824 * Check whether one field width is numeric while the other is string.
\r
826 * TODO (xji): make it general
\r
828 * @param fieldWidth one field width
\r
829 * @param anotherFieldWidth another field width
\r
830 * @param patternLetter pattern letter char
\r
831 * @return true if one field width is numeric and the other is string,
\r
834 private static boolean stringNumeric(int fieldWidth,
\r
835 int anotherFieldWidth,
\r
836 char patternLetter) {
\r
837 if ( patternLetter == 'M' ) {
\r
838 if ( fieldWidth <= 2 && anotherFieldWidth > 2 ||
\r
839 fieldWidth > 2 && anotherFieldWidth <= 2 ) {
\r
848 * given an input skeleton, get the best match skeleton
\r
849 * which has pre-defined interval pattern in resource file.
\r
851 * TODO (xji): set field weight or
\r
852 * isolate the funtionality in DateTimePatternGenerator
\r
853 * @param inputSkeleton input skeleton
\r
854 * @return 0, if there is exact match for input skeleton
\r
855 * 1, if there is only field width difference between
\r
856 * the best match and the input skeleton
\r
857 * 2, the only field difference is 'v' and 'z'
\r
858 * -1, if there is calendar field difference between
\r
859 * the best match and the input skeleton
\r
861 DateIntervalFormat.BestMatchInfo getBestSkeleton(String inputSkeleton) {
\r
862 String bestSkeleton = inputSkeleton;
\r
863 int[] inputSkeletonFieldWidth = new int[58];
\r
864 int[] skeletonFieldWidth = new int[58];
\r
866 final int DIFFERENT_FIELD = 0x1000;
\r
867 final int STRING_NUMERIC_DIFFERENCE = 0x100;
\r
868 final int BASE = 0x41;
\r
870 // TODO: this is a hack for 'v' and 'z'
\r
871 // resource bundle only have time skeletons ending with 'v',
\r
872 // but not for time skeletons ending with 'z'.
\r
873 boolean replaceZWithV = false;
\r
874 if ( inputSkeleton.indexOf('z') != -1 ) {
\r
875 inputSkeleton = inputSkeleton.replace('z', 'v');
\r
876 replaceZWithV = true;
\r
879 parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
\r
880 int bestDistance = Integer.MAX_VALUE;
\r
881 // 0 means exact the same skeletons;
\r
882 // 1 means having the same field, but with different length,
\r
883 // 2 means only z/v differs
\r
884 // -1 means having different field.
\r
885 int bestFieldDifference = 0;
\r
886 Iterator iter = fIntervalPatterns.keySet().iterator();
\r
887 while ( iter.hasNext() ) {
\r
888 String skeleton = (String)iter.next();
\r
889 // clear skeleton field width
\r
890 for ( int i = 0; i < skeletonFieldWidth.length; ++i ) {
\r
891 skeletonFieldWidth[i] = 0;
\r
893 parseSkeleton(skeleton, skeletonFieldWidth);
\r
894 // calculate distance
\r
896 int fieldDifference = 1;
\r
897 for ( int i = 0; i < inputSkeletonFieldWidth.length; ++i ) {
\r
898 int inputFieldWidth = inputSkeletonFieldWidth[i];
\r
899 int fieldWidth = skeletonFieldWidth[i];
\r
900 if ( inputFieldWidth == fieldWidth ) {
\r
903 if ( inputFieldWidth == 0 ) {
\r
904 fieldDifference = -1;
\r
905 distance += DIFFERENT_FIELD;
\r
906 } else if ( fieldWidth == 0 ) {
\r
907 fieldDifference = -1;
\r
908 distance += DIFFERENT_FIELD;
\r
909 } else if (stringNumeric(inputFieldWidth, fieldWidth,
\r
910 (char)(i+BASE) ) ) {
\r
911 distance += STRING_NUMERIC_DIFFERENCE;
\r
913 distance += Math.abs(inputFieldWidth - fieldWidth);
\r
916 if ( distance < bestDistance ) {
\r
917 bestSkeleton = skeleton;
\r
918 bestDistance = distance;
\r
919 bestFieldDifference = fieldDifference;
\r
921 if ( distance == 0 ) {
\r
922 bestFieldDifference = 0;
\r
926 if ( replaceZWithV && bestFieldDifference != -1 ) {
\r
927 bestFieldDifference = 2;
\r
929 return new DateIntervalFormat.BestMatchInfo(bestSkeleton, bestFieldDifference);
\r
936 public boolean equals(Object a) {
\r
937 if ( a instanceof DateIntervalInfo ) {
\r
938 DateIntervalInfo dtInfo = (DateIntervalInfo)a;
\r
939 return fIntervalPatterns.equals(dtInfo.fIntervalPatterns);
\r
945 * Override hashcode
\r
948 public int hashCode() {
\r
949 return fIntervalPatterns.hashCode();
\r
952 }// end class DateIntervalInfo
\r