com.ibm.icu.text.SimpleDateFormat Maven / Gradle / Ivy
Show all versions of icu4j Show documentation
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 1996-2016, 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 java.util.UUID;
import com.ibm.icu.impl.DateNumberFormat;
import com.ibm.icu.impl.DayPeriodRules;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.SimpleFormatterImpl;
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.BasicTimeZone.LocalOption;
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;
import com.ibm.icu.util.UResourceBundle;
/**
* {@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}.
*
*
Date and Time Patterns:
*
* Date and time formats are specified by date and time pattern strings.
* Within date and time pattern strings, all unquoted ASCII letters [A-Za-z] are reserved
* as pattern letters representing calendar fields. SimpleDateFormat
supports
* the date and time formatting algorithm and pattern letters defined by UTS#35
* Unicode Locale Data Markup Language (LDML). The following pattern letters are
* currently available (note that the actual values depend on CLDR and may change from the
* examples shown here):
*
*
*
* Field
* Sym.
* No.
* Example
* Description
*
*
* era
* G
* 1..3
* AD
* Era - Replaced with the Era string for the current date. One to three letters for the
* abbreviated form, four letters for the long (wide) form, five for the narrow form.
*
*
* 4
* Anno Domini
*
*
* 5
* A
*
*
* year
* y
* 1..n
* 1996
* Year. Normally the length specifies the padding, but for two letters it also specifies the maximum
* length. Example:
*
*
*
* Year
* y
* yy
* yyy
* yyyy
* yyyyy
*
*
* AD 1
* 1
* 01
* 001
* 0001
* 00001
*
*
* AD 12
* 12
* 12
* 012
* 0012
* 00012
*
*
* AD 123
* 123
* 23
* 123
* 0123
* 00123
*
*
* AD 1234
* 1234
* 34
* 1234
* 1234
* 01234
*
*
* AD 12345
* 12345
* 45
* 12345
* 12345
* 12345
*
*
*
*
*
*
* Y
* 1..n
* 1997
* Year (in "Week of Year" based calendars). Normally the length specifies the padding,
* but for two letters it also specifies the maximum length. This year designation is used in ISO
* year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems
* where week date processing is desired. May not always be the same value as calendar year.
*
*
* u
* 1..n
* 4601
* Extended year. This is a single number designating the year of this calendar system, encompassing
* all supra-year fields. For example, for the Julian calendar system, year numbers are positive, with an
* era of BCE or CE. An extended year value for the Julian calendar system assigns positive values to CE
* years and negative values to BCE years, with 1 BCE being year 0.
*
*
* U
* 1..3
* 甲子
* Cyclic year name. Calendars such as the Chinese lunar calendar (and related calendars)
* and the Hindu calendars use 60-year cycles of year names. Use one through three letters for the abbreviated
* name, four for the full (wide) name, or five for the narrow name (currently the data only provides abbreviated names,
* which will be used for all requested name widths). If the calendar does not provide cyclic year name data,
* or if the year value to be formatted is out of the range of years for which cyclic name data is provided,
* then numeric formatting is used (behaves like 'y').
*
*
* 4
* (currently also 甲子)
*
*
* 5
* (currently also 甲子)
*
*
* quarter
* Q
* 1..2
* 02
* Quarter - Use one or two for the numerical quarter, three for the abbreviation, or four
* for the full (wide) name (five for the narrow name is not yet supported).
*
*
* 3
* Q2
*
*
* 4
* 2nd quarter
*
*
* q
* 1..2
* 02
* Stand-Alone Quarter - Use one or two for the numerical quarter, three for the abbreviation,
* or four for the full name (five for the narrow name is not yet supported).
*
*
* 3
* Q2
*
*
* 4
* 2nd quarter
*
*
* month
* M
* 1..2
* 09
* Month - Use one or two for the numerical month, three for the abbreviation, four for
* the full (wide) name, or five for the narrow name. With two ("MM"), the month number is zero-padded
* if necessary (e.g. "08").
*
*
* 3
* Sep
*
*
* 4
* September
*
*
* 5
* S
*
*
* L
* 1..2
* 09
* Stand-Alone Month - Use one or two for the numerical month, three for the abbreviation,
* four for the full (wide) name, or 5 for the narrow name. With two ("LL"), the month number is zero-padded if
* necessary (e.g. "08").
*
*
* 3
* Sep
*
*
* 4
* September
*
*
* 5
* S
*
*
* week
* w
* 1..2
* 27
* Week of Year. Use "w" to show the minimum number of digits, or "ww" to always show two digits
* (zero-padding if necessary, e.g. "08").
*
*
* W
* 1
* 3
* Week of Month
*
*
* day
* d
* 1..2
* 1
* Date - Day of the month. Use "d" to show the minimum number of digits, or "dd" to always show
* two digits (zero-padding if necessary, e.g. "08").
*
*
* D
* 1..3
* 345
* Day of year
*
*
* F
* 1
* 2
* Day of Week in Month. The example is for the 2nd Wed in July
*
*
* g
* 1..n
* 2451334
* Modified Julian day. This is different from the conventional Julian day number in two regards.
* First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number;
* that is, it depends on the local time zone. It can be thought of as a single number that encompasses
* all the date-related fields.
*
*
* week
* day
* E
* 1..3
* Tue
* Day of week - Use one through three letters for the short day, four for the full (wide) name,
* five for the narrow name, or six for the short name.
*
*
* 4
* Tuesday
*
*
* 5
* T
*
*
* 6
* Tu
*
*
* e
* 1..2
* 2
* Local day of week. Same as E except adds a numeric value that will depend on the local
* starting day of the week, using one or two letters. For this example, Monday is the first day of the week.
*
*
* 3
* Tue
*
*
* 4
* Tuesday
*
*
* 5
* T
*
*
* 6
* Tu
*
*
* c
* 1
* 2
* Stand-Alone local day of week - Use one letter for the local numeric value (same
* as 'e'), three for the short day, four for the full (wide) name, five for the narrow name, or six for
* the short name.
*
*
* 3
* Tue
*
*
* 4
* Tuesday
*
*
* 5
* T
*
*
* 6
* Tu
*
*
* period
* a
* 1
* AM
* AM or PM
*
*
* hour
* h
* 1..2
* 11
* Hour [1-12]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern
* generation, it should match the 12-hour-cycle format preferred by the locale (h or K); it should not match
* a 24-hour-cycle format (H or k). Use hh for zero padding.
*
*
* H
* 1..2
* 13
* Hour [0-23]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern
* generation, it should match the 24-hour-cycle format preferred by the locale (H or k); it should not match a
* 12-hour-cycle format (h or K). Use HH for zero padding.
*
*
* K
* 1..2
* 0
* Hour [0-11]. When used in a skeleton, only matches K or h, see above. Use KK for zero padding.
*
*
* k
* 1..2
* 24
* Hour [1-24]. When used in a skeleton, only matches k or H, see above. Use kk for zero padding.
*
*
* minute
* m
* 1..2
* 59
* Minute. Use "m" to show the minimum number of digits, or "mm" to always show two digits
* (zero-padding if necessary, e.g. "08")..
*
*
* second
* s
* 1..2
* 12
* Second. Use "s" to show the minimum number of digits, or "ss" to always show two digits
* (zero-padding if necessary, e.g. "08").
*
*
* S
* 1..n
* 3450
* Fractional Second - truncates (like other time fields) to the count of letters when formatting. Appends zeros if more than 3 letters specified. Truncates at three significant digits when parsing.
* (example shows display using pattern SSSS for seconds value 12.34567)
*
*
* A
* 1..n
* 69540000
* Milliseconds in day. This field behaves exactly like a composite of all time-related fields,
* not including the zone fields. As such, it also reflects discontinuities of those fields on DST transition
* days. On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. This
* reflects the fact that is must be combined with the offset field to obtain a unique local time value.
*
*
* zone
* z
* 1..3
* PDT
* The short specific non-location format.
* Where that is unavailable, falls back to the short localized GMT format ("O").
*
*
* 4
* Pacific Daylight Time
* The long specific non-location format.
* Where that is unavailable, falls back to the long localized GMT format ("OOOO").
*
*
* Z
* 1..3
* -0800
* The ISO8601 basic format with hours, minutes and optional seconds fields.
* The format is equivalent to RFC 822 zone format (when optional seconds field is absent).
* This is equivalent to the "xxxx" specifier.
*
*
* 4
* GMT-8:00
* The long localized GMT format.
* This is equivalent to the "OOOO" specifier.
*
*
* 5
* -08:00
* -07:52:58
* The ISO8601 extended format with hours, minutes and optional seconds fields.
* The ISO8601 UTC indicator "Z" is used when local time offset is 0.
* This is equivalent to the "XXXXX" specifier.
*
*
* O
* 1
* GMT-8
* The short localized GMT format.
*
*
* 4
* GMT-08:00
* The long localized GMT format.
*
*
* v
* 1
* PT
* The short generic non-location format.
* Where that is unavailable, falls back to the generic location format ("VVVV"),
* then the short localized GMT format as the final fallback.
*
*
* 4
* Pacific Time
* The long generic non-location format.
* Where that is unavailable, falls back to generic location format ("VVVV").
*
*
* V
* 1
* uslax
* The short time zone ID.
* Where that is unavailable, the special short time zone ID unk (Unknown Zone) is used.
* Note: This specifier was originally used for a variant of the short specific non-location format,
* but it was deprecated in the later version of the LDML specification. In CLDR 23/ICU 51, the definition of
* the specifier was changed to designate a short time zone ID.
*
*
* 2
* America/Los_Angeles
* The long time zone ID.
*
*
* 3
* Los Angeles
* The exemplar city (location) for the time zone.
* Where that is unavailable, the localized exemplar city name for the special zone Etc/Unknown is used
* as the fallback (for example, "Unknown City").
*
*
* 4
* Los Angeles Time
* The generic location format.
* Where that is unavailable, falls back to the long localized GMT format ("OOOO";
* Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.)
* This is especially useful when presenting possible timezone choices for user selection,
* since the naming is more uniform than the "v" format.
*
*
* X
* 1
* -08
* +0530
* Z
* The ISO8601 basic format with hours field and optional minutes field.
* The ISO8601 UTC indicator "Z" is used when local time offset is 0.
*
*
* 2
* -0800
* Z
* The ISO8601 basic format with hours and minutes fields.
* The ISO8601 UTC indicator "Z" is used when local time offset is 0.
*
*
* 3
* -08:00
* Z
* The ISO8601 extended format with hours and minutes fields.
* The ISO8601 UTC indicator "Z" is used when local time offset is 0.
*
*
* 4
* -0800
* -075258
* Z
* The ISO8601 basic format with hours, minutes and optional seconds fields.
* (Note: The seconds field is not supported by the ISO8601 specification.)
* The ISO8601 UTC indicator "Z" is used when local time offset is 0.
*
*
* 5
* -08:00
* -07:52:58
* Z
* The ISO8601 extended format with hours, minutes and optional seconds fields.
* (Note: The seconds field is not supported by the ISO8601 specification.)
* The ISO8601 UTC indicator "Z" is used when local time offset is 0.
*
*
* x
* 1
* -08
* +0530
* The ISO8601 basic format with hours field and optional minutes field.
*
*
* 2
* -0800
* The ISO8601 basic format with hours and minutes fields.
*
*
* 3
* -08:00
* The ISO8601 extended format with hours and minutes fields.
*
*
* 4
* -0800
* -075258
* The ISO8601 basic format with hours, minutes and optional seconds fields.
* (Note: The seconds field is not supported by the ISO8601 specification.)
*
*
* 5
* -08:00
* -07:52:58
* The ISO8601 extended format with hours, minutes and optional seconds fields.
* (Note: The seconds field is not supported by the ISO8601 specification.)
*
*
*
*
*
* 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
* @see TimeZoneFormat
* @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 capitalizationSetting
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, 0, 0
};
/*
* 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 =
{
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
//
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// ! " # $ % & ' ( ) * + , - . /
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// @ 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, 0,
// P Q R S T U V W X Y Z [ \ ] ^ _
-1, 20, -1, 80, -1, 10, 0, 30, 0, 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, 10, 70, -1, 10, 0, 20, 0, 10, 0, -1, -1, -1, -1, -1,
};
/**
* Map calendar field letter into calendar field level.
*/
private static int getLevelFromChar(char ch) {
return ch < PATTERN_CHAR_TO_LEVEL.length ? PATTERN_CHAR_TO_LEVEL[ch & 0xff] : -1;
}
private static final boolean[] PATTERN_CHAR_IS_SYNTAX =
{
//
false, false, false, false, false, false, false, false,
//
false, false, false, false, false, false, false, false,
//
false, false, false, false, false, false, false, false,
//
false, false, false, false, false, false, false, false,
// ! " # $ % & '
false, false, false, false, false, false, false, false,
// ( ) * + , - . /
false, false, false, false, false, false, false, false,
// 0 1 2 3 4 5 6 7
false, false, false, false, false, false, false, false,
// 8 9 : ; < = > ?
false, false, false, false, false, false, false, false,
// @ A B C D E F G
false, true, true, true, true, true, true, true,
// H I J K L M N O
true, true, true, true, true, true, true, true,
// P Q R S T U V W
true, true, true, true, true, true, true, true,
// X Y Z [ \ ] ^ _
true, true, true, false, false, false, false, false,
// ` a b c d e f g
false, true, true, true, true, true, true, true,
// h i j k l m n o
true, true, true, true, true, true, true, true,
// p q r s t u v w
true, true, true, true, true, true, true, true,
// x y z { | } ~
true, true, true, false, false, false, false, false,
};
/**
* Tell if a character can be used to define a field in a format string.
*/
private static boolean isSyntaxChar(char ch) {
return ch < PATTERN_CHAR_IS_SYNTAX.length ? PATTERN_CHAR_IS_SYNTAX[ch & 0xff] : false;
}
// 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
*
capitalizationSetting
.
*
* 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;
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;
/**
* BreakIterator to use for capitalization (will be cloned for actual use)
*/
private transient BreakIterator capitalizationBrkIter = null;
/**
* DateFormat pattern contains the minute field.
*/
private transient boolean hasMinute;
/**
* DateFormat pattern contains the second field.
*/
private transient boolean hasSecond;
/**
* DateFormat pattern contains the Han year character \u5E74=年, => non-numeric E Asian format.
*/
private transient boolean hasHanYearChar;
/*
* Capitalization setting, introduced in ICU 50
* Special serialization, see writeObject & readObject below
*
* Hoisted to DateFormat in ICU 53, get value with
* getContext(DisplayContext.Type.CAPITALIZATION)
*/
// 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.
*/
@Deprecated
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.
*/
@Deprecated
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);
String digitString = ns.getDescription();
// DateNumberFormat does not support non-BMP digits at this moment.
if (ns.isAlgorithmic() || digitString.length() != 10) {
numberFormat = NumberFormat.getInstance(locale);
} else {
String nsName = ns.getName();
// Use a NumberFormat optimized for date formatting
numberFormat = new DateNumberFormat(locale, digitString, nsName);
}
}
if (numberFormat instanceof DecimalFormat) {
fixNumberFormatForDates(numberFormat);
}
// 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();
parsePattern(); // Need this before initNumberFormatters(), to set hasHanYearChar
// Simple-minded hack to force Gannen year numbering for ja@calendar=japanese
// if format is non-numeric (includes 年) and overrides are not already specified.
// Now this does get updated if applyPattern subsequently changes the pattern type.
if (override == null && hasHanYearChar &&
calendar != null && calendar.getType().equals("japanese") &&
locale != null && locale.getLanguage().equals("ja")) {
override = "y=jpanyear";
}
if (override != null) {
initNumberFormatters(locale);
}
}
/**
* 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();
String[] strDigits = decsym.getDigitStringsLocal();
// Note: TimeZoneFormat#setGMTOffsetDigits() does not support string array,
// so we need to concatenate digits to make a single string.
StringBuilder digitsBuf = new StringBuilder();
for (String digit : strDigits) {
digitsBuf.append(digit);
}
digits = digitsBuf.toString();
} 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 {
// Load the calendar data directly.
ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(
ICUData.ICU_BASE_NAME, cachedDefaultLocale);
String resourcePath = "calendar/" + cal.getType() + "/DateTimePatterns";
ICUResourceBundle patternsRb= rb.findWithFallback(resourcePath);
if (patternsRb == null) {
patternsRb = rb.findWithFallback("calendar/gregorian/DateTimePatterns");
}
if (patternsRb == null || patternsRb.getSize() < 9) {
cachedDefaultPattern = FALLBACKPATTERN;
} else {
String basePattern = Calendar.getDateAtTimePattern(cal, cachedDefaultLocale, SHORT);
cachedDefaultPattern = SimpleFormatterImpl.formatRawPattern(
basePattern, 2, 2,
patternsRb.getString(SHORT), patternsRb.getString(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();
}
/**
* {@icu} Set a particular DisplayContext value in the formatter,
* such as CAPITALIZATION_FOR_STANDALONE. Note: For getContext, see
* DateFormat.
*
* @param context The DisplayContext value to set.
* @stable ICU 53
*/
// Here we override the DateFormat implementation in order to lazily initialize relevant items
@Override
public void setContext(DisplayContext context) {
super.setContext(context);
if (capitalizationBrkIter == null && (context==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU ||
context==DisplayContext.CAPITALIZATION_FOR_STANDALONE)) {
capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
}
}
/**
* 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
*/
@Override
public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
FieldPosition pos) {
return format(cal, toAppendTo, pos, null);
}
/** Internal formatting method that accepts an attributes list. */
StringBuffer format(Calendar cal, StringBuffer toAppendTo, FieldPosition pos, List attributes) {
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, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, attributes);
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, item.type, cal);
} else {
toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(),
i, capitalizationContext, pos, item.type, 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_TO_INDEX =
{
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
//
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// ! " # $ % & ' ( ) * + , - . /
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// @ A B C D E F G H I J K L M N O
-1, 22, 36, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, 31,
// P Q R S T U V W X Y Z [ \ ] ^ _
-1, 27, -1, 8, -1, 30, 29, 13, 32, 18, 23, -1, -1, -1, -1, -1,
// ` a b c d e f g h i j k l m n o
-1, 14, 35, 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, 34, 7, -1, 20, 24, 12, 33, 1, 17, -1, -1, -1, -1, -1,
};
private static int getIndexFromChar(char ch) {
return ch < PATTERN_CHAR_TO_INDEX.length ? PATTERN_CHAR_TO_INDEX[ch & 0xff] : -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 /* also DST_OFFSET */,
/*v*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */,
/*c*/ Calendar.DOW_LOCAL,
/*L*/ Calendar.MONTH,
/*Qq*/ Calendar.MONTH, Calendar.MONTH,
/*V*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */,
/*U*/ Calendar.YEAR,
/*O*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */,
/*Xx*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, Calendar.ZONE_OFFSET /* also DST_OFFSET */,
/*r*/ Calendar.EXTENDED_YEAR /* not an exact match */,
/*bB*/ -1, -1 /* am/pm/midnight/noon and flexible day period fields; no mapping to calendar fields */
/*:*/ -1, /* => no useful mapping to any calendar field, can't use protected Calendar.BASE_FIELD_COUNT */
};
// 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,
/*O*/ DateFormat.TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD,
/*Xx*/ DateFormat.TIMEZONE_ISO_FIELD, DateFormat.TIMEZONE_ISO_LOCAL_FIELD,
/*r*/ DateFormat.RELATED_YEAR,
/*bB*/ DateFormat.AM_PM_MIDNIGHT_NOON_FIELD, DateFormat.FLEXIBLE_DAY_PERIOD_FIELD,
/*(no pattern character defined for this)*/ DateFormat.TIME_SEPARATOR,
};
// 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,
/*O*/ DateFormat.Field.TIME_ZONE,
/*Xx*/ DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE,
/*r*/ DateFormat.Field.RELATED_YEAR,
/*bB*/ DateFormat.Field.AM_PM_MIDNIGHT_NOON, DateFormat.Field.FLEXIBLE_DAY_PERIOD,
/*(no pattern character defined for this)*/ DateFormat.Field.TIME_SEPARATOR,
};
/**
* 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 = getIndexFromChar(ch);
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, ch, 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.
*/
@Deprecated
protected String subFormat(char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
char patternCharToOutput,
Calendar cal)
{
StringBuffer buf = new StringBuffer();
subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, patternCharToOutput, 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.
*/
@Deprecated
@SuppressWarnings("fallthrough")
protected void subFormat(StringBuffer buf,
char ch, int count, int beginOffset,
int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
char patternCharToOutput,
Calendar cal) {
final int maxIntCount = Integer.MAX_VALUE;
final int bufstart = buf.length();
TimeZone tz = cal.getTimeZone();
long date = cal.getTimeInMillis();
String result = null;
int patternCharIndex = getIndexFromChar(ch);
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 = 0;
// Don't get value unless it is useful
if (field >= 0) {
value = (patternCharIndex != DateFormat.RELATED_YEAR)? cal.get(field): cal.getRelatedYear();
}
NumberFormat currentNumberFormat = getNumberFormat(ch);
DateFormatSymbols.CapitalizationContextUsage capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.OTHER;
switch (patternCharIndex) {
case 0: // 'G' - ERA
if ( cal.getType().equals("chinese") || cal.getType().equals("dangi") ) {
// 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(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 if (count == 6 && formatData.shorterWeekdays != null) {
safeAppend(formatData.shorterWeekdays, 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
// formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version
if (count < 5 || formatData.ampmsNarrow == null) {
safeAppend(formatData.ampms, value, buf);
} else {
safeAppend(formatData.ampmsNarrow, 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' - TIMEZONE_FIELD
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_FIELD
if (count < 4) {
// RFC822 format - equivalent to ISO 8601 local offset fixed width format
result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
} else if (count == 5) {
// ISO 8601 extended format
result = tzFormat().format(Style.ISO_EXTENDED_FULL, 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_FIELD
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 29: // 'V' - TIMEZONE_SPECIAL_FIELD
if (count == 1) {
// "V"
result = tzFormat().format(Style.ZONE_ID_SHORT, tz, date);
} else if (count == 2) {
// "VV"
result = tzFormat().format(Style.ZONE_ID, tz, date);
} else if (count == 3) {
// "VVV"
result = tzFormat().format(Style.EXEMPLAR_LOCATION, tz, date);
} else if (count == 4) {
// "VVVV"
result = tzFormat().format(Style.GENERIC_LOCATION, tz, date);
capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ZONE_LONG;
}
buf.append(result);
break;
case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
if (count == 1) {
// "O" - Short Localized GMT format
result = tzFormat().format(Style.LOCALIZED_GMT_SHORT, tz, date);
} else if (count == 4) {
// "OOOO" - Localized GMT format
result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
}
buf.append(result);
break;
case 32: // 'X' - TIMEZONE_ISO_FIELD
if (count == 1) {
// "X" - ISO Basic/Short
result = tzFormat().format(Style.ISO_BASIC_SHORT, tz, date);
} else if (count == 2) {
// "XX" - ISO Basic/Fixed
result = tzFormat().format(Style.ISO_BASIC_FIXED, tz, date);
} else if (count == 3) {
// "XXX" - ISO Extended/Fixed
result = tzFormat().format(Style.ISO_EXTENDED_FIXED, tz, date);
} else if (count == 4) {
// "XXXX" - ISO Basic/Optional second field
result = tzFormat().format(Style.ISO_BASIC_FULL, tz, date);
} else if (count == 5) {
// "XXXXX" - ISO Extended/Optional second field
result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
}
buf.append(result);
break;
case 33: // 'x' - TIMEZONE_ISO_LOCAL_FIELD
if (count == 1) {
// "x" - ISO Local Basic/Short
result = tzFormat().format(Style.ISO_BASIC_LOCAL_SHORT, tz, date);
} else if (count == 2) {
// "x" - ISO Local Basic/Fixed
result = tzFormat().format(Style.ISO_BASIC_LOCAL_FIXED, tz, date);
} else if (count == 3) {
// "xxx" - ISO Local Extended/Fixed
result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FIXED, tz, date);
} else if (count == 4) {
// "xxxx" - ISO Local Basic/Optional second field
result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
} else if (count == 5) {
// "xxxxx" - ISO Local Extended/Optional second field
result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FULL, tz, date);
}
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 if (count == 6 && formatData.standaloneShorterWeekdays != null) {
safeAppend(formatData.standaloneShorterWeekdays, 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 >= 5) {
safeAppend(formatData.narrowQuarters, value/3, buf);
} else 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 >= 5) {
safeAppend(formatData.standaloneNarrowQuarters, value/3, buf);
} else 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 35: // 'b' - am/pm/noon/midnight
{
// Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day.
// For ICU 57 output of "midnight" is temporarily suppressed.
int hour = cal.get(Calendar.HOUR_OF_DAY);
String toAppend = null;
// For "midnight" and "noon":
// Time, as displayed, must be exactly noon or midnight.
// This means minutes and seconds, if present, must be zero.
if ((/*hour == 0 ||*/ hour == 12) &&
(!hasMinute || cal.get(Calendar.MINUTE) == 0) &&
(!hasSecond || cal.get(Calendar.SECOND) == 0)) {
// Stealing am/pm value to use as our array index.
// It works out: am/midnight are both 0, pm/noon are both 1,
// 12 am is 12 midnight, and 12 pm is 12 noon.
value = cal.get(Calendar.AM_PM);
if (count <= 3) {
toAppend = formatData.abbreviatedDayPeriods[value];
} else if (count == 4 || count > 5) {
toAppend = formatData.wideDayPeriods[value];
} else { // count == 5
toAppend = formatData.narrowDayPeriods[value];
}
}
if (toAppend == null) {
// Time isn't exactly midnight or noon (as displayed) or localized string doesn't
// exist for requested period. Fall back to am/pm instead.
// We are passing a different patternCharToOutput because we want to add
// 'b' to field position. This makes this fallback stable when
// there is a data change on locales.
subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'b', cal);
} else {
buf.append(toAppend);
}
break;
}
case 36: // 'B' - flexible day period
{
// TODO: Maybe fetch the DayperiodRules during initialization (instead of at the first
// loading of an instance) if a relevant pattern character (b or B) is used.
DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale());
if (ruleSet == null) {
// Data doesn't exist for the locale we're looking for.
// Fall back to am/pm.
// We are passing a different patternCharToOutput because we want to add
// 'B' to field position. This makes this fallback stable when
// there is a data change on locales.
subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'B', cal);
return;
}
// Get current display time.
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = 0;
int second = 0;
if (hasMinute) { minute = cal.get(Calendar.MINUTE); }
if (hasSecond) { second = cal.get(Calendar.SECOND); }
// Determine day period.
DayPeriodRules.DayPeriod periodType;
if (hour == 0 && minute == 0 && second == 0 && ruleSet.hasMidnight()) {
periodType = DayPeriodRules.DayPeriod.MIDNIGHT;
} else if (hour == 12 && minute == 0 && second == 0 && ruleSet.hasNoon()) {
periodType = DayPeriodRules.DayPeriod.NOON;
} else {
periodType = ruleSet.getDayPeriodForHour(hour);
}
// Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day.
// For ICU 57 output of "midnight" is temporarily suppressed.
// Rule set exists, therefore periodType can't be null.
// Get localized string.
assert(periodType != null);
String toAppend = null;
int index;
if (periodType != DayPeriodRules.DayPeriod.AM && periodType != DayPeriodRules.DayPeriod.PM &&
periodType != DayPeriodRules.DayPeriod.MIDNIGHT) {
index = periodType.ordinal();
if (count <= 3) {
toAppend = formatData.abbreviatedDayPeriods[index]; // i.e. short
} else if (count == 4 || count > 5) {
toAppend = formatData.wideDayPeriods[index];
} else { // count == 5
toAppend = formatData.narrowDayPeriods[index];
}
}
// Fallback schedule:
// Midnight/Noon -> General Periods -> AM/PM.
// Midnight/Noon -> General Periods.
if (toAppend == null &&
(periodType == DayPeriodRules.DayPeriod.MIDNIGHT ||
periodType == DayPeriodRules.DayPeriod.NOON)) {
periodType = ruleSet.getDayPeriodForHour(hour);
index = periodType.ordinal();
if (count <= 3) {
toAppend = formatData.abbreviatedDayPeriods[index]; // i.e. short
} else if (count == 4 || count > 5) {
toAppend = formatData.wideDayPeriods[index];
} else { // count == 5
toAppend = formatData.narrowDayPeriods[index];
}
}
// General Periods -> AM/PM.
if (periodType == DayPeriodRules.DayPeriod.AM ||
periodType == DayPeriodRules.DayPeriod.PM ||
toAppend == null) {
// We are passing a different patternCharToOutput because we want to add
// 'B' to field position. This makes this fallback stable when
// there is a data change on locales.
subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, 'B', cal);
return;
}
else {
buf.append(toAppend);
}
break;
}
case 37: // TIME SEPARATOR (no pattern character currently defined, we should
// not get here but leave support in for future definition.
buf.append(formatData.getTimeSeparatorString());
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 && capitalizationContext != null && buf.length() > bufstart &&
UCharacter.isLowerCase(buf.codePointAt(bufstart))) {
boolean titlecase = false;
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) {
if (capitalizationBrkIter == null) {
// should only happen when deserializing, etc.
capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
}
// Note, the call to UCharacter.toTitleCase below is the only place that
// (the clone of) capitalizationBrkIter is actually used.
BreakIterator mutableCapitalizationBrkIter = (BreakIterator)capitalizationBrkIter.clone();
String firstField = buf.substring(bufstart); // bufstart or beginOffset, should be the same
String firstFieldTitleCase = UCharacter.toTitleCase(locale, firstField, mutableCapitalizationBrkIter,
UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
buf.replace(bufstart, buf.length(), firstFieldTitleCase);
}
}
// Set the FieldPosition (for the first occurrence only)
int outputCharIndex = getIndexFromChar(patternCharToOutput);
if (pos.getBeginIndex() == pos.getEndIndex()) {
if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[outputCharIndex]) {
pos.setBeginIndex(beginOffset);
pos.setEndIndex(beginOffset + buf.length() - bufstart);
} else if (pos.getFieldAttribute() ==
PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[outputCharIndex]) {
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 {
String s = SimpleFormatterImpl.formatRawPattern(monthPattern, 1, 1, array[value]);
appendTo.append(s);
}
}
}
/*
* 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