/* * Copyright (C) 2008-2010, International Business Machines * Corporation and others. All Rights Reserved. */ package com.ibm.icu.text; import java.io.IOException; import java.io.ObjectInputStream; import java.text.FieldPosition; import java.text.ParsePosition; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; import com.ibm.icu.impl.CalendarData; import com.ibm.icu.impl.ICUCache; import com.ibm.icu.impl.SimpleCache; import com.ibm.icu.text.DateIntervalInfo.PatternInfo; import com.ibm.icu.util.Calendar; import com.ibm.icu.util.DateInterval; import com.ibm.icu.util.ULocale; /** * DateIntervalFormat is a class for formatting and parsing date * intervals in a language-independent manner. * Only formatting is supported. Parsing is not supported. * *

* Date interval means from one date to another date, * for example, from "Jan 11, 2008" to "Jan 18, 2008". * We introduced class DateInterval to represent it. * DateInterval is a pair of UDate, which is * the standard milliseconds since 24:00 GMT, Jan 1, 1970. * *

* DateIntervalFormat formats a DateInterval into * text as compactly as possible. * For example, the date interval format from "Jan 11, 2008" to "Jan 18,. 2008" * is "Jan 11-18, 2008" for English. * And it parses text into DateInterval, * although initially, parsing is not supported. * *

* There is no structural information in date time patterns. * For any punctuations and string literals inside a date time pattern, * we do not know whether it is just a separator, or a prefix, or a suffix. * Without such information, so, it is difficult to generate a sub-pattern * (or super-pattern) by algorithm. * So, formatting a DateInterval is pattern-driven. It is very * similar to formatting in SimpleDateFormat. * We introduce class DateIntervalInfo to save date interval * patterns, similar to date time pattern in SimpleDateFormat. * *

* Logically, the interval patterns are mappings * from (skeleton, the_largest_different_calendar_field) * to (date_interval_pattern). * *

* A skeleton *

    *
  1. * only keeps the field pattern letter and ignores all other parts * in a pattern, such as space, punctuations, and string literals. *
  2. * hides the order of fields. *
  3. * might hide a field's pattern letter length. * * For those non-digit calendar fields, the pattern letter length is * important, such as MMM, MMMM, and MMMMM; EEE and EEEE, * and the field's pattern letter length is honored. * * For the digit calendar fields, such as M or MM, d or dd, yy or yyyy, * the field pattern length is ignored and the best match, which is defined * in date time patterns, will be returned without honor the field pattern * letter length in skeleton. *
* *

* The calendar fields we support for interval formatting are: * year, month, date, day-of-week, am-pm, hour, hour-of-day, and minute. * Those calendar fields can be defined in the following order: * year > month > date > hour (in day) > minute * * The largest different calendar fields between 2 calendars is the * first different calendar field in above order. * * For example: the largest different calendar fields between "Jan 10, 2007" * and "Feb 20, 2008" is year. * *

* For other calendar fields, the compact interval formatting is not * supported. And the interval format will be fall back to fall-back * patterns, which is mostly "{date0} - {date1}". * *

* There is a set of pre-defined static skeleton strings in DateFormat, * There are pre-defined interval patterns for those pre-defined skeletons * in locales' resource files. * For example, for a skeleton YEAR_ABBR_MONTH_DAY, which is "yMMMd", * in en_US, if the largest different calendar field between date1 and date2 * is "year", the date interval pattern is "MMM d, yyyy - MMM d, yyyy", * such as "Jan 10, 2007 - Jan 10, 2008". * If the largest different calendar field between date1 and date2 is "month", * the date interval pattern is "MMM d - MMM d, yyyy", * such as "Jan 10 - Feb 10, 2007". * If the largest different calendar field between date1 and date2 is "day", * the date interval pattern is ""MMM d-d, yyyy", such as "Jan 10-20, 2007". * * For date skeleton, the interval patterns when year, or month, or date is * different are defined in resource files. * For time skeleton, the interval patterns when am/pm, or hour, or minute is * different are defined in resource files. * *

* If a skeleton is not found in a locale's DateIntervalInfo, which means * the interval patterns for the skeleton is not defined in resource file, * the interval pattern will falls back to the interval "fallback" pattern * defined in resource file. * If the interval "fallback" pattern is not defined, the default fall-back * is "{date0} - {data1}". * *

* For the combination of date and time, * The rule to genearte interval patterns are: *

    *
  1. * when the year, month, or day differs, falls back to fall-back * interval pattern, which mostly is the concatenate the two original * expressions with a separator between, * For example, interval pattern from "Jan 10, 2007 10:10 am" * to "Jan 11, 2007 10:10am" is * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am" *
  2. * otherwise, present the date followed by the range expression * for the time. * For example, interval pattern from "Jan 10, 2007 10:10 am" * to "Jan 10, 2007 11:10am" is "Jan 10, 2007 10:10 am - 11:10am" *
* * *

* If two dates are the same, the interval pattern is the single date pattern. * For example, interval pattern from "Jan 10, 2007" to "Jan 10, 2007" is * "Jan 10, 2007". * * Or if the presenting fields between 2 dates have the exact same values, * the interval pattern is the single date pattern. * For example, if user only requests year and month, * the interval pattern from "Jan 10, 2007" to "Jan 20, 2007" is "Jan 2007". * *

* DateIntervalFormat needs the following information for correct * formatting: time zone, calendar type, pattern, date format symbols, * and date interval patterns. * It can be instantiated in several ways: *

    *
  1. * create an instance using default or given locale plus given skeleton. * Users are encouraged to created date interval formatter this way and * to use the pre-defined skeleton macros, such as * YEAR_NUM_MONTH, which consists the calendar fields and * the format style. *
  2. *
  3. * create an instance using default or given locale plus given skeleton * plus a given DateIntervalInfo. * This factory method is for powerful users who want to provide their own * interval patterns. * Locale provides the timezone, calendar, and format symbols information. * Local plus skeleton provides full pattern information. * DateIntervalInfo provides the date interval patterns. *
  4. *
* *

* For the calendar field pattern letter, such as G, y, M, d, a, h, H, m, s etc. * DateIntervalFormat uses the same syntax as that of * DateTime format. * *

* Code Sample: general usage *

 *
 *   // the date interval object which the DateIntervalFormat formats on
 *   // and parses into
 *   DateInterval dtInterval = new DateInterval(1000*3600*24L, 1000*3600*24*2L);
 *   DateIntervalFormat dtIntervalFmt = DateIntervalFormat.getInstance(
 *                   YEAR_MONTH_DAY, Locale("en", "GB", ""));
 *   StringBuffer str = new StringBuffer("");
 *   FieldPosition pos = new FieldPosition(0);
 *   // formatting
 *   dtIntervalFmt.format(dtInterval, dateIntervalString, pos);
 *
 * 
* *

* Code Sample: for powerful users who wants to use their own interval pattern *

 *
 *     import com.ibm.icu.text.DateIntervalInfo;
 *     import com.ibm.icu.text.DateIntervalFormat;
 *     ....................
 *     
 *     // Get DateIntervalFormat instance using default locale
 *     DateIntervalFormat dtitvfmt = DateIntervalFormat.getInstance(YEAR_MONTH_DAY);
 *     
 *     // Create an empty DateIntervalInfo object, which does not have any interval patterns inside.
 *     dtitvinf = new DateIntervalInfo();
 *     
 *     // a series of set interval patterns.
 *     // Only ERA, YEAR, MONTH, DATE,  DAY_OF_MONTH, DAY_OF_WEEK, AM_PM,  HOUR, HOUR_OF_DAY, and MINUTE  are supported.
 *     dtitvinf.setIntervalPattern("yMMMd", Calendar.YEAR, "'y ~ y'"); 
 *     dtitvinf.setIntervalPattern("yMMMd", Calendar.MONTH, "yyyy 'diff' MMM d - MMM d");
 *     dtitvinf.setIntervalPattern("yMMMd", Calendar.DATE, "yyyy MMM d ~ d");
 *     dtitvinf.setIntervalPattern("yMMMd", Calendar.HOUR_OF_DAY, "yyyy MMM d HH:mm ~ HH:mm");
 *     
 *     // Set fallback interval pattern. Fallback pattern is used when interval pattern is not found.
 *     // If the fall-back pattern is not set,  falls back to {date0} - {date1} if interval pattern is not found.
 *     dtitvinf.setFallbackIntervalPattern("{0} - {1}");
 *     
 *     // Set above DateIntervalInfo object as the interval patterns of date interval formatter
 *     dtitvfmt.setDateIntervalInfo(dtitvinf);
 *     
 *     // Prepare to format
 *     pos = new FieldPosition(0);
 *     str = new StringBuffer("");
 *     
 *     // The 2 calendars should be equivalent, otherwise,  IllegalArgumentException will be thrown by format()
 *     Calendar fromCalendar = (Calendar) dtfmt.getCalendar().clone();
 *     Calendar toCalendar = (Calendar) dtfmt.getCalendar().clone();
 *     fromCalendar.setTimeInMillis(....);
 *     toCalendar.setTimeInMillis(...);
 *     
 *     //Formatting given 2 calendars
 *     dtitvfmt.format(fromCalendar, toCalendar, str, pos);
 * 
 *
 * 
* @stable ICU 4.0 */ public class DateIntervalFormat extends UFormat { private static final long serialVersionUID = 1; /** * Used to save the information for a skeleton's best match skeleton. * It is package accessible since it is used in DateIntervalInfo too. */ static final class BestMatchInfo { // the best match skeleton final String bestMatchSkeleton; // 0 means the best matched skeleton is the same as input skeleton // 1 means the fields are the same, but field width are different // 2 means the only difference between fields are v/z, // -1 means there are other fields difference final int bestMatchDistanceInfo; BestMatchInfo(String bestSkeleton, int difference) { bestMatchSkeleton = bestSkeleton; bestMatchDistanceInfo = difference; } } /* * Used to save the information on a skeleton and its best match. */ private static final class SkeletonAndItsBestMatch { final String skeleton; final String bestMatchSkeleton; SkeletonAndItsBestMatch(String skeleton, String bestMatch) { this.skeleton = skeleton; bestMatchSkeleton = bestMatch; } } // Cache for the locale interval pattern private static ICUCache> LOCAL_PATTERN_CACHE = new SimpleCache>(); /* * The interval patterns for this locale. */ private DateIntervalInfo fInfo; /* * The DateFormat object used to format single pattern */ private SimpleDateFormat fDateFormat; /* * The 2 calendars with the from and to date. * could re-use the calendar in fDateFormat, * but keeping 2 calendars make it clear and clean. */ private Calendar fFromCalendar; private Calendar fToCalendar; /* * Following are transient interval information * relavent (locale) to this formatter. */ private String fSkeleton = null; private transient Map fIntervalPatterns = null; /* * default constructor */ private DateIntervalFormat() { } /* * Construct a DateIntervalFormat from DateFormat, * a DateIntervalInfo, and skeleton. * DateFormat provides the timezone, calendar, * full pattern, and date format symbols information. * It should be a SimpleDateFormat object which * has a pattern in it. * the DateIntervalInfo provides the interval patterns. * * @param locale the locale of this date interval formatter. * @param dtitvinf the DateIntervalInfo object to be adopted. * @param skeleton the skeleton of the date formatter */ private DateIntervalFormat(ULocale locale, DateIntervalInfo dtItvInfo, String skeleton) { // freeze date interval info dtItvInfo.freeze(); fSkeleton = skeleton; fInfo = dtItvInfo; DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale); final String bestPattern = generator.getBestPattern(skeleton); fDateFormat = new SimpleDateFormat(bestPattern, locale); fFromCalendar = (Calendar) fDateFormat.getCalendar().clone(); fToCalendar = (Calendar) fDateFormat.getCalendar().clone(); initializePattern(); } /** * Construct a DateIntervalFormat from skeleton and the default locale. * * This is a convenient override of * getInstance(String skeleton, ULocale locale) * with the value of locale as default locale. * * @param skeleton the skeleton on which interval format based. * @return a date time interval formatter. * @stable ICU 4.0 */ public static final DateIntervalFormat getInstance(String skeleton) { return getInstance(skeleton, ULocale.getDefault()); } /** * Construct a DateIntervalFormat from skeleton and a given locale. * * This is a convenient override of * getInstance(String skeleton, ULocale locale) * * @param skeleton the skeleton on which interval format based. * @param locale the given locale * @return a date time interval formatter. * @stable ICU 4.0 */ public static final DateIntervalFormat getInstance(String skeleton, Locale locale) { return getInstance(skeleton, ULocale.forLocale(locale)); } /** * Construct a DateIntervalFormat from skeleton and a given locale. *

* In this factory method, * the date interval pattern information is load from resource files. * Users are encouraged to created date interval formatter this way and * to use the pre-defined skeleton macros. * *

* There are pre-defined skeletons in DateFormat, * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc. * * Those skeletons have pre-defined interval patterns in resource files. * Users are encouraged to use them. * For example: * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc); * * The given Locale provides the interval patterns. * For example, for en_GB, if skeleton is YEAR_ABBR_MONTH_WEEKDAY_DAY, * which is "yMMMEEEd", * the interval patterns defined in resource file to above skeleton are: * "EEE, d MMM, yyyy - EEE, d MMM, yyyy" for year differs, * "EEE, d MMM - EEE, d MMM, yyyy" for month differs, * "EEE, d - EEE, d MMM, yyyy" for day differs, * @param skeleton the skeleton on which interval format based. * @param locale the given locale * @return a date time interval formatter. * @stable ICU 4.0 */ public static final DateIntervalFormat getInstance(String skeleton, ULocale locale) { DateIntervalInfo dtitvinf = new DateIntervalInfo(locale); return new DateIntervalFormat(locale, dtitvinf, skeleton); } /** * Construct a DateIntervalFormat from skeleton * DateIntervalInfo, and default locale. * * This is a convenient override of * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf) * with the locale value as default locale. * * @param skeleton the skeleton on which interval format based. * @param dtitvinf the DateIntervalInfo object to be adopted. * @return a date time interval formatter. * @stable ICU 4.0 */ public static final DateIntervalFormat getInstance(String skeleton, DateIntervalInfo dtitvinf) { return getInstance(skeleton, ULocale.getDefault(), dtitvinf); } /** * Construct a DateIntervalFormat from skeleton * a DateIntervalInfo, and the given locale. * * This is a convenient override of * getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf) * * @param skeleton the skeleton on which interval format based. * @param locale the given locale * @param dtitvinf the DateIntervalInfo object to be adopted. * @return a date time interval formatter. * @stable ICU 4.0 */ public static final DateIntervalFormat getInstance(String skeleton, Locale locale, DateIntervalInfo dtitvinf) { return getInstance(skeleton, ULocale.forLocale(locale), dtitvinf); } /** * Construct a DateIntervalFormat from skeleton * a DateIntervalInfo, and the given locale. * *

* In this factory method, user provides its own date interval pattern * information, instead of using those pre-defined data in resource file. * This factory method is for powerful users who want to provide their own * interval patterns. * *

* There are pre-defined skeleton in DateFormat, * such as MONTH_DAY, YEAR_MONTH_WEEKDAY_DAY etc. * * Those skeletons have pre-defined interval patterns in resource files. * Users are encouraged to use them. * For example: * DateIntervalFormat.getInstance(DateFormat.MONTH_DAY, false, loc,itvinf); * * the DateIntervalInfo provides the interval patterns. * * User are encouraged to set default interval pattern in DateIntervalInfo * as well, if they want to set other interval patterns ( instead of * reading the interval patterns from resource files). * When the corresponding interval pattern for a largest calendar different * field is not found ( if user not set it ), interval format fallback to * the default interval pattern. * If user does not provide default interval pattern, it fallback to * "{date0} - {date1}" * * @param skeleton the skeleton on which interval format based. * @param locale the given locale * @param dtitvinf the DateIntervalInfo object to be adopted. * @return a date time interval formatter. * @stable ICU 4.0 */ public static final DateIntervalFormat getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf) { LOCAL_PATTERN_CACHE.clear(); // clone. If it is frozen, clone returns itself, otherwise, clone // returns a copy. dtitvinf = (DateIntervalInfo)dtitvinf.clone(); return new DateIntervalFormat(locale, dtitvinf, skeleton); } /** * Clone this Format object polymorphically. * @return A copy of the object. * @stable ICU 4.0 */ public Object clone() { DateIntervalFormat other = (DateIntervalFormat) super.clone(); other.fDateFormat = (SimpleDateFormat) fDateFormat.clone(); other.fInfo = (DateIntervalInfo) fInfo.clone(); other.fFromCalendar = (Calendar) fFromCalendar.clone(); other.fToCalendar = (Calendar) fToCalendar.clone(); other.fSkeleton = fSkeleton; other.fIntervalPatterns = fIntervalPatterns; return other; } /** * Format an object to produce a string. This method handles Formattable * objects with a DateInterval type. * If a the Formattable object type is not a DateInterval, * IllegalArgumentException is thrown. * * @param obj The object to format. * Must be a DateInterval. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param fieldPosition On input: an alignment field, if desired. * On output: the offsets of the alignment field. * @return Reference to 'appendTo' parameter. * @throws IllegalArgumentException if the formatted object is not * DateInterval object * @stable ICU 4.0 */ public final StringBuffer format(Object obj, StringBuffer appendTo, FieldPosition fieldPosition) { if ( obj instanceof DateInterval ) { return format( (DateInterval)obj, appendTo, fieldPosition); } else { throw new IllegalArgumentException("Cannot format given Object (" + obj.getClass().getName() + ") as a DateInterval"); } } /** * Format a DateInterval to produce a string. * * @param dtInterval DateInterval to be formatted. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param fieldPosition On input: an alignment field, if desired. * On output: the offsets of the alignment field. * @return Reference to 'appendTo' parameter. * @stable ICU 4.0 */ public final StringBuffer format(DateInterval dtInterval, StringBuffer appendTo, FieldPosition fieldPosition) { fFromCalendar.setTimeInMillis(dtInterval.getFromDate()); fToCalendar.setTimeInMillis(dtInterval.getToDate()); return format(fFromCalendar, fToCalendar, appendTo, fieldPosition); } /** * Format 2 Calendars to produce a string. * * @param fromCalendar calendar set to the from date in date interval * to be formatted into date interval string * @param toCalendar calendar set to the to date in date interval * to be formatted into date interval string * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param pos On input: an alignment field, if desired. * On output: the offsets of the alignment field. * @return Reference to 'appendTo' parameter. * @throws IllegalArgumentException if the two calendars are not equivalent. * @stable ICU 4.0 */ public final StringBuffer format(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos) { // not support different calendar types and time zones if ( !fromCalendar.isEquivalentTo(toCalendar) ) { throw new IllegalArgumentException("can not format on two different calendars"); } // First, find the largest different calendar field. int field = -1; //init with an invalid value. if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) { field = Calendar.ERA; } else if ( fromCalendar.get(Calendar.YEAR) != toCalendar.get(Calendar.YEAR) ) { field = Calendar.YEAR; } else if ( fromCalendar.get(Calendar.MONTH) != toCalendar.get(Calendar.MONTH) ) { field = Calendar.MONTH; } else if ( fromCalendar.get(Calendar.DATE) != toCalendar.get(Calendar.DATE) ) { field = Calendar.DATE; } else if ( fromCalendar.get(Calendar.AM_PM) != toCalendar.get(Calendar.AM_PM) ) { field = Calendar.AM_PM; } else if ( fromCalendar.get(Calendar.HOUR) != toCalendar.get(Calendar.HOUR) ) { field = Calendar.HOUR; } else if ( fromCalendar.get(Calendar.MINUTE) != toCalendar.get(Calendar.MINUTE) ) { field = Calendar.MINUTE; } else { /* ignore the second/millisecond etc. small fields' difference. * use single date when all the above are the same. */ return fDateFormat.format(fromCalendar, appendTo, pos); } // get interval pattern PatternInfo intervalPattern = fIntervalPatterns.get( DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]); if ( intervalPattern == null ) { if ( fDateFormat.isFieldUnitIgnored(field) ) { /* the largest different calendar field is small than * the smallest calendar field in pattern, * return single date format. */ return fDateFormat.format(fromCalendar, appendTo, pos); } return fallbackFormat(fromCalendar, toCalendar, appendTo, pos); } // If the first part in interval pattern is empty, // the 2nd part of it saves the full-pattern used in fall-back. // For a 'real' interval pattern, the first part will never be empty. if ( intervalPattern.getFirstPart() == null ) { // fall back return fallbackFormat(fromCalendar, toCalendar, appendTo, pos, intervalPattern.getSecondPart()); } Calendar firstCal; Calendar secondCal; if ( intervalPattern.firstDateInPtnIsLaterDate() ) { firstCal = toCalendar; secondCal = fromCalendar; } else { firstCal = fromCalendar; secondCal = toCalendar; } // break the interval pattern into 2 parts // first part should not be empty, String originalPattern = fDateFormat.toPattern(); fDateFormat.applyPattern(intervalPattern.getFirstPart()); fDateFormat.format(firstCal, appendTo, pos); if ( intervalPattern.getSecondPart() != null ) { fDateFormat.applyPattern(intervalPattern.getSecondPart()); fDateFormat.format(secondCal, appendTo, pos); } fDateFormat.applyPattern(originalPattern); return appendTo; } /* * Format 2 Calendars to using fall-back interval pattern * * The full pattern used in this fall-back format is the * full pattern of the date formatter. * * @param fromCalendar calendar set to the from date in date interval * to be formatted into date interval string * @param toCalendar calendar set to the to date in date interval * to be formatted into date interval string * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param pos On input: an alignment field, if desired. * On output: the offsets of the alignment field. * @return Reference to 'appendTo' parameter. */ private final StringBuffer fallbackFormat(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos) { // the fall back StringBuffer earlierDate = new StringBuffer(64); earlierDate = fDateFormat.format(fromCalendar, earlierDate, pos); StringBuffer laterDate = new StringBuffer(64); laterDate = fDateFormat.format(toCalendar, laterDate, pos); String fallbackPattern = fInfo.getFallbackIntervalPattern(); String fallback = MessageFormat.format(fallbackPattern, new Object[] {earlierDate.toString(), laterDate.toString()}); appendTo.append(fallback); return appendTo; } /* * Format 2 Calendars to using fall-back interval pattern * * This fall-back pattern is generated on a given full pattern, * not the full pattern of the date formatter. * * @param fromCalendar calendar set to the from date in date interval * to be formatted into date interval string * @param toCalendar calendar set to the to date in date interval * to be formatted into date interval string * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param pos On input: an alignment field, if desired. * On output: the offsets of the alignment field. * @param fullPattern the full pattern need to apply to date formatter * @return Reference to 'appendTo' parameter. */ private final StringBuffer fallbackFormat(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos, String fullPattern) { String originalPattern = fDateFormat.toPattern(); fDateFormat.applyPattern(fullPattern); fallbackFormat(fromCalendar, toCalendar, appendTo, pos); fDateFormat.applyPattern(originalPattern); return appendTo; } /** * Date interval parsing is not supported. *

* This method should handle parsing of * date time interval strings into Formattable objects with * DateInterval type, which is a pair of UDate. *

*

* Before calling, set parse_pos.index to the offset you want to start * parsing at in the source. After calling, parse_pos.index is the end of * the text you parsed. If error occurs, index is unchanged. *

* When parsing, leading whitespace is discarded (with a successful parse), * while trailing whitespace is left as is. *

* See Format.parseObject() for more. * * @param source The string to be parsed into an object. * @param parse_pos The position to start parsing at. Since no parsing * is supported, upon return this param is unchanged. * @return A newly created Formattable* object, or NULL * on failure. * @internal * @deprecated This API is ICU internal only. */ public Object parseObject(String source, ParsePosition parse_pos) { throw new UnsupportedOperationException("parsing is not supported"); } /** * Gets the date time interval patterns. * @return a copy of the date time interval patterns associated with * this date interval formatter. * @stable ICU 4.0 */ public DateIntervalInfo getDateIntervalInfo() { return (DateIntervalInfo)fInfo.clone(); } /** * Set the date time interval patterns. * @param newItvPattern the given interval patterns to copy. * @stable ICU 4.0 */ public void setDateIntervalInfo(DateIntervalInfo newItvPattern) { // clone it. If it is frozen, the clone returns itself. // Otherwise, clone returns a copy fInfo = (DateIntervalInfo)newItvPattern.clone(); fInfo.freeze(); // freeze it LOCAL_PATTERN_CACHE.clear(); if ( fDateFormat != null ) { initializePattern(); } } /** * Gets the date formatter * @return a copy of the date formatter associated with * this date interval formatter. * @stable ICU 4.0 */ public DateFormat getDateFormat() { return (DateFormat)fDateFormat.clone(); } /* * Below are for generating interval patterns locale to the formatter */ /* * Initialize interval patterns locale to this formatter. */ private void initializePattern() { String fullPattern = fDateFormat.toPattern(); ULocale locale = fDateFormat.getLocale(); String key; if ( fSkeleton != null ) { key = locale.toString() + "+" + fullPattern + "+" + fSkeleton; } else { key = locale.toString() + "+" + fullPattern; } Map patterns = LOCAL_PATTERN_CACHE.get(key); if ( patterns == null ) { Map intervalPatterns = initializeIntervalPattern(fullPattern, locale); patterns = Collections.unmodifiableMap(intervalPatterns); LOCAL_PATTERN_CACHE.put(key, patterns); } fIntervalPatterns = patterns; } /* * Initialize interval patterns locale to this formatter * * This code is a bit complicated since * 1. the interval patterns saved in resource bundle files are interval * patterns based on date or time only. * It does not have interval patterns based on both date and time. * Interval patterns on both date and time are algorithm generated. * * For example, it has interval patterns on skeleton "dMy" and "hm", * but it does not have interval patterns on skeleton "dMyhm". * * The rule to generate interval patterns for both date and time skeleton are * 1) when the year, month, or day differs, concatenate the two original * expressions with a separator between, * For example, interval pattern from "Jan 10, 2007 10:10 am" * to "Jan 11, 2007 10:10am" is * "Jan 10, 2007 10:10 am - Jan 11, 2007 10:10am" * * 2) otherwise, present the date followed by the range expression * for the time. * For example, interval pattern from "Jan 10, 2007 10:10 am" * to "Jan 10, 2007 11:10am" is * "Jan 10, 2007 10:10 am - 11:10am" * * 2. even a pattern does not request a certain calendar field, * the interval pattern needs to include such field if such fields are * different between 2 dates. * For example, a pattern/skeleton is "hm", but the interval pattern * includes year, month, and date when year, month, and date differs. * * * @param fullPattern formatter's full pattern * @param locale the given locale. * @return interval patterns' hash map */ private Map initializeIntervalPattern(String fullPattern, ULocale locale) { DateTimePatternGenerator dtpng = DateTimePatternGenerator.getInstance(locale); if ( fSkeleton == null ) { // fSkeleton is already set by getDateIntervalInstance() // or by getInstance(String skeleton, .... ) fSkeleton = dtpng.getSkeleton(fullPattern); } String skeleton = fSkeleton; HashMap intervalPatterns = new HashMap(); /* Check whether the skeleton is a combination of date and time. * For the complication reason 1 explained above. */ StringBuilder date = new StringBuilder(skeleton.length()); StringBuilder normalizedDate = new StringBuilder(skeleton.length()); StringBuilder time = new StringBuilder(skeleton.length()); StringBuilder normalizedTime = new StringBuilder(skeleton.length()); /* the difference between time skeleton and normalizedTimeSkeleton are: * 1. (Formerly, normalized time skeleton folded 'H' to 'h'; no longer true) * 2. 'a' is omitted in normalized time skeleton. * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized * time skeleton * * The difference between date skeleton and normalizedDateSkeleton are: * 1. both 'y' and 'd' appear only once in normalizeDateSkeleton * 2. 'E' and 'EE' are normalized into 'EEE' * 3. 'MM' is normalized into 'M' */ getDateTimeSkeleton(skeleton, date, normalizedDate, time, normalizedTime); String dateSkeleton = date.toString(); String timeSkeleton = time.toString(); String normalizedDateSkeleton = normalizedDate.toString(); String normalizedTimeSkeleton = normalizedTime.toString(); boolean found = genSeparateDateTimePtn(normalizedDateSkeleton, normalizedTimeSkeleton, intervalPatterns); if ( found == false ) { // use fallback // TODO: if user asks "m", but "d" differ //StringBuffer skeleton = new StringBuffer(skeleton); if ( time.length() != 0 ) { //genFallbackForNotFound(Calendar.MINUTE, skeleton); //genFallbackForNotFound(Calendar.HOUR, skeleton); //genFallbackForNotFound(Calendar.AM_PM, skeleton); if ( date.length() == 0 ) { // prefix with yMd timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton; String pattern =dtpng.getBestPattern(timeSkeleton); // for fall back interval patterns, // the first part of the pattern is empty, // the second part of the pattern is the full-pattern // should be used in fall-back. PatternInfo ptn = new PatternInfo(null, pattern, fInfo.getDefaultOrder()); intervalPatterns.put(DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn); // share interval pattern intervalPatterns.put(DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn); // share interval pattern intervalPatterns.put(DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn); } else { //genFallbackForNotFound(Calendar.DATE, skeleton); //genFallbackForNotFound(Calendar.MONTH, skeleton); //genFallbackForNotFound(Calendar.YEAR, skeleton); } } else { //genFallbackForNotFound(Calendar.DATE, skeleton); //genFallbackForNotFound(Calendar.MONTH, skeleton); //genFallbackForNotFound(Calendar.YEAR, skeleton); } return intervalPatterns; } // end of skeleton not found // interval patterns for skeleton are found in resource if ( time.length() == 0 ) { // done } else if ( date.length() == 0 ) { // need to set up patterns for y/M/d differ /* result from following looks confusing. * for example: 10 10:10 - 11 10:10, it is not * clear that the first 10 is the 10th day time.insert(0, 'd'); genFallbackPattern(Calendar.DATE, time); time.insert(0, 'M'); genFallbackPattern(Calendar.MONTH, time); time.insert(0, 'y'); genFallbackPattern(Calendar.YEAR, time); */ // prefix with yMd timeSkeleton = DateFormat.YEAR_NUM_MONTH_DAY + timeSkeleton; String pattern =dtpng.getBestPattern(timeSkeleton); // for fall back interval patterns, // the first part of the pattern is empty, // the second part of the pattern is the full-pattern // should be used in fall-back. PatternInfo ptn = new PatternInfo( null, pattern, fInfo.getDefaultOrder()); intervalPatterns.put(DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE], ptn); intervalPatterns.put(DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH], ptn); intervalPatterns.put(DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR], ptn); } else { /* if both present, * 1) when the year, month, or day differs, * concatenate the two original expressions with a separator between, * 2) otherwise, present the date followed by the * range expression for the time. */ /* * 1) when the year, month, or day differs, * concatenate the two original expressions with a separator between, */ // if field exists, use fall back if ( !fieldExistsInSkeleton(Calendar.DATE, dateSkeleton) ) { // prefix skeleton with 'd' skeleton = DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.DATE] + skeleton; genFallbackPattern(Calendar.DATE, skeleton, intervalPatterns, dtpng); } if ( !fieldExistsInSkeleton(Calendar.MONTH, dateSkeleton) ) { // then prefix skeleton with 'M' skeleton = DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.MONTH] + skeleton; genFallbackPattern(Calendar.MONTH, skeleton, intervalPatterns, dtpng); } if ( !fieldExistsInSkeleton(Calendar.YEAR, dateSkeleton) ) { // then prefix skeleton with 'y' skeleton = DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.YEAR] + skeleton; genFallbackPattern(Calendar.YEAR, skeleton, intervalPatterns, dtpng); } /* * 2) otherwise, present the date followed by the * range expression for the time. */ // Need the Date/Time pattern for concatnation the date with // the time interval. // The date/time pattern ( such as {0} {1} ) is saved in // calendar, that is why need to get the CalendarData here. CalendarData calData = new CalendarData(locale, null); String[] patterns = calData.getDateTimePatterns(); String datePattern =dtpng.getBestPattern(dateSkeleton); concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.AM_PM, intervalPatterns); concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.HOUR, intervalPatterns); concatSingleDate2TimeInterval(patterns[8], datePattern, Calendar.MINUTE, intervalPatterns); } return intervalPatterns; } /* * Generate fall back interval pattern given a calendar field, * a skeleton, and a date time pattern generator * @param field the largest different calendar field * @param skeleton a skeleton * @param dtpng date time pattern generator * @param intervalPatterns interval patterns */ private void genFallbackPattern(int field, String skeleton, Map intervalPatterns, DateTimePatternGenerator dtpng) { String pattern = dtpng.getBestPattern(skeleton); // for fall back interval patterns, // the first part of the pattern is empty, // the second part of the pattern is the full-pattern // should be used in fall-back. PatternInfo ptn = new PatternInfo( null, pattern, fInfo.getDefaultOrder()); intervalPatterns.put( DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptn); } /* private void genFallbackForNotFound(String field, StringBuffer skeleton) { if ( SimpleDateFormat.isFieldUnitIgnored(skeleton.toString(), field) ) { // single date DateIntervalInfo.PatternInfo ptnInfo = new DateIntervalInfo.PatternInfo(null, fDateFormat.toPattern(), fInfo.getDefaultOrder()); fIntervalPatterns.put(field, ptnInfo); return; } else if ( skeleton.indexOf(field) == -1 ) { skeleton.insert(0,field); genFallbackPattern(field, skeleton, dtpng); } } */ /* * get separated date and time skeleton from a combined skeleton. * * The difference between date skeleton and normalizedDateSkeleton are: * 1. both 'y' and 'd' are appeared only once in normalizeDateSkeleton * 2. 'E' and 'EE' are normalized into 'EEE' * 3. 'MM' is normalized into 'M' * ** the difference between time skeleton and normalizedTimeSkeleton are: * 1. both 'H' and 'h' are normalized as 'h' in normalized time skeleton, * 2. 'a' is omitted in normalized time skeleton. * 3. there is only one appearance for 'h', 'm','v', 'z' in normalized time * skeleton * * * @param skeleton given combined skeleton. * @param date Output parameter for date only skeleton. * @param normalizedDate Output parameter for normalized date only * * @param time Output parameter for time only skeleton. * @param normalizedTime Output parameter for normalized time only * skeleton. */ private static void getDateTimeSkeleton(String skeleton, StringBuilder dateSkeleton, StringBuilder normalizedDateSkeleton, StringBuilder timeSkeleton, StringBuilder normalizedTimeSkeleton) { // dateSkeleton follows the sequence of y*M*E*d* // timeSkeleton follows the sequence of hm*[v|z]? int i; int ECount = 0; int dCount = 0; int MCount = 0; int yCount = 0; int hCount = 0; int HCount = 0; int mCount = 0; int vCount = 0; int zCount = 0; for (i = 0; i < skeleton.length(); ++i) { char ch = skeleton.charAt(i); switch ( ch ) { case 'E': dateSkeleton.append(ch); ++ECount; break; case 'd': dateSkeleton.append(ch); ++dCount; break; case 'M': dateSkeleton.append(ch); ++MCount; break; case 'y': dateSkeleton.append(ch); ++yCount; break; case 'G': case 'Y': case 'u': case 'Q': case 'q': case 'L': case 'l': case 'W': case 'w': case 'D': case 'F': case 'g': case 'e': case 'c': normalizedDateSkeleton.append(ch); dateSkeleton.append(ch); break; case 'a': // 'a' is implicitly handled timeSkeleton.append(ch); break; case 'h': timeSkeleton.append(ch); ++hCount; break; case 'H': timeSkeleton.append(ch); ++HCount; break; case 'm': timeSkeleton.append(ch); ++mCount; break; case 'z': ++zCount; timeSkeleton.append(ch); break; case 'v': ++vCount; timeSkeleton.append(ch); break; case 'V': case 'Z': case 'k': case 'K': case 'j': case 's': case 'S': case 'A': timeSkeleton.append(ch); normalizedTimeSkeleton.append(ch); break; } } /* generate normalized form for date*/ if ( yCount != 0 ) { normalizedDateSkeleton.append('y'); } if ( MCount != 0 ) { if ( MCount < 3 ) { normalizedDateSkeleton.append('M'); } else { for ( i = 0; i < MCount && i < 5; ++i ) { normalizedDateSkeleton.append('M'); } } } if ( ECount != 0 ) { if ( ECount <= 3 ) { normalizedDateSkeleton.append('E'); } else { for ( i = 0; i < ECount && i < 5; ++i ) { normalizedDateSkeleton.append('E'); } } } if ( dCount != 0 ) { normalizedDateSkeleton.append('d'); } /* generate normalized form for time */ if ( HCount != 0 ) { normalizedTimeSkeleton.append('H'); } else if ( hCount != 0 ) { normalizedTimeSkeleton.append('h'); } if ( mCount != 0 ) { normalizedTimeSkeleton.append('m'); } if ( zCount != 0 ) { normalizedTimeSkeleton.append('z'); } if ( vCount != 0 ) { normalizedTimeSkeleton.append('v'); } } /* * Generate date or time interval pattern from resource. * * It needs to handle the following: * 1. need to adjust field width. * For example, the interval patterns saved in DateIntervalInfo * includes "dMMMy", but not "dMMMMy". * Need to get interval patterns for dMMMMy from dMMMy. * Another example, the interval patterns saved in DateIntervalInfo * includes "hmv", but not "hmz". * Need to get interval patterns for "hmz' from 'hmv' * * 2. there might be no pattern for 'y' differ for skeleton "Md", * in order to get interval patterns for 'y' differ, * need to look for it from skeleton 'yMd' * * @param dateSkeleton normalized date skeleton * @param timeSkeleton normalized time skeleton * @param intervalPatterns interval patterns * @return whether there is interval patterns for the skeleton. * true if there is, false otherwise */ private boolean genSeparateDateTimePtn(String dateSkeleton, String timeSkeleton, Map intervalPatterns) { String skeleton; // if both date and time skeleton present, // the final interval pattern might include time interval patterns // ( when, am_pm, hour, minute differ ), // but not date interval patterns ( when year, month, day differ ). // For year/month/day differ, it falls back to fall-back pattern. if ( timeSkeleton.length() != 0 ) { skeleton = timeSkeleton; } else { skeleton = dateSkeleton; } /* interval patterns for skeleton "dMMMy" (but not "dMMMMy") * are defined in resource, * interval patterns for skeleton "dMMMMy" are calculated by * 1. get the best match skeleton for "dMMMMy", which is "dMMMy" * 2. get the interval patterns for "dMMMy", * 3. extend "MMM" to "MMMM" in above interval patterns for "dMMMMy" * getBestSkeleton() is step 1. */ // best skeleton, and the difference information BestMatchInfo retValue = fInfo.getBestSkeleton(skeleton); String bestSkeleton = retValue.bestMatchSkeleton; int differenceInfo = retValue.bestMatchDistanceInfo; // difference: // 0 means the best matched skeleton is the same as input skeleton // 1 means the fields are the same, but field width are different // 2 means the only difference between fields are v/z, // -1 means there are other fields difference if ( differenceInfo == -1 ) { // skeleton has different fields, not only v/z difference return false; } if ( timeSkeleton.length() == 0 ) { // only has date skeleton genIntervalPattern(Calendar.DATE, skeleton, bestSkeleton, differenceInfo, intervalPatterns); SkeletonAndItsBestMatch skeletons = genIntervalPattern( Calendar.MONTH, skeleton, bestSkeleton, differenceInfo, intervalPatterns); if ( skeletons != null ) { bestSkeleton = skeletons.skeleton; skeleton = skeletons.bestMatchSkeleton; } genIntervalPattern(Calendar.YEAR, skeleton, bestSkeleton, differenceInfo, intervalPatterns); } else { genIntervalPattern(Calendar.MINUTE, skeleton, bestSkeleton, differenceInfo, intervalPatterns); genIntervalPattern(Calendar.HOUR, skeleton, bestSkeleton, differenceInfo, intervalPatterns); genIntervalPattern(Calendar.AM_PM, skeleton, bestSkeleton, differenceInfo, intervalPatterns); } return true; } /* * Generate interval pattern from existing resource * * It not only save the interval patterns, * but also return the skeleton and its best match skeleton. * * @param field largest different calendar field * @param skeleton skeleton * @param bestSkeleton the best match skeleton which has interval pattern * defined in resource * @param differenceInfo the difference between skeleton and best skeleton * 0 means the best matched skeleton is the same as input skeleton * 1 means the fields are the same, but field width are different * 2 means the only difference between fields are v/z, * -1 means there are other fields difference * * @param intervalPatterns interval patterns * * @return an extended skeleton or extended best skeleton if applicable. * null otherwise. */ private SkeletonAndItsBestMatch genIntervalPattern( int field, String skeleton, String bestSkeleton, int differenceInfo, Map intervalPatterns) { SkeletonAndItsBestMatch retValue = null; PatternInfo pattern = fInfo.getIntervalPattern( bestSkeleton, field); if ( pattern == null ) { // single date if ( SimpleDateFormat.isFieldUnitIgnored(bestSkeleton, field) ) { PatternInfo ptnInfo = new PatternInfo(fDateFormat.toPattern(), null, fInfo.getDefaultOrder()); intervalPatterns.put(DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[field], ptnInfo); return null; } // for 24 hour system, interval patterns in resource file // might not include pattern when am_pm differ, // which should be the same as hour differ. // add it here for simplicity if ( field == Calendar.AM_PM ) { pattern = fInfo.getIntervalPattern(bestSkeleton, Calendar.HOUR); if ( pattern != null ) { // share intervalPatterns.put(DateIntervalInfo. CALENDAR_FIELD_TO_PATTERN_LETTER[field], pattern); } return null; } // else, looking for pattern when 'y' differ for 'dMMMM' skeleton, // first, get best match pattern "MMMd", // since there is no pattern for 'y' differs for skeleton 'MMMd', // need to look for it from skeleton 'yMMMd', // if found, adjust field width in interval pattern from // "MMM" to "MMMM". String fieldLetter = DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]; bestSkeleton = fieldLetter + bestSkeleton; skeleton = fieldLetter + skeleton; // for example, looking for patterns when 'y' differ for // skeleton "MMMM". pattern = fInfo.getIntervalPattern(bestSkeleton, field); if ( pattern == null && differenceInfo == 0 ) { // if there is no skeleton "yMMMM" defined, // look for the best match skeleton, for example: "yMMM" BestMatchInfo tmpRetValue = fInfo.getBestSkeleton(skeleton); String tmpBestSkeleton = tmpRetValue.bestMatchSkeleton; differenceInfo = tmpRetValue.bestMatchDistanceInfo; if ( tmpBestSkeleton.length() != 0 && differenceInfo != -1 ) { pattern = fInfo.getIntervalPattern(tmpBestSkeleton, field); bestSkeleton = tmpBestSkeleton; } } if ( pattern != null ) { retValue = new SkeletonAndItsBestMatch(skeleton, bestSkeleton); } } if ( pattern != null ) { if ( differenceInfo != 0 ) { String part1 = adjustFieldWidth(skeleton, bestSkeleton, pattern.getFirstPart(), differenceInfo); String part2 = adjustFieldWidth(skeleton, bestSkeleton, pattern.getSecondPart(), differenceInfo); pattern = new PatternInfo(part1, part2, pattern.firstDateInPtnIsLaterDate()); } else { // pattern is immutable, no need to clone; // pattern = (PatternInfo)pattern.clone(); } intervalPatterns.put( DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], pattern); } return retValue; } /* * Adjust field width in best match interval pattern to match * the field width in input skeleton. * * TODO (xji) make a general solution * The adjusting rule can be: * 1. always adjust * 2. never adjust * 3. default adjust, which means adjust according to the following rules * 3.1 always adjust string, such as MMM and MMMM * 3.2 never adjust between string and numeric, such as MM and MMM * 3.3 always adjust year * 3.4 do not adjust 'd', 'h', or 'm' if h presents * 3.5 do not adjust 'M' if it is numeric(?) * * Since date interval format is well-formed format, * date and time skeletons are normalized previously, * till this stage, the adjust here is only "adjust strings, such as MMM * and MMMM, EEE and EEEE. * * @param inputSkeleton the input skeleton * @param bestMatchSkeleton the best match skeleton * @param bestMatchIntervalpattern the best match interval pattern * @param differenceInfo the difference between 2 skeletons * 1 means only field width differs * 2 means v/z exchange * @return the adjusted interval pattern */ private static String adjustFieldWidth(String inputSkeleton, String bestMatchSkeleton, String bestMatchIntervalPattern, int differenceInfo ) { if ( bestMatchIntervalPattern == null ) { return null; // the 2nd part could be null } int[] inputSkeletonFieldWidth = new int[58]; int[] bestMatchSkeletonFieldWidth = new int[58]; /* initialize as following { // A B C D E F G H I J K L M N O 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // P Q R S T U V W X Y Z 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a b c d e f g h i j k l m n o 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // p q r s t u v w x y z 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; */ DateIntervalInfo.parseSkeleton(inputSkeleton, inputSkeletonFieldWidth); DateIntervalInfo.parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth); if ( differenceInfo == 2 ) { bestMatchIntervalPattern = bestMatchIntervalPattern.replace('v', 'z'); } StringBuilder adjustedPtn = new StringBuilder(bestMatchIntervalPattern); boolean inQuote = false; char prevCh = 0; int count = 0; int PATTERN_CHAR_BASE = 0x41; // loop through the pattern string character by character int adjustedPtnLength = adjustedPtn.length(); for (int i = 0; i < adjustedPtnLength; ++i) { char ch = adjustedPtn.charAt(i); if (ch != prevCh && count > 0) { // check the repeativeness of pattern letter char skeletonChar = prevCh; if ( skeletonChar == 'L' ) { // for skeleton "M+", the pattern is "...L..." skeletonChar = 'M'; } int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE]; int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE]; if ( fieldCount == count && inputFieldCount > fieldCount ) { count = inputFieldCount - fieldCount; for ( int j = 0; j < count; ++j ) { adjustedPtn.insert(i, prevCh); } i += count; adjustedPtnLength += count; } count = 0; } if (ch == '\'') { // Consecutive single quotes are a single quote literal, // either outside of quotes or between quotes if ((i+1) < adjustedPtn.length() && adjustedPtn.charAt(i+1) == '\'') { ++i; } else { inQuote = ! inQuote; } } else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) { // ch is a date-time pattern character prevCh = ch; ++count; } } if ( count > 0 ) { // last item // check the repeativeness of pattern letter char skeletonChar = prevCh; if ( skeletonChar == 'L' ) { // for skeleton "M+", the pattern is "...L..." skeletonChar = 'M'; } int fieldCount = bestMatchSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE]; int inputFieldCount = inputSkeletonFieldWidth[skeletonChar - PATTERN_CHAR_BASE]; if ( fieldCount == count && inputFieldCount > fieldCount ) { count = inputFieldCount - fieldCount; for ( int j = 0; j < count; ++j ) { adjustedPtn.append(prevCh); } } } return adjustedPtn.toString(); } /* * Concat a single date pattern with a time interval pattern, * set it into the intervalPatterns, while field is time field. * This is used to handle time interval patterns on skeleton with * both time and date. Present the date followed by * the range expression for the time. * @param dtfmt date and time format * @param datePattern date pattern * @param field time calendar field: AM_PM, HOUR, MINUTE * @param intervalPatterns interval patterns */ private void concatSingleDate2TimeInterval(String dtfmt, String datePattern, int field, Map intervalPatterns) { PatternInfo timeItvPtnInfo = intervalPatterns.get(DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]); if ( timeItvPtnInfo != null ) { String timeIntervalPattern = timeItvPtnInfo.getFirstPart() + timeItvPtnInfo.getSecondPart(); String pattern = MessageFormat.format(dtfmt, new Object[] {timeIntervalPattern, datePattern}); timeItvPtnInfo = DateIntervalInfo.genPatternInfo(pattern, timeItvPtnInfo.firstDateInPtnIsLaterDate()); intervalPatterns.put( DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field], timeItvPtnInfo); } // else: fall back // it should not happen if the interval format defined is valid } /* * check whether a calendar field present in a skeleton. * @param field calendar field need to check * @param skeleton given skeleton on which to check the calendar field * @return true if field present in a skeleton. */ private static boolean fieldExistsInSkeleton(int field, String skeleton) { String fieldChar = DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]; return ( (skeleton.indexOf(fieldChar) == -1) ? false : true ) ; } /* * readObject. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); initializePattern(); } }