Path: blob/master/src/java.base/share/classes/java/text/SimpleDateFormat.java
41152 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* (C) Copyright Taligent, Inc. 1996 - All Rights Reserved27* (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved28*29* The original version of this source code and documentation is copyrighted30* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These31* materials are provided under terms of a License Agreement between Taligent32* and Sun. This technology is protected by multiple US and International33* patents. This notice and attribution to Taligent may not be removed.34* Taligent is a registered trademark of Taligent, Inc.35*36*/3738package java.text;3940import java.io.IOException;41import java.io.InvalidObjectException;42import java.io.ObjectInputStream;43import static java.text.DateFormatSymbols.*;44import java.util.Calendar;45import java.util.Date;46import java.util.GregorianCalendar;47import java.util.Locale;48import java.util.Map;49import java.util.SimpleTimeZone;50import java.util.SortedMap;51import java.util.TimeZone;52import java.util.concurrent.ConcurrentHashMap;53import java.util.concurrent.ConcurrentMap;54import sun.util.calendar.CalendarUtils;55import sun.util.calendar.ZoneInfoFile;56import sun.util.locale.provider.LocaleProviderAdapter;57import sun.util.locale.provider.TimeZoneNameUtility;5859/**60* {@code SimpleDateFormat} is a concrete class for formatting and61* parsing dates in a locale-sensitive manner. It allows for formatting62* (date → text), parsing (text → date), and normalization.63*64* <p>65* {@code SimpleDateFormat} allows you to start by choosing66* any user-defined patterns for date-time formatting. However, you67* are encouraged to create a date-time formatter with either68* {@code getTimeInstance}, {@code getDateInstance}, or69* {@code getDateTimeInstance} in {@code DateFormat}. Each70* of these class methods can return a date/time formatter initialized71* with a default format pattern. You may modify the format pattern72* using the {@code applyPattern} methods as desired.73* For more information on using these methods, see74* {@link DateFormat}.75*76* <h2>Date and Time Patterns</h2>77* <p>78* Date and time formats are specified by <em>date and time pattern</em>79* strings.80* Within date and time pattern strings, unquoted letters from81* {@code 'A'} to {@code 'Z'} and from {@code 'a'} to82* {@code 'z'} are interpreted as pattern letters representing the83* components of a date or time string.84* Text can be quoted using single quotes ({@code '}) to avoid85* interpretation.86* {@code "''"} represents a single quote.87* All other characters are not interpreted; they're simply copied into the88* output string during formatting or matched against the input string89* during parsing.90* <p>91* The following pattern letters are defined (all other characters from92* {@code 'A'} to {@code 'Z'} and from {@code 'a'} to93* {@code 'z'} are reserved):94* <blockquote>95* <table class="striped">96* <caption style="display:none">Chart shows pattern letters, date/time component, presentation, and examples.</caption>97* <thead>98* <tr>99* <th scope="col" style="text-align:left">Letter100* <th scope="col" style="text-align:left">Date or Time Component101* <th scope="col" style="text-align:left">Presentation102* <th scope="col" style="text-align:left">Examples103* </thead>104* <tbody>105* <tr>106* <th scope="row">{@code G}107* <td>Era designator108* <td><a href="#text">Text</a>109* <td>{@code AD}110* <tr>111* <th scope="row">{@code y}112* <td>Year113* <td><a href="#year">Year</a>114* <td>{@code 1996}; {@code 96}115* <tr>116* <th scope="row">{@code Y}117* <td>Week year118* <td><a href="#year">Year</a>119* <td>{@code 2009}; {@code 09}120* <tr>121* <th scope="row">{@code M}122* <td>Month in year (context sensitive)123* <td><a href="#month">Month</a>124* <td>{@code July}; {@code Jul}; {@code 07}125* <tr>126* <th scope="row">{@code L}127* <td>Month in year (standalone form)128* <td><a href="#month">Month</a>129* <td>{@code July}; {@code Jul}; {@code 07}130* <tr>131* <th scope="row">{@code w}132* <td>Week in year133* <td><a href="#number">Number</a>134* <td>{@code 27}135* <tr>136* <th scope="row">{@code W}137* <td>Week in month138* <td><a href="#number">Number</a>139* <td>{@code 2}140* <tr>141* <th scope="row">{@code D}142* <td>Day in year143* <td><a href="#number">Number</a>144* <td>{@code 189}145* <tr>146* <th scope="row">{@code d}147* <td>Day in month148* <td><a href="#number">Number</a>149* <td>{@code 10}150* <tr>151* <th scope="row">{@code F}152* <td>Day of week in month153* <td><a href="#number">Number</a>154* <td>{@code 2}155* <tr>156* <th scope="row">{@code E}157* <td>Day name in week158* <td><a href="#text">Text</a>159* <td>{@code Tuesday}; {@code Tue}160* <tr>161* <th scope="row">{@code u}162* <td>Day number of week (1 = Monday, ..., 7 = Sunday)163* <td><a href="#number">Number</a>164* <td>{@code 1}165* <tr>166* <th scope="row">{@code a}167* <td>Am/pm marker168* <td><a href="#text">Text</a>169* <td>{@code PM}170* <tr>171* <th scope="row">{@code H}172* <td>Hour in day (0-23)173* <td><a href="#number">Number</a>174* <td>{@code 0}175* <tr>176* <th scope="row">{@code k}177* <td>Hour in day (1-24)178* <td><a href="#number">Number</a>179* <td>{@code 24}180* <tr>181* <th scope="row">{@code K}182* <td>Hour in am/pm (0-11)183* <td><a href="#number">Number</a>184* <td>{@code 0}185* <tr>186* <th scope="row">{@code h}187* <td>Hour in am/pm (1-12)188* <td><a href="#number">Number</a>189* <td>{@code 12}190* <tr>191* <th scope="row">{@code m}192* <td>Minute in hour193* <td><a href="#number">Number</a>194* <td>{@code 30}195* <tr>196* <th scope="row">{@code s}197* <td>Second in minute198* <td><a href="#number">Number</a>199* <td>{@code 55}200* <tr>201* <th scope="row">{@code S}202* <td>Millisecond203* <td><a href="#number">Number</a>204* <td>{@code 978}205* <tr>206* <th scope="row">{@code z}207* <td>Time zone208* <td><a href="#timezone">General time zone</a>209* <td>{@code Pacific Standard Time}; {@code PST}; {@code GMT-08:00}210* <tr>211* <th scope="row">{@code Z}212* <td>Time zone213* <td><a href="#rfc822timezone">RFC 822 time zone</a>214* <td>{@code -0800}215* <tr>216* <th scope="row">{@code X}217* <td>Time zone218* <td><a href="#iso8601timezone">ISO 8601 time zone</a>219* <td>{@code -08}; {@code -0800}; {@code -08:00}220* </tbody>221* </table>222* </blockquote>223* Pattern letters are usually repeated, as their number determines the224* exact presentation:225* <ul>226* <li><strong><a id="text">Text:</a></strong>227* For formatting, if the number of pattern letters is 4 or more,228* the full form is used; otherwise a short or abbreviated form229* is used if available.230* For parsing, both forms are accepted, independent of the number231* of pattern letters.<br><br></li>232* <li><strong><a id="number">Number:</a></strong>233* For formatting, the number of pattern letters is the minimum234* number of digits, and shorter numbers are zero-padded to this amount.235* For parsing, the number of pattern letters is ignored unless236* it's needed to separate two adjacent fields.<br><br></li>237* <li><strong><a id="year">Year:</a></strong>238* If the formatter's {@link #getCalendar() Calendar} is the Gregorian239* calendar, the following rules are applied.<br>240* <ul>241* <li>For formatting, if the number of pattern letters is 2, the year242* is truncated to 2 digits; otherwise it is interpreted as a243* <a href="#number">number</a>.244* <li>For parsing, if the number of pattern letters is more than 2,245* the year is interpreted literally, regardless of the number of246* digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to247* Jan 11, 12 A.D.248* <li>For parsing with the abbreviated year pattern ("y" or "yy"),249* {@code SimpleDateFormat} must interpret the abbreviated year250* relative to some century. It does this by adjusting dates to be251* within 80 years before and 20 years after the time the {@code SimpleDateFormat}252* instance is created. For example, using a pattern of "MM/dd/yy" and a253* {@code SimpleDateFormat} instance created on Jan 1, 1997, the string254* "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"255* would be interpreted as May 4, 1964.256* During parsing, only strings consisting of exactly two digits, as defined by257* {@link Character#isDigit(char)}, will be parsed into the default century.258* Any other numeric string, such as a one digit string, a three or more digit259* string, or a two digit string that isn't all digits (for example, "-1"), is260* interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the261* same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.262* </ul>263* Otherwise, calendar system specific forms are applied.264* For both formatting and parsing, if the number of pattern265* letters is 4 or more, a calendar specific {@linkplain266* Calendar#LONG long form} is used. Otherwise, a calendar267* specific {@linkplain Calendar#SHORT short or abbreviated form}268* is used.<br>269* <br>270* If week year {@code 'Y'} is specified and the {@linkplain271* #getCalendar() calendar} doesn't support any <a272* href="../util/GregorianCalendar.html#week_year"> week273* years</a>, the calendar year ({@code 'y'}) is used instead. The274* support of week years can be tested with a call to {@link275* DateFormat#getCalendar() getCalendar()}.{@link276* java.util.Calendar#isWeekDateSupported()277* isWeekDateSupported()}.<br><br></li>278* <li><strong><a id="month">Month:</a></strong>279* If the number of pattern letters is 3 or more, the month is280* interpreted as <a href="#text">text</a>; otherwise,281* it is interpreted as a <a href="#number">number</a>.<br>282* <ul>283* <li>Letter <em>M</em> produces context-sensitive month names, such as the284* embedded form of names. Letter <em>M</em> is context-sensitive in the285* sense that when it is used in the standalone pattern, for example,286* "MMMM", it gives the standalone form of a month name and when it is287* used in the pattern containing other field(s), for example, "d MMMM",288* it gives the format form of a month name. For example, January in the289* Catalan language is "de gener" in the format form while it is "gener"290* in the standalone form. In this case, "MMMM" will produce "gener" and291* the month part of the "d MMMM" will produce "de gener". If a292* {@code DateFormatSymbols} has been set explicitly with constructor293* {@link #SimpleDateFormat(String,DateFormatSymbols)} or method {@link294* #setDateFormatSymbols(DateFormatSymbols)}, the month names given by295* the {@code DateFormatSymbols} are used.</li>296* <li>Letter <em>L</em> produces the standalone form of month names.</li>297* </ul>298* <br></li>299* <li><strong><a id="timezone">General time zone:</a></strong>300* Time zones are interpreted as <a href="#text">text</a> if they have301* names. For time zones representing a GMT offset value, the302* following syntax is used:303* <pre>304* <a id="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a>305* {@code GMT} <i>Sign</i> <i>Hours</i> {@code :} <i>Minutes</i>306* <i>Sign:</i> one of307* {@code + -}308* <i>Hours:</i>309* <i>Digit</i>310* <i>Digit</i> <i>Digit</i>311* <i>Minutes:</i>312* <i>Digit</i> <i>Digit</i>313* <i>Digit:</i> one of314* {@code 0 1 2 3 4 5 6 7 8 9}</pre>315* <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between316* 00 and 59. The format is locale independent and digits must be taken317* from the Basic Latin block of the Unicode standard.318* <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also319* accepted.<br><br></li>320* <li><strong><a id="rfc822timezone">RFC 822 time zone:</a></strong>321* For formatting, the RFC 822 4-digit time zone format is used:322*323* <pre>324* <i>RFC822TimeZone:</i>325* <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>326* <i>TwoDigitHours:</i>327* <i>Digit Digit</i></pre>328* <i>TwoDigitHours</i> must be between 00 and 23. Other definitions329* are as for <a href="#timezone">general time zones</a>.330*331* <p>For parsing, <a href="#timezone">general time zones</a> are also332* accepted.333* <li><strong><a id="iso8601timezone">ISO 8601 Time zone:</a></strong>334* The number of pattern letters designates the format for both formatting335* and parsing as follows:336* <pre>337* <i>ISO8601TimeZone:</i>338* <i>OneLetterISO8601TimeZone</i>339* <i>TwoLetterISO8601TimeZone</i>340* <i>ThreeLetterISO8601TimeZone</i>341* <i>OneLetterISO8601TimeZone:</i>342* <i>Sign</i> <i>TwoDigitHours</i>343* {@code Z}344* <i>TwoLetterISO8601TimeZone:</i>345* <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>346* {@code Z}347* <i>ThreeLetterISO8601TimeZone:</i>348* <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i>349* {@code Z}</pre>350* Other definitions are as for <a href="#timezone">general time zones</a> or351* <a href="#rfc822timezone">RFC 822 time zones</a>.352*353* <p>For formatting, if the offset value from GMT is 0, {@code "Z"} is354* produced. If the number of pattern letters is 1, any fraction of an hour355* is ignored. For example, if the pattern is {@code "X"} and the time zone is356* {@code "GMT+05:30"}, {@code "+05"} is produced.357*358* <p>For parsing, {@code "Z"} is parsed as the UTC time zone designator.359* <a href="#timezone">General time zones</a> are <em>not</em> accepted.360*361* <p>If the number of pattern letters is 4 or more, {@link362* IllegalArgumentException} is thrown when constructing a {@code363* SimpleDateFormat} or {@linkplain #applyPattern(String) applying a364* pattern}.365* </ul>366* {@code SimpleDateFormat} also supports <em>localized date and time367* pattern</em> strings. In these strings, the pattern letters described above368* may be replaced with other, locale dependent, pattern letters.369* {@code SimpleDateFormat} does not deal with the localization of text370* other than the pattern letters; that's up to the client of the class.371*372* <h3>Examples</h3>373*374* The following examples show how date and time patterns are interpreted in375* the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time376* in the U.S. Pacific Time time zone.377* <blockquote>378* <table class="striped">379* <caption style="display:none">Examples of date and time patterns interpreted in the U.S. locale</caption>380* <thead>381* <tr>382* <th scope="col" style="text-align:left">Date and Time Pattern383* <th scope="col" style="text-align:left">Result384* </thead>385* <tbody>386* <tr>387* <th scope="row">{@code "yyyy.MM.dd G 'at' HH:mm:ss z"}388* <td>{@code 2001.07.04 AD at 12:08:56 PDT}389* <tr>390* <th scope="row">{@code "EEE, MMM d, ''yy"}391* <td>{@code Wed, Jul 4, '01}392* <tr>393* <th scope="row">{@code "h:mm a"}394* <td>{@code 12:08 PM}395* <tr>396* <th scope="row">{@code "hh 'o''clock' a, zzzz"}397* <td>{@code 12 o'clock PM, Pacific Daylight Time}398* <tr>399* <th scope="row">{@code "K:mm a, z"}400* <td>{@code 0:08 PM, PDT}401* <tr>402* <th scope="row">{@code "yyyyy.MMMMM.dd GGG hh:mm aaa"}403* <td>{@code 02001.July.04 AD 12:08 PM}404* <tr>405* <th scope="row">{@code "EEE, d MMM yyyy HH:mm:ss Z"}406* <td>{@code Wed, 4 Jul 2001 12:08:56 -0700}407* <tr>408* <th scope="row">{@code "yyMMddHHmmssZ"}409* <td>{@code 010704120856-0700}410* <tr>411* <th scope="row">{@code "yyyy-MM-dd'T'HH:mm:ss.SSSZ"}412* <td>{@code 2001-07-04T12:08:56.235-0700}413* <tr>414* <th scope="row">{@code "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"}415* <td>{@code 2001-07-04T12:08:56.235-07:00}416* <tr>417* <th scope="row">{@code "YYYY-'W'ww-u"}418* <td>{@code 2001-W27-3}419* </tbody>420* </table>421* </blockquote>422*423* <h3><a id="synchronization">Synchronization</a></h3>424*425* <p>426* Date formats are not synchronized.427* It is recommended to create separate format instances for each thread.428* If multiple threads access a format concurrently, it must be synchronized429* externally.430* @apiNote Consider using {@link java.time.format.DateTimeFormatter} as an431* immutable and thread-safe alternative.432*433* @see <a href="http://docs.oracle.com/javase/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a>434* @see java.util.Calendar435* @see java.util.TimeZone436* @see DateFormat437* @see DateFormatSymbols438* @see java.time.format.DateTimeFormatter439* @author Mark Davis, Chen-Lieh Huang, Alan Liu440* @since 1.1441*/442public class SimpleDateFormat extends DateFormat {443444// the official serial version ID which says cryptically445// which version we're compatible with446@java.io.Serial447static final long serialVersionUID = 4774881970558875024L;448449// the internal serial version which says which version was written450// - 0 (default) for version up to JDK 1.1.3451// - 1 for version from JDK 1.1.4, which includes a new field452static final int currentSerialVersion = 1;453454/**455* The version of the serialized data on the stream. Possible values:456* <ul>457* <li><b>0</b> or not present on stream: JDK 1.1.3. This version458* has no {@code defaultCenturyStart} on stream.459* <li><b>1</b> JDK 1.1.4 or later. This version adds460* {@code defaultCenturyStart}.461* </ul>462* When streaming out this class, the most recent format463* and the highest allowable {@code serialVersionOnStream}464* is written.465* @serial466* @since 1.1.4467*/468private int serialVersionOnStream = currentSerialVersion;469470/**471* The pattern string of this formatter. This is always a non-localized472* pattern. May not be null. See class documentation for details.473* @serial474*/475private String pattern;476477/**478* Saved numberFormat and pattern.479* @see SimpleDateFormat#checkNegativeNumberExpression480*/481private transient NumberFormat originalNumberFormat;482private transient String originalNumberPattern;483484/**485* The minus sign to be used with format and parse.486*/487private transient char minusSign = '-';488489/**490* True when a negative sign follows a number.491* (True as default in Arabic.)492*/493private transient boolean hasFollowingMinusSign = false;494495/**496* True if standalone form needs to be used.497*/498private transient boolean forceStandaloneForm = false;499500/**501* The compiled pattern.502*/503private transient char[] compiledPattern;504505/**506* Tags for the compiled pattern.507*/508private static final int TAG_QUOTE_ASCII_CHAR = 100;509private static final int TAG_QUOTE_CHARS = 101;510511/**512* Locale dependent digit zero.513* @see #zeroPaddingNumber514* @see java.text.DecimalFormatSymbols#getZeroDigit515*/516private transient char zeroDigit;517518/**519* The symbols used by this formatter for week names, month names,520* etc. May not be null.521* @serial522* @see java.text.DateFormatSymbols523*/524private DateFormatSymbols formatData;525526/**527* We map dates with two-digit years into the century starting at528* {@code defaultCenturyStart}, which may be any date. May529* not be null.530* @serial531* @since 1.1.4532*/533private Date defaultCenturyStart;534535private transient int defaultCenturyStartYear;536537private static final int MILLIS_PER_MINUTE = 60 * 1000;538539// For time zones that have no names, use strings GMT+minutes and540// GMT-minutes. For instance, in France the time zone is GMT+60.541private static final String GMT = "GMT";542543/**544* Cache NumberFormat instances with Locale key.545*/546private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData547= new ConcurrentHashMap<>(3);548549/**550* The Locale used to instantiate this551* {@code SimpleDateFormat}. The value may be null if this object552* has been created by an older {@code SimpleDateFormat} and553* deserialized.554*555* @serial556* @since 1.6557*/558private Locale locale;559560/**561* Indicates whether this {@code SimpleDateFormat} should use562* the DateFormatSymbols. If true, the format and parse methods563* use the DateFormatSymbols values. If false, the format and564* parse methods call Calendar.getDisplayName or565* Calendar.getDisplayNames.566*/567transient boolean useDateFormatSymbols;568569/**570* Constructs a {@code SimpleDateFormat} using the default pattern and571* date format symbols for the default572* {@link java.util.Locale.Category#FORMAT FORMAT} locale.573* <b>Note:</b> This constructor may not support all locales.574* For full coverage, use the factory methods in the {@link DateFormat}575* class.576*/577public SimpleDateFormat() {578this("", Locale.getDefault(Locale.Category.FORMAT));579applyPatternImpl(LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale)580.getDateTimePattern(SHORT, SHORT, calendar));581}582583/**584* Constructs a {@code SimpleDateFormat} using the given pattern and585* the default date format symbols for the default586* {@link java.util.Locale.Category#FORMAT FORMAT} locale.587* <b>Note:</b> This constructor may not support all locales.588* For full coverage, use the factory methods in the {@link DateFormat}589* class.590* <p>This is equivalent to calling591* {@link #SimpleDateFormat(String, Locale)592* SimpleDateFormat(pattern, Locale.getDefault(Locale.Category.FORMAT))}.593*594* @see java.util.Locale#getDefault(java.util.Locale.Category)595* @see java.util.Locale.Category#FORMAT596* @param pattern the pattern describing the date and time format597* @throws NullPointerException if the given pattern is null598* @throws IllegalArgumentException if the given pattern is invalid599*/600public SimpleDateFormat(String pattern)601{602this(pattern, Locale.getDefault(Locale.Category.FORMAT));603}604605/**606* Constructs a {@code SimpleDateFormat} using the given pattern and607* the default date format symbols for the given locale.608* <b>Note:</b> This constructor may not support all locales.609* For full coverage, use the factory methods in the {@link DateFormat}610* class.611*612* @param pattern the pattern describing the date and time format613* @param locale the locale whose date format symbols should be used614* @throws NullPointerException if the given pattern or locale is null615* @throws IllegalArgumentException if the given pattern is invalid616*/617public SimpleDateFormat(String pattern, Locale locale)618{619if (pattern == null || locale == null) {620throw new NullPointerException();621}622623initializeCalendar(locale);624this.pattern = pattern;625this.formatData = DateFormatSymbols.getInstanceRef(locale);626this.locale = locale;627initialize(locale);628}629630/**631* Constructs a {@code SimpleDateFormat} using the given pattern and632* date format symbols.633*634* @param pattern the pattern describing the date and time format635* @param formatSymbols the date format symbols to be used for formatting636* @throws NullPointerException if the given pattern or formatSymbols is null637* @throws IllegalArgumentException if the given pattern is invalid638*/639public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)640{641if (pattern == null || formatSymbols == null) {642throw new NullPointerException();643}644645this.pattern = pattern;646this.formatData = (DateFormatSymbols) formatSymbols.clone();647this.locale = Locale.getDefault(Locale.Category.FORMAT);648initializeCalendar(this.locale);649initialize(this.locale);650useDateFormatSymbols = true;651}652653/* Initialize compiledPattern and numberFormat fields */654private void initialize(Locale loc) {655// Verify and compile the given pattern.656compiledPattern = compile(pattern);657658/* try the cache first */659numberFormat = cachedNumberFormatData.get(loc);660if (numberFormat == null) { /* cache miss */661numberFormat = NumberFormat.getIntegerInstance(loc);662numberFormat.setGroupingUsed(false);663664/* update cache */665cachedNumberFormatData.putIfAbsent(loc, numberFormat);666}667numberFormat = (NumberFormat) numberFormat.clone();668669initializeDefaultCentury();670}671672private void initializeCalendar(Locale loc) {673if (calendar == null) {674assert loc != null;675// The format object must be constructed using the symbols for this zone.676// However, the calendar should use the current default TimeZone.677// If this is not contained in the locale zone strings, then the zone678// will be formatted using generic GMT+/-H:MM nomenclature.679calendar = Calendar.getInstance(loc);680}681}682683/**684* Returns the compiled form of the given pattern. The syntax of685* the compiled pattern is:686* <blockquote>687* CompiledPattern:688* EntryList689* EntryList:690* Entry691* EntryList Entry692* Entry:693* TagField694* TagField data695* TagField:696* Tag Length697* TaggedData698* Tag:699* pattern_char_index700* TAG_QUOTE_CHARS701* Length:702* short_length703* long_length704* TaggedData:705* TAG_QUOTE_ASCII_CHAR ascii_char706*707* </blockquote>708*709* where `short_length' is an 8-bit unsigned integer between 0 and710* 254. `long_length' is a sequence of an 8-bit integer 255 and a711* 32-bit signed integer value which is split into upper and lower712* 16-bit fields in two char's. `pattern_char_index' is an 8-bit713* integer between 0 and 18. `ascii_char' is an 7-bit ASCII714* character value. `data' depends on its Tag value.715* <p>716* If Length is short_length, Tag and short_length are packed in a717* single char, as illustrated below.718* <blockquote>719* char[0] = (Tag << 8) | short_length;720* </blockquote>721*722* If Length is long_length, Tag and 255 are packed in the first723* char and a 32-bit integer, as illustrated below.724* <blockquote>725* char[0] = (Tag << 8) | 255;726* char[1] = (char) (long_length >>> 16);727* char[2] = (char) (long_length & 0xffff);728* </blockquote>729* <p>730* If Tag is a pattern_char_index, its Length is the number of731* pattern characters. For example, if the given pattern is732* "yyyy", Tag is 1 and Length is 4, followed by no data.733* <p>734* If Tag is TAG_QUOTE_CHARS, its Length is the number of char's735* following the TagField. For example, if the given pattern is736* "'o''clock'", Length is 7 followed by a char sequence of737* <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>.738* <p>739* TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII740* character in place of Length. For example, if the given pattern741* is "'o'", the TaggedData entry is742* <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>.743*744* @throws NullPointerException if the given pattern is null745* @throws IllegalArgumentException if the given pattern is invalid746*/747private char[] compile(String pattern) {748int length = pattern.length();749boolean inQuote = false;750StringBuilder compiledCode = new StringBuilder(length * 2);751StringBuilder tmpBuffer = null;752int count = 0, tagcount = 0;753int lastTag = -1, prevTag = -1;754755for (int i = 0; i < length; i++) {756char c = pattern.charAt(i);757758if (c == '\'') {759// '' is treated as a single quote regardless of being760// in a quoted section.761if ((i + 1) < length) {762c = pattern.charAt(i + 1);763if (c == '\'') {764i++;765if (count != 0) {766encode(lastTag, count, compiledCode);767tagcount++;768prevTag = lastTag;769lastTag = -1;770count = 0;771}772if (inQuote) {773tmpBuffer.append(c);774} else {775compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));776}777continue;778}779}780if (!inQuote) {781if (count != 0) {782encode(lastTag, count, compiledCode);783tagcount++;784prevTag = lastTag;785lastTag = -1;786count = 0;787}788if (tmpBuffer == null) {789tmpBuffer = new StringBuilder(length);790} else {791tmpBuffer.setLength(0);792}793inQuote = true;794} else {795int len = tmpBuffer.length();796if (len == 1) {797char ch = tmpBuffer.charAt(0);798if (ch < 128) {799compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch));800} else {801compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | 1));802compiledCode.append(ch);803}804} else {805encode(TAG_QUOTE_CHARS, len, compiledCode);806compiledCode.append(tmpBuffer);807}808inQuote = false;809}810continue;811}812if (inQuote) {813tmpBuffer.append(c);814continue;815}816if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {817if (count != 0) {818encode(lastTag, count, compiledCode);819tagcount++;820prevTag = lastTag;821lastTag = -1;822count = 0;823}824if (c < 128) {825// In most cases, c would be a delimiter, such as ':'.826compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));827} else {828// Take any contiguous non-ASCII alphabet characters and829// put them in a single TAG_QUOTE_CHARS.830int j;831for (j = i + 1; j < length; j++) {832char d = pattern.charAt(j);833if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {834break;835}836}837encode(TAG_QUOTE_CHARS, j - i, compiledCode);838for (; i < j; i++) {839compiledCode.append(pattern.charAt(i));840}841i--;842}843continue;844}845846int tag;847if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {848throw new IllegalArgumentException("Illegal pattern character " +849"'" + c + "'");850}851if (lastTag == -1 || lastTag == tag) {852lastTag = tag;853count++;854continue;855}856encode(lastTag, count, compiledCode);857tagcount++;858prevTag = lastTag;859lastTag = tag;860count = 1;861}862863if (inQuote) {864throw new IllegalArgumentException("Unterminated quote");865}866867if (count != 0) {868encode(lastTag, count, compiledCode);869tagcount++;870prevTag = lastTag;871}872873forceStandaloneForm = (tagcount == 1 && prevTag == PATTERN_MONTH);874875// Copy the compiled pattern to a char array876int len = compiledCode.length();877char[] r = new char[len];878compiledCode.getChars(0, len, r, 0);879return r;880}881882/**883* Encodes the given tag and length and puts encoded char(s) into buffer.884*/885private static void encode(int tag, int length, StringBuilder buffer) {886if (tag == PATTERN_ISO_ZONE && length >= 4) {887throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);888}889if (length < 255) {890buffer.append((char)(tag << 8 | length));891} else {892buffer.append((char)((tag << 8) | 0xff));893buffer.append((char)(length >>> 16));894buffer.append((char)(length & 0xffff));895}896}897898/* Initialize the fields we use to disambiguate ambiguous years. Separate899* so we can call it from readObject().900*/901private void initializeDefaultCentury() {902calendar.setTimeInMillis(System.currentTimeMillis());903calendar.add( Calendar.YEAR, -80 );904parseAmbiguousDatesAsAfter(calendar.getTime());905}906907/* Define one-century window into which to disambiguate dates using908* two-digit years.909*/910private void parseAmbiguousDatesAsAfter(Date startDate) {911defaultCenturyStart = startDate;912calendar.setTime(startDate);913defaultCenturyStartYear = calendar.get(Calendar.YEAR);914}915916/**917* Sets the 100-year period 2-digit years will be interpreted as being in918* to begin on the date the user specifies.919*920* @param startDate During parsing, two digit years will be placed in the range921* {@code startDate} to {@code startDate + 100 years}.922* @see #get2DigitYearStart923* @throws NullPointerException if {@code startDate} is {@code null}.924* @since 1.2925*/926public void set2DigitYearStart(Date startDate) {927parseAmbiguousDatesAsAfter(new Date(startDate.getTime()));928}929930/**931* Returns the beginning date of the 100-year period 2-digit years are interpreted932* as being within.933*934* @return the start of the 100-year period into which two digit years are935* parsed936* @see #set2DigitYearStart937* @since 1.2938*/939public Date get2DigitYearStart() {940return (Date) defaultCenturyStart.clone();941}942943/**944* Formats the given {@code Date} into a date/time string and appends945* the result to the given {@code StringBuffer}.946*947* @param date the date-time value to be formatted into a date-time string.948* @param toAppendTo where the new date-time text is to be appended.949* @param pos keeps track on the position of the field within950* the returned string. For example, given a date-time text951* {@code "1996.07.10 AD at 15:08:56 PDT"}, if the given {@code fieldPosition}952* is {@link DateFormat#YEAR_FIELD}, the begin index and end index of953* {@code fieldPosition} will be set to 0 and 4, respectively.954* Notice that if the same date-time field appears more than once in a955* pattern, the {@code fieldPosition} will be set for the first occurrence956* of that date-time field. For instance, formatting a {@code Date} to the957* date-time string {@code "1 PM PDT (Pacific Daylight Time)"} using the958* pattern {@code "h a z (zzzz)"} and the alignment field959* {@link DateFormat#TIMEZONE_FIELD}, the begin index and end index of960* {@code fieldPosition} will be set to 5 and 8, respectively, for the961* first occurrence of the timezone pattern character {@code 'z'}.962* @return the formatted date-time string.963* @throws NullPointerException if any of the parameters is {@code null}.964*/965@Override966public StringBuffer format(Date date, StringBuffer toAppendTo,967FieldPosition pos)968{969pos.beginIndex = pos.endIndex = 0;970return format(date, toAppendTo, pos.getFieldDelegate());971}972973// Called from Format after creating a FieldDelegate974private StringBuffer format(Date date, StringBuffer toAppendTo,975FieldDelegate delegate) {976// Convert input date to time field list977calendar.setTime(date);978979boolean useDateFormatSymbols = useDateFormatSymbols();980981for (int i = 0; i < compiledPattern.length; ) {982int tag = compiledPattern[i] >>> 8;983int count = compiledPattern[i++] & 0xff;984if (count == 255) {985count = compiledPattern[i++] << 16;986count |= compiledPattern[i++];987}988989switch (tag) {990case TAG_QUOTE_ASCII_CHAR:991toAppendTo.append((char)count);992break;993994case TAG_QUOTE_CHARS:995toAppendTo.append(compiledPattern, i, count);996i += count;997break;998999default:1000subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);1001break;1002}1003}1004return toAppendTo;1005}10061007/**1008* Formats an Object producing an {@code AttributedCharacterIterator}.1009* You can use the returned {@code AttributedCharacterIterator}1010* to build the resulting String, as well as to determine information1011* about the resulting String.1012* <p>1013* Each attribute key of the AttributedCharacterIterator will be of type1014* {@code DateFormat.Field}, with the corresponding attribute value1015* being the same as the attribute key.1016*1017* @throws NullPointerException if obj is null.1018* @throws IllegalArgumentException if the Format cannot format the1019* given object, or if the Format's pattern string is invalid.1020* @param obj The object to format1021* @return AttributedCharacterIterator describing the formatted value.1022* @since 1.41023*/1024@Override1025public AttributedCharacterIterator formatToCharacterIterator(Object obj) {1026StringBuffer sb = new StringBuffer();1027CharacterIteratorFieldDelegate delegate = new1028CharacterIteratorFieldDelegate();10291030if (obj instanceof Date) {1031format((Date)obj, sb, delegate);1032}1033else if (obj instanceof Number) {1034format(new Date(((Number)obj).longValue()), sb, delegate);1035}1036else if (obj == null) {1037throw new NullPointerException(1038"formatToCharacterIterator must be passed non-null object");1039}1040else {1041throw new IllegalArgumentException(1042"Cannot format given Object as a Date");1043}1044return delegate.getIterator(sb.toString());1045}10461047// Map index into pattern character string to Calendar field number1048private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = {1049Calendar.ERA,1050Calendar.YEAR,1051Calendar.MONTH,1052Calendar.DATE,1053Calendar.HOUR_OF_DAY,1054Calendar.HOUR_OF_DAY,1055Calendar.MINUTE,1056Calendar.SECOND,1057Calendar.MILLISECOND,1058Calendar.DAY_OF_WEEK,1059Calendar.DAY_OF_YEAR,1060Calendar.DAY_OF_WEEK_IN_MONTH,1061Calendar.WEEK_OF_YEAR,1062Calendar.WEEK_OF_MONTH,1063Calendar.AM_PM,1064Calendar.HOUR,1065Calendar.HOUR,1066Calendar.ZONE_OFFSET,1067Calendar.ZONE_OFFSET,1068CalendarBuilder.WEEK_YEAR, // Pseudo Calendar field1069CalendarBuilder.ISO_DAY_OF_WEEK, // Pseudo Calendar field1070Calendar.ZONE_OFFSET,1071Calendar.MONTH1072};10731074// Map index into pattern character string to DateFormat field number1075private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {1076DateFormat.ERA_FIELD,1077DateFormat.YEAR_FIELD,1078DateFormat.MONTH_FIELD,1079DateFormat.DATE_FIELD,1080DateFormat.HOUR_OF_DAY1_FIELD,1081DateFormat.HOUR_OF_DAY0_FIELD,1082DateFormat.MINUTE_FIELD,1083DateFormat.SECOND_FIELD,1084DateFormat.MILLISECOND_FIELD,1085DateFormat.DAY_OF_WEEK_FIELD,1086DateFormat.DAY_OF_YEAR_FIELD,1087DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,1088DateFormat.WEEK_OF_YEAR_FIELD,1089DateFormat.WEEK_OF_MONTH_FIELD,1090DateFormat.AM_PM_FIELD,1091DateFormat.HOUR1_FIELD,1092DateFormat.HOUR0_FIELD,1093DateFormat.TIMEZONE_FIELD,1094DateFormat.TIMEZONE_FIELD,1095DateFormat.YEAR_FIELD,1096DateFormat.DAY_OF_WEEK_FIELD,1097DateFormat.TIMEZONE_FIELD,1098DateFormat.MONTH_FIELD1099};11001101// Maps from DecimalFormatSymbols index to Field constant1102private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = {1103Field.ERA,1104Field.YEAR,1105Field.MONTH,1106Field.DAY_OF_MONTH,1107Field.HOUR_OF_DAY1,1108Field.HOUR_OF_DAY0,1109Field.MINUTE,1110Field.SECOND,1111Field.MILLISECOND,1112Field.DAY_OF_WEEK,1113Field.DAY_OF_YEAR,1114Field.DAY_OF_WEEK_IN_MONTH,1115Field.WEEK_OF_YEAR,1116Field.WEEK_OF_MONTH,1117Field.AM_PM,1118Field.HOUR1,1119Field.HOUR0,1120Field.TIME_ZONE,1121Field.TIME_ZONE,1122Field.YEAR,1123Field.DAY_OF_WEEK,1124Field.TIME_ZONE,1125Field.MONTH1126};11271128/**1129* Private member function that does the real date/time formatting.1130*/1131private void subFormat(int patternCharIndex, int count,1132FieldDelegate delegate, StringBuffer buffer,1133boolean useDateFormatSymbols)1134{1135int maxIntCount = Integer.MAX_VALUE;1136String current = null;1137int beginOffset = buffer.length();11381139int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];1140int value;1141if (field == CalendarBuilder.WEEK_YEAR) {1142if (calendar.isWeekDateSupported()) {1143value = calendar.getWeekYear();1144} else {1145// use calendar year 'y' instead1146patternCharIndex = PATTERN_YEAR;1147field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];1148value = calendar.get(field);1149}1150} else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {1151value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));1152} else {1153value = calendar.get(field);1154}11551156int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;1157if (!useDateFormatSymbols && field < Calendar.ZONE_OFFSET1158&& patternCharIndex != PATTERN_MONTH_STANDALONE) {1159current = calendar.getDisplayName(field, style, locale);1160}11611162// Note: zeroPaddingNumber() assumes that maxDigits is either1163// 2 or maxIntCount. If we make any changes to this,1164// zeroPaddingNumber() must be fixed.11651166switch (patternCharIndex) {1167case PATTERN_ERA: // 'G'1168if (useDateFormatSymbols) {1169String[] eras = formatData.getEras();1170if (value < eras.length) {1171current = eras[value];1172}1173}1174if (current == null) {1175current = "";1176}1177break;11781179case PATTERN_WEEK_YEAR: // 'Y'1180case PATTERN_YEAR: // 'y'1181if (calendar instanceof GregorianCalendar) {1182if (count != 2) {1183zeroPaddingNumber(value, count, maxIntCount, buffer);1184} else {1185zeroPaddingNumber(value, 2, 2, buffer);1186} // clip 1996 to 961187} else {1188if (current == null) {1189zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count,1190maxIntCount, buffer);1191}1192}1193break;11941195case PATTERN_MONTH: // 'M' (context sensitive)1196if (useDateFormatSymbols) {1197String[] months;1198if (count >= 4) {1199months = formatData.getMonths();1200current = months[value];1201} else if (count == 3) {1202months = formatData.getShortMonths();1203current = months[value];1204}1205} else {1206if (count < 3) {1207current = null;1208} else if (forceStandaloneForm) {1209current = calendar.getDisplayName(field, style | 0x8000, locale);1210if (current == null) {1211current = calendar.getDisplayName(field, style, locale);1212}1213}1214}1215if (current == null) {1216zeroPaddingNumber(value+1, count, maxIntCount, buffer);1217}1218break;12191220case PATTERN_MONTH_STANDALONE: // 'L'1221assert current == null;1222if (locale == null) {1223String[] months;1224if (count >= 4) {1225months = formatData.getMonths();1226current = months[value];1227} else if (count == 3) {1228months = formatData.getShortMonths();1229current = months[value];1230}1231} else {1232if (count >= 3) {1233current = calendar.getDisplayName(field, style | 0x8000, locale);1234}1235}1236if (current == null) {1237zeroPaddingNumber(value+1, count, maxIntCount, buffer);1238}1239break;12401241case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:591242if (current == null) {1243if (value == 0) {1244zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1,1245count, maxIntCount, buffer);1246} else {1247zeroPaddingNumber(value, count, maxIntCount, buffer);1248}1249}1250break;12511252case PATTERN_DAY_OF_WEEK: // 'E'1253if (useDateFormatSymbols) {1254String[] weekdays;1255if (count >= 4) {1256weekdays = formatData.getWeekdays();1257current = weekdays[value];1258} else { // count < 4, use abbreviated form if exists1259weekdays = formatData.getShortWeekdays();1260current = weekdays[value];1261}1262}1263break;12641265case PATTERN_AM_PM: // 'a'1266if (useDateFormatSymbols) {1267String[] ampm = formatData.getAmPmStrings();1268current = ampm[value];1269}1270break;12711272case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM1273if (current == null) {1274if (value == 0) {1275zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1,1276count, maxIntCount, buffer);1277} else {1278zeroPaddingNumber(value, count, maxIntCount, buffer);1279}1280}1281break;12821283case PATTERN_ZONE_NAME: // 'z'1284if (current == null) {1285if (formatData.locale == null || formatData.isZoneStringsSet) {1286int zoneIndex =1287formatData.getZoneIndex(calendar.getTimeZone().getID());1288if (zoneIndex == -1) {1289value = calendar.get(Calendar.ZONE_OFFSET) +1290calendar.get(Calendar.DST_OFFSET);1291buffer.append(ZoneInfoFile.toCustomID(value));1292} else {1293int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3;1294if (count < 4) {1295// Use the short name1296index++;1297}1298String[][] zoneStrings = formatData.getZoneStringsWrapper();1299buffer.append(zoneStrings[zoneIndex][index]);1300}1301} else {1302TimeZone tz = calendar.getTimeZone();1303boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);1304int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG);1305buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale));1306}1307}1308break;13091310case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)1311value = (calendar.get(Calendar.ZONE_OFFSET) +1312calendar.get(Calendar.DST_OFFSET)) / 60000;13131314int width = 4;1315if (value >= 0) {1316buffer.append('+');1317} else {1318width++;1319}13201321int num = (value / 60) * 100 + (value % 60);1322CalendarUtils.sprintf0d(buffer, num, width);1323break;13241325case PATTERN_ISO_ZONE: // 'X'1326value = calendar.get(Calendar.ZONE_OFFSET)1327+ calendar.get(Calendar.DST_OFFSET);13281329if (value == 0) {1330buffer.append('Z');1331break;1332}13331334value /= 60000;1335if (value >= 0) {1336buffer.append('+');1337} else {1338buffer.append('-');1339value = -value;1340}13411342CalendarUtils.sprintf0d(buffer, value / 60, 2);1343if (count == 1) {1344break;1345}13461347if (count == 3) {1348buffer.append(':');1349}1350CalendarUtils.sprintf0d(buffer, value % 60, 2);1351break;13521353default:1354// case PATTERN_DAY_OF_MONTH: // 'd'1355// case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:591356// case PATTERN_MINUTE: // 'm'1357// case PATTERN_SECOND: // 's'1358// case PATTERN_MILLISECOND: // 'S'1359// case PATTERN_DAY_OF_YEAR: // 'D'1360// case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'1361// case PATTERN_WEEK_OF_YEAR: // 'w'1362// case PATTERN_WEEK_OF_MONTH: // 'W'1363// case PATTERN_HOUR0: // 'K' eg, 11PM + 1 hour =>> 0 AM1364// case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 71365if (current == null) {1366zeroPaddingNumber(value, count, maxIntCount, buffer);1367}1368break;1369} // switch (patternCharIndex)13701371if (current != null) {1372buffer.append(current);1373}13741375int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];1376Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];13771378delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);1379}13801381/**1382* Formats a number with the specified minimum and maximum number of digits.1383*/1384private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)1385{1386// Optimization for 1, 2 and 4 digit numbers. This should1387// cover most cases of formatting date/time related items.1388// Note: This optimization code assumes that maxDigits is1389// either 2 or Integer.MAX_VALUE (maxIntCount in format()).1390try {1391if (zeroDigit == 0) {1392zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();1393}1394if (value >= 0) {1395if (value < 100 && minDigits >= 1 && minDigits <= 2) {1396if (value < 10) {1397if (minDigits == 2) {1398buffer.append(zeroDigit);1399}1400buffer.append((char)(zeroDigit + value));1401} else {1402buffer.append((char)(zeroDigit + value / 10));1403buffer.append((char)(zeroDigit + value % 10));1404}1405return;1406} else if (value >= 1000 && value < 10000) {1407if (minDigits == 4) {1408buffer.append((char)(zeroDigit + value / 1000));1409value %= 1000;1410buffer.append((char)(zeroDigit + value / 100));1411value %= 100;1412buffer.append((char)(zeroDigit + value / 10));1413buffer.append((char)(zeroDigit + value % 10));1414return;1415}1416if (minDigits == 2 && maxDigits == 2) {1417zeroPaddingNumber(value % 100, 2, 2, buffer);1418return;1419}1420}1421}1422} catch (Exception e) {1423}14241425numberFormat.setMinimumIntegerDigits(minDigits);1426numberFormat.setMaximumIntegerDigits(maxDigits);1427numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE);1428}142914301431/**1432* Parses text from a string to produce a {@code Date}.1433* <p>1434* The method attempts to parse text starting at the index given by1435* {@code pos}.1436* If parsing succeeds, then the index of {@code pos} is updated1437* to the index after the last character used (parsing does not necessarily1438* use all characters up to the end of the string), and the parsed1439* date is returned. The updated {@code pos} can be used to1440* indicate the starting point for the next call to this method.1441* If an error occurs, then the index of {@code pos} is not1442* changed, the error index of {@code pos} is set to the index of1443* the character where the error occurred, and null is returned.1444*1445* <p>This parsing operation uses the {@link DateFormat#calendar1446* calendar} to produce a {@code Date}. All of the {@code1447* calendar}'s date-time fields are {@linkplain Calendar#clear()1448* cleared} before parsing, and the {@code calendar}'s default1449* values of the date-time fields are used for any missing1450* date-time information. For example, the year value of the1451* parsed {@code Date} is 1970 with {@link GregorianCalendar} if1452* no year value is given from the parsing operation. The {@code1453* TimeZone} value may be overwritten, depending on the given1454* pattern and the time zone value in {@code text}. Any {@code1455* TimeZone} value that has previously been set by a call to1456* {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need1457* to be restored for further operations.1458*1459* @param text A {@code String}, part of which should be parsed.1460* @param pos A {@code ParsePosition} object with index and error1461* index information as described above.1462* @return A {@code Date} parsed from the string. In case of1463* error, returns null.1464* @throws NullPointerException if {@code text} or {@code pos} is null.1465*/1466@Override1467public Date parse(String text, ParsePosition pos)1468{1469checkNegativeNumberExpression();14701471int start = pos.index;1472int oldStart = start;1473int textLength = text.length();14741475boolean[] ambiguousYear = {false};14761477CalendarBuilder calb = new CalendarBuilder();14781479for (int i = 0; i < compiledPattern.length; ) {1480int tag = compiledPattern[i] >>> 8;1481int count = compiledPattern[i++] & 0xff;1482if (count == 255) {1483count = compiledPattern[i++] << 16;1484count |= compiledPattern[i++];1485}14861487switch (tag) {1488case TAG_QUOTE_ASCII_CHAR:1489if (start >= textLength || text.charAt(start) != (char)count) {1490pos.index = oldStart;1491pos.errorIndex = start;1492return null;1493}1494start++;1495break;14961497case TAG_QUOTE_CHARS:1498while (count-- > 0) {1499if (start >= textLength || text.charAt(start) != compiledPattern[i++]) {1500pos.index = oldStart;1501pos.errorIndex = start;1502return null;1503}1504start++;1505}1506break;15071508default:1509// Peek the next pattern to determine if we need to1510// obey the number of pattern letters for1511// parsing. It's required when parsing contiguous1512// digit text (e.g., "20010704") with a pattern which1513// has no delimiters between fields, like "yyyyMMdd".1514boolean obeyCount = false;15151516// In Arabic, a minus sign for a negative number is put after1517// the number. Even in another locale, a minus sign can be1518// put after a number using DateFormat.setNumberFormat().1519// If both the minus sign and the field-delimiter are '-',1520// subParse() needs to determine whether a '-' after a number1521// in the given text is a delimiter or is a minus sign for the1522// preceding number. We give subParse() a clue based on the1523// information in compiledPattern.1524boolean useFollowingMinusSignAsDelimiter = false;15251526if (i < compiledPattern.length) {1527int nextTag = compiledPattern[i] >>> 8;1528int nextCount = compiledPattern[i] & 0xff;1529obeyCount = shouldObeyCount(nextTag, nextCount);15301531if (hasFollowingMinusSign &&1532(nextTag == TAG_QUOTE_ASCII_CHAR ||1533nextTag == TAG_QUOTE_CHARS)) {15341535if (nextTag != TAG_QUOTE_ASCII_CHAR) {1536nextCount = compiledPattern[i+1];1537}15381539if (nextCount == minusSign) {1540useFollowingMinusSignAsDelimiter = true;1541}1542}1543}1544start = subParse(text, start, tag, count, obeyCount,1545ambiguousYear, pos,1546useFollowingMinusSignAsDelimiter, calb);1547if (start < 0) {1548pos.index = oldStart;1549return null;1550}1551}1552}15531554// At this point the fields of Calendar have been set. Calendar1555// will fill in default values for missing fields when the time1556// is computed.15571558pos.index = start;15591560Date parsedDate;1561try {1562parsedDate = calb.establish(calendar).getTime();1563// If the year value is ambiguous,1564// then the two-digit year == the default start year1565if (ambiguousYear[0]) {1566if (parsedDate.before(defaultCenturyStart)) {1567parsedDate = calb.addYear(100).establish(calendar).getTime();1568}1569}1570}1571// An IllegalArgumentException will be thrown by Calendar.getTime()1572// if any fields are out of range, e.g., MONTH == 17.1573catch (IllegalArgumentException e) {1574pos.errorIndex = start;1575pos.index = oldStart;1576return null;1577}15781579return parsedDate;1580}15811582/* If the next tag/pattern is a <Numeric_Field> then the parser1583* should consider the count of digits while parsing the contigous digits1584* for the current tag/pattern1585*/1586private boolean shouldObeyCount(int tag, int count) {1587switch (tag) {1588case PATTERN_MONTH:1589case PATTERN_MONTH_STANDALONE:1590return count <= 2;1591case PATTERN_YEAR:1592case PATTERN_DAY_OF_MONTH:1593case PATTERN_HOUR_OF_DAY1:1594case PATTERN_HOUR_OF_DAY0:1595case PATTERN_MINUTE:1596case PATTERN_SECOND:1597case PATTERN_MILLISECOND:1598case PATTERN_DAY_OF_YEAR:1599case PATTERN_DAY_OF_WEEK_IN_MONTH:1600case PATTERN_WEEK_OF_YEAR:1601case PATTERN_WEEK_OF_MONTH:1602case PATTERN_HOUR1:1603case PATTERN_HOUR0:1604case PATTERN_WEEK_YEAR:1605case PATTERN_ISO_DAY_OF_WEEK:1606return true;1607default:1608return false;1609}1610}16111612/**1613* Private code-size reduction function used by subParse.1614* @param text the time text being parsed.1615* @param start where to start parsing.1616* @param field the date field being parsed.1617* @param data the string array to parsed.1618* @return the new start position if matching succeeded; a negative number1619* indicating matching failure, otherwise.1620*/1621private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)1622{1623int i = 0;1624int count = data.length;16251626if (field == Calendar.DAY_OF_WEEK) {1627i = 1;1628}16291630// There may be multiple strings in the data[] array which begin with1631// the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).1632// We keep track of the longest match, and return that. Note that this1633// unfortunately requires us to test all array elements.1634int bestMatchLength = 0, bestMatch = -1;1635for (; i<count; ++i)1636{1637int length = data[i].length();1638// Always compare if we have no match yet; otherwise only compare1639// against potentially better matches (longer strings).1640if (length > bestMatchLength &&1641text.regionMatches(true, start, data[i], 0, length))1642{1643bestMatch = i;1644bestMatchLength = length;1645}1646}1647if (bestMatch >= 0)1648{1649calb.set(field, bestMatch);1650return start + bestMatchLength;1651}1652return -start;1653}16541655/**1656* Performs the same thing as matchString(String, int, int,1657* String[]). This method takes a Map<String, Integer> instead of1658* String[].1659*/1660private int matchString(String text, int start, int field,1661Map<String,Integer> data, CalendarBuilder calb) {1662if (data != null) {1663// TODO: make this default when it's in the spec.1664if (data instanceof SortedMap) {1665for (String name : data.keySet()) {1666if (text.regionMatches(true, start, name, 0, name.length())) {1667calb.set(field, data.get(name));1668return start + name.length();1669}1670}1671return -start;1672}16731674String bestMatch = null;16751676for (String name : data.keySet()) {1677int length = name.length();1678if (bestMatch == null || length > bestMatch.length()) {1679if (text.regionMatches(true, start, name, 0, length)) {1680bestMatch = name;1681}1682}1683}16841685if (bestMatch != null) {1686calb.set(field, data.get(bestMatch));1687return start + bestMatch.length();1688}1689}1690return -start;1691}16921693private int matchZoneString(String text, int start, String[] zoneNames) {1694for (int i = 1; i <= 4; ++i) {1695// Checking long and short zones [1 & 2],1696// and long and short daylight [3 & 4].1697String zoneName = zoneNames[i];1698if (zoneName.isEmpty()) {1699// fill in by retrieving single name1700zoneName = TimeZoneNameUtility.retrieveDisplayName(1701zoneNames[0], i >= 3, i % 2, locale);1702zoneNames[i] = zoneName;1703}1704if (text.regionMatches(true, start,1705zoneName, 0, zoneName.length())) {1706return i;1707}1708}1709return -1;1710}17111712private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex,1713String[][] zoneStrings) {1714int index = standardIndex + 2;1715String zoneName = zoneStrings[zoneIndex][index];1716if (text.regionMatches(true, start,1717zoneName, 0, zoneName.length())) {1718return true;1719}1720return false;1721}17221723/**1724* find time zone 'text' matched zoneStrings and set to internal1725* calendar.1726*/1727private int subParseZoneString(String text, int start, CalendarBuilder calb) {1728boolean useSameName = false; // true if standard and daylight time use the same abbreviation.1729TimeZone currentTimeZone = getTimeZone();17301731// At this point, check for named time zones by looking through1732// the locale data from the TimeZoneNames strings.1733// Want to be able to parse both short and long forms.1734int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID());1735TimeZone tz = null;1736String[][] zoneStrings = formatData.getZoneStringsWrapper();1737String[] zoneNames = null;1738int nameIndex = 0;1739if (zoneIndex != -1) {1740zoneNames = zoneStrings[zoneIndex];1741if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {1742if (nameIndex <= 2) {1743// Check if the standard name (abbr) and the daylight name are the same.1744useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);1745}1746tz = TimeZone.getTimeZone(zoneNames[0]);1747}1748}1749if (tz == null) {1750zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID());1751if (zoneIndex != -1) {1752zoneNames = zoneStrings[zoneIndex];1753if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {1754if (nameIndex <= 2) {1755useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);1756}1757tz = TimeZone.getTimeZone(zoneNames[0]);1758}1759}1760}17611762if (tz == null) {1763int len = zoneStrings.length;1764for (int i = 0; i < len; i++) {1765zoneNames = zoneStrings[i];1766if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {1767if (nameIndex <= 2) {1768useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);1769}1770tz = TimeZone.getTimeZone(zoneNames[0]);1771break;1772}1773}1774}1775if (tz != null) { // Matched any ?1776if (!tz.equals(currentTimeZone)) {1777setTimeZone(tz);1778}1779// If the time zone matched uses the same name1780// (abbreviation) for both standard and daylight time,1781// let the time zone in the Calendar decide which one.1782//1783// Also if tz.getDSTSaving() returns 0 for DST, use tz to1784// determine the local time. (6645292)1785int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0;1786if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) {1787calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount);1788}1789return (start + zoneNames[nameIndex].length());1790}1791return -start;1792}17931794/**1795* Parses numeric forms of time zone offset, such as "hh:mm", and1796* sets calb to the parsed value.1797*1798* @param text the text to be parsed1799* @param start the character position to start parsing1800* @param sign 1: positive; -1: negative1801* @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's1802* @param colon true - colon required between hh and mm; false - no colon required1803* @param calb a CalendarBuilder in which the parsed value is stored1804* @return updated parsed position, or its negative value to indicate a parsing error1805*/1806private int subParseNumericZone(String text, int start, int sign, int count,1807boolean colon, CalendarBuilder calb) {1808int index = start;18091810parse:1811try {1812char c = text.charAt(index++);1813// Parse hh1814int hours;1815if (!isDigit(c)) {1816break parse;1817}1818hours = c - '0';1819c = text.charAt(index++);1820if (isDigit(c)) {1821hours = hours * 10 + (c - '0');1822} else {1823// If no colon in RFC 822 or 'X' (ISO), two digits are1824// required.1825if (count > 0 || !colon) {1826break parse;1827}1828--index;1829}1830if (hours > 23) {1831break parse;1832}1833int minutes = 0;1834if (count != 1) {1835// Proceed with parsing mm1836c = text.charAt(index++);1837if (colon) {1838if (c != ':') {1839break parse;1840}1841c = text.charAt(index++);1842}1843if (!isDigit(c)) {1844break parse;1845}1846minutes = c - '0';1847c = text.charAt(index++);1848if (!isDigit(c)) {1849break parse;1850}1851minutes = minutes * 10 + (c - '0');1852if (minutes > 59) {1853break parse;1854}1855}1856minutes += hours * 60;1857calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign)1858.set(Calendar.DST_OFFSET, 0);1859return index;1860} catch (IndexOutOfBoundsException e) {1861}1862return 1 - index; // -(index - 1)1863}18641865private boolean isDigit(char c) {1866return c >= '0' && c <= '9';1867}18681869/**1870* Private member function that converts the parsed date strings into1871* timeFields. Returns -start (for ParsePosition) if failed.1872* @param text the time text to be parsed.1873* @param start where to start parsing.1874* @param patternCharIndex the index of the pattern character.1875* @param count the count of a pattern character.1876* @param obeyCount if true, then the next field directly abuts this one,1877* and we should use the count to know when to stop parsing.1878* @param ambiguousYear return parameter; upon return, if ambiguousYear[0]1879* is true, then a two-digit year was parsed and may need to be readjusted.1880* @param origPos origPos.errorIndex is used to return an error index1881* at which a parse error occurred, if matching failure occurs.1882* @return the new start position if matching succeeded; -1 indicating1883* matching failure, otherwise. In case matching failure occurred,1884* an error index is set to origPos.errorIndex.1885*/1886private int subParse(String text, int start, int patternCharIndex, int count,1887boolean obeyCount, boolean[] ambiguousYear,1888ParsePosition origPos,1889boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {1890Number number;1891int value = 0;1892ParsePosition pos = new ParsePosition(0);1893pos.index = start;1894if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {1895// use calendar year 'y' instead1896patternCharIndex = PATTERN_YEAR;1897}1898int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];18991900// If there are any spaces here, skip over them. If we hit the end1901// of the string, then fail.1902for (;;) {1903if (pos.index >= text.length()) {1904origPos.errorIndex = start;1905return -1;1906}1907char c = text.charAt(pos.index);1908if (c != ' ' && c != '\t') {1909break;1910}1911++pos.index;1912}1913// Remember the actual start index1914int actualStart = pos.index;19151916parsing:1917{1918// We handle a few special cases here where we need to parse1919// a number value. We handle further, more generic cases below. We need1920// to handle some of them here because some fields require extra processing on1921// the parsed value.1922if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||1923patternCharIndex == PATTERN_HOUR1 ||1924(patternCharIndex == PATTERN_MONTH && count <= 2) ||1925(patternCharIndex == PATTERN_MONTH_STANDALONE && count <= 2) ||1926patternCharIndex == PATTERN_YEAR ||1927patternCharIndex == PATTERN_WEEK_YEAR) {1928// It would be good to unify this with the obeyCount logic below,1929// but that's going to be difficult.1930if (obeyCount) {1931if ((start+count) > text.length()) {1932break parsing;1933}1934number = numberFormat.parse(text.substring(0, start+count), pos);1935} else {1936number = numberFormat.parse(text, pos);1937}1938if (number == null) {1939if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) {1940break parsing;1941}1942} else {1943value = number.intValue();19441945if (useFollowingMinusSignAsDelimiter && (value < 0) &&1946(((pos.index < text.length()) &&1947(text.charAt(pos.index) != minusSign)) ||1948((pos.index == text.length()) &&1949(text.charAt(pos.index-1) == minusSign)))) {1950value = -value;1951pos.index--;1952}1953}1954}19551956boolean useDateFormatSymbols = useDateFormatSymbols();19571958int index;1959switch (patternCharIndex) {1960case PATTERN_ERA: // 'G'1961if (useDateFormatSymbols) {1962if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) {1963return index;1964}1965} else {1966Map<String, Integer> map = getDisplayNamesMap(field, locale);1967if ((index = matchString(text, start, field, map, calb)) > 0) {1968return index;1969}1970}1971break parsing;19721973case PATTERN_WEEK_YEAR: // 'Y'1974case PATTERN_YEAR: // 'y'1975if (!(calendar instanceof GregorianCalendar)) {1976// calendar might have text representations for year values,1977// such as "\u5143" in JapaneseImperialCalendar.1978int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;1979Map<String, Integer> map = calendar.getDisplayNames(field, style, locale);1980if (map != null) {1981if ((index = matchString(text, start, field, map, calb)) > 0) {1982return index;1983}1984}1985calb.set(field, value);1986return pos.index;1987}19881989// If there are 3 or more YEAR pattern characters, this indicates1990// that the year value is to be treated literally, without any1991// two-digit year adjustments (e.g., from "01" to 2001). Otherwise1992// we made adjustments to place the 2-digit year in the proper1993// century, for parsed strings from "00" to "99". Any other string1994// is treated literally: "2250", "-1", "1", "002".1995if (count <= 2 && (pos.index - actualStart) == 21996&& Character.isDigit(text.charAt(actualStart))1997&& Character.isDigit(text.charAt(actualStart + 1))) {1998// Assume for example that the defaultCenturyStart is 6/18/1903.1999// This means that two-digit years will be forced into the range2000// 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 022001// correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond2002// to 1904, 1905, etc. If the year is 03, then it is 2003 if the2003// other fields specify a date before 6/18, or 1903 if they specify a2004// date afterwards. As a result, 03 is an ambiguous year. All other2005// two-digit years are unambiguous.2006int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;2007ambiguousYear[0] = value == ambiguousTwoDigitYear;2008value += (defaultCenturyStartYear/100)*100 +2009(value < ambiguousTwoDigitYear ? 100 : 0);2010}2011calb.set(field, value);2012return pos.index;20132014case PATTERN_MONTH: // 'M'2015if (count <= 2) // i.e., M or MM.2016{2017// Don't want to parse the month if it is a string2018// while pattern uses numeric style: M or MM.2019// [We computed 'value' above.]2020calb.set(Calendar.MONTH, value - 1);2021return pos.index;2022}20232024if (useDateFormatSymbols) {2025// count >= 3 // i.e., MMM or MMMM2026// Want to be able to parse both short and long forms.2027// Try count == 4 first:2028int newStart;2029if ((newStart = matchString(text, start, Calendar.MONTH,2030formatData.getMonths(), calb)) > 0) {2031return newStart;2032}2033// count == 4 failed, now try count == 32034if ((index = matchString(text, start, Calendar.MONTH,2035formatData.getShortMonths(), calb)) > 0) {2036return index;2037}2038} else {2039Map<String, Integer> map = getDisplayContextNamesMap(field, locale);2040if ((index = matchString(text, start, field, map, calb)) > 0) {2041return index;2042}2043}2044break parsing;20452046case PATTERN_MONTH_STANDALONE: // 'L'2047if (count <= 2) {2048// Don't want to parse the month if it is a string2049// while pattern uses numeric style: L or LL2050//[we computed 'value' above.]2051calb.set(Calendar.MONTH, value - 1);2052return pos.index;2053}2054Map<String, Integer> maps = getDisplayNamesMap(field, locale);2055if ((index = matchString(text, start, field, maps, calb)) > 0) {2056return index;2057}2058break parsing;20592060case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:592061if (!isLenient()) {2062// Validate the hour value in non-lenient2063if (value < 1 || value > 24) {2064break parsing;2065}2066}2067// [We computed 'value' above.]2068if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) {2069value = 0;2070}2071calb.set(Calendar.HOUR_OF_DAY, value);2072return pos.index;20732074case PATTERN_DAY_OF_WEEK: // 'E'2075{2076if (useDateFormatSymbols) {2077// Want to be able to parse both short and long forms.2078// Try count == 4 (DDDD) first:2079int newStart;2080if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,2081formatData.getWeekdays(), calb)) > 0) {2082return newStart;2083}2084// DDDD failed, now try DDD2085if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,2086formatData.getShortWeekdays(), calb)) > 0) {2087return index;2088}2089} else {2090int[] styles = { Calendar.LONG, Calendar.SHORT };2091for (int style : styles) {2092Map<String,Integer> map = calendar.getDisplayNames(field, style, locale);2093if ((index = matchString(text, start, field, map, calb)) > 0) {2094return index;2095}2096}2097}2098}2099break parsing;21002101case PATTERN_AM_PM: // 'a'2102if (useDateFormatSymbols) {2103if ((index = matchString(text, start, Calendar.AM_PM,2104formatData.getAmPmStrings(), calb)) > 0) {2105return index;2106}2107} else {2108Map<String,Integer> map = getDisplayNamesMap(field, locale);2109if ((index = matchString(text, start, field, map, calb)) > 0) {2110return index;2111}2112}2113break parsing;21142115case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM2116if (!isLenient()) {2117// Validate the hour value in non-lenient2118if (value < 1 || value > 12) {2119break parsing;2120}2121}2122// [We computed 'value' above.]2123if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) {2124value = 0;2125}2126calb.set(Calendar.HOUR, value);2127return pos.index;21282129case PATTERN_ZONE_NAME: // 'z'2130case PATTERN_ZONE_VALUE: // 'Z'2131{2132int sign = 0;2133try {2134char c = text.charAt(pos.index);2135if (c == '+') {2136sign = 1;2137} else if (c == '-') {2138sign = -1;2139}2140if (sign == 0) {2141// Try parsing a custom time zone "GMT+hh:mm" or "GMT".2142if ((c == 'G' || c == 'g')2143&& (text.length() - start) >= GMT.length()2144&& text.regionMatches(true, start, GMT, 0, GMT.length())) {2145pos.index = start + GMT.length();21462147if ((text.length() - pos.index) > 0) {2148c = text.charAt(pos.index);2149if (c == '+') {2150sign = 1;2151} else if (c == '-') {2152sign = -1;2153}2154}21552156if (sign == 0) { /* "GMT" without offset */2157calb.set(Calendar.ZONE_OFFSET, 0)2158.set(Calendar.DST_OFFSET, 0);2159return pos.index;2160}21612162// Parse the rest as "hh:mm"2163int i = subParseNumericZone(text, ++pos.index,2164sign, 0, true, calb);2165if (i > 0) {2166return i;2167}2168pos.index = -i;2169} else {2170// Try parsing the text as a time zone2171// name or abbreviation.2172int i = subParseZoneString(text, pos.index, calb);2173if (i > 0) {2174return i;2175}2176pos.index = -i;2177}2178} else {2179// Parse the rest as "hhmm" (RFC 822)2180int i = subParseNumericZone(text, ++pos.index,2181sign, 0, false, calb);2182if (i > 0) {2183return i;2184}2185pos.index = -i;2186}2187} catch (IndexOutOfBoundsException e) {2188}2189}2190break parsing;21912192case PATTERN_ISO_ZONE: // 'X'2193{2194if ((text.length() - pos.index) <= 0) {2195break parsing;2196}21972198int sign;2199char c = text.charAt(pos.index);2200if (c == 'Z') {2201calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0);2202return ++pos.index;2203}22042205// parse text as "+/-hh[[:]mm]" based on count2206if (c == '+') {2207sign = 1;2208} else if (c == '-') {2209sign = -1;2210} else {2211++pos.index;2212break parsing;2213}2214int i = subParseNumericZone(text, ++pos.index, sign, count,2215count == 3, calb);2216if (i > 0) {2217return i;2218}2219pos.index = -i;2220}2221break parsing;22222223default:2224// case PATTERN_DAY_OF_MONTH: // 'd'2225// case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:592226// case PATTERN_MINUTE: // 'm'2227// case PATTERN_SECOND: // 's'2228// case PATTERN_MILLISECOND: // 'S'2229// case PATTERN_DAY_OF_YEAR: // 'D'2230// case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'2231// case PATTERN_WEEK_OF_YEAR: // 'w'2232// case PATTERN_WEEK_OF_MONTH: // 'W'2233// case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM2234// case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field);22352236// Handle "generic" fields2237if (obeyCount) {2238if ((start+count) > text.length()) {2239break parsing;2240}2241number = numberFormat.parse(text.substring(0, start+count), pos);2242} else {2243number = numberFormat.parse(text, pos);2244}2245if (number != null) {2246value = number.intValue();22472248if (useFollowingMinusSignAsDelimiter && (value < 0) &&2249(((pos.index < text.length()) &&2250(text.charAt(pos.index) != minusSign)) ||2251((pos.index == text.length()) &&2252(text.charAt(pos.index-1) == minusSign)))) {2253value = -value;2254pos.index--;2255}22562257calb.set(field, value);2258return pos.index;2259}2260break parsing;2261}2262}22632264// Parsing failed.2265origPos.errorIndex = pos.index;2266return -1;2267}22682269/**2270* Returns true if the DateFormatSymbols has been set explicitly or locale2271* is null.2272*/2273private boolean useDateFormatSymbols() {2274return useDateFormatSymbols || locale == null;2275}22762277/**2278* Translates a pattern, mapping each character in the from string to the2279* corresponding character in the to string.2280*2281* @throws IllegalArgumentException if the given pattern is invalid2282*/2283private String translatePattern(String pattern, String from, String to) {2284StringBuilder result = new StringBuilder();2285boolean inQuote = false;2286for (int i = 0; i < pattern.length(); ++i) {2287char c = pattern.charAt(i);2288if (inQuote) {2289if (c == '\'') {2290inQuote = false;2291}2292}2293else {2294if (c == '\'') {2295inQuote = true;2296} else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {2297int ci = from.indexOf(c);2298if (ci >= 0) {2299// patternChars is longer than localPatternChars due2300// to serialization compatibility. The pattern letters2301// unsupported by localPatternChars pass through.2302if (ci < to.length()) {2303c = to.charAt(ci);2304}2305} else {2306throw new IllegalArgumentException("Illegal pattern " +2307" character '" +2308c + "'");2309}2310}2311}2312result.append(c);2313}2314if (inQuote) {2315throw new IllegalArgumentException("Unfinished quote in pattern");2316}2317return result.toString();2318}23192320/**2321* Returns a pattern string describing this date format.2322*2323* @return a pattern string describing this date format.2324*/2325public String toPattern() {2326return pattern;2327}23282329/**2330* Returns a localized pattern string describing this date format.2331*2332* @return a localized pattern string describing this date format.2333*/2334public String toLocalizedPattern() {2335return translatePattern(pattern,2336DateFormatSymbols.patternChars,2337formatData.getLocalPatternChars());2338}23392340/**2341* Applies the given pattern string to this date format.2342*2343* @param pattern the new date and time pattern for this date format2344* @throws NullPointerException if the given pattern is null2345* @throws IllegalArgumentException if the given pattern is invalid2346*/2347public void applyPattern(String pattern)2348{2349applyPatternImpl(pattern);2350}23512352private void applyPatternImpl(String pattern) {2353compiledPattern = compile(pattern);2354this.pattern = pattern;2355}23562357/**2358* Applies the given localized pattern string to this date format.2359*2360* @param pattern a String to be mapped to the new date and time format2361* pattern for this format2362* @throws NullPointerException if the given pattern is null2363* @throws IllegalArgumentException if the given pattern is invalid2364*/2365public void applyLocalizedPattern(String pattern) {2366String p = translatePattern(pattern,2367formatData.getLocalPatternChars(),2368DateFormatSymbols.patternChars);2369compiledPattern = compile(p);2370this.pattern = p;2371}23722373/**2374* Gets a copy of the date and time format symbols of this date format.2375*2376* @return the date and time format symbols of this date format2377* @see #setDateFormatSymbols2378*/2379public DateFormatSymbols getDateFormatSymbols()2380{2381return (DateFormatSymbols)formatData.clone();2382}23832384/**2385* Sets the date and time format symbols of this date format.2386*2387* @param newFormatSymbols the new date and time format symbols2388* @throws NullPointerException if the given newFormatSymbols is null2389* @see #getDateFormatSymbols2390*/2391public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)2392{2393this.formatData = (DateFormatSymbols)newFormatSymbols.clone();2394useDateFormatSymbols = true;2395}23962397/**2398* Creates a copy of this {@code SimpleDateFormat}. This also2399* clones the format's date format symbols.2400*2401* @return a clone of this {@code SimpleDateFormat}2402*/2403@Override2404public Object clone() {2405SimpleDateFormat other = (SimpleDateFormat) super.clone();2406other.formatData = (DateFormatSymbols) formatData.clone();2407return other;2408}24092410/**2411* Returns the hash code value for this {@code SimpleDateFormat} object.2412*2413* @return the hash code value for this {@code SimpleDateFormat} object.2414*/2415@Override2416public int hashCode()2417{2418return pattern.hashCode();2419// just enough fields for a reasonable distribution2420}24212422/**2423* Compares the given object with this {@code SimpleDateFormat} for2424* equality.2425*2426* @return true if the given object is equal to this2427* {@code SimpleDateFormat}2428*/2429@Override2430public boolean equals(Object obj)2431{2432if (!super.equals(obj)) {2433return false; // super does class check2434}2435SimpleDateFormat that = (SimpleDateFormat) obj;2436return (pattern.equals(that.pattern)2437&& formatData.equals(that.formatData));2438}24392440private static final int[] REST_OF_STYLES = {2441Calendar.SHORT_STANDALONE, Calendar.LONG_FORMAT, Calendar.LONG_STANDALONE,2442};2443private Map<String, Integer> getDisplayNamesMap(int field, Locale locale) {2444Map<String, Integer> map = calendar.getDisplayNames(field, Calendar.SHORT_FORMAT, locale);2445// Get all SHORT and LONG styles (avoid NARROW styles).2446for (int style : REST_OF_STYLES) {2447Map<String, Integer> m = calendar.getDisplayNames(field, style, locale);2448if (m != null) {2449map.putAll(m);2450}2451}2452return map;2453}24542455/**2456* Obtains display names map, taking the context into account. Currently only2457* the month name pattern 'M' is context dependent.2458*/2459private Map<String, Integer> getDisplayContextNamesMap(int field, Locale locale) {2460Map<String, Integer> map = calendar.getDisplayNames(field,2461forceStandaloneForm ? Calendar.SHORT_STANDALONE : Calendar.SHORT_FORMAT, locale);2462// Get the LONG style2463Map<String, Integer> m = calendar.getDisplayNames(field,2464forceStandaloneForm ? Calendar.LONG_STANDALONE : Calendar.LONG_FORMAT, locale);2465if (m != null) {2466map.putAll(m);2467}2468return map;2469}24702471/**2472* After reading an object from the input stream, the format2473* pattern in the object is verified.2474*2475* @throws InvalidObjectException if the pattern is invalid2476*/2477@java.io.Serial2478private void readObject(ObjectInputStream stream)2479throws IOException, ClassNotFoundException {2480stream.defaultReadObject();24812482try {2483compiledPattern = compile(pattern);2484} catch (Exception e) {2485throw new InvalidObjectException("invalid pattern");2486}24872488if (serialVersionOnStream < 1) {2489// didn't have defaultCenturyStart field2490initializeDefaultCentury();2491}2492else {2493// fill in dependent transient field2494parseAmbiguousDatesAsAfter(defaultCenturyStart);2495}2496serialVersionOnStream = currentSerialVersion;24972498// If the deserialized object has a SimpleTimeZone, try2499// to replace it with a ZoneInfo equivalent in order to2500// be compatible with the SimpleTimeZone-based2501// implementation as much as possible.2502TimeZone tz = getTimeZone();2503if (tz instanceof SimpleTimeZone) {2504String id = tz.getID();2505TimeZone zi = TimeZone.getTimeZone(id);2506if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) {2507setTimeZone(zi);2508}2509}2510}25112512/**2513* Analyze the negative subpattern of DecimalFormat and set/update values2514* as necessary.2515*/2516private void checkNegativeNumberExpression() {2517if ((numberFormat instanceof DecimalFormat) &&2518!numberFormat.equals(originalNumberFormat)) {2519String numberPattern = ((DecimalFormat)numberFormat).toPattern();2520if (!numberPattern.equals(originalNumberPattern)) {2521hasFollowingMinusSign = false;25222523int separatorIndex = numberPattern.indexOf(';');2524// If the negative subpattern is not absent, we have to analayze2525// it in order to check if it has a following minus sign.2526if (separatorIndex > -1) {2527int minusIndex = numberPattern.indexOf('-', separatorIndex);2528if ((minusIndex > numberPattern.lastIndexOf('0')) &&2529(minusIndex > numberPattern.lastIndexOf('#'))) {2530hasFollowingMinusSign = true;2531minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign();2532}2533}2534originalNumberPattern = numberPattern;2535}2536originalNumberFormat = numberFormat;2537}2538}25392540}254125422543