2323 lines
97 KiB
Java
2323 lines
97 KiB
Java
/* GENERATED SOURCE. DO NOT MODIFY. */
|
||
// © 2016 and later: Unicode, Inc. and others.
|
||
// License & terms of use: http://www.unicode.org/copyright.html
|
||
/*
|
||
* Copyright (C) 2008-2016, International Business Machines
|
||
* Corporation and others. All Rights Reserved.
|
||
*/
|
||
|
||
package android.icu.text;
|
||
|
||
import java.io.IOException;
|
||
import java.io.InvalidObjectException;
|
||
import java.io.ObjectInputStream;
|
||
import java.text.AttributedCharacterIterator;
|
||
import java.text.FieldPosition;
|
||
import java.text.ParsePosition;
|
||
import java.util.ArrayList;
|
||
import java.util.Collections;
|
||
import java.util.HashMap;
|
||
import java.util.List;
|
||
import java.util.Locale;
|
||
import java.util.Map;
|
||
|
||
import android.icu.impl.FormattedValueFieldPositionIteratorImpl;
|
||
import android.icu.impl.ICUCache;
|
||
import android.icu.impl.ICUData;
|
||
import android.icu.impl.ICUResourceBundle;
|
||
import android.icu.impl.SimpleCache;
|
||
import android.icu.impl.SimpleFormatterImpl;
|
||
import android.icu.impl.Utility;
|
||
import android.icu.text.DateIntervalInfo.PatternInfo;
|
||
import android.icu.util.Calendar;
|
||
import android.icu.util.DateInterval;
|
||
import android.icu.util.Output;
|
||
import android.icu.util.TimeZone;
|
||
import android.icu.util.ULocale;
|
||
import android.icu.util.ULocale.Category;
|
||
import android.icu.util.UResourceBundle;
|
||
|
||
|
||
/**
|
||
* DateIntervalFormat is a class for formatting and parsing date
|
||
* intervals in a language-independent manner.
|
||
* Only formatting is supported. Parsing is not supported.
|
||
*
|
||
* <P>
|
||
* 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.
|
||
*
|
||
* <P>
|
||
* 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.
|
||
*
|
||
* <P>
|
||
* 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.
|
||
*
|
||
* <P>
|
||
* Logically, the interval patterns are mappings
|
||
* from (skeleton, the_largest_different_calendar_field)
|
||
* to (date_interval_pattern).
|
||
*
|
||
* <P>
|
||
* A skeleton
|
||
* <ol>
|
||
* <li>
|
||
* only keeps the field pattern letter and ignores all other parts
|
||
* in a pattern, such as space, punctuations, and string literals.
|
||
* <li>
|
||
* hides the order of fields.
|
||
* <li>
|
||
* 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.
|
||
* </ol>
|
||
*
|
||
* <P>
|
||
* The calendar fields we support for interval formatting are:
|
||
* year, month, date, day-of-week, am-pm, hour, hour-of-day, minute, and
|
||
* second (though we do not currently have specific intervalFormat data for
|
||
* skeletons with seconds).
|
||
* Those calendar fields can be defined in the following order:
|
||
* year > month > date > hour (in day) > minute > second
|
||
*
|
||
* 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.
|
||
*
|
||
* <P>
|
||
* 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}".
|
||
*
|
||
* <P>
|
||
* 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.
|
||
*
|
||
* <P>
|
||
* 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}".
|
||
*
|
||
* <P>
|
||
* For the combination of date and time,
|
||
* The rule to genearte interval patterns are:
|
||
* <ol>
|
||
* <li>
|
||
* 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"
|
||
* <li>
|
||
* 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"
|
||
* </ol>
|
||
*
|
||
*
|
||
* <P>
|
||
* 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".
|
||
*
|
||
* <P>
|
||
* 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:
|
||
* <ol>
|
||
* <li>
|
||
* 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.
|
||
* </li>
|
||
* <li>
|
||
* 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.
|
||
* </li>
|
||
* </ol>
|
||
*
|
||
* <P>
|
||
* 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.
|
||
*
|
||
* <P>
|
||
* Code Sample: general usage
|
||
* <pre>
|
||
*
|
||
* // 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(
|
||
* DateFormat.YEAR_MONTH_DAY, new Locale("en", "GB", ""));
|
||
* StringBuffer result = new StringBuffer("");
|
||
* FieldPosition pos = new FieldPosition(-1);
|
||
* // formatting
|
||
* dtIntervalFmt.format(dtInterval, result, pos);
|
||
* assertEquals("interval", "1–2 January 1970", result.toString());
|
||
*
|
||
* </pre>
|
||
*
|
||
* <P>
|
||
* Code Sample: for powerful users who wants to use their own interval pattern
|
||
* <pre>
|
||
*
|
||
* import android.icu.text.DateIntervalInfo;
|
||
* import android.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,
|
||
* MINUTE, SECOND and MILLISECOND 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);
|
||
*
|
||
*
|
||
* </pre>
|
||
* <h3>Synchronization</h3>
|
||
*
|
||
* The format methods of DateIntervalFormat may be used concurrently from multiple threads.
|
||
* Functions that alter the state of a DateIntervalFormat object (setters)
|
||
* may not be used concurrently with any other functions.
|
||
*/
|
||
|
||
public class DateIntervalFormat extends UFormat {
|
||
|
||
/**
|
||
* An immutable class containing the result of a date interval formatting operation.
|
||
*
|
||
* Instances of this class are immutable and thread-safe.
|
||
*
|
||
* Not intended for public subclassing.
|
||
*/
|
||
public static final class FormattedDateInterval implements FormattedValue {
|
||
private final String string;
|
||
private final List<FieldPosition> attributes;
|
||
|
||
FormattedDateInterval(CharSequence cs, List<FieldPosition> attributes) {
|
||
this.string = cs.toString();
|
||
this.attributes = Collections.unmodifiableList(attributes);
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
@Override
|
||
public String toString() {
|
||
return string;
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
@Override
|
||
public int length() {
|
||
return string.length();
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
@Override
|
||
public char charAt(int index) {
|
||
return string.charAt(index);
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
@Override
|
||
public CharSequence subSequence(int start, int end) {
|
||
return string.subSequence(start, end);
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
@Override
|
||
public <A extends Appendable> A appendTo(A appendable) {
|
||
return Utility.appendTo(string, appendable);
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
@Override
|
||
public boolean nextPosition(ConstrainedFieldPosition cfpos) {
|
||
return FormattedValueFieldPositionIteratorImpl.nextPosition(attributes, cfpos);
|
||
}
|
||
|
||
/**
|
||
* {@inheritDoc}
|
||
*/
|
||
@Override
|
||
public AttributedCharacterIterator toCharacterIterator() {
|
||
return FormattedValueFieldPositionIteratorImpl.toCharacterIterator(string, attributes);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Class for span fields in FormattedDateInterval.
|
||
*
|
||
* @hide Only a subset of ICU is exposed in Android
|
||
*/
|
||
public static final class SpanField extends UFormat.SpanField {
|
||
private static final long serialVersionUID = -6330879259553618133L;
|
||
|
||
/**
|
||
* The concrete field used for spans in FormattedDateInterval.
|
||
*
|
||
* Instances of DATE_INTERVAL_SPAN should have an associated value. If
|
||
* 0, the date fields within the span are for the "from" date; if 1,
|
||
* the date fields within the span are for the "to" date.
|
||
*/
|
||
public static final SpanField DATE_INTERVAL_SPAN = new SpanField("date-interval-span");
|
||
|
||
private SpanField(String name) {
|
||
super(name);
|
||
}
|
||
|
||
/**
|
||
* serialization method resolve instances to the constant
|
||
* DateIntervalFormat.SpanField values
|
||
* @deprecated This API is ICU internal only.
|
||
* @hide draft / provisional / internal are hidden on Android
|
||
*/
|
||
@Deprecated
|
||
@Override
|
||
protected Object readResolve() throws InvalidObjectException {
|
||
if (this.getName().equals(DATE_INTERVAL_SPAN.getName()))
|
||
return DATE_INTERVAL_SPAN;
|
||
|
||
throw new InvalidObjectException("An invalid object.");
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
/** Used to output information during formatting. */
|
||
private static final class FormatOutput {
|
||
int firstIndex = -1;
|
||
|
||
public void register(int i) {
|
||
if (firstIndex == -1) {
|
||
firstIndex = i;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// Cache for the locale interval pattern
|
||
private static ICUCache<String, Map<String, PatternInfo>> LOCAL_PATTERN_CACHE =
|
||
new SimpleCache<>();
|
||
|
||
/*
|
||
* The interval patterns for this locale.
|
||
*/
|
||
private DateIntervalInfo fInfo;
|
||
|
||
/*
|
||
* The DateFormat object used to format single pattern.
|
||
* Because fDateFormat is modified during format operations, all
|
||
* access to it from logically const, thread safe functions must be synchronized.
|
||
*/
|
||
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.
|
||
* Because these Calendars are modified during format operations, all
|
||
* access to them from logically const, thread safe functions must be synchronized.
|
||
*/
|
||
private Calendar fFromCalendar;
|
||
private Calendar fToCalendar;
|
||
|
||
/*
|
||
* Following are transient interval information
|
||
* relevant (locale) to this formatter.
|
||
*/
|
||
private String fSkeleton = null;
|
||
|
||
/*
|
||
* Needed for efficient deserialization. If set, it means we can use the
|
||
* cache to initialize fIntervalPatterns.
|
||
*/
|
||
private boolean isDateIntervalInfoDefault;
|
||
|
||
/**
|
||
* Interval patterns for this instance's locale.
|
||
*/
|
||
private transient Map<String, PatternInfo> fIntervalPatterns = null;
|
||
|
||
/*
|
||
* Patterns for fallback formatting.
|
||
*/
|
||
private String fDatePattern = null;
|
||
private String fTimePattern = null;
|
||
private String fDateTimeFormat = null;
|
||
|
||
/*
|
||
* Capitalization context, new in ICU 68
|
||
*/
|
||
private DisplayContext fCapitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
|
||
|
||
/*
|
||
* default constructor; private because we don't want anyone to use
|
||
*/
|
||
@android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
|
||
@SuppressWarnings("unused")
|
||
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 skeleton the skeleton of the date formatter
|
||
* @param dtItvInfo the DateIntervalInfo object to be adopted.
|
||
* @param simpleDateFormat will be used for formatting
|
||
*
|
||
* @deprecated This API is ICU internal only.
|
||
* @hide original deprecated declaration
|
||
* @hide draft / provisional / internal are hidden on Android
|
||
*/
|
||
@Deprecated
|
||
public DateIntervalFormat(String skeleton, DateIntervalInfo dtItvInfo,
|
||
SimpleDateFormat simpleDateFormat)
|
||
{
|
||
fDateFormat = simpleDateFormat;
|
||
// freeze date interval info
|
||
dtItvInfo.freeze();
|
||
fSkeleton = skeleton;
|
||
fInfo = dtItvInfo;
|
||
isDateIntervalInfoDefault = false;
|
||
fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
|
||
fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
|
||
initializePattern(null);
|
||
}
|
||
|
||
private DateIntervalFormat(String skeleton, ULocale locale,
|
||
SimpleDateFormat simpleDateFormat)
|
||
{
|
||
fDateFormat = simpleDateFormat;
|
||
fSkeleton = skeleton;
|
||
fInfo = new DateIntervalInfo(locale).freeze();
|
||
isDateIntervalInfoDefault = true;
|
||
fFromCalendar = (Calendar) fDateFormat.getCalendar().clone();
|
||
fToCalendar = (Calendar) fDateFormat.getCalendar().clone();
|
||
initializePattern(LOCAL_PATTERN_CACHE);
|
||
}
|
||
|
||
|
||
/**
|
||
* Construct a DateIntervalFormat from skeleton and the default <code>FORMAT</code> locale.
|
||
*
|
||
* This is a convenient override of
|
||
* getInstance(String skeleton, ULocale locale)
|
||
* with the value of locale as default <code>FORMAT</code> locale.
|
||
*
|
||
* @param skeleton the skeleton on which interval format based.
|
||
* @return a date time interval formatter.
|
||
* @see Category#FORMAT
|
||
*/
|
||
public static final DateIntervalFormat
|
||
getInstance(String skeleton)
|
||
|
||
{
|
||
return getInstance(skeleton, ULocale.getDefault(Category.FORMAT));
|
||
}
|
||
|
||
|
||
/**
|
||
* Construct a DateIntervalFormat from skeleton and a given locale.
|
||
*
|
||
* This is a convenient override of
|
||
* getInstance(String skeleton, ULocale locale)
|
||
*
|
||
* <p>Example code:{@sample external/icu/android_icu4j/src/samples/java/android/icu/samples/text/dateintervalformat/DateIntervalFormatSample.java dtitvfmtPreDefinedExample}
|
||
* @param skeleton the skeleton on which interval format based.
|
||
* @param locale the given locale
|
||
* @return a date time interval formatter.
|
||
*/
|
||
public static final DateIntervalFormat
|
||
getInstance(String skeleton, Locale locale)
|
||
{
|
||
return getInstance(skeleton, ULocale.forLocale(locale));
|
||
}
|
||
|
||
|
||
/**
|
||
* Construct a DateIntervalFormat from skeleton and a given locale.
|
||
* <P>
|
||
* 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.
|
||
*
|
||
* <P>
|
||
* 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.
|
||
*/
|
||
public static final DateIntervalFormat
|
||
getInstance(String skeleton, ULocale locale)
|
||
{
|
||
DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
|
||
return new DateIntervalFormat(skeleton, locale, new SimpleDateFormat(generator.getBestPattern(skeleton), locale));
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Construct a DateIntervalFormat from skeleton
|
||
* DateIntervalInfo, and the default <code>FORMAT</code> locale.
|
||
*
|
||
* This is a convenient override of
|
||
* getInstance(String skeleton, ULocale locale, DateIntervalInfo dtitvinf)
|
||
* with the locale value as default <code>FORMAT</code> locale.
|
||
*
|
||
* @param skeleton the skeleton on which interval format based.
|
||
* @param dtitvinf the DateIntervalInfo object to be adopted.
|
||
* @return a date time interval formatter.
|
||
* @see Category#FORMAT
|
||
*/
|
||
public static final DateIntervalFormat getInstance(String skeleton,
|
||
DateIntervalInfo dtitvinf)
|
||
{
|
||
return getInstance(skeleton, ULocale.getDefault(Category.FORMAT), 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)
|
||
*
|
||
* <p>Example code:{@sample external/icu/android_icu4j/src/samples/java/android/icu/samples/text/dateintervalformat/DateIntervalFormatSample.java dtitvfmtCustomizedExample}
|
||
* @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.
|
||
*/
|
||
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.
|
||
*
|
||
* <P>
|
||
* 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.
|
||
*
|
||
* <P>
|
||
* 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.
|
||
*/
|
||
public static final DateIntervalFormat getInstance(String skeleton,
|
||
ULocale locale,
|
||
DateIntervalInfo dtitvinf)
|
||
{
|
||
// clone. If it is frozen, clone returns itself, otherwise, clone
|
||
// returns a copy.
|
||
dtitvinf = (DateIntervalInfo)dtitvinf.clone();
|
||
DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(locale);
|
||
return new DateIntervalFormat(skeleton, dtitvinf, new SimpleDateFormat(generator.getBestPattern(skeleton), locale));
|
||
}
|
||
|
||
|
||
/**
|
||
* Clone this Format object polymorphically.
|
||
* @return A copy of the object.
|
||
*/
|
||
@Override
|
||
public synchronized 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.fDatePattern = fDatePattern;
|
||
other.fTimePattern = fTimePattern;
|
||
other.fDateTimeFormat = fDateTimeFormat;
|
||
other.fCapitalizationSetting = fCapitalizationSetting;
|
||
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.
|
||
* There may be multiple instances of a given field type
|
||
* in an interval format; in this case the fieldPosition
|
||
* offsets refer to the first instance.
|
||
* @return Reference to 'appendTo' parameter.
|
||
* @throws IllegalArgumentException if the formatted object is not
|
||
* DateInterval object
|
||
*/
|
||
@Override
|
||
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.
|
||
* There may be multiple instances of a given field type
|
||
* in an interval format; in this case the fieldPosition
|
||
* offsets refer to the first instance.
|
||
* @return Reference to 'appendTo' parameter.
|
||
*/
|
||
public final StringBuffer format(DateInterval dtInterval,
|
||
StringBuffer appendTo,
|
||
FieldPosition fieldPosition) {
|
||
return formatIntervalImpl(dtInterval, appendTo, fieldPosition, null, null);
|
||
}
|
||
|
||
/**
|
||
* Format a DateInterval to produce a FormattedDateInterval.
|
||
*
|
||
* The FormattedDateInterval exposes field information about the formatted string.
|
||
*
|
||
* @param dtInterval DateInterval to be formatted.
|
||
* @return A FormattedDateInterval containing the format result.
|
||
*/
|
||
public FormattedDateInterval formatToValue(DateInterval dtInterval) {
|
||
StringBuffer sb = new StringBuffer();
|
||
FieldPosition ignore = new FieldPosition(0);
|
||
FormatOutput output = new FormatOutput();
|
||
List<FieldPosition> attributes = new ArrayList<>();
|
||
formatIntervalImpl(dtInterval, sb, ignore, output, attributes);
|
||
if (output.firstIndex != -1) {
|
||
FormattedValueFieldPositionIteratorImpl.addOverlapSpans(
|
||
attributes, SpanField.DATE_INTERVAL_SPAN, output.firstIndex);
|
||
FormattedValueFieldPositionIteratorImpl.sort(attributes);
|
||
}
|
||
return new FormattedDateInterval(sb, attributes);
|
||
}
|
||
|
||
private synchronized StringBuffer formatIntervalImpl(
|
||
DateInterval dtInterval,
|
||
StringBuffer appendTo,
|
||
FieldPosition pos,
|
||
FormatOutput output,
|
||
List<FieldPosition> attributes) {
|
||
fFromCalendar.setTimeInMillis(dtInterval.getFromDate());
|
||
fToCalendar.setTimeInMillis(dtInterval.getToDate());
|
||
return formatImpl(fFromCalendar, fToCalendar, appendTo, pos, output, attributes);
|
||
}
|
||
|
||
/**
|
||
* @deprecated This API is ICU internal only.
|
||
* @hide original deprecated declaration
|
||
* @hide draft / provisional / internal are hidden on Android
|
||
*/
|
||
@Deprecated
|
||
public String getPatterns(Calendar fromCalendar,
|
||
Calendar toCalendar,
|
||
Output<String> part2) {
|
||
// First, find the largest different calendar field.
|
||
int field;
|
||
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 if ( fromCalendar.get(Calendar.SECOND) !=
|
||
toCalendar.get(Calendar.SECOND) ) {
|
||
field = Calendar.SECOND;
|
||
} else if ( fromCalendar.get(Calendar.MILLISECOND) !=
|
||
toCalendar.get(Calendar.MILLISECOND) ) {
|
||
field = Calendar.MILLISECOND;
|
||
} else {
|
||
return null;
|
||
}
|
||
PatternInfo intervalPattern = fIntervalPatterns.get(
|
||
DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
|
||
part2.value = intervalPattern.getSecondPart();
|
||
return intervalPattern.getFirstPart();
|
||
}
|
||
|
||
/**
|
||
* 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.
|
||
* There may be multiple instances of a given field type
|
||
* in an interval format; in this case the fieldPosition
|
||
* offsets refer to the first instance.
|
||
* @return Reference to 'appendTo' parameter.
|
||
* @throws IllegalArgumentException if the two calendars are not equivalent.
|
||
*/
|
||
public final StringBuffer format(Calendar fromCalendar,
|
||
Calendar toCalendar,
|
||
StringBuffer appendTo,
|
||
FieldPosition pos) {
|
||
return formatImpl(fromCalendar, toCalendar, appendTo, pos, null, null);
|
||
}
|
||
|
||
/**
|
||
* Format 2 Calendars to produce a FormattedDateInterval.
|
||
*
|
||
* The FormattedDateInterval exposes field information about the formatted 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
|
||
* @return A FormattedDateInterval containing the format result.
|
||
*/
|
||
public FormattedDateInterval formatToValue(Calendar fromCalendar, Calendar toCalendar) {
|
||
StringBuffer sb = new StringBuffer();
|
||
FieldPosition ignore = new FieldPosition(0);
|
||
FormatOutput output = new FormatOutput();
|
||
List<FieldPosition> attributes = new ArrayList<>();
|
||
formatImpl(fromCalendar, toCalendar, sb, ignore, output, attributes);
|
||
if (output.firstIndex != -1) {
|
||
FormattedValueFieldPositionIteratorImpl.addOverlapSpans(
|
||
attributes, SpanField.DATE_INTERVAL_SPAN, output.firstIndex);
|
||
FormattedValueFieldPositionIteratorImpl.sort(attributes);
|
||
}
|
||
return new FormattedDateInterval(sb, attributes);
|
||
}
|
||
|
||
private synchronized StringBuffer formatImpl(Calendar fromCalendar,
|
||
Calendar toCalendar,
|
||
StringBuffer appendTo,
|
||
FieldPosition pos,
|
||
FormatOutput output,
|
||
List<FieldPosition> attributes)
|
||
{
|
||
// not support different calendar types and time zones
|
||
if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
|
||
throw new IllegalArgumentException("can not format on two different calendars");
|
||
}
|
||
|
||
// Set up fDateFormat to handle the first or only part of the interval
|
||
// (override later for any second part).
|
||
fDateFormat.setContext(fCapitalizationSetting);
|
||
|
||
// 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 if ( fromCalendar.get(Calendar.SECOND) !=
|
||
toCalendar.get(Calendar.SECOND) ) {
|
||
field = Calendar.SECOND;
|
||
} else if ( fromCalendar.get(Calendar.MILLISECOND) !=
|
||
toCalendar.get(Calendar.MILLISECOND) ) {
|
||
field = Calendar.MILLISECOND;
|
||
} else {
|
||
/* ignore the millisecond etc. small fields' difference.
|
||
* use single date when all the above are the same.
|
||
*/
|
||
return fDateFormat.format(fromCalendar, appendTo, pos, attributes);
|
||
}
|
||
boolean fromToOnSameDay = (field==Calendar.AM_PM || field==Calendar.HOUR || field==Calendar.MINUTE || field==Calendar.SECOND || field==Calendar.MILLISECOND);
|
||
|
||
// 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, attributes);
|
||
}
|
||
|
||
return fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos,
|
||
output, attributes);
|
||
}
|
||
|
||
// 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, fromToOnSameDay, appendTo, pos,
|
||
output, attributes, intervalPattern.getSecondPart());
|
||
}
|
||
Calendar firstCal;
|
||
Calendar secondCal;
|
||
if ( intervalPattern.firstDateInPtnIsLaterDate() ) {
|
||
if (output != null) {
|
||
output.register(1);
|
||
}
|
||
firstCal = toCalendar;
|
||
secondCal = fromCalendar;
|
||
} else {
|
||
if (output != null) {
|
||
output.register(0);
|
||
}
|
||
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, attributes);
|
||
// Only accept the first instance of the field
|
||
if (pos.getEndIndex() > 0) {
|
||
pos = new FieldPosition(0);
|
||
}
|
||
if ( intervalPattern.getSecondPart() != null ) {
|
||
fDateFormat.applyPattern(intervalPattern.getSecondPart());
|
||
// No capitalization for second part of interval
|
||
fDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE);
|
||
fDateFormat.format(secondCal, appendTo, pos, attributes);
|
||
}
|
||
fDateFormat.applyPattern(originalPattern);
|
||
return appendTo;
|
||
}
|
||
|
||
/** Like fallbackFormat, but specifically for ranges. */
|
||
private final void fallbackFormatRange(Calendar fromCalendar,
|
||
Calendar toCalendar,
|
||
StringBuffer appendTo,
|
||
StringBuilder patternSB,
|
||
FieldPosition pos,
|
||
FormatOutput output,
|
||
List<FieldPosition> attributes) {
|
||
String compiledPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(
|
||
fInfo.getFallbackIntervalPattern(), patternSB, 2, 2);
|
||
long state = 0;
|
||
while (true) {
|
||
state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo);
|
||
if (state == SimpleFormatterImpl.IterInternal.DONE) {
|
||
break;
|
||
}
|
||
if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) {
|
||
if (output != null) {
|
||
output.register(0);
|
||
}
|
||
fDateFormat.format(fromCalendar, appendTo, pos, attributes);
|
||
} else {
|
||
if (output != null) {
|
||
output.register(1);
|
||
}
|
||
fDateFormat.format(toCalendar, appendTo, pos, attributes);
|
||
}
|
||
// Only accept the first instance of the field
|
||
if (pos.getEndIndex() > 0) {
|
||
pos = new FieldPosition(0);
|
||
}
|
||
// No capitalization for second portion
|
||
fDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 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,
|
||
boolean fromToOnSameDay,
|
||
StringBuffer appendTo,
|
||
FieldPosition pos,
|
||
FormatOutput output,
|
||
List<FieldPosition> attributes) {
|
||
StringBuilder patternSB = new StringBuilder();
|
||
boolean formatDatePlusTimeRange = (fromToOnSameDay && fDatePattern != null && fTimePattern != null);
|
||
if (formatDatePlusTimeRange) {
|
||
String compiledPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(
|
||
fDateTimeFormat, patternSB, 2, 2);
|
||
|
||
String fullPattern; // for saving the pattern in fDateFormat
|
||
fullPattern = fDateFormat.toPattern(); // save current pattern, restore later
|
||
|
||
// {0} is time range
|
||
// {1} is single date portion
|
||
long state = 0;
|
||
while (true) {
|
||
state = SimpleFormatterImpl.IterInternal.step(state, compiledPattern, appendTo);
|
||
if (state == SimpleFormatterImpl.IterInternal.DONE) {
|
||
break;
|
||
}
|
||
if (SimpleFormatterImpl.IterInternal.getArgIndex(state) == 0) {
|
||
fDateFormat.applyPattern(fTimePattern);
|
||
fallbackFormatRange(fromCalendar, toCalendar, appendTo, patternSB, pos, output, attributes);
|
||
} else {
|
||
fDateFormat.applyPattern(fDatePattern);
|
||
fDateFormat.format(fromCalendar, appendTo, pos, attributes);
|
||
}
|
||
// Only accept the first instance of the field
|
||
if (pos.getEndIndex() > 0) {
|
||
pos = new FieldPosition(0);
|
||
}
|
||
// No capitalization for second portion
|
||
fDateFormat.setContext(DisplayContext.CAPITALIZATION_NONE);
|
||
}
|
||
|
||
// restore full pattern
|
||
fDateFormat.applyPattern(fullPattern);
|
||
} else {
|
||
fallbackFormatRange(fromCalendar, toCalendar, appendTo, patternSB, pos, output, attributes);
|
||
}
|
||
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,
|
||
boolean fromToOnSameDay,
|
||
StringBuffer appendTo,
|
||
FieldPosition pos,
|
||
FormatOutput output,
|
||
List<FieldPosition> attributes,
|
||
String fullPattern) {
|
||
String originalPattern = fDateFormat.toPattern();
|
||
fDateFormat.applyPattern(fullPattern);
|
||
fallbackFormat(fromCalendar, toCalendar, fromToOnSameDay, appendTo, pos, output, attributes);
|
||
fDateFormat.applyPattern(originalPattern);
|
||
return appendTo;
|
||
}
|
||
|
||
|
||
/**
|
||
* Date interval parsing is not supported.
|
||
* <P>
|
||
* This method should handle parsing of
|
||
* date time interval strings into Formattable objects with
|
||
* DateInterval type, which is a pair of UDate.
|
||
* <P>
|
||
* 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.
|
||
* <P>
|
||
* When parsing, leading whitespace is discarded (with a successful parse),
|
||
* while trailing whitespace is left as is.
|
||
* <P>
|
||
* 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 {@code Formattable} object, or NULL
|
||
* on failure.
|
||
* @deprecated This API is ICU internal only.
|
||
* @hide original deprecated declaration
|
||
* @hide draft / provisional / internal are hidden on Android
|
||
*/
|
||
@Override
|
||
@Deprecated
|
||
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.
|
||
*/
|
||
public DateIntervalInfo getDateIntervalInfo()
|
||
{
|
||
return (DateIntervalInfo)fInfo.clone();
|
||
}
|
||
|
||
|
||
/**
|
||
* Set the date time interval patterns.
|
||
* @param newItvPattern the given interval patterns to copy.
|
||
*/
|
||
public void setDateIntervalInfo(DateIntervalInfo newItvPattern)
|
||
{
|
||
// clone it. If it is frozen, the clone returns itself.
|
||
// Otherwise, clone returns a copy
|
||
fInfo = (DateIntervalInfo)newItvPattern.clone();
|
||
this.isDateIntervalInfoDefault = false;
|
||
fInfo.freeze(); // freeze it
|
||
if ( fDateFormat != null ) {
|
||
initializePattern(null);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get the TimeZone
|
||
* @return A copy of the TimeZone associated with this date interval formatter.
|
||
*/
|
||
public TimeZone getTimeZone()
|
||
{
|
||
if ( fDateFormat != null ) {
|
||
// Here we clone, like other getters here, but unlike
|
||
// DateFormat.getTimeZone() and Calendar.getTimeZone()
|
||
// which return the TimeZone from the Calendar's zone variable
|
||
return (TimeZone)(fDateFormat.getTimeZone().clone());
|
||
}
|
||
// If fDateFormat is null (unexpected), return default timezone.
|
||
return TimeZone.getDefault();
|
||
}
|
||
|
||
|
||
/**
|
||
* Set the TimeZone for the calendar used by this DateIntervalFormat object.
|
||
* @param zone The new TimeZone, will be cloned for use by this DateIntervalFormat.
|
||
*/
|
||
public void setTimeZone(TimeZone zone)
|
||
{
|
||
// zone is cloned once for all three usages below:
|
||
TimeZone zoneToSet = (TimeZone)zone.clone();
|
||
if (fDateFormat != null) {
|
||
fDateFormat.setTimeZone(zoneToSet);
|
||
}
|
||
// fDateFormat has the primary calendar for the DateIntervalFormat;
|
||
// fFromCalendar and fToCalendar are internal work clones of that calendar.
|
||
if (fFromCalendar != null) {
|
||
fFromCalendar.setTimeZone(zoneToSet);
|
||
}
|
||
if (fToCalendar != null) {
|
||
fToCalendar.setTimeZone(zoneToSet);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <strong>[icu]</strong> Set a particular DisplayContext value in the formatter,
|
||
* such as CAPITALIZATION_FOR_STANDALONE. This causes the formatted
|
||
* result to be capitalized appropriately for the context in which
|
||
* it is intended to be used, considering both the locale and the
|
||
* type of field at the beginning of the formatted result.
|
||
*
|
||
* @param context The DisplayContext value to set.
|
||
*/
|
||
public void setContext(DisplayContext context)
|
||
{
|
||
if (context.type() == DisplayContext.Type.CAPITALIZATION) {
|
||
fCapitalizationSetting = context;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* <strong>[icu]</strong> Get the formatter's DisplayContext value for the specified DisplayContext.Type,
|
||
* such as CAPITALIZATION.
|
||
*
|
||
* @param type the DisplayContext.Type whose value to return
|
||
* @return the current DisplayContext setting for the specified type
|
||
*/
|
||
public DisplayContext getContext(DisplayContext.Type type)
|
||
{
|
||
return (type == DisplayContext.Type.CAPITALIZATION && fCapitalizationSetting != null)?
|
||
fCapitalizationSetting: DisplayContext.CAPITALIZATION_NONE;
|
||
}
|
||
|
||
/**
|
||
* Gets the date formatter
|
||
* @return a copy of the date formatter associated with
|
||
* this date interval formatter.
|
||
*/
|
||
public synchronized 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(ICUCache<String, Map<String, PatternInfo>> cache) {
|
||
String fullPattern = fDateFormat.toPattern();
|
||
ULocale locale = fDateFormat.getLocale();
|
||
String key = null;
|
||
Map<String, PatternInfo> patterns = null;
|
||
if (cache != null) {
|
||
if ( fSkeleton != null ) {
|
||
key = locale.toString() + "+" + fullPattern + "+" + fSkeleton;
|
||
} else {
|
||
key = locale.toString() + "+" + fullPattern;
|
||
}
|
||
patterns = cache.get(key);
|
||
}
|
||
if (patterns == null) {
|
||
Map<String, PatternInfo> intervalPatterns = initializeIntervalPattern(fullPattern, locale);
|
||
patterns = Collections.unmodifiableMap(intervalPatterns);
|
||
if (cache != null) {
|
||
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<String, PatternInfo> 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 = normalizeHourMetacharacters(fSkeleton, locale);
|
||
|
||
HashMap<String, PatternInfo> 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();
|
||
|
||
// move this up here since we need it for fallbacks
|
||
if (time.length() != 0 && date.length() != 0) {
|
||
// Need the Date/Time pattern for concatenating 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.
|
||
fDateTimeFormat = getConcatenationPattern(locale);
|
||
}
|
||
|
||
boolean found = genSeparateDateTimePtn(normalizedDateSkeleton,
|
||
normalizedTimeSkeleton,
|
||
intervalPatterns, dtpng);
|
||
|
||
// for skeletons with seconds, found is false and we enter this block
|
||
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);
|
||
|
||
pattern =dtpng.getBestPattern(timeSkeleton + "G");
|
||
ptn = new PatternInfo(null, pattern, fInfo.getDefaultOrder());
|
||
// share interval pattern
|
||
intervalPatterns.put(DateIntervalInfo.
|
||
CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.ERA], 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);
|
||
|
||
pattern =dtpng.getBestPattern(timeSkeleton + "G");
|
||
ptn = new PatternInfo(null, pattern, fInfo.getDefaultOrder());
|
||
intervalPatterns.put(DateIntervalInfo.
|
||
CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.ERA], 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 era, 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);
|
||
}
|
||
if ( !fieldExistsInSkeleton(Calendar.ERA, dateSkeleton) ) {
|
||
// then prefix skeleton with 'G'
|
||
skeleton = DateIntervalInfo.
|
||
CALENDAR_FIELD_TO_PATTERN_LETTER[Calendar.ERA] + skeleton;
|
||
genFallbackPattern(Calendar.ERA, skeleton, intervalPatterns, dtpng);
|
||
}
|
||
|
||
/*
|
||
* 2) otherwise, present the date followed by the
|
||
* range expression for the time.
|
||
*/
|
||
if (fDateTimeFormat == null) {
|
||
fDateTimeFormat = "{1} {0}";
|
||
}
|
||
String datePattern =dtpng.getBestPattern(dateSkeleton);
|
||
concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.AM_PM, intervalPatterns);
|
||
concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.HOUR, intervalPatterns);
|
||
concatSingleDate2TimeInterval(fDateTimeFormat, datePattern, Calendar.MINUTE, intervalPatterns);
|
||
}
|
||
|
||
return intervalPatterns;
|
||
}
|
||
|
||
/**
|
||
* Retrieves the concatenation DateTime pattern from the resource bundle.
|
||
* @param locale Locale to retrieve.
|
||
* @return Concatenation DateTime pattern.
|
||
*/
|
||
private String getConcatenationPattern(ULocale locale) {
|
||
ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||
ICUResourceBundle dtPatternsRb = rb.getWithFallback("calendar/gregorian/DateTimePatterns");
|
||
ICUResourceBundle concatenationPatternRb = (ICUResourceBundle) dtPatternsRb.get(8);
|
||
if (concatenationPatternRb.getType() == UResourceBundle.STRING) {
|
||
return concatenationPatternRb.getString();
|
||
} else {
|
||
return concatenationPatternRb.getString(0);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 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<String, PatternInfo> 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);
|
||
}
|
||
}
|
||
*/
|
||
|
||
private String normalizeHourMetacharacters(String skeleton, ULocale locale) {
|
||
StringBuilder result = new StringBuilder(skeleton);
|
||
|
||
char hourMetachar = '\0';
|
||
char dayPeriodChar = '\0';
|
||
int hourFieldStart = 0;
|
||
int hourFieldLength = 0;
|
||
int dayPeriodStart = 0;
|
||
int dayPeriodLength = 0;
|
||
for (int i = 0; i < result.length(); i++) {
|
||
char c = result.charAt(i);
|
||
if (c == 'j' || c == 'J' || c == 'C' || c == 'h' || c == 'H' || c == 'k' || c == 'K') {
|
||
if (hourMetachar == '\0') {
|
||
hourMetachar = c;
|
||
hourFieldStart = i;
|
||
}
|
||
++hourFieldLength;
|
||
} else if (c == 'a' || c == 'b' || c == 'B') {
|
||
if (dayPeriodChar == '\0') {
|
||
dayPeriodChar = c;
|
||
dayPeriodStart = i;
|
||
}
|
||
++dayPeriodLength;
|
||
} else {
|
||
if (hourMetachar != '\0' && dayPeriodChar != '\0') {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (hourMetachar != '\0') {
|
||
char hourChar = 'H';
|
||
|
||
DateTimePatternGenerator dtptng = DateTimePatternGenerator.getInstance(locale);
|
||
String convertedPattern = dtptng.getBestPattern(String.valueOf(hourMetachar));
|
||
|
||
// strip literal text from the pattern (so literal characters don't get mistaken for pattern
|
||
// characters-- such as the 'h' in 'Uhr' in German)
|
||
int firstQuotePos;
|
||
while ((firstQuotePos = convertedPattern.indexOf('\'')) != -1) {
|
||
int secondQuotePos = convertedPattern.indexOf('\'', firstQuotePos + 1);
|
||
if (secondQuotePos == -1) {
|
||
secondQuotePos = firstQuotePos;
|
||
}
|
||
convertedPattern = convertedPattern.substring(0, firstQuotePos) + convertedPattern.substring(secondQuotePos + 1);
|
||
}
|
||
|
||
if (convertedPattern.indexOf('h') != -1) {
|
||
hourChar = 'h';
|
||
} else if (convertedPattern.indexOf('K') != -1) {
|
||
hourChar = 'K';
|
||
} else if (convertedPattern.indexOf('k') != -1) {
|
||
hourChar = 'k';
|
||
}
|
||
|
||
if (convertedPattern.indexOf('b') != -1) {
|
||
dayPeriodChar = 'b';
|
||
} else if (convertedPattern.indexOf('B') != -1) {
|
||
dayPeriodChar = 'B';
|
||
} else if (dayPeriodChar == '\0') {
|
||
dayPeriodChar = 'a';
|
||
}
|
||
|
||
StringBuilder hourAndDayPeriod = new StringBuilder();
|
||
hourAndDayPeriod.append(hourChar);
|
||
if (hourChar != 'H' && hourChar != 'k') {
|
||
int newDayPeriodLength = 0;
|
||
if (dayPeriodLength >= 5 || hourFieldLength >= 5) {
|
||
newDayPeriodLength = 5;
|
||
} else if (dayPeriodLength >= 3 || hourFieldLength >= 3) {
|
||
newDayPeriodLength = 3;
|
||
} else {
|
||
newDayPeriodLength = 1;
|
||
}
|
||
for (int i = 0; i < newDayPeriodLength; i++) {
|
||
hourAndDayPeriod.append(dayPeriodChar);
|
||
}
|
||
}
|
||
result.replace(hourFieldStart, hourFieldStart + hourFieldLength, hourAndDayPeriod.toString());
|
||
if (dayPeriodStart > hourFieldStart) {
|
||
dayPeriodStart += hourAndDayPeriod.length() - hourFieldLength;
|
||
}
|
||
result.delete(dayPeriodStart, dayPeriodStart + dayPeriodLength);
|
||
}
|
||
return result.toString();
|
||
}
|
||
|
||
/*
|
||
* 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 mCount = 0;
|
||
int vCount = 0;
|
||
int zCount = 0;
|
||
char hourChar = '\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':
|
||
case 'U':
|
||
case 'r':
|
||
normalizedDateSkeleton.append(ch);
|
||
dateSkeleton.append(ch);
|
||
break;
|
||
case 'h':
|
||
case 'H':
|
||
case 'k':
|
||
case 'K':
|
||
timeSkeleton.append(ch);
|
||
if (hourChar == '\0') {
|
||
hourChar = ch;
|
||
}
|
||
break;
|
||
case 'm':
|
||
timeSkeleton.append(ch);
|
||
++mCount;
|
||
break;
|
||
case 'z':
|
||
++zCount;
|
||
timeSkeleton.append(ch);
|
||
break;
|
||
case 'v':
|
||
++vCount;
|
||
timeSkeleton.append(ch);
|
||
break;
|
||
case 'a':
|
||
case 'V':
|
||
case 'Z':
|
||
case 'j':
|
||
case 's':
|
||
case 'S':
|
||
case 'A':
|
||
case 'b':
|
||
case 'B':
|
||
timeSkeleton.append(ch);
|
||
normalizedTimeSkeleton.append(ch);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* generate normalized form for date*/
|
||
if ( yCount != 0 ) {
|
||
for (i = 0; i < yCount; i++) {
|
||
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 ( hourChar != '\0' ) {
|
||
normalizedTimeSkeleton.append(hourChar);
|
||
}
|
||
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<String, PatternInfo> intervalPatterns,
|
||
DateTimePatternGenerator dtpng)
|
||
{
|
||
String skeleton;
|
||
// if both date and time skeleton present,
|
||
// the final interval pattern might include time interval patterns
|
||
// ( when, am_pm, hour, minute, second 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;
|
||
|
||
// Set patterns for fallback use, need to do this
|
||
// before returning if differenceInfo == -1
|
||
if (dateSkeleton.length() != 0 ) {
|
||
fDatePattern = dtpng.getBestPattern(dateSkeleton);
|
||
}
|
||
if (timeSkeleton.length() != 0 ) {
|
||
fTimePattern = dtpng.getBestPattern(timeSkeleton);
|
||
}
|
||
|
||
// 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
|
||
// (this will happen, for instance, if the supplied skeleton has seconds,
|
||
// but no skeletons in the intervalFormats data do)
|
||
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);
|
||
genIntervalPattern(Calendar.ERA, 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<String, PatternInfo> 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 ) {
|
||
boolean suppressDayPeriodField = fSkeleton.indexOf('J') != -1;
|
||
String part1 = adjustFieldWidth(skeleton, bestSkeleton,
|
||
pattern.getFirstPart(), differenceInfo, suppressDayPeriodField);
|
||
String part2 = adjustFieldWidth(skeleton, bestSkeleton,
|
||
pattern.getSecondPart(), differenceInfo, suppressDayPeriodField);
|
||
pattern = new PatternInfo(part1, part2,
|
||
pattern.firstDateInPtnIsLaterDate());
|
||
|
||
// 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 ) {
|
||
boolean suppressDayPeriodField = fSkeleton.indexOf('J') != -1;
|
||
String part1 = adjustFieldWidth(skeleton, bestSkeleton,
|
||
pattern.getFirstPart(), differenceInfo, suppressDayPeriodField);
|
||
String part2 = adjustFieldWidth(skeleton, bestSkeleton,
|
||
pattern.getSecondPart(), differenceInfo, suppressDayPeriodField);
|
||
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
|
||
* @param suppressDayPeriodField if true, remove the day period field from the result
|
||
* @return the adjusted interval pattern
|
||
*/
|
||
private static String adjustFieldWidth(String inputSkeleton,
|
||
String bestMatchSkeleton,
|
||
String bestMatchIntervalPattern,
|
||
int differenceInfo,
|
||
boolean suppressDayPeriodField ) {
|
||
|
||
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,
|
||
};
|
||
*/
|
||
|
||
|
||
int PATTERN_CHAR_BASE = 0x41;
|
||
|
||
DateIntervalInfo.parseSkeleton(inputSkeleton, inputSkeletonFieldWidth);
|
||
DateIntervalInfo.parseSkeleton(bestMatchSkeleton, bestMatchSkeletonFieldWidth);
|
||
if (suppressDayPeriodField) {
|
||
if (bestMatchIntervalPattern.indexOf(" a") != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, " a", "");
|
||
} else if (bestMatchIntervalPattern.indexOf("\u00A0a") != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "\u00A0a", "");
|
||
} else if (bestMatchIntervalPattern.indexOf("\u202Fa") != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "\u202Fa", "");
|
||
} else if (bestMatchIntervalPattern.indexOf("a ") != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a ", "");
|
||
} else if (bestMatchIntervalPattern.indexOf("a\u00A0") != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a\u00A0", "");
|
||
} else if (bestMatchIntervalPattern.indexOf("a\u202F") != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a\u202F", "");
|
||
}
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a", "");
|
||
}
|
||
if ( differenceInfo == 2 ) {
|
||
if (inputSkeleton.indexOf('z') != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "v", "z");
|
||
}
|
||
if (inputSkeleton.indexOf('K') != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "h", "K");
|
||
}
|
||
if (inputSkeleton.indexOf('k') != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "H", "k");
|
||
}
|
||
if (inputSkeleton.indexOf('b') != -1) {
|
||
bestMatchIntervalPattern = findReplaceInPattern(bestMatchIntervalPattern, "a", "b");
|
||
}
|
||
}
|
||
if (bestMatchIntervalPattern.indexOf('a') != -1 && bestMatchSkeletonFieldWidth['a' - PATTERN_CHAR_BASE] == 0) {
|
||
bestMatchSkeletonFieldWidth['a' - PATTERN_CHAR_BASE] = 1;
|
||
}
|
||
if (bestMatchIntervalPattern.indexOf('b') != -1 && bestMatchSkeletonFieldWidth['b' - PATTERN_CHAR_BASE] == 0) {
|
||
bestMatchSkeletonFieldWidth['b' - PATTERN_CHAR_BASE] = 1;
|
||
}
|
||
|
||
StringBuilder adjustedPtn = new StringBuilder(bestMatchIntervalPattern);
|
||
|
||
|
||
boolean inQuote = false;
|
||
char prevCh = 0;
|
||
int count = 0;
|
||
|
||
// 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();
|
||
}
|
||
|
||
/**
|
||
* Does the same thing as String.replace(), except that it won't perform the
|
||
* substitution inside quoted literal text.
|
||
* @param targetString The string to perform the find-replace operation on.
|
||
* @param strToReplace The string to search for and replace in the target string.
|
||
* @param strToReplaceWith The string to substitute in wherever {@code stringToReplace} was found.
|
||
*/
|
||
private static String findReplaceInPattern(String targetString,
|
||
String strToReplace,
|
||
String strToReplaceWith) {
|
||
int firstQuoteIndex = targetString.indexOf("\'");
|
||
if (firstQuoteIndex < 0) {
|
||
return targetString.replace(strToReplace, strToReplaceWith);
|
||
} else {
|
||
StringBuilder result = new StringBuilder();
|
||
String source = targetString;
|
||
|
||
while (firstQuoteIndex >= 0) {
|
||
int secondQuoteIndex = source.indexOf("\'", firstQuoteIndex + 1);
|
||
if (secondQuoteIndex < 0) {
|
||
secondQuoteIndex = source.length() - 1;
|
||
}
|
||
|
||
String unquotedText = source.substring(0, firstQuoteIndex);
|
||
String quotedText = source.substring(firstQuoteIndex, secondQuoteIndex + 1);
|
||
|
||
result.append(unquotedText.replace(strToReplace, strToReplaceWith));
|
||
result.append(quotedText);
|
||
|
||
source = source.substring(secondQuoteIndex + 1);
|
||
firstQuoteIndex = source.indexOf("\'");
|
||
}
|
||
result.append(source.replace(strToReplace, strToReplaceWith));
|
||
return result.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<String, PatternInfo> intervalPatterns)
|
||
{
|
||
|
||
PatternInfo timeItvPtnInfo =
|
||
intervalPatterns.get(DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
|
||
if ( timeItvPtnInfo != null ) {
|
||
String timeIntervalPattern = timeItvPtnInfo.getFirstPart() +
|
||
timeItvPtnInfo.getSecondPart();
|
||
String pattern = SimpleFormatterImpl.formatRawPattern(
|
||
dtfmt, 2, 2, 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];
|
||
boolean result = skeleton.contains(fieldChar);
|
||
if (!result) {
|
||
if (fieldChar.equals("M")) {
|
||
// if the caller specified Calendar.MONTH, check the pattern for both M and L
|
||
result = skeleton.contains("L");
|
||
} else if (fieldChar.equals("y")) {
|
||
// if the caller specified Calendar.YEAR, check the pattern for y, Y, u, U, and r
|
||
result = skeleton.contains("U") || skeleton.contains("Y") || skeleton.contains("u") || skeleton.contains("r");
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
|
||
/*
|
||
* readObject.
|
||
*/
|
||
private void readObject(ObjectInputStream stream)
|
||
throws IOException, ClassNotFoundException {
|
||
stream.defaultReadObject();
|
||
initializePattern(isDateIntervalInfoDefault ? LOCAL_PATTERN_CACHE : null);
|
||
// if deserialized from a release that didn't have fCapitalizationSetting, set it to default
|
||
if (fCapitalizationSetting == null) {
|
||
fCapitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get the internal patterns for the skeleton
|
||
* @deprecated This API is ICU internal only.
|
||
* @hide original deprecated declaration
|
||
* @hide draft / provisional / internal are hidden on Android
|
||
*/
|
||
@Deprecated
|
||
public Map<String, PatternInfo> getRawPatterns() {
|
||
// this is unmodifiable, so ok to return directly
|
||
return fIntervalPatterns;
|
||
}
|
||
}
|