com.ibm.icu.text.SimpleDateFormat Maven / Gradle / Ivy
Show all versions of icu4j Show documentation
/*
*******************************************************************************
* Copyright (C) 1996-2015, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import com.ibm.icu.impl.CalendarData;
import com.ibm.icu.impl.DateNumberFormat;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.TimeZoneFormat.Style;
import com.ibm.icu.text.TimeZoneFormat.TimeType;
import com.ibm.icu.util.BasicTimeZone;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.HebrewCalendar;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.TimeZoneTransition;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
/**
* {@icuenhanced java.text.SimpleDateFormat}.{@icu _usage_}
*
* SimpleDateFormat
is a concrete class for formatting and
* parsing dates in a locale-sensitive manner. It allows for formatting
* (date -> text), parsing (text -> date), and normalization.
*
*
* SimpleDateFormat
allows you to start by choosing
* any user-defined patterns for date-time formatting. However, you
* are encouraged to create a date-time formatter with either
* getTimeInstance
, getDateInstance
, or
* getDateTimeInstance
in DateFormat
. Each
* of these class methods can return a date/time formatter initialized
* with a default format pattern. You may modify the format pattern
* using the applyPattern
methods as desired.
* For more information on using these methods, see
* {@link DateFormat}.
*
*
* Time Format Syntax:
*
* To specify the time format use a time pattern string.
* In this pattern, all ASCII letters are reserved as pattern letters,
* which are defined as the following:
*
*
* Symbol Meaning Presentation Example
* ------ ------- ------------ -------
* G era designator (Text) AD
* y† year (Number) 1996
* Y* year (week of year) (Number) 1997
* u* extended year (Number) 4601
* U* cyclic year name (Text,NumFallback) ren-chen (29)
* M month in year (Text & Number) July & 07
* d day in month (Number) 10
* h hour in am/pm (1~12) (Number) 12
* H hour in day (0~23) (Number) 0
* m minute in hour (Number) 30
* s second in minute (Number) 55
* S fractional second (Number) 978
* (maximum resolution of SSS; truncated if shorter, zero-padded if longer)
* E day of week (Text) Tuesday
* e* day of week (local 1~7) (Text & Number) Tuesday & 2
* D day in year (Number) 189
* F day of week in month (Number) 2 (2nd Wed in July)
* w week in year (Number) 27
* W week in month (Number) 2
* a am/pm marker (Text) PM
* k hour in day (1~24) (Number) 24
* K hour in am/pm (0~11) (Number) 0
* z time zone (Text) PST
* zzzz time zone (Text) Pacific Standard Time
* Z time zone (RFC 822) (Number) -0800
* ZZZZ time zone (GMT offset) (Text & Number) GMT-08:00
* ZZZZZ time zone (ISO 8601) (Text & Number) -08:00 & Z (UTC)
* v time zone (generic) (Text) PT
* vvvv time zone (generic) (Text) Pacific Time
* V time zone (abreviation) (Text) PST
* VVVV time zone (location) (Text) United States Time (Los Angeles)
* g* Julian day (Number) 2451334
* A* milliseconds in day (Number) 69540000
* Q* quarter in year (Text & Number) Q1 & 01
* c* stand alone day of week (Text & Number) Tuesday & 2
* L* stand alone month (Text & Number) July & 07
* q* stand alone quarter (Text & Number) Q1 & 01
* ' escape for text (Delimiter) 'Date='
* '' single quote (Literal) 'o''clock'
*
*
* * These items are not supported by Java's SimpleDateFormat.
* † ICU interprets a single 'y' differently than Java.
*
* The count of pattern letters determine the format.
*
* (Text): 4 or more pattern letters--use full form,
* < 4--use short or abbreviated form if one exists.
*
* (Number): the minimum number of digits. Shorter
* numbers are zero-padded to this amount. Year is handled specially;
* that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
* (e.g., if "yyyy" produces "1997", "yy" produces "97".)
* Unlike other fields, fractional seconds are padded on the right with zero.
*
* (Text & Number): 3 or over, use text, otherwise use number.
*
* (Text,NumFallback): Behaves like Text if there is supporting
* data, like Number otherwise.
*
* Any characters in the pattern that are not in the ranges of ['a'..'z']
* and ['A'..'Z'] will be treated as quoted text. For instance, characters
* like ':', '.', ' ', '#' and '@' will appear in the resulting time text
* even they are not embraced within single quotes.
*
* A pattern containing any invalid pattern letter will result in a thrown
* exception during formatting or parsing.
*
*
* Examples Using the US Locale:
*
*
* Format Pattern Result
* -------------- -------
* "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time
* "EEE, MMM d, ''yy" ->> Wed, July 10, '96
* "h:mm a" ->> 12:08 PM
* "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
* "K:mm a, vvv" ->> 0:00 PM, PT
* "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM
*
*
* Code Sample:
*
*
* SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
* pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
* pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
*
* // Format the current time.
* SimpleDateFormat formatter
* = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
* Date currentTime_1 = new Date();
* String dateString = formatter.format(currentTime_1);
*
* // Parse the previous string back into a Date.
* ParsePosition pos = new ParsePosition(0);
* Date currentTime_2 = formatter.parse(dateString, pos);
*
*
* In the example, the time value currentTime_2
obtained from
* parsing will be equal to currentTime_1
. However, they may not be
* equal if the am/pm marker 'a' is left out from the format pattern while
* the "hour in am/pm" pattern symbol is used. This information loss can
* happen when formatting the time in PM.
*
* When parsing a date string using the abbreviated year pattern ("yy"),
* SimpleDateFormat must interpret the abbreviated year
* relative to some century. It does this by adjusting dates to be
* within 80 years before and 20 years after the time the SimpleDateFormat
* instance is created. For example, using a pattern of "MM/dd/yy" and a
* SimpleDateFormat instance created on Jan 1, 1997, the string
* "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
* would be interpreted as May 4, 1964.
* During parsing, only strings consisting of exactly two digits, as defined by
* {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default
* century.
* Any other numeric string, such as a one digit string, a three or more digit
* string, or a two digit string that isn't all digits (for example, "-1"), is
* interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
* same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
*
*
If the year pattern does not have exactly two 'y' characters, the year is
* interpreted literally, regardless of the number of digits. So using the
* pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
*
*
When numeric fields abut one another directly, with no intervening delimiter
* characters, they constitute a run of abutting numeric fields. Such runs are
* parsed specially. For example, the format "HHmmss" parses the input text
* "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
* parse "1234". In other words, the leftmost field of the run is flexible,
* while the others keep a fixed width. If the parse fails anywhere in the run,
* then the leftmost field is shortened by one character, and the entire run is
* parsed again. This is repeated until either the parse succeeds or the
* leftmost field is one character in length. If the parse still fails at that
* point, the parse of the run fails.
*
*
For time zones that have no names, use strings GMT+hours:minutes or
* GMT-hours:minutes.
*
*
The calendar defines what is the first day of the week, the first week
* of the year, whether hours are zero based or not (0 vs 12 or 24), and the
* time zone. There is one common decimal format to handle all the numbers;
* the digit count is handled programmatically according to the pattern.
*
*
Synchronization
*
* Date formats are not synchronized. It is recommended to create separate
* format instances for each thread. If multiple threads access a format
* concurrently, it must be synchronized externally.
*
* @see com.ibm.icu.util.Calendar
* @see com.ibm.icu.util.GregorianCalendar
* @see com.ibm.icu.util.TimeZone
* @see DateFormat
* @see DateFormatSymbols
* @see DecimalFormat
* @author Mark Davis, Chen-Lieh Huang, Alan Liu
* @stable ICU 2.0
*/
public class SimpleDateFormat extends DateFormat {
// the official serial version ID which says cryptically
// which version we're compatible with
private static final long serialVersionUID = 4774881970558875024L;
// the internal serial version which says which version was written
// - 0 (default) for version up to JDK 1.1.3
// - 1 for version from JDK 1.1.4, which includes a new field
// - 2 we write additional int for capitalizationContext
static final int currentSerialVersion = 2;
static boolean DelayedHebrewMonthCheck = false;
/*
* From calendar field to its level.
* Used to order calendar field.
* For example, calendar fields can be defined in the following order:
* year > month > date > am-pm > hour > minute
* YEAR --> 10, MONTH -->20, DATE --> 30;
* AM_PM -->40, HOUR --> 50, MINUTE -->60
*/
private static final int[] CALENDAR_FIELD_TO_LEVEL =
{
/*GyM*/ 0, 10, 20,
/*wW*/ 20, 30,
/*dDEF*/ 30, 20, 30, 30,
/*ahHm*/ 40, 50, 50, 60,
/*sS..*/ 70, 80,
/*z?Y*/ 0, 0, 10,
/*eug*/ 30, 10, 0,
/*A*/ 40
};
/*
* From calendar field letter to its level.
* Used to order calendar field.
* For example, calendar fields can be defined in the following order:
* year > month > date > am-pm > hour > minute
* 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60
*/
private static final int[] PATTERN_CHAR_TO_LEVEL =
{
// A B C D E F G H I J K L M N O
-1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, -1,
// P Q R S T U V W X Y Z
-1, 20, -1, 80, -1, 10, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,
// a b c d e f g h i j k l m n o
-1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,
// p q r s t u v w x y z
-1, 20, -1, 70, -1, 10, 0, 20, -1, 10, 0, -1, -1, -1, -1, -1
};
// When calendar uses hebr numbering (i.e. he@calendar=hebrew),
// offset the years within the current millenium down to 1-999
private static final int HEBREW_CAL_CUR_MILLENIUM_START_YEAR = 5000;
private static final int HEBREW_CAL_CUR_MILLENIUM_END_YEAR = 6000;
/**
* The version of the serialized data on the stream. Possible values:
*
* - 0 or not present on stream: JDK 1.1.3. This version
* has no
defaultCenturyStart
on stream.
* - 1 JDK 1.1.4 or later. This version adds
*
defaultCenturyStart
.
* - 2 This version writes an additional int for
*
capitalizationContext
.
*
* When streaming out this class, the most recent format
* and the highest allowable serialVersionOnStream
* is written.
* @serial
*/
private int serialVersionOnStream = currentSerialVersion;
/**
* The pattern string of this formatter. This is always a non-localized
* pattern. May not be null. See class documentation for details.
* @serial
*/
private String pattern;
/**
* The override string of this formatter. Used to override the
* numbering system for one or more fields.
* @serial
*/
private String override;
/**
* The hash map used for number format overrides.
* @serial
*/
private HashMap numberFormatters;
/**
* The hash map used for number format overrides.
* @serial
*/
private HashMap overrideMap;
/**
* The symbols used by this formatter for week names, month names,
* etc. May not be null.
* @serial
* @see DateFormatSymbols
*/
private DateFormatSymbols formatData;
private transient ULocale locale;
/**
* We map dates with two-digit years into the century starting at
* defaultCenturyStart
, which may be any date. May
* not be null.
* @serial
* @since JDK1.1.4
*/
private Date defaultCenturyStart;
private transient int defaultCenturyStartYear;
// defaultCenturyBase is set when an instance is created
// and may be used for calculating defaultCenturyStart when needed.
private transient long defaultCenturyBase;
// We need to preserve time zone type when parsing specific
// time zone text (xxx Standard Time vs xxx Daylight Time)
private transient TimeType tztype = TimeType.UNKNOWN;
private static final int millisPerHour = 60 * 60 * 1000;
// When possessing ISO format, the ERA may be ommitted is the
// year specifier is a negative number.
private static final int ISOSpecialEra = -32000;
// This prefix is designed to NEVER MATCH real text, in order to
// suppress the parsing of negative numbers. Adjust as needed (if
// this becomes valid Unicode).
private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
/**
* If true, this object supports fast formatting using the
* subFormat variant that takes a StringBuffer.
*/
private transient boolean useFastFormat;
/*
* The time zone sub-formatter, introduced in ICU 4.8
*/
private volatile TimeZoneFormat tzFormat;
/*
* Capitalization setting, introduced in ICU 50
* Special serialization, see writeObject & readObject below
*/
private transient DisplayContext capitalizationSetting;
/*
* Old defaultCapitalizationContext field
* from ICU 49.1:
*/
//private ContextValue defaultCapitalizationContext;
/**
* Old ContextValue enum, preserved only to avoid
* deserialization errs from ICU 49.1.
*/
@SuppressWarnings("unused")
private enum ContextValue {
UNKNOWN,
CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,
CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE,
CAPITALIZATION_FOR_UI_LIST_OR_MENU,
CAPITALIZATION_FOR_STANDALONE
}
/**
* Constructs a SimpleDateFormat using the default pattern for the default FORMAT
* locale. Note: Not all locales support SimpleDateFormat; for full
* generality, use the factory methods in the DateFormat class.
*
* @see DateFormat
* @see Category#FORMAT
* @stable ICU 2.0
*/
public SimpleDateFormat() {
this(getDefaultPattern(), null, null, null, null, true, null);
}
/**
* Constructs a SimpleDateFormat using the given pattern in the default FORMAT
* locale. Note: Not all locales support SimpleDateFormat; for full
* generality, use the factory methods in the DateFormat class.
* @see Category#FORMAT
* @stable ICU 2.0
*/
public SimpleDateFormat(String pattern)
{
this(pattern, null, null, null, null, true, null);
}
/**
* Constructs a SimpleDateFormat using the given pattern and locale.
* Note: Not all locales support SimpleDateFormat; for full
* generality, use the factory methods in the DateFormat class.
* @stable ICU 2.0
*/
public SimpleDateFormat(String pattern, Locale loc)
{
this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
}
/**
* Constructs a SimpleDateFormat using the given pattern and locale.
* Note: Not all locales support SimpleDateFormat; for full
* generality, use the factory methods in the DateFormat class.
* @stable ICU 3.2
*/
public SimpleDateFormat(String pattern, ULocale loc)
{
this(pattern, null, null, null, loc, true, null);
}
/**
* Constructs a SimpleDateFormat using the given pattern , override and locale.
* @param pattern The pattern to be used
* @param override The override string. A numbering system override string can take one of the following forms:
* 1). If just a numbering system name is specified, it applies to all numeric fields in the date format pattern.
* 2). To specify an alternate numbering system on a field by field basis, use the field letters from the pattern
* followed by an = sign, followed by the numbering system name. For example, to specify that just the year
* be formatted using Hebrew digits, use the override "y=hebr". Multiple overrides can be specified in a single
* string by separating them with a semi-colon. For example, the override string "m=thai;y=deva" would format using
* Thai digits for the month and Devanagari digits for the year.
* @param loc The locale to be used
* @stable ICU 4.2
*/
public SimpleDateFormat(String pattern, String override, ULocale loc)
{
this(pattern, null, null, null, loc, false,override);
}
/**
* Constructs a SimpleDateFormat using the given pattern and
* locale-specific symbol data.
* Warning: uses default FORMAT
locale for digits!
* @stable ICU 2.0
*/
public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
{
this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
{
this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
}
/**
* Package-private constructor that allows a subclass to specify
* whether it supports fast formatting.
*
* TODO make this API public.
*/
SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
boolean useFastFormat, String override) {
this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);
}
/*
* The constructor called from all other SimpleDateFormat constructors
*/
private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,
NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {
this.pattern = pattern;
this.formatData = formatData;
this.calendar = calendar;
this.numberFormat = numberFormat;
this.locale = locale; // time zone formatting
this.useFastFormat = useFastFormat;
this.override = override;
initialize();
}
/**
* Creates an instance of SimpleDateFormat for the given format configuration
* @param formatConfig the format configuration
* @return A SimpleDateFormat instance
* @internal
* @deprecated This API is ICU internal only.
*/
public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
String ostr = formatConfig.getOverrideString();
boolean useFast = ( ostr != null && ostr.length() > 0 );
return new SimpleDateFormat(formatConfig.getPatternString(),
formatConfig.getDateFormatSymbols(),
formatConfig.getCalendar(),
null,
formatConfig.getLocale(),
useFast,
formatConfig.getOverrideString());
}
/*
* Initialized fields
*/
private void initialize() {
if (locale == null) {
locale = ULocale.getDefault(Category.FORMAT);
}
if (formatData == null) {
formatData = new DateFormatSymbols(locale);
}
if (calendar == null) {
calendar = Calendar.getInstance(locale);
}
if (numberFormat == null) {
NumberingSystem ns = NumberingSystem.getInstance(locale);
if (ns.isAlgorithmic()) {
numberFormat = NumberFormat.getInstance(locale);
} else {
String digitString = ns.getDescription();
String nsName = ns.getName();
// Use a NumberFormat optimized for date formatting
numberFormat = new DateNumberFormat(locale, digitString, nsName);
}
}
// Note: deferring calendar calculation until when we really need it.
// Instead, we just record time of construction for backward compatibility.
defaultCenturyBase = System.currentTimeMillis();
setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
initLocalZeroPaddingNumberFormat();
if (override != null) {
initNumberFormatters(locale);
}
capitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
}
/**
* Private method lazily instantiate the TimeZoneFormat field
* @param bForceUpdate when true, check if tzFormat is synchronized with
* the current numberFormat and update its digits if necessary. When false,
* this check is skipped.
*/
private synchronized void initializeTimeZoneFormat(boolean bForceUpdate) {
if (bForceUpdate || tzFormat == null) {
tzFormat = TimeZoneFormat.getInstance(locale);
String digits = null;
if (numberFormat instanceof DecimalFormat) {
DecimalFormatSymbols decsym = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
digits = new String(decsym.getDigits());
} else if (numberFormat instanceof DateNumberFormat) {
digits = new String(((DateNumberFormat)numberFormat).getDigits());
}
if (digits != null) {
if (!tzFormat.getGMTOffsetDigits().equals(digits)) {
if (tzFormat.isFrozen()) {
tzFormat = tzFormat.cloneAsThawed();
}
tzFormat.setGMTOffsetDigits(digits);
}
}
}
}
/**
* Private method, returns non-null TimeZoneFormat.
* @return the TimeZoneFormat used by this formatter.
*/
private TimeZoneFormat tzFormat() {
if (tzFormat == null) {
initializeTimeZoneFormat(false);
}
return tzFormat;
}
// privates for the default pattern
private static ULocale cachedDefaultLocale = null;
private static String cachedDefaultPattern = null;
private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
/*
* Returns the default date and time pattern (SHORT) for the default locale.
* This method is only used by the default SimpleDateFormat constructor.
*/
private static synchronized String getDefaultPattern() {
ULocale defaultLocale = ULocale.getDefault(Category.FORMAT);
if (!defaultLocale.equals(cachedDefaultLocale)) {
cachedDefaultLocale = defaultLocale;
Calendar cal = Calendar.getInstance(cachedDefaultLocale);
try {
CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
String[] dateTimePatterns = calData.getDateTimePatterns();
int glueIndex = 8;
if (dateTimePatterns.length >= 13)
{
glueIndex += (SHORT + 1);
}
cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],
new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
} catch (MissingResourceException e) {
cachedDefaultPattern = FALLBACKPATTERN;
}
}
return cachedDefaultPattern;
}
/* Define one-century window into which to disambiguate dates using
* two-digit years.
*/
private void parseAmbiguousDatesAsAfter(Date startDate) {
defaultCenturyStart = startDate;
calendar.setTime(startDate);
defaultCenturyStartYear = calendar.get(Calendar.YEAR);
}
/* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
* The default start time is 80 years before the creation time of this object.
*/
private void initializeDefaultCenturyStart(long baseTime) {
defaultCenturyBase = baseTime;
// clone to avoid messing up date stored in calendar object
// when this method is called while parsing
Calendar tmpCal = (Calendar)calendar.clone();
tmpCal.setTimeInMillis(baseTime);
tmpCal.add(Calendar.YEAR, -80);
defaultCenturyStart = tmpCal.getTime();
defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
}
/* Gets the default century start date for this object */
private Date getDefaultCenturyStart() {
if (defaultCenturyStart == null) {
// not yet initialized
initializeDefaultCenturyStart(defaultCenturyBase);
}
return defaultCenturyStart;
}
/* Gets the default century start year for this object */
private int getDefaultCenturyStartYear() {
if (defaultCenturyStart == null) {
// not yet initialized
initializeDefaultCenturyStart(defaultCenturyBase);
}
return defaultCenturyStartYear;
}
/**
* Sets the 100-year period 2-digit years will be interpreted as being in
* to begin on the date the user specifies.
* @param startDate During parsing, two digit years will be placed in the range
* startDate
to startDate + 100 years
.
* @stable ICU 2.0
*/
public void set2DigitYearStart(Date startDate) {
parseAmbiguousDatesAsAfter(startDate);
}
/**
* Returns the beginning date of the 100-year period 2-digit years are interpreted
* as being within.
* @return the start of the 100-year period into which two digit years are
* parsed
* @stable ICU 2.0
*/
public Date get2DigitYearStart() {
return getDefaultCenturyStart();
}
/**
* Formats a date or time, which is the standard millis
* since January 1, 1970, 00:00:00 GMT.
* Example: using the US locale:
* "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
* @param cal the calendar whose date-time value is to be formatted into a date-time string
* @param toAppendTo where the new date-time text is to be appended
* @param pos the formatting position. On input: an alignment field,
* if desired. On output: the offsets of the alignment field.
* @return the formatted date-time string.
* @see DateFormat
* @stable ICU 2.0
*/
public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
FieldPosition pos) {
TimeZone backupTZ = null;
if (cal != calendar && !cal.getType().equals(calendar.getType())) {
// Different calendar type
// We use the time and time zone from the input calendar, but
// do not use the input calendar for field calculation.
calendar.setTimeInMillis(cal.getTimeInMillis());
backupTZ = calendar.getTimeZone();
calendar.setTimeZone(cal.getTimeZone());
cal = calendar;
}
StringBuffer result = format(cal, capitalizationSetting, toAppendTo, pos, null);
if (backupTZ != null) {
// Restore the original time zone
calendar.setTimeZone(backupTZ);
}
return result;
}
// The actual method to format date. If List attributes is not null,
// then attribute information will be recorded.
private StringBuffer format(Calendar cal, DisplayContext capitalizationContext,
StringBuffer toAppendTo, FieldPosition pos, List attributes) {
// Initialize
pos.setBeginIndex(0);
pos.setEndIndex(0);
// Careful: For best performance, minimize the number of calls
// to StringBuffer.append() by consolidating appends when
// possible.
Object[] items = getPatternItems();
for (int i = 0; i < items.length; i++) {
if (items[i] instanceof String) {
toAppendTo.append((String)items[i]);
} else {
PatternItem item = (PatternItem)items[i];
int start = 0;
if (attributes != null) {
// Save the current length
start = toAppendTo.length();
}
if (useFastFormat) {
subFormat(toAppendTo, item.type, item.length, toAppendTo.length(),
i, capitalizationContext, pos, cal);
} else {
toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(),
i, capitalizationContext, pos, cal));
}
if (attributes != null) {
// Check the sub format length
int end = toAppendTo.length();
if (end - start > 0) {
// Append the attribute to the list
DateFormat.Field attr = patternCharToDateFormatField(item.type);
FieldPosition fp = new FieldPosition(attr);
fp.setBeginIndex(start);
fp.setEndIndex(end);
attributes.add(fp);
}
}
}
}
return toAppendTo;
}
// Map pattern character to index
private static final int PATTERN_CHAR_BASE = 0x40;
private static final int[] PATTERN_CHAR_TO_INDEX =
{
// A B C D E F G H I J K L M N O
-1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1,
// P Q R S T U V W X Y Z
-1, 27, -1, 8, -1, 30, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,
// a b c d e f g h i j k l m n o
-1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,
// p q r s t u v w x y z
-1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1
};
// Map pattern character index to Calendar field number
private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
{
/*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
/*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
/*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
/*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
/*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
/*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
/*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
/*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,
/*v*/ Calendar.ZONE_OFFSET,
/*c*/ Calendar.DOW_LOCAL,
/*L*/ Calendar.MONTH,
/*Qq*/ Calendar.MONTH, Calendar.MONTH,
/*V*/ Calendar.ZONE_OFFSET,
/*U*/ Calendar.YEAR,
};
// Map pattern character index to DateFormat field number
private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
/*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
/*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
/*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
/*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
/*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
/*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
/*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
/*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
/*v*/ DateFormat.TIMEZONE_GENERIC_FIELD,
/*c*/ DateFormat.STANDALONE_DAY_FIELD,
/*L*/ DateFormat.STANDALONE_MONTH_FIELD,
/*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
/*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD,
/*U*/ DateFormat.YEAR_NAME_FIELD,
};
// Map pattern character index to DateFormat.Field
private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
/*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
/*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
/*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
/*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
/*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
/*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
/*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
/*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
/*v*/ DateFormat.Field.TIME_ZONE,
/*c*/ DateFormat.Field.DAY_OF_WEEK,
/*L*/ DateFormat.Field.MONTH,
/*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
/*V*/ DateFormat.Field.TIME_ZONE,
/*U*/ DateFormat.Field.YEAR,
};
/**
* Returns a DateFormat.Field constant associated with the specified format pattern
* character.
*
* @param ch The pattern character
* @return DateFormat.Field associated with the pattern character
*
* @stable ICU 3.8
*/
protected DateFormat.Field patternCharToDateFormatField(char ch) {
int patternCharIndex = -1;
if ('A' <= ch && ch <= 'z') {
patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
}
if (patternCharIndex != -1) {
return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
}
return null;
}
/**
* Formats a single field, given its pattern character. Subclasses may
* override this method in order to modify or add formatting
* capabilities.
* @param ch the pattern character
* @param count the number of times ch is repeated in the pattern
* @param beginOffset the offset of the output string at the start of
* this field; used to set pos when appropriate
* @param pos receives the position of a field, when appropriate
* @param fmtData the symbols for this formatter
* @stable ICU 2.0
*/
protected String subFormat(char ch, int count, int beginOffset,
FieldPosition pos, DateFormatSymbols fmtData,
Calendar cal)
throws IllegalArgumentException
{
// Note: formatData is ignored
return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal);
}
/**
* Formats a single field. This is the version called internally; it
* adds fieldNum and capitalizationContext parameters.
*
* @internal
* @deprecated This API is ICU internal only.
*/
protected String subFormat(char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
Calendar cal)
{
StringBuffer buf = new StringBuffer();
subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
return buf.toString();
}
/**
* Formats a single field; useFastFormat variant. Reuses a
* StringBuffer for results instead of creating a String on the
* heap for each call.
*
* NOTE We don't really need the beginOffset parameter, EXCEPT for
* the need to support the slow subFormat variant (above) which
* has to pass it in to us.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@SuppressWarnings("fallthrough")
protected void subFormat(StringBuffer buf,
char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
Calendar cal) {
final int maxIntCount = Integer.MAX_VALUE;
final int bufstart = buf.length();
TimeZone tz = cal.getTimeZone();
long date = cal.getTimeInMillis();
String result = null;
// final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);
int patternCharIndex = -1;
if ('A' <= ch && ch <= 'z') {
patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
}
if (patternCharIndex == -1) {
if (ch == 'l') { // (SMALL LETTER L) deprecated placeholder for leap month marker, ignore
return;
} else {
throw new IllegalArgumentException("Illegal pattern character " +
"'" + ch + "' in \"" +
pattern + '"');
}
}
final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
int value = cal.get(field);
NumberFormat currentNumberFormat = getNumberFormat(ch);
DateFormatSymbols.CapitalizationContextUsage capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.OTHER;
switch (patternCharIndex) {
case 0: // 'G' - ERA
if ( cal.getType().equals("chinese")) {
// moved from ChineseDateFormat
zeroPaddingNumber(currentNumberFormat, buf, value, 1, 9);
} else {
if (count == 5) {
safeAppend(formatData.narrowEras, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_NARROW;
} else if (count == 4) {
safeAppend(formatData.eraNames, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_WIDE;
} else {
safeAppend(formatData.eras, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_ABBREV;
}
}
break;
case 30: // 'U' - YEAR_NAME_FIELD
if (formatData.shortYearNames != null && value <= formatData.shortYearNames.length) {
safeAppend(formatData.shortYearNames, value-1, buf);
break;
}
// else fall through to numeric year handling, do not break here
case 1: // 'y' - YEAR
case 18: // 'Y' - YEAR_WOY
if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) &&
value > HEBREW_CAL_CUR_MILLENIUM_START_YEAR && value < HEBREW_CAL_CUR_MILLENIUM_END_YEAR ) {
value -= HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
}
/* According to the specification, if the number of pattern letters ('y') is 2,
* the year is truncated to 2 digits; otherwise it is interpreted as a number.
* But the original code process 'y', 'yy', 'yyy' in the same way. and process
* patterns with 4 or more than 4 'y' characters in the same way.
* So I change the codes to meet the specification. [Richard/GCl]
*/
if (count == 2) {
zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
} else { //count = 1 or count > 2
zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
}
break;
case 2: // 'M' - MONTH
case 26: // 'L' - STANDALONE MONTH
if ( cal.getType().equals("hebrew")) {
boolean isLeap = HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR));
if (isLeap && value == 6 && count >= 3 ) {
value = 13; // Show alternate form for Adar II in leap years in Hebrew calendar.
}
if (!isLeap && value >= 6 && count < 3 ) {
value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7.
}
}
int isLeapMonth = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT)?
cal.get(Calendar.IS_LEAP_MONTH): 0;
// should consolidate the next section by using arrays of pointers & counts for the right symbols...
if (count == 5) {
if (patternCharIndex == 2) {
safeAppendWithMonthPattern(formatData.narrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_NARROW]: null);
} else {
safeAppendWithMonthPattern(formatData.standaloneNarrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW]: null);
}
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_NARROW;
} else if (count == 4) {
if (patternCharIndex == 2) {
safeAppendWithMonthPattern(formatData.months, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
} else {
safeAppendWithMonthPattern(formatData.standaloneMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
}
} else if (count == 3) {
if (patternCharIndex == 2) {
safeAppendWithMonthPattern(formatData.shortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
} else {
safeAppendWithMonthPattern(formatData.standaloneShortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
}
} else {
StringBuffer monthNumber = new StringBuffer();
zeroPaddingNumber(currentNumberFormat, monthNumber, value+1, count, maxIntCount);
String[] monthNumberStrings = new String[1];
monthNumberStrings[0] = monthNumber.toString();
safeAppendWithMonthPattern(monthNumberStrings, 0, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC]: null);
}
break;
case 4: // 'k' - HOUR_OF_DAY (1..24)
if (value == 0) {
zeroPaddingNumber(currentNumberFormat,buf,
cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
count, maxIntCount);
} else {
zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
}
break;
case 8: // 'S' - FRACTIONAL_SECOND
// Fractional seconds left-justify
{
numberFormat.setMinimumIntegerDigits(Math.min(3, count));
numberFormat.setMaximumIntegerDigits(maxIntCount);
if (count == 1) {
value /= 100;
} else if (count == 2) {
value /= 10;
}
FieldPosition p = new FieldPosition(-1);
numberFormat.format((long) value, buf, p);
if (count > 3) {
numberFormat.setMinimumIntegerDigits(count - 3);
numberFormat.format(0L, buf, p);
}
}
break;
case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
if (count < 3) {
zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
break;
}
// For alpha day-of-week, we don't want DOW_LOCAL,
// we need the standard DAY_OF_WEEK.
value = cal.get(Calendar.DAY_OF_WEEK);
// fall through, do not break here
case 9: // 'E' - DAY_OF_WEEK
if (count == 5) {
safeAppend(formatData.narrowWeekdays, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
} else if (count == 4) {
safeAppend(formatData.weekdays, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
} else {// count <= 3, use abbreviated form if exists
safeAppend(formatData.shortWeekdays, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
}
break;
case 14: // 'a' - AM_PM
safeAppend(formatData.ampms, value, buf);
break;
case 15: // 'h' - HOUR (1..12)
if (value == 0) {
zeroPaddingNumber(currentNumberFormat,buf,
cal.getLeastMaximum(Calendar.HOUR)+1,
count, maxIntCount);
} else {
zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
}
break;
case 17: // 'z' - ZONE_OFFSET
if (count < 4) {
// "z", "zz", "zzz"
result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
} else {
result = tzFormat().format(Style.SPECIFIC_LONG, tz, date);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
}
buf.append(result);
break;
case 23: // 'Z' - TIMEZONE_RFC
{
if (count < 4) {
// RFC822 format
result = tzFormat().format(Style.RFC822, tz, date);
} else if (count == 5) {
// ISO 8601 extended format
result = tzFormat().format(Style.ISO8601, tz, date);
} else {
// long form, localized GMT pattern
result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
}
buf.append(result);
break;
}
case 24: // 'v' - TIMEZONE_GENERIC
if (count == 1) {
// "v"
result = tzFormat().format(Style.GENERIC_SHORT, tz, date);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
} else if (count == 4) {
// "vvvv"
result = tzFormat().format(Style.GENERIC_LONG, tz, date);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
}
buf.append(result);
break;
case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone)
if (count < 3) {
zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
break;
}
// For alpha day-of-week, we don't want DOW_LOCAL,
// we need the standard DAY_OF_WEEK.
value = cal.get(Calendar.DAY_OF_WEEK);
if (count == 5) {
safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
} else if (count == 4) {
safeAppend(formatData.standaloneWeekdays, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
} else { // count == 3
safeAppend(formatData.standaloneShortWeekdays, value, buf);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
}
break;
case 27: // 'Q' - QUARTER
if (count >= 4) {
safeAppend(formatData.quarters, value/3, buf);
} else if (count == 3) {
safeAppend(formatData.shortQuarters, value/3, buf);
} else {
zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
}
break;
case 28: // 'q' - STANDALONE QUARTER
if (count >= 4) {
safeAppend(formatData.standaloneQuarters, value/3, buf);
} else if (count == 3) {
safeAppend(formatData.standaloneShortQuarters, value/3, buf);
} else {
zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
}
break;
case 29: // 'V' - TIMEZONE_SPECIAL
if (count == 1) {
// "V"
result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
} else if (count == 4) {
// "VVVV"
result = tzFormat().format(Style.GENERIC_LOCATION, tz, date);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ZONE_LONG;
}
buf.append(result);
break;
default:
// case 3: // 'd' - DATE
// case 5: // 'H' - HOUR_OF_DAY (0..23)
// case 6: // 'm' - MINUTE
// case 7: // 's' - SECOND
// case 10: // 'D' - DAY_OF_YEAR
// case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
// case 12: // 'w' - WEEK_OF_YEAR
// case 13: // 'W' - WEEK_OF_MONTH
// case 16: // 'K' - HOUR (0..11)
// case 20: // 'u' - EXTENDED_YEAR
// case 21: // 'g' - JULIAN_DAY
// case 22: // 'A' - MILLISECONDS_IN_DAY
zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
break;
} // switch (patternCharIndex)
if (fieldNum == 0) {
boolean titlecase = false;
if (capitalizationContext != null) {
switch (capitalizationContext) {
case CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE:
titlecase = true;
break;
case CAPITALIZATION_FOR_UI_LIST_OR_MENU:
case CAPITALIZATION_FOR_STANDALONE:
if (formatData.capitalization != null) {
boolean[] transforms = formatData.capitalization.get(capContextUsageType);
titlecase = (capitalizationContext==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)?
transforms[0]: transforms[1];
}
break;
default:
break;
}
}
if (titlecase) {
String firstField = buf.substring(bufstart); // bufstart or beginOffset, should be the same
String firstFieldTitleCase = UCharacter.toTitleCase(locale, firstField, null,
UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
buf.replace(bufstart, buf.length(), firstFieldTitleCase);
}
}
// Set the FieldPosition (for the first occurrence only)
if (pos.getBeginIndex() == pos.getEndIndex()) {
if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
pos.setBeginIndex(beginOffset);
pos.setEndIndex(beginOffset + buf.length() - bufstart);
} else if (pos.getFieldAttribute() ==
PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
pos.setBeginIndex(beginOffset);
pos.setEndIndex(beginOffset + buf.length() - bufstart);
}
}
}
private static void safeAppend(String[] array, int value, StringBuffer appendTo) {
if (array != null && value >= 0 && value < array.length) {
appendTo.append(array[value]);
}
}
private static void safeAppendWithMonthPattern(String[] array, int value, StringBuffer appendTo, String monthPattern) {
if (array != null && value >= 0 && value < array.length) {
if (monthPattern == null) {
appendTo.append(array[value]);
} else {
appendTo.append(MessageFormat.format(monthPattern, array[value]));
}
}
}
/*
* PatternItem store parsed date/time field pattern information.
*/
private static class PatternItem {
final char type;
final int length;
final boolean isNumeric;
PatternItem(char type, int length) {
this.type = type;
this.length = length;
isNumeric = isNumeric(type, length);
}
}
private static ICUCache PARSED_PATTERN_CACHE =
new SimpleCache();
private transient Object[] patternItems;
/*
* Returns parsed pattern items. Each item is either String or
* PatternItem.
*/
private Object[] getPatternItems() {
if (patternItems != null) {
return patternItems;
}
patternItems = PARSED_PATTERN_CACHE.get(pattern);
if (patternItems != null) {
return patternItems;
}
boolean isPrevQuote = false;
boolean inQuote = false;
StringBuilder text = new StringBuilder();
char itemType = 0; // 0 for string literal, otherwise date/time pattern character
int itemLength = 1;
List