Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/text/SimpleDateFormat.java
38829 views
/*1* Copyright (c) 1996, 2013, 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;5758/**59* <code>SimpleDateFormat</code> is a concrete class for formatting and60* parsing dates in a locale-sensitive manner. It allows for formatting61* (date → text), parsing (text → date), and normalization.62*63* <p>64* <code>SimpleDateFormat</code> allows you to start by choosing65* any user-defined patterns for date-time formatting. However, you66* are encouraged to create a date-time formatter with either67* <code>getTimeInstance</code>, <code>getDateInstance</code>, or68* <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each69* of these class methods can return a date/time formatter initialized70* with a default format pattern. You may modify the format pattern71* using the <code>applyPattern</code> methods as desired.72* For more information on using these methods, see73* {@link DateFormat}.74*75* <h3>Date and Time Patterns</h3>76* <p>77* Date and time formats are specified by <em>date and time pattern</em>78* strings.79* Within date and time pattern strings, unquoted letters from80* <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to81* <code>'z'</code> are interpreted as pattern letters representing the82* components of a date or time string.83* Text can be quoted using single quotes (<code>'</code>) to avoid84* interpretation.85* <code>"''"</code> represents a single quote.86* All other characters are not interpreted; they're simply copied into the87* output string during formatting or matched against the input string88* during parsing.89* <p>90* The following pattern letters are defined (all other characters from91* <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to92* <code>'z'</code> are reserved):93* <blockquote>94* <table border=0 cellspacing=3 cellpadding=0 summary="Chart shows pattern letters, date/time component, presentation, and examples.">95* <tr style="background-color: rgb(204, 204, 255);">96* <th align=left>Letter97* <th align=left>Date or Time Component98* <th align=left>Presentation99* <th align=left>Examples100* <tr>101* <td><code>G</code>102* <td>Era designator103* <td><a href="#text">Text</a>104* <td><code>AD</code>105* <tr style="background-color: rgb(238, 238, 255);">106* <td><code>y</code>107* <td>Year108* <td><a href="#year">Year</a>109* <td><code>1996</code>; <code>96</code>110* <tr>111* <td><code>Y</code>112* <td>Week year113* <td><a href="#year">Year</a>114* <td><code>2009</code>; <code>09</code>115* <tr style="background-color: rgb(238, 238, 255);">116* <td><code>M</code>117* <td>Month in year (context sensitive)118* <td><a href="#month">Month</a>119* <td><code>July</code>; <code>Jul</code>; <code>07</code>120* <tr>121* <td><code>L</code>122* <td>Month in year (standalone form)123* <td><a href="#month">Month</a>124* <td><code>July</code>; <code>Jul</code>; <code>07</code>125* <tr style="background-color: rgb(238, 238, 255);">126* <td><code>w</code>127* <td>Week in year128* <td><a href="#number">Number</a>129* <td><code>27</code>130* <tr>131* <td><code>W</code>132* <td>Week in month133* <td><a href="#number">Number</a>134* <td><code>2</code>135* <tr style="background-color: rgb(238, 238, 255);">136* <td><code>D</code>137* <td>Day in year138* <td><a href="#number">Number</a>139* <td><code>189</code>140* <tr>141* <td><code>d</code>142* <td>Day in month143* <td><a href="#number">Number</a>144* <td><code>10</code>145* <tr style="background-color: rgb(238, 238, 255);">146* <td><code>F</code>147* <td>Day of week in month148* <td><a href="#number">Number</a>149* <td><code>2</code>150* <tr>151* <td><code>E</code>152* <td>Day name in week153* <td><a href="#text">Text</a>154* <td><code>Tuesday</code>; <code>Tue</code>155* <tr style="background-color: rgb(238, 238, 255);">156* <td><code>u</code>157* <td>Day number of week (1 = Monday, ..., 7 = Sunday)158* <td><a href="#number">Number</a>159* <td><code>1</code>160* <tr>161* <td><code>a</code>162* <td>Am/pm marker163* <td><a href="#text">Text</a>164* <td><code>PM</code>165* <tr style="background-color: rgb(238, 238, 255);">166* <td><code>H</code>167* <td>Hour in day (0-23)168* <td><a href="#number">Number</a>169* <td><code>0</code>170* <tr>171* <td><code>k</code>172* <td>Hour in day (1-24)173* <td><a href="#number">Number</a>174* <td><code>24</code>175* <tr style="background-color: rgb(238, 238, 255);">176* <td><code>K</code>177* <td>Hour in am/pm (0-11)178* <td><a href="#number">Number</a>179* <td><code>0</code>180* <tr>181* <td><code>h</code>182* <td>Hour in am/pm (1-12)183* <td><a href="#number">Number</a>184* <td><code>12</code>185* <tr style="background-color: rgb(238, 238, 255);">186* <td><code>m</code>187* <td>Minute in hour188* <td><a href="#number">Number</a>189* <td><code>30</code>190* <tr>191* <td><code>s</code>192* <td>Second in minute193* <td><a href="#number">Number</a>194* <td><code>55</code>195* <tr style="background-color: rgb(238, 238, 255);">196* <td><code>S</code>197* <td>Millisecond198* <td><a href="#number">Number</a>199* <td><code>978</code>200* <tr>201* <td><code>z</code>202* <td>Time zone203* <td><a href="#timezone">General time zone</a>204* <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code>205* <tr style="background-color: rgb(238, 238, 255);">206* <td><code>Z</code>207* <td>Time zone208* <td><a href="#rfc822timezone">RFC 822 time zone</a>209* <td><code>-0800</code>210* <tr>211* <td><code>X</code>212* <td>Time zone213* <td><a href="#iso8601timezone">ISO 8601 time zone</a>214* <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code>215* </table>216* </blockquote>217* Pattern letters are usually repeated, as their number determines the218* exact presentation:219* <ul>220* <li><strong><a name="text">Text:</a></strong>221* For formatting, if the number of pattern letters is 4 or more,222* the full form is used; otherwise a short or abbreviated form223* is used if available.224* For parsing, both forms are accepted, independent of the number225* of pattern letters.<br><br></li>226* <li><strong><a name="number">Number:</a></strong>227* For formatting, the number of pattern letters is the minimum228* number of digits, and shorter numbers are zero-padded to this amount.229* For parsing, the number of pattern letters is ignored unless230* it's needed to separate two adjacent fields.<br><br></li>231* <li><strong><a name="year">Year:</a></strong>232* If the formatter's {@link #getCalendar() Calendar} is the Gregorian233* calendar, the following rules are applied.<br>234* <ul>235* <li>For formatting, if the number of pattern letters is 2, the year236* is truncated to 2 digits; otherwise it is interpreted as a237* <a href="#number">number</a>.238* <li>For parsing, if the number of pattern letters is more than 2,239* the year is interpreted literally, regardless of the number of240* digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to241* Jan 11, 12 A.D.242* <li>For parsing with the abbreviated year pattern ("y" or "yy"),243* <code>SimpleDateFormat</code> must interpret the abbreviated year244* relative to some century. It does this by adjusting dates to be245* within 80 years before and 20 years after the time the <code>SimpleDateFormat</code>246* instance is created. For example, using a pattern of "MM/dd/yy" and a247* <code>SimpleDateFormat</code> instance created on Jan 1, 1997, the string248* "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"249* would be interpreted as May 4, 1964.250* During parsing, only strings consisting of exactly two digits, as defined by251* {@link Character#isDigit(char)}, will be parsed into the default century.252* Any other numeric string, such as a one digit string, a three or more digit253* string, or a two digit string that isn't all digits (for example, "-1"), is254* interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the255* same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.256* </ul>257* Otherwise, calendar system specific forms are applied.258* For both formatting and parsing, if the number of pattern259* letters is 4 or more, a calendar specific {@linkplain260* Calendar#LONG long form} is used. Otherwise, a calendar261* specific {@linkplain Calendar#SHORT short or abbreviated form}262* is used.<br>263* <br>264* If week year {@code 'Y'} is specified and the {@linkplain265* #getCalendar() calendar} doesn't support any <a266* href="../util/GregorianCalendar.html#week_year"> week267* years</a>, the calendar year ({@code 'y'}) is used instead. The268* support of week years can be tested with a call to {@link269* DateFormat#getCalendar() getCalendar()}.{@link270* java.util.Calendar#isWeekDateSupported()271* isWeekDateSupported()}.<br><br></li>272* <li><strong><a name="month">Month:</a></strong>273* If the number of pattern letters is 3 or more, the month is274* interpreted as <a href="#text">text</a>; otherwise,275* it is interpreted as a <a href="#number">number</a>.<br>276* <ul>277* <li>Letter <em>M</em> produces context-sensitive month names, such as the278* embedded form of names. If a {@code DateFormatSymbols} has been set279* explicitly with constructor {@link #SimpleDateFormat(String,280* DateFormatSymbols)} or method {@link281* #setDateFormatSymbols(DateFormatSymbols)}, the month names given by282* the {@code DateFormatSymbols} are used.</li>283* <li>Letter <em>L</em> produces the standalone form of month names.</li>284* </ul>285* <br></li>286* <li><strong><a name="timezone">General time zone:</a></strong>287* Time zones are interpreted as <a href="#text">text</a> if they have288* names. For time zones representing a GMT offset value, the289* following syntax is used:290* <pre>291* <a name="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a>292* <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i>293* <i>Sign:</i> one of294* <code>+ -</code>295* <i>Hours:</i>296* <i>Digit</i>297* <i>Digit</i> <i>Digit</i>298* <i>Minutes:</i>299* <i>Digit</i> <i>Digit</i>300* <i>Digit:</i> one of301* <code>0 1 2 3 4 5 6 7 8 9</code></pre>302* <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between303* 00 and 59. The format is locale independent and digits must be taken304* from the Basic Latin block of the Unicode standard.305* <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also306* accepted.<br><br></li>307* <li><strong><a name="rfc822timezone">RFC 822 time zone:</a></strong>308* For formatting, the RFC 822 4-digit time zone format is used:309*310* <pre>311* <i>RFC822TimeZone:</i>312* <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>313* <i>TwoDigitHours:</i>314* <i>Digit Digit</i></pre>315* <i>TwoDigitHours</i> must be between 00 and 23. Other definitions316* are as for <a href="#timezone">general time zones</a>.317*318* <p>For parsing, <a href="#timezone">general time zones</a> are also319* accepted.320* <li><strong><a name="iso8601timezone">ISO 8601 Time zone:</a></strong>321* The number of pattern letters designates the format for both formatting322* and parsing as follows:323* <pre>324* <i>ISO8601TimeZone:</i>325* <i>OneLetterISO8601TimeZone</i>326* <i>TwoLetterISO8601TimeZone</i>327* <i>ThreeLetterISO8601TimeZone</i>328* <i>OneLetterISO8601TimeZone:</i>329* <i>Sign</i> <i>TwoDigitHours</i>330* {@code Z}331* <i>TwoLetterISO8601TimeZone:</i>332* <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>333* {@code Z}334* <i>ThreeLetterISO8601TimeZone:</i>335* <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i>336* {@code Z}</pre>337* Other definitions are as for <a href="#timezone">general time zones</a> or338* <a href="#rfc822timezone">RFC 822 time zones</a>.339*340* <p>For formatting, if the offset value from GMT is 0, {@code "Z"} is341* produced. If the number of pattern letters is 1, any fraction of an hour342* is ignored. For example, if the pattern is {@code "X"} and the time zone is343* {@code "GMT+05:30"}, {@code "+05"} is produced.344*345* <p>For parsing, {@code "Z"} is parsed as the UTC time zone designator.346* <a href="#timezone">General time zones</a> are <em>not</em> accepted.347*348* <p>If the number of pattern letters is 4 or more, {@link349* IllegalArgumentException} is thrown when constructing a {@code350* SimpleDateFormat} or {@linkplain #applyPattern(String) applying a351* pattern}.352* </ul>353* <code>SimpleDateFormat</code> also supports <em>localized date and time354* pattern</em> strings. In these strings, the pattern letters described above355* may be replaced with other, locale dependent, pattern letters.356* <code>SimpleDateFormat</code> does not deal with the localization of text357* other than the pattern letters; that's up to the client of the class.358*359* <h4>Examples</h4>360*361* The following examples show how date and time patterns are interpreted in362* the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time363* in the U.S. Pacific Time time zone.364* <blockquote>365* <table border=0 cellspacing=3 cellpadding=0 summary="Examples of date and time patterns interpreted in the U.S. locale">366* <tr style="background-color: rgb(204, 204, 255);">367* <th align=left>Date and Time Pattern368* <th align=left>Result369* <tr>370* <td><code>"yyyy.MM.dd G 'at' HH:mm:ss z"</code>371* <td><code>2001.07.04 AD at 12:08:56 PDT</code>372* <tr style="background-color: rgb(238, 238, 255);">373* <td><code>"EEE, MMM d, ''yy"</code>374* <td><code>Wed, Jul 4, '01</code>375* <tr>376* <td><code>"h:mm a"</code>377* <td><code>12:08 PM</code>378* <tr style="background-color: rgb(238, 238, 255);">379* <td><code>"hh 'o''clock' a, zzzz"</code>380* <td><code>12 o'clock PM, Pacific Daylight Time</code>381* <tr>382* <td><code>"K:mm a, z"</code>383* <td><code>0:08 PM, PDT</code>384* <tr style="background-color: rgb(238, 238, 255);">385* <td><code>"yyyyy.MMMMM.dd GGG hh:mm aaa"</code>386* <td><code>02001.July.04 AD 12:08 PM</code>387* <tr>388* <td><code>"EEE, d MMM yyyy HH:mm:ss Z"</code>389* <td><code>Wed, 4 Jul 2001 12:08:56 -0700</code>390* <tr style="background-color: rgb(238, 238, 255);">391* <td><code>"yyMMddHHmmssZ"</code>392* <td><code>010704120856-0700</code>393* <tr>394* <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSZ"</code>395* <td><code>2001-07-04T12:08:56.235-0700</code>396* <tr style="background-color: rgb(238, 238, 255);">397* <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"</code>398* <td><code>2001-07-04T12:08:56.235-07:00</code>399* <tr>400* <td><code>"YYYY-'W'ww-u"</code>401* <td><code>2001-W27-3</code>402* </table>403* </blockquote>404*405* <h4><a name="synchronization">Synchronization</a></h4>406*407* <p>408* Date formats are not synchronized.409* It is recommended to create separate format instances for each thread.410* If multiple threads access a format concurrently, it must be synchronized411* externally.412*413* @see <a href="https://docs.oracle.com/javase/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a>414* @see java.util.Calendar415* @see java.util.TimeZone416* @see DateFormat417* @see DateFormatSymbols418* @author Mark Davis, Chen-Lieh Huang, Alan Liu419*/420public class SimpleDateFormat extends DateFormat {421422// the official serial version ID which says cryptically423// which version we're compatible with424static final long serialVersionUID = 4774881970558875024L;425426// the internal serial version which says which version was written427// - 0 (default) for version up to JDK 1.1.3428// - 1 for version from JDK 1.1.4, which includes a new field429static final int currentSerialVersion = 1;430431/**432* The version of the serialized data on the stream. Possible values:433* <ul>434* <li><b>0</b> or not present on stream: JDK 1.1.3. This version435* has no <code>defaultCenturyStart</code> on stream.436* <li><b>1</b> JDK 1.1.4 or later. This version adds437* <code>defaultCenturyStart</code>.438* </ul>439* When streaming out this class, the most recent format440* and the highest allowable <code>serialVersionOnStream</code>441* is written.442* @serial443* @since JDK1.1.4444*/445private int serialVersionOnStream = currentSerialVersion;446447/**448* The pattern string of this formatter. This is always a non-localized449* pattern. May not be null. See class documentation for details.450* @serial451*/452private String pattern;453454/**455* Saved numberFormat and pattern.456* @see SimpleDateFormat#checkNegativeNumberExpression457*/458transient private NumberFormat originalNumberFormat;459transient private String originalNumberPattern;460461/**462* The minus sign to be used with format and parse.463*/464transient private char minusSign = '-';465466/**467* True when a negative sign follows a number.468* (True as default in Arabic.)469*/470transient private boolean hasFollowingMinusSign = false;471472/**473* True if standalone form needs to be used.474*/475transient private boolean forceStandaloneForm = false;476477/**478* The compiled pattern.479*/480transient private char[] compiledPattern;481482/**483* Tags for the compiled pattern.484*/485private final static int TAG_QUOTE_ASCII_CHAR = 100;486private final static int TAG_QUOTE_CHARS = 101;487488/**489* Locale dependent digit zero.490* @see #zeroPaddingNumber491* @see java.text.DecimalFormatSymbols#getZeroDigit492*/493transient private char zeroDigit;494495/**496* The symbols used by this formatter for week names, month names,497* etc. May not be null.498* @serial499* @see java.text.DateFormatSymbols500*/501private DateFormatSymbols formatData;502503/**504* We map dates with two-digit years into the century starting at505* <code>defaultCenturyStart</code>, which may be any date. May506* not be null.507* @serial508* @since JDK1.1.4509*/510private Date defaultCenturyStart;511512transient private int defaultCenturyStartYear;513514private static final int MILLIS_PER_MINUTE = 60 * 1000;515516// For time zones that have no names, use strings GMT+minutes and517// GMT-minutes. For instance, in France the time zone is GMT+60.518private static final String GMT = "GMT";519520/**521* Cache NumberFormat instances with Locale key.522*/523private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData524= new ConcurrentHashMap<>(3);525526/**527* The Locale used to instantiate this528* <code>SimpleDateFormat</code>. The value may be null if this object529* has been created by an older <code>SimpleDateFormat</code> and530* deserialized.531*532* @serial533* @since 1.6534*/535private Locale locale;536537/**538* Indicates whether this <code>SimpleDateFormat</code> should use539* the DateFormatSymbols. If true, the format and parse methods540* use the DateFormatSymbols values. If false, the format and541* parse methods call Calendar.getDisplayName or542* Calendar.getDisplayNames.543*/544transient boolean useDateFormatSymbols;545546/**547* Constructs a <code>SimpleDateFormat</code> using the default pattern and548* date format symbols for the default549* {@link java.util.Locale.Category#FORMAT FORMAT} locale.550* <b>Note:</b> This constructor may not support all locales.551* For full coverage, use the factory methods in the {@link DateFormat}552* class.553*/554public SimpleDateFormat() {555this("", Locale.getDefault(Locale.Category.FORMAT));556applyPatternImpl(LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale)557.getDateTimePattern(SHORT, SHORT, calendar));558}559560/**561* Constructs a <code>SimpleDateFormat</code> using the given pattern and562* the default date format symbols for the default563* {@link java.util.Locale.Category#FORMAT FORMAT} locale.564* <b>Note:</b> This constructor may not support all locales.565* For full coverage, use the factory methods in the {@link DateFormat}566* class.567* <p>This is equivalent to calling568* {@link #SimpleDateFormat(String, Locale)569* SimpleDateFormat(pattern, Locale.getDefault(Locale.Category.FORMAT))}.570*571* @see java.util.Locale#getDefault(java.util.Locale.Category)572* @see java.util.Locale.Category#FORMAT573* @param pattern the pattern describing the date and time format574* @exception NullPointerException if the given pattern is null575* @exception IllegalArgumentException if the given pattern is invalid576*/577public SimpleDateFormat(String pattern)578{579this(pattern, Locale.getDefault(Locale.Category.FORMAT));580}581582/**583* Constructs a <code>SimpleDateFormat</code> using the given pattern and584* the default date format symbols for the given locale.585* <b>Note:</b> This constructor may not support all locales.586* For full coverage, use the factory methods in the {@link DateFormat}587* class.588*589* @param pattern the pattern describing the date and time format590* @param locale the locale whose date format symbols should be used591* @exception NullPointerException if the given pattern or locale is null592* @exception IllegalArgumentException if the given pattern is invalid593*/594public SimpleDateFormat(String pattern, Locale locale)595{596if (pattern == null || locale == null) {597throw new NullPointerException();598}599600initializeCalendar(locale);601this.pattern = pattern;602this.formatData = DateFormatSymbols.getInstanceRef(locale);603this.locale = locale;604initialize(locale);605}606607/**608* Constructs a <code>SimpleDateFormat</code> using the given pattern and609* date format symbols.610*611* @param pattern the pattern describing the date and time format612* @param formatSymbols the date format symbols to be used for formatting613* @exception NullPointerException if the given pattern or formatSymbols is null614* @exception IllegalArgumentException if the given pattern is invalid615*/616public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)617{618if (pattern == null || formatSymbols == null) {619throw new NullPointerException();620}621622this.pattern = pattern;623this.formatData = (DateFormatSymbols) formatSymbols.clone();624this.locale = Locale.getDefault(Locale.Category.FORMAT);625initializeCalendar(this.locale);626initialize(this.locale);627useDateFormatSymbols = true;628}629630/* Initialize compiledPattern and numberFormat fields */631private void initialize(Locale loc) {632// Verify and compile the given pattern.633compiledPattern = compile(pattern);634635/* try the cache first */636numberFormat = cachedNumberFormatData.get(loc);637if (numberFormat == null) { /* cache miss */638numberFormat = NumberFormat.getIntegerInstance(loc);639numberFormat.setGroupingUsed(false);640641/* update cache */642cachedNumberFormatData.putIfAbsent(loc, numberFormat);643}644numberFormat = (NumberFormat) numberFormat.clone();645646initializeDefaultCentury();647}648649private void initializeCalendar(Locale loc) {650if (calendar == null) {651assert loc != null;652// The format object must be constructed using the symbols for this zone.653// However, the calendar should use the current default TimeZone.654// If this is not contained in the locale zone strings, then the zone655// will be formatted using generic GMT+/-H:MM nomenclature.656calendar = Calendar.getInstance(TimeZone.getDefault(), loc);657}658}659660/**661* Returns the compiled form of the given pattern. The syntax of662* the compiled pattern is:663* <blockquote>664* CompiledPattern:665* EntryList666* EntryList:667* Entry668* EntryList Entry669* Entry:670* TagField671* TagField data672* TagField:673* Tag Length674* TaggedData675* Tag:676* pattern_char_index677* TAG_QUOTE_CHARS678* Length:679* short_length680* long_length681* TaggedData:682* TAG_QUOTE_ASCII_CHAR ascii_char683*684* </blockquote>685*686* where `short_length' is an 8-bit unsigned integer between 0 and687* 254. `long_length' is a sequence of an 8-bit integer 255 and a688* 32-bit signed integer value which is split into upper and lower689* 16-bit fields in two char's. `pattern_char_index' is an 8-bit690* integer between 0 and 18. `ascii_char' is an 7-bit ASCII691* character value. `data' depends on its Tag value.692* <p>693* If Length is short_length, Tag and short_length are packed in a694* single char, as illustrated below.695* <blockquote>696* char[0] = (Tag << 8) | short_length;697* </blockquote>698*699* If Length is long_length, Tag and 255 are packed in the first700* char and a 32-bit integer, as illustrated below.701* <blockquote>702* char[0] = (Tag << 8) | 255;703* char[1] = (char) (long_length >>> 16);704* char[2] = (char) (long_length & 0xffff);705* </blockquote>706* <p>707* If Tag is a pattern_char_index, its Length is the number of708* pattern characters. For example, if the given pattern is709* "yyyy", Tag is 1 and Length is 4, followed by no data.710* <p>711* If Tag is TAG_QUOTE_CHARS, its Length is the number of char's712* following the TagField. For example, if the given pattern is713* "'o''clock'", Length is 7 followed by a char sequence of714* <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>.715* <p>716* TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII717* character in place of Length. For example, if the given pattern718* is "'o'", the TaggedData entry is719* <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>.720*721* @exception NullPointerException if the given pattern is null722* @exception IllegalArgumentException if the given pattern is invalid723*/724private char[] compile(String pattern) {725int length = pattern.length();726boolean inQuote = false;727StringBuilder compiledCode = new StringBuilder(length * 2);728StringBuilder tmpBuffer = null;729int count = 0, tagcount = 0;730int lastTag = -1, prevTag = -1;731732for (int i = 0; i < length; i++) {733char c = pattern.charAt(i);734735if (c == '\'') {736// '' is treated as a single quote regardless of being737// in a quoted section.738if ((i + 1) < length) {739c = pattern.charAt(i + 1);740if (c == '\'') {741i++;742if (count != 0) {743encode(lastTag, count, compiledCode);744tagcount++;745prevTag = lastTag;746lastTag = -1;747count = 0;748}749if (inQuote) {750tmpBuffer.append(c);751} else {752compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));753}754continue;755}756}757if (!inQuote) {758if (count != 0) {759encode(lastTag, count, compiledCode);760tagcount++;761prevTag = lastTag;762lastTag = -1;763count = 0;764}765if (tmpBuffer == null) {766tmpBuffer = new StringBuilder(length);767} else {768tmpBuffer.setLength(0);769}770inQuote = true;771} else {772int len = tmpBuffer.length();773if (len == 1) {774char ch = tmpBuffer.charAt(0);775if (ch < 128) {776compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch));777} else {778compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | 1));779compiledCode.append(ch);780}781} else {782encode(TAG_QUOTE_CHARS, len, compiledCode);783compiledCode.append(tmpBuffer);784}785inQuote = false;786}787continue;788}789if (inQuote) {790tmpBuffer.append(c);791continue;792}793if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {794if (count != 0) {795encode(lastTag, count, compiledCode);796tagcount++;797prevTag = lastTag;798lastTag = -1;799count = 0;800}801if (c < 128) {802// In most cases, c would be a delimiter, such as ':'.803compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));804} else {805// Take any contiguous non-ASCII alphabet characters and806// put them in a single TAG_QUOTE_CHARS.807int j;808for (j = i + 1; j < length; j++) {809char d = pattern.charAt(j);810if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {811break;812}813}814compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | (j - i)));815for (; i < j; i++) {816compiledCode.append(pattern.charAt(i));817}818i--;819}820continue;821}822823int tag;824if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {825throw new IllegalArgumentException("Illegal pattern character " +826"'" + c + "'");827}828if (lastTag == -1 || lastTag == tag) {829lastTag = tag;830count++;831continue;832}833encode(lastTag, count, compiledCode);834tagcount++;835prevTag = lastTag;836lastTag = tag;837count = 1;838}839840if (inQuote) {841throw new IllegalArgumentException("Unterminated quote");842}843844if (count != 0) {845encode(lastTag, count, compiledCode);846tagcount++;847prevTag = lastTag;848}849850forceStandaloneForm = (tagcount == 1 && prevTag == PATTERN_MONTH);851852// Copy the compiled pattern to a char array853int len = compiledCode.length();854char[] r = new char[len];855compiledCode.getChars(0, len, r, 0);856return r;857}858859/**860* Encodes the given tag and length and puts encoded char(s) into buffer.861*/862private static void encode(int tag, int length, StringBuilder buffer) {863if (tag == PATTERN_ISO_ZONE && length >= 4) {864throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);865}866if (length < 255) {867buffer.append((char)(tag << 8 | length));868} else {869buffer.append((char)((tag << 8) | 0xff));870buffer.append((char)(length >>> 16));871buffer.append((char)(length & 0xffff));872}873}874875/* Initialize the fields we use to disambiguate ambiguous years. Separate876* so we can call it from readObject().877*/878private void initializeDefaultCentury() {879calendar.setTimeInMillis(System.currentTimeMillis());880calendar.add( Calendar.YEAR, -80 );881parseAmbiguousDatesAsAfter(calendar.getTime());882}883884/* Define one-century window into which to disambiguate dates using885* two-digit years.886*/887private void parseAmbiguousDatesAsAfter(Date startDate) {888defaultCenturyStart = startDate;889calendar.setTime(startDate);890defaultCenturyStartYear = calendar.get(Calendar.YEAR);891}892893/**894* Sets the 100-year period 2-digit years will be interpreted as being in895* to begin on the date the user specifies.896*897* @param startDate During parsing, two digit years will be placed in the range898* <code>startDate</code> to <code>startDate + 100 years</code>.899* @see #get2DigitYearStart900* @since 1.2901*/902public void set2DigitYearStart(Date startDate) {903parseAmbiguousDatesAsAfter(new Date(startDate.getTime()));904}905906/**907* Returns the beginning date of the 100-year period 2-digit years are interpreted908* as being within.909*910* @return the start of the 100-year period into which two digit years are911* parsed912* @see #set2DigitYearStart913* @since 1.2914*/915public Date get2DigitYearStart() {916return (Date) defaultCenturyStart.clone();917}918919/**920* Formats the given <code>Date</code> into a date/time string and appends921* the result to the given <code>StringBuffer</code>.922*923* @param date the date-time value to be formatted into a date-time string.924* @param toAppendTo where the new date-time text is to be appended.925* @param pos the formatting position. On input: an alignment field,926* if desired. On output: the offsets of the alignment field.927* @return the formatted date-time string.928* @exception NullPointerException if the given {@code date} is {@code null}.929*/930@Override931public StringBuffer format(Date date, StringBuffer toAppendTo,932FieldPosition pos)933{934pos.beginIndex = pos.endIndex = 0;935return format(date, toAppendTo, pos.getFieldDelegate());936}937938// Called from Format after creating a FieldDelegate939private StringBuffer format(Date date, StringBuffer toAppendTo,940FieldDelegate delegate) {941// Convert input date to time field list942calendar.setTime(date);943944boolean useDateFormatSymbols = useDateFormatSymbols();945946for (int i = 0; i < compiledPattern.length; ) {947int tag = compiledPattern[i] >>> 8;948int count = compiledPattern[i++] & 0xff;949if (count == 255) {950count = compiledPattern[i++] << 16;951count |= compiledPattern[i++];952}953954switch (tag) {955case TAG_QUOTE_ASCII_CHAR:956toAppendTo.append((char)count);957break;958959case TAG_QUOTE_CHARS:960toAppendTo.append(compiledPattern, i, count);961i += count;962break;963964default:965subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);966break;967}968}969return toAppendTo;970}971972/**973* Formats an Object producing an <code>AttributedCharacterIterator</code>.974* You can use the returned <code>AttributedCharacterIterator</code>975* to build the resulting String, as well as to determine information976* about the resulting String.977* <p>978* Each attribute key of the AttributedCharacterIterator will be of type979* <code>DateFormat.Field</code>, with the corresponding attribute value980* being the same as the attribute key.981*982* @exception NullPointerException if obj is null.983* @exception IllegalArgumentException if the Format cannot format the984* given object, or if the Format's pattern string is invalid.985* @param obj The object to format986* @return AttributedCharacterIterator describing the formatted value.987* @since 1.4988*/989@Override990public AttributedCharacterIterator formatToCharacterIterator(Object obj) {991StringBuffer sb = new StringBuffer();992CharacterIteratorFieldDelegate delegate = new993CharacterIteratorFieldDelegate();994995if (obj instanceof Date) {996format((Date)obj, sb, delegate);997}998else if (obj instanceof Number) {999format(new Date(((Number)obj).longValue()), sb, delegate);1000}1001else if (obj == null) {1002throw new NullPointerException(1003"formatToCharacterIterator must be passed non-null object");1004}1005else {1006throw new IllegalArgumentException(1007"Cannot format given Object as a Date");1008}1009return delegate.getIterator(sb.toString());1010}10111012// Map index into pattern character string to Calendar field number1013private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = {1014Calendar.ERA,1015Calendar.YEAR,1016Calendar.MONTH,1017Calendar.DATE,1018Calendar.HOUR_OF_DAY,1019Calendar.HOUR_OF_DAY,1020Calendar.MINUTE,1021Calendar.SECOND,1022Calendar.MILLISECOND,1023Calendar.DAY_OF_WEEK,1024Calendar.DAY_OF_YEAR,1025Calendar.DAY_OF_WEEK_IN_MONTH,1026Calendar.WEEK_OF_YEAR,1027Calendar.WEEK_OF_MONTH,1028Calendar.AM_PM,1029Calendar.HOUR,1030Calendar.HOUR,1031Calendar.ZONE_OFFSET,1032Calendar.ZONE_OFFSET,1033CalendarBuilder.WEEK_YEAR, // Pseudo Calendar field1034CalendarBuilder.ISO_DAY_OF_WEEK, // Pseudo Calendar field1035Calendar.ZONE_OFFSET,1036Calendar.MONTH1037};10381039// Map index into pattern character string to DateFormat field number1040private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {1041DateFormat.ERA_FIELD,1042DateFormat.YEAR_FIELD,1043DateFormat.MONTH_FIELD,1044DateFormat.DATE_FIELD,1045DateFormat.HOUR_OF_DAY1_FIELD,1046DateFormat.HOUR_OF_DAY0_FIELD,1047DateFormat.MINUTE_FIELD,1048DateFormat.SECOND_FIELD,1049DateFormat.MILLISECOND_FIELD,1050DateFormat.DAY_OF_WEEK_FIELD,1051DateFormat.DAY_OF_YEAR_FIELD,1052DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,1053DateFormat.WEEK_OF_YEAR_FIELD,1054DateFormat.WEEK_OF_MONTH_FIELD,1055DateFormat.AM_PM_FIELD,1056DateFormat.HOUR1_FIELD,1057DateFormat.HOUR0_FIELD,1058DateFormat.TIMEZONE_FIELD,1059DateFormat.TIMEZONE_FIELD,1060DateFormat.YEAR_FIELD,1061DateFormat.DAY_OF_WEEK_FIELD,1062DateFormat.TIMEZONE_FIELD,1063DateFormat.MONTH_FIELD1064};10651066// Maps from DecimalFormatSymbols index to Field constant1067private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = {1068Field.ERA,1069Field.YEAR,1070Field.MONTH,1071Field.DAY_OF_MONTH,1072Field.HOUR_OF_DAY1,1073Field.HOUR_OF_DAY0,1074Field.MINUTE,1075Field.SECOND,1076Field.MILLISECOND,1077Field.DAY_OF_WEEK,1078Field.DAY_OF_YEAR,1079Field.DAY_OF_WEEK_IN_MONTH,1080Field.WEEK_OF_YEAR,1081Field.WEEK_OF_MONTH,1082Field.AM_PM,1083Field.HOUR1,1084Field.HOUR0,1085Field.TIME_ZONE,1086Field.TIME_ZONE,1087Field.YEAR,1088Field.DAY_OF_WEEK,1089Field.TIME_ZONE,1090Field.MONTH1091};10921093/**1094* Private member function that does the real date/time formatting.1095*/1096private void subFormat(int patternCharIndex, int count,1097FieldDelegate delegate, StringBuffer buffer,1098boolean useDateFormatSymbols)1099{1100int maxIntCount = Integer.MAX_VALUE;1101String current = null;1102int beginOffset = buffer.length();11031104int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];1105int value;1106if (field == CalendarBuilder.WEEK_YEAR) {1107if (calendar.isWeekDateSupported()) {1108value = calendar.getWeekYear();1109} else {1110// use calendar year 'y' instead1111patternCharIndex = PATTERN_YEAR;1112field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];1113value = calendar.get(field);1114}1115} else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {1116value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));1117} else {1118value = calendar.get(field);1119}11201121int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;1122if (!useDateFormatSymbols && field < Calendar.ZONE_OFFSET1123&& patternCharIndex != PATTERN_MONTH_STANDALONE) {1124current = calendar.getDisplayName(field, style, locale);1125}11261127// Note: zeroPaddingNumber() assumes that maxDigits is either1128// 2 or maxIntCount. If we make any changes to this,1129// zeroPaddingNumber() must be fixed.11301131switch (patternCharIndex) {1132case PATTERN_ERA: // 'G'1133if (useDateFormatSymbols) {1134String[] eras = formatData.getEras();1135if (value < eras.length) {1136current = eras[value];1137}1138}1139if (current == null) {1140current = "";1141}1142break;11431144case PATTERN_WEEK_YEAR: // 'Y'1145case PATTERN_YEAR: // 'y'1146if (calendar instanceof GregorianCalendar) {1147if (count != 2) {1148zeroPaddingNumber(value, count, maxIntCount, buffer);1149} else {1150zeroPaddingNumber(value, 2, 2, buffer);1151} // clip 1996 to 961152} else {1153if (current == null) {1154zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count,1155maxIntCount, buffer);1156}1157}1158break;11591160case PATTERN_MONTH: // 'M' (context seinsive)1161if (useDateFormatSymbols) {1162String[] months;1163if (count >= 4) {1164months = formatData.getMonths();1165current = months[value];1166} else if (count == 3) {1167months = formatData.getShortMonths();1168current = months[value];1169}1170} else {1171if (count < 3) {1172current = null;1173} else if (forceStandaloneForm) {1174current = calendar.getDisplayName(field, style | 0x8000, locale);1175if (current == null) {1176current = calendar.getDisplayName(field, style, locale);1177}1178}1179}1180if (current == null) {1181zeroPaddingNumber(value+1, count, maxIntCount, buffer);1182}1183break;11841185case PATTERN_MONTH_STANDALONE: // 'L'1186assert current == null;1187if (locale == null) {1188String[] months;1189if (count >= 4) {1190months = formatData.getMonths();1191current = months[value];1192} else if (count == 3) {1193months = formatData.getShortMonths();1194current = months[value];1195}1196} else {1197if (count >= 3) {1198current = calendar.getDisplayName(field, style | 0x8000, locale);1199}1200}1201if (current == null) {1202zeroPaddingNumber(value+1, count, maxIntCount, buffer);1203}1204break;12051206case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:591207if (current == null) {1208if (value == 0) {1209zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1,1210count, maxIntCount, buffer);1211} else {1212zeroPaddingNumber(value, count, maxIntCount, buffer);1213}1214}1215break;12161217case PATTERN_DAY_OF_WEEK: // 'E'1218if (useDateFormatSymbols) {1219String[] weekdays;1220if (count >= 4) {1221weekdays = formatData.getWeekdays();1222current = weekdays[value];1223} else { // count < 4, use abbreviated form if exists1224weekdays = formatData.getShortWeekdays();1225current = weekdays[value];1226}1227}1228break;12291230case PATTERN_AM_PM: // 'a'1231if (useDateFormatSymbols) {1232String[] ampm = formatData.getAmPmStrings();1233current = ampm[value];1234}1235break;12361237case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM1238if (current == null) {1239if (value == 0) {1240zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1,1241count, maxIntCount, buffer);1242} else {1243zeroPaddingNumber(value, count, maxIntCount, buffer);1244}1245}1246break;12471248case PATTERN_ZONE_NAME: // 'z'1249if (current == null) {1250if (formatData.locale == null || formatData.isZoneStringsSet) {1251int zoneIndex =1252formatData.getZoneIndex(calendar.getTimeZone().getID());1253if (zoneIndex == -1) {1254value = calendar.get(Calendar.ZONE_OFFSET) +1255calendar.get(Calendar.DST_OFFSET);1256buffer.append(ZoneInfoFile.toCustomID(value));1257} else {1258int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3;1259if (count < 4) {1260// Use the short name1261index++;1262}1263String[][] zoneStrings = formatData.getZoneStringsWrapper();1264buffer.append(zoneStrings[zoneIndex][index]);1265}1266} else {1267TimeZone tz = calendar.getTimeZone();1268boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);1269int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG);1270buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale));1271}1272}1273break;12741275case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)1276value = (calendar.get(Calendar.ZONE_OFFSET) +1277calendar.get(Calendar.DST_OFFSET)) / 60000;12781279int width = 4;1280if (value >= 0) {1281buffer.append('+');1282} else {1283width++;1284}12851286int num = (value / 60) * 100 + (value % 60);1287CalendarUtils.sprintf0d(buffer, num, width);1288break;12891290case PATTERN_ISO_ZONE: // 'X'1291value = calendar.get(Calendar.ZONE_OFFSET)1292+ calendar.get(Calendar.DST_OFFSET);12931294if (value == 0) {1295buffer.append('Z');1296break;1297}12981299value /= 60000;1300if (value >= 0) {1301buffer.append('+');1302} else {1303buffer.append('-');1304value = -value;1305}13061307CalendarUtils.sprintf0d(buffer, value / 60, 2);1308if (count == 1) {1309break;1310}13111312if (count == 3) {1313buffer.append(':');1314}1315CalendarUtils.sprintf0d(buffer, value % 60, 2);1316break;13171318default:1319// case PATTERN_DAY_OF_MONTH: // 'd'1320// case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:591321// case PATTERN_MINUTE: // 'm'1322// case PATTERN_SECOND: // 's'1323// case PATTERN_MILLISECOND: // 'S'1324// case PATTERN_DAY_OF_YEAR: // 'D'1325// case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'1326// case PATTERN_WEEK_OF_YEAR: // 'w'1327// case PATTERN_WEEK_OF_MONTH: // 'W'1328// case PATTERN_HOUR0: // 'K' eg, 11PM + 1 hour =>> 0 AM1329// case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 71330if (current == null) {1331zeroPaddingNumber(value, count, maxIntCount, buffer);1332}1333break;1334} // switch (patternCharIndex)13351336if (current != null) {1337buffer.append(current);1338}13391340int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];1341Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];13421343delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);1344}13451346/**1347* Formats a number with the specified minimum and maximum number of digits.1348*/1349private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)1350{1351// Optimization for 1, 2 and 4 digit numbers. This should1352// cover most cases of formatting date/time related items.1353// Note: This optimization code assumes that maxDigits is1354// either 2 or Integer.MAX_VALUE (maxIntCount in format()).1355try {1356if (zeroDigit == 0) {1357zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();1358}1359if (value >= 0) {1360if (value < 100 && minDigits >= 1 && minDigits <= 2) {1361if (value < 10) {1362if (minDigits == 2) {1363buffer.append(zeroDigit);1364}1365buffer.append((char)(zeroDigit + value));1366} else {1367buffer.append((char)(zeroDigit + value / 10));1368buffer.append((char)(zeroDigit + value % 10));1369}1370return;1371} else if (value >= 1000 && value < 10000) {1372if (minDigits == 4) {1373buffer.append((char)(zeroDigit + value / 1000));1374value %= 1000;1375buffer.append((char)(zeroDigit + value / 100));1376value %= 100;1377buffer.append((char)(zeroDigit + value / 10));1378buffer.append((char)(zeroDigit + value % 10));1379return;1380}1381if (minDigits == 2 && maxDigits == 2) {1382zeroPaddingNumber(value % 100, 2, 2, buffer);1383return;1384}1385}1386}1387} catch (Exception e) {1388}13891390numberFormat.setMinimumIntegerDigits(minDigits);1391numberFormat.setMaximumIntegerDigits(maxDigits);1392numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE);1393}139413951396/**1397* Parses text from a string to produce a <code>Date</code>.1398* <p>1399* The method attempts to parse text starting at the index given by1400* <code>pos</code>.1401* If parsing succeeds, then the index of <code>pos</code> is updated1402* to the index after the last character used (parsing does not necessarily1403* use all characters up to the end of the string), and the parsed1404* date is returned. The updated <code>pos</code> can be used to1405* indicate the starting point for the next call to this method.1406* If an error occurs, then the index of <code>pos</code> is not1407* changed, the error index of <code>pos</code> is set to the index of1408* the character where the error occurred, and null is returned.1409*1410* <p>This parsing operation uses the {@link DateFormat#calendar1411* calendar} to produce a {@code Date}. All of the {@code1412* calendar}'s date-time fields are {@linkplain Calendar#clear()1413* cleared} before parsing, and the {@code calendar}'s default1414* values of the date-time fields are used for any missing1415* date-time information. For example, the year value of the1416* parsed {@code Date} is 1970 with {@link GregorianCalendar} if1417* no year value is given from the parsing operation. The {@code1418* TimeZone} value may be overwritten, depending on the given1419* pattern and the time zone value in {@code text}. Any {@code1420* TimeZone} value that has previously been set by a call to1421* {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need1422* to be restored for further operations.1423*1424* @param text A <code>String</code>, part of which should be parsed.1425* @param pos A <code>ParsePosition</code> object with index and error1426* index information as described above.1427* @return A <code>Date</code> parsed from the string. In case of1428* error, returns null.1429* @exception NullPointerException if <code>text</code> or <code>pos</code> is null.1430*/1431@Override1432public Date parse(String text, ParsePosition pos)1433{1434checkNegativeNumberExpression();14351436int start = pos.index;1437int oldStart = start;1438int textLength = text.length();14391440boolean[] ambiguousYear = {false};14411442CalendarBuilder calb = new CalendarBuilder();14431444for (int i = 0; i < compiledPattern.length; ) {1445int tag = compiledPattern[i] >>> 8;1446int count = compiledPattern[i++] & 0xff;1447if (count == 255) {1448count = compiledPattern[i++] << 16;1449count |= compiledPattern[i++];1450}14511452switch (tag) {1453case TAG_QUOTE_ASCII_CHAR:1454if (start >= textLength || text.charAt(start) != (char)count) {1455pos.index = oldStart;1456pos.errorIndex = start;1457return null;1458}1459start++;1460break;14611462case TAG_QUOTE_CHARS:1463while (count-- > 0) {1464if (start >= textLength || text.charAt(start) != compiledPattern[i++]) {1465pos.index = oldStart;1466pos.errorIndex = start;1467return null;1468}1469start++;1470}1471break;14721473default:1474// Peek the next pattern to determine if we need to1475// obey the number of pattern letters for1476// parsing. It's required when parsing contiguous1477// digit text (e.g., "20010704") with a pattern which1478// has no delimiters between fields, like "yyyyMMdd".1479boolean obeyCount = false;14801481// In Arabic, a minus sign for a negative number is put after1482// the number. Even in another locale, a minus sign can be1483// put after a number using DateFormat.setNumberFormat().1484// If both the minus sign and the field-delimiter are '-',1485// subParse() needs to determine whether a '-' after a number1486// in the given text is a delimiter or is a minus sign for the1487// preceding number. We give subParse() a clue based on the1488// information in compiledPattern.1489boolean useFollowingMinusSignAsDelimiter = false;14901491if (i < compiledPattern.length) {1492int nextTag = compiledPattern[i] >>> 8;1493if (!(nextTag == TAG_QUOTE_ASCII_CHAR ||1494nextTag == TAG_QUOTE_CHARS)) {1495obeyCount = true;1496}14971498if (hasFollowingMinusSign &&1499(nextTag == TAG_QUOTE_ASCII_CHAR ||1500nextTag == TAG_QUOTE_CHARS)) {1501int c;1502if (nextTag == TAG_QUOTE_ASCII_CHAR) {1503c = compiledPattern[i] & 0xff;1504} else {1505c = compiledPattern[i+1];1506}15071508if (c == minusSign) {1509useFollowingMinusSignAsDelimiter = true;1510}1511}1512}1513start = subParse(text, start, tag, count, obeyCount,1514ambiguousYear, pos,1515useFollowingMinusSignAsDelimiter, calb);1516if (start < 0) {1517pos.index = oldStart;1518return null;1519}1520}1521}15221523// At this point the fields of Calendar have been set. Calendar1524// will fill in default values for missing fields when the time1525// is computed.15261527pos.index = start;15281529Date parsedDate;1530try {1531parsedDate = calb.establish(calendar).getTime();1532// If the year value is ambiguous,1533// then the two-digit year == the default start year1534if (ambiguousYear[0]) {1535if (parsedDate.before(defaultCenturyStart)) {1536parsedDate = calb.addYear(100).establish(calendar).getTime();1537}1538}1539}1540// An IllegalArgumentException will be thrown by Calendar.getTime()1541// if any fields are out of range, e.g., MONTH == 17.1542catch (IllegalArgumentException e) {1543pos.errorIndex = start;1544pos.index = oldStart;1545return null;1546}15471548return parsedDate;1549}15501551/**1552* Private code-size reduction function used by subParse.1553* @param text the time text being parsed.1554* @param start where to start parsing.1555* @param field the date field being parsed.1556* @param data the string array to parsed.1557* @return the new start position if matching succeeded; a negative number1558* indicating matching failure, otherwise.1559*/1560private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)1561{1562int i = 0;1563int count = data.length;15641565if (field == Calendar.DAY_OF_WEEK) {1566i = 1;1567}15681569// There may be multiple strings in the data[] array which begin with1570// the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).1571// We keep track of the longest match, and return that. Note that this1572// unfortunately requires us to test all array elements.1573int bestMatchLength = 0, bestMatch = -1;1574for (; i<count; ++i)1575{1576int length = data[i].length();1577// Always compare if we have no match yet; otherwise only compare1578// against potentially better matches (longer strings).1579if (length > bestMatchLength &&1580text.regionMatches(true, start, data[i], 0, length))1581{1582bestMatch = i;1583bestMatchLength = length;1584}1585}1586if (bestMatch >= 0)1587{1588calb.set(field, bestMatch);1589return start + bestMatchLength;1590}1591return -start;1592}15931594/**1595* Performs the same thing as matchString(String, int, int,1596* String[]). This method takes a Map<String, Integer> instead of1597* String[].1598*/1599private int matchString(String text, int start, int field,1600Map<String,Integer> data, CalendarBuilder calb) {1601if (data != null) {1602// TODO: make this default when it's in the spec.1603if (data instanceof SortedMap) {1604for (String name : data.keySet()) {1605if (text.regionMatches(true, start, name, 0, name.length())) {1606calb.set(field, data.get(name));1607return start + name.length();1608}1609}1610return -start;1611}16121613String bestMatch = null;16141615for (String name : data.keySet()) {1616int length = name.length();1617if (bestMatch == null || length > bestMatch.length()) {1618if (text.regionMatches(true, start, name, 0, length)) {1619bestMatch = name;1620}1621}1622}16231624if (bestMatch != null) {1625calb.set(field, data.get(bestMatch));1626return start + bestMatch.length();1627}1628}1629return -start;1630}16311632private int matchZoneString(String text, int start, String[] zoneNames) {1633for (int i = 1; i <= 4; ++i) {1634// Checking long and short zones [1 & 2],1635// and long and short daylight [3 & 4].1636String zoneName = zoneNames[i];1637if (text.regionMatches(true, start,1638zoneName, 0, zoneName.length())) {1639return i;1640}1641}1642return -1;1643}16441645private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex,1646String[][] zoneStrings) {1647int index = standardIndex + 2;1648String zoneName = zoneStrings[zoneIndex][index];1649if (text.regionMatches(true, start,1650zoneName, 0, zoneName.length())) {1651return true;1652}1653return false;1654}16551656/**1657* find time zone 'text' matched zoneStrings and set to internal1658* calendar.1659*/1660private int subParseZoneString(String text, int start, CalendarBuilder calb) {1661boolean useSameName = false; // true if standard and daylight time use the same abbreviation.1662TimeZone currentTimeZone = getTimeZone();16631664// At this point, check for named time zones by looking through1665// the locale data from the TimeZoneNames strings.1666// Want to be able to parse both short and long forms.1667int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID());1668TimeZone tz = null;1669String[][] zoneStrings = formatData.getZoneStringsWrapper();1670String[] zoneNames = null;1671int nameIndex = 0;1672if (zoneIndex != -1) {1673zoneNames = zoneStrings[zoneIndex];1674if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {1675if (nameIndex <= 2) {1676// Check if the standard name (abbr) and the daylight name are the same.1677useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);1678}1679tz = TimeZone.getTimeZone(zoneNames[0]);1680}1681}1682if (tz == null) {1683zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID());1684if (zoneIndex != -1) {1685zoneNames = zoneStrings[zoneIndex];1686if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {1687if (nameIndex <= 2) {1688useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);1689}1690tz = TimeZone.getTimeZone(zoneNames[0]);1691}1692}1693}16941695if (tz == null) {1696int len = zoneStrings.length;1697for (int i = 0; i < len; i++) {1698zoneNames = zoneStrings[i];1699if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {1700if (nameIndex <= 2) {1701useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);1702}1703tz = TimeZone.getTimeZone(zoneNames[0]);1704break;1705}1706}1707}1708if (tz != null) { // Matched any ?1709if (!tz.equals(currentTimeZone)) {1710setTimeZone(tz);1711}1712// If the time zone matched uses the same name1713// (abbreviation) for both standard and daylight time,1714// let the time zone in the Calendar decide which one.1715//1716// Also if tz.getDSTSaving() returns 0 for DST, use tz to1717// determine the local time. (6645292)1718int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0;1719if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) {1720calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount);1721}1722return (start + zoneNames[nameIndex].length());1723}1724return -start;1725}17261727/**1728* Parses numeric forms of time zone offset, such as "hh:mm", and1729* sets calb to the parsed value.1730*1731* @param text the text to be parsed1732* @param start the character position to start parsing1733* @param sign 1: positive; -1: negative1734* @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's1735* @param colon true - colon required between hh and mm; false - no colon required1736* @param calb a CalendarBuilder in which the parsed value is stored1737* @return updated parsed position, or its negative value to indicate a parsing error1738*/1739private int subParseNumericZone(String text, int start, int sign, int count,1740boolean colon, CalendarBuilder calb) {1741int index = start;17421743parse:1744try {1745char c = text.charAt(index++);1746// Parse hh1747int hours;1748if (!isDigit(c)) {1749break parse;1750}1751hours = c - '0';1752c = text.charAt(index++);1753if (isDigit(c)) {1754hours = hours * 10 + (c - '0');1755} else {1756// If no colon in RFC 822 or 'X' (ISO), two digits are1757// required.1758if (count > 0 || !colon) {1759break parse;1760}1761--index;1762}1763if (hours > 23) {1764break parse;1765}1766int minutes = 0;1767if (count != 1) {1768// Proceed with parsing mm1769c = text.charAt(index++);1770if (colon) {1771if (c != ':') {1772break parse;1773}1774c = text.charAt(index++);1775}1776if (!isDigit(c)) {1777break parse;1778}1779minutes = c - '0';1780c = text.charAt(index++);1781if (!isDigit(c)) {1782break parse;1783}1784minutes = minutes * 10 + (c - '0');1785if (minutes > 59) {1786break parse;1787}1788}1789minutes += hours * 60;1790calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign)1791.set(Calendar.DST_OFFSET, 0);1792return index;1793} catch (IndexOutOfBoundsException e) {1794}1795return 1 - index; // -(index - 1)1796}17971798private boolean isDigit(char c) {1799return c >= '0' && c <= '9';1800}18011802/**1803* Private member function that converts the parsed date strings into1804* timeFields. Returns -start (for ParsePosition) if failed.1805* @param text the time text to be parsed.1806* @param start where to start parsing.1807* @param patternCharIndex the index of the pattern character.1808* @param count the count of a pattern character.1809* @param obeyCount if true, then the next field directly abuts this one,1810* and we should use the count to know when to stop parsing.1811* @param ambiguousYear return parameter; upon return, if ambiguousYear[0]1812* is true, then a two-digit year was parsed and may need to be readjusted.1813* @param origPos origPos.errorIndex is used to return an error index1814* at which a parse error occurred, if matching failure occurs.1815* @return the new start position if matching succeeded; -1 indicating1816* matching failure, otherwise. In case matching failure occurred,1817* an error index is set to origPos.errorIndex.1818*/1819private int subParse(String text, int start, int patternCharIndex, int count,1820boolean obeyCount, boolean[] ambiguousYear,1821ParsePosition origPos,1822boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {1823Number number;1824int value = 0;1825ParsePosition pos = new ParsePosition(0);1826pos.index = start;1827if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {1828// use calendar year 'y' instead1829patternCharIndex = PATTERN_YEAR;1830}1831int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];18321833// If there are any spaces here, skip over them. If we hit the end1834// of the string, then fail.1835for (;;) {1836if (pos.index >= text.length()) {1837origPos.errorIndex = start;1838return -1;1839}1840char c = text.charAt(pos.index);1841if (c != ' ' && c != '\t') {1842break;1843}1844++pos.index;1845}1846// Remember the actual start index1847int actualStart = pos.index;18481849parsing:1850{1851// We handle a few special cases here where we need to parse1852// a number value. We handle further, more generic cases below. We need1853// to handle some of them here because some fields require extra processing on1854// the parsed value.1855if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||1856patternCharIndex == PATTERN_HOUR1 ||1857(patternCharIndex == PATTERN_MONTH && count <= 2) ||1858patternCharIndex == PATTERN_YEAR ||1859patternCharIndex == PATTERN_WEEK_YEAR) {1860// It would be good to unify this with the obeyCount logic below,1861// but that's going to be difficult.1862if (obeyCount) {1863if ((start+count) > text.length()) {1864break parsing;1865}1866number = numberFormat.parse(text.substring(0, start+count), pos);1867} else {1868number = numberFormat.parse(text, pos);1869}1870if (number == null) {1871if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) {1872break parsing;1873}1874} else {1875value = number.intValue();18761877if (useFollowingMinusSignAsDelimiter && (value < 0) &&1878(((pos.index < text.length()) &&1879(text.charAt(pos.index) != minusSign)) ||1880((pos.index == text.length()) &&1881(text.charAt(pos.index-1) == minusSign)))) {1882value = -value;1883pos.index--;1884}1885}1886}18871888boolean useDateFormatSymbols = useDateFormatSymbols();18891890int index;1891switch (patternCharIndex) {1892case PATTERN_ERA: // 'G'1893if (useDateFormatSymbols) {1894if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) {1895return index;1896}1897} else {1898Map<String, Integer> map = getDisplayNamesMap(field, locale);1899if ((index = matchString(text, start, field, map, calb)) > 0) {1900return index;1901}1902}1903break parsing;19041905case PATTERN_WEEK_YEAR: // 'Y'1906case PATTERN_YEAR: // 'y'1907if (!(calendar instanceof GregorianCalendar)) {1908// calendar might have text representations for year values,1909// such as "\u5143" in JapaneseImperialCalendar.1910int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;1911Map<String, Integer> map = calendar.getDisplayNames(field, style, locale);1912if (map != null) {1913if ((index = matchString(text, start, field, map, calb)) > 0) {1914return index;1915}1916}1917calb.set(field, value);1918return pos.index;1919}19201921// If there are 3 or more YEAR pattern characters, this indicates1922// that the year value is to be treated literally, without any1923// two-digit year adjustments (e.g., from "01" to 2001). Otherwise1924// we made adjustments to place the 2-digit year in the proper1925// century, for parsed strings from "00" to "99". Any other string1926// is treated literally: "2250", "-1", "1", "002".1927if (count <= 2 && (pos.index - actualStart) == 21928&& Character.isDigit(text.charAt(actualStart))1929&& Character.isDigit(text.charAt(actualStart + 1))) {1930// Assume for example that the defaultCenturyStart is 6/18/1903.1931// This means that two-digit years will be forced into the range1932// 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 021933// correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond1934// to 1904, 1905, etc. If the year is 03, then it is 2003 if the1935// other fields specify a date before 6/18, or 1903 if they specify a1936// date afterwards. As a result, 03 is an ambiguous year. All other1937// two-digit years are unambiguous.1938int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;1939ambiguousYear[0] = value == ambiguousTwoDigitYear;1940value += (defaultCenturyStartYear/100)*100 +1941(value < ambiguousTwoDigitYear ? 100 : 0);1942}1943calb.set(field, value);1944return pos.index;19451946case PATTERN_MONTH: // 'M'1947if (count <= 2) // i.e., M or MM.1948{1949// Don't want to parse the month if it is a string1950// while pattern uses numeric style: M or MM.1951// [We computed 'value' above.]1952calb.set(Calendar.MONTH, value - 1);1953return pos.index;1954}19551956if (useDateFormatSymbols) {1957// count >= 3 // i.e., MMM or MMMM1958// Want to be able to parse both short and long forms.1959// Try count == 4 first:1960int newStart;1961if ((newStart = matchString(text, start, Calendar.MONTH,1962formatData.getMonths(), calb)) > 0) {1963return newStart;1964}1965// count == 4 failed, now try count == 31966if ((index = matchString(text, start, Calendar.MONTH,1967formatData.getShortMonths(), calb)) > 0) {1968return index;1969}1970} else {1971Map<String, Integer> map = getDisplayNamesMap(field, locale);1972if ((index = matchString(text, start, field, map, calb)) > 0) {1973return index;1974}1975}1976break parsing;19771978case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:591979if (!isLenient()) {1980// Validate the hour value in non-lenient1981if (value < 1 || value > 24) {1982break parsing;1983}1984}1985// [We computed 'value' above.]1986if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) {1987value = 0;1988}1989calb.set(Calendar.HOUR_OF_DAY, value);1990return pos.index;19911992case PATTERN_DAY_OF_WEEK: // 'E'1993{1994if (useDateFormatSymbols) {1995// Want to be able to parse both short and long forms.1996// Try count == 4 (DDDD) first:1997int newStart;1998if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,1999formatData.getWeekdays(), calb)) > 0) {2000return newStart;2001}2002// DDDD failed, now try DDD2003if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,2004formatData.getShortWeekdays(), calb)) > 0) {2005return index;2006}2007} else {2008int[] styles = { Calendar.LONG, Calendar.SHORT };2009for (int style : styles) {2010Map<String,Integer> map = calendar.getDisplayNames(field, style, locale);2011if ((index = matchString(text, start, field, map, calb)) > 0) {2012return index;2013}2014}2015}2016}2017break parsing;20182019case PATTERN_AM_PM: // 'a'2020if (useDateFormatSymbols) {2021if ((index = matchString(text, start, Calendar.AM_PM,2022formatData.getAmPmStrings(), calb)) > 0) {2023return index;2024}2025} else {2026Map<String,Integer> map = getDisplayNamesMap(field, locale);2027if ((index = matchString(text, start, field, map, calb)) > 0) {2028return index;2029}2030}2031break parsing;20322033case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM2034if (!isLenient()) {2035// Validate the hour value in non-lenient2036if (value < 1 || value > 12) {2037break parsing;2038}2039}2040// [We computed 'value' above.]2041if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) {2042value = 0;2043}2044calb.set(Calendar.HOUR, value);2045return pos.index;20462047case PATTERN_ZONE_NAME: // 'z'2048case PATTERN_ZONE_VALUE: // 'Z'2049{2050int sign = 0;2051try {2052char c = text.charAt(pos.index);2053if (c == '+') {2054sign = 1;2055} else if (c == '-') {2056sign = -1;2057}2058if (sign == 0) {2059// Try parsing a custom time zone "GMT+hh:mm" or "GMT".2060if ((c == 'G' || c == 'g')2061&& (text.length() - start) >= GMT.length()2062&& text.regionMatches(true, start, GMT, 0, GMT.length())) {2063pos.index = start + GMT.length();20642065if ((text.length() - pos.index) > 0) {2066c = text.charAt(pos.index);2067if (c == '+') {2068sign = 1;2069} else if (c == '-') {2070sign = -1;2071}2072}20732074if (sign == 0) { /* "GMT" without offset */2075calb.set(Calendar.ZONE_OFFSET, 0)2076.set(Calendar.DST_OFFSET, 0);2077return pos.index;2078}20792080// Parse the rest as "hh:mm"2081int i = subParseNumericZone(text, ++pos.index,2082sign, 0, true, calb);2083if (i > 0) {2084return i;2085}2086pos.index = -i;2087} else {2088// Try parsing the text as a time zone2089// name or abbreviation.2090int i = subParseZoneString(text, pos.index, calb);2091if (i > 0) {2092return i;2093}2094pos.index = -i;2095}2096} else {2097// Parse the rest as "hhmm" (RFC 822)2098int i = subParseNumericZone(text, ++pos.index,2099sign, 0, false, calb);2100if (i > 0) {2101return i;2102}2103pos.index = -i;2104}2105} catch (IndexOutOfBoundsException e) {2106}2107}2108break parsing;21092110case PATTERN_ISO_ZONE: // 'X'2111{2112if ((text.length() - pos.index) <= 0) {2113break parsing;2114}21152116int sign;2117char c = text.charAt(pos.index);2118if (c == 'Z') {2119calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0);2120return ++pos.index;2121}21222123// parse text as "+/-hh[[:]mm]" based on count2124if (c == '+') {2125sign = 1;2126} else if (c == '-') {2127sign = -1;2128} else {2129++pos.index;2130break parsing;2131}2132int i = subParseNumericZone(text, ++pos.index, sign, count,2133count == 3, calb);2134if (i > 0) {2135return i;2136}2137pos.index = -i;2138}2139break parsing;21402141default:2142// case PATTERN_DAY_OF_MONTH: // 'd'2143// case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:592144// case PATTERN_MINUTE: // 'm'2145// case PATTERN_SECOND: // 's'2146// case PATTERN_MILLISECOND: // 'S'2147// case PATTERN_DAY_OF_YEAR: // 'D'2148// case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'2149// case PATTERN_WEEK_OF_YEAR: // 'w'2150// case PATTERN_WEEK_OF_MONTH: // 'W'2151// case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM2152// case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field);21532154// Handle "generic" fields2155if (obeyCount) {2156if ((start+count) > text.length()) {2157break parsing;2158}2159number = numberFormat.parse(text.substring(0, start+count), pos);2160} else {2161number = numberFormat.parse(text, pos);2162}2163if (number != null) {2164value = number.intValue();21652166if (useFollowingMinusSignAsDelimiter && (value < 0) &&2167(((pos.index < text.length()) &&2168(text.charAt(pos.index) != minusSign)) ||2169((pos.index == text.length()) &&2170(text.charAt(pos.index-1) == minusSign)))) {2171value = -value;2172pos.index--;2173}21742175calb.set(field, value);2176return pos.index;2177}2178break parsing;2179}2180}21812182// Parsing failed.2183origPos.errorIndex = pos.index;2184return -1;2185}21862187/**2188* Returns true if the DateFormatSymbols has been set explicitly or locale2189* is null.2190*/2191private boolean useDateFormatSymbols() {2192return useDateFormatSymbols || locale == null;2193}21942195/**2196* Translates a pattern, mapping each character in the from string to the2197* corresponding character in the to string.2198*2199* @exception IllegalArgumentException if the given pattern is invalid2200*/2201private String translatePattern(String pattern, String from, String to) {2202StringBuilder result = new StringBuilder();2203boolean inQuote = false;2204for (int i = 0; i < pattern.length(); ++i) {2205char c = pattern.charAt(i);2206if (inQuote) {2207if (c == '\'') {2208inQuote = false;2209}2210}2211else {2212if (c == '\'') {2213inQuote = true;2214} else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {2215int ci = from.indexOf(c);2216if (ci >= 0) {2217// patternChars is longer than localPatternChars due2218// to serialization compatibility. The pattern letters2219// unsupported by localPatternChars pass through.2220if (ci < to.length()) {2221c = to.charAt(ci);2222}2223} else {2224throw new IllegalArgumentException("Illegal pattern " +2225" character '" +2226c + "'");2227}2228}2229}2230result.append(c);2231}2232if (inQuote) {2233throw new IllegalArgumentException("Unfinished quote in pattern");2234}2235return result.toString();2236}22372238/**2239* Returns a pattern string describing this date format.2240*2241* @return a pattern string describing this date format.2242*/2243public String toPattern() {2244return pattern;2245}22462247/**2248* Returns a localized pattern string describing this date format.2249*2250* @return a localized pattern string describing this date format.2251*/2252public String toLocalizedPattern() {2253return translatePattern(pattern,2254DateFormatSymbols.patternChars,2255formatData.getLocalPatternChars());2256}22572258/**2259* Applies the given pattern string to this date format.2260*2261* @param pattern the new date and time pattern for this date format2262* @exception NullPointerException if the given pattern is null2263* @exception IllegalArgumentException if the given pattern is invalid2264*/2265public void applyPattern(String pattern)2266{2267applyPatternImpl(pattern);2268}22692270private void applyPatternImpl(String pattern) {2271compiledPattern = compile(pattern);2272this.pattern = pattern;2273}22742275/**2276* Applies the given localized pattern string to this date format.2277*2278* @param pattern a String to be mapped to the new date and time format2279* pattern for this format2280* @exception NullPointerException if the given pattern is null2281* @exception IllegalArgumentException if the given pattern is invalid2282*/2283public void applyLocalizedPattern(String pattern) {2284String p = translatePattern(pattern,2285formatData.getLocalPatternChars(),2286DateFormatSymbols.patternChars);2287compiledPattern = compile(p);2288this.pattern = p;2289}22902291/**2292* Gets a copy of the date and time format symbols of this date format.2293*2294* @return the date and time format symbols of this date format2295* @see #setDateFormatSymbols2296*/2297public DateFormatSymbols getDateFormatSymbols()2298{2299return (DateFormatSymbols)formatData.clone();2300}23012302/**2303* Sets the date and time format symbols of this date format.2304*2305* @param newFormatSymbols the new date and time format symbols2306* @exception NullPointerException if the given newFormatSymbols is null2307* @see #getDateFormatSymbols2308*/2309public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)2310{2311this.formatData = (DateFormatSymbols)newFormatSymbols.clone();2312useDateFormatSymbols = true;2313}23142315/**2316* Creates a copy of this <code>SimpleDateFormat</code>. This also2317* clones the format's date format symbols.2318*2319* @return a clone of this <code>SimpleDateFormat</code>2320*/2321@Override2322public Object clone() {2323SimpleDateFormat other = (SimpleDateFormat) super.clone();2324other.formatData = (DateFormatSymbols) formatData.clone();2325return other;2326}23272328/**2329* Returns the hash code value for this <code>SimpleDateFormat</code> object.2330*2331* @return the hash code value for this <code>SimpleDateFormat</code> object.2332*/2333@Override2334public int hashCode()2335{2336return pattern.hashCode();2337// just enough fields for a reasonable distribution2338}23392340/**2341* Compares the given object with this <code>SimpleDateFormat</code> for2342* equality.2343*2344* @return true if the given object is equal to this2345* <code>SimpleDateFormat</code>2346*/2347@Override2348public boolean equals(Object obj)2349{2350if (!super.equals(obj)) {2351return false; // super does class check2352}2353SimpleDateFormat that = (SimpleDateFormat) obj;2354return (pattern.equals(that.pattern)2355&& formatData.equals(that.formatData));2356}23572358private static final int[] REST_OF_STYLES = {2359Calendar.SHORT_STANDALONE, Calendar.LONG_FORMAT, Calendar.LONG_STANDALONE,2360};2361private Map<String, Integer> getDisplayNamesMap(int field, Locale locale) {2362Map<String, Integer> map = calendar.getDisplayNames(field, Calendar.SHORT_FORMAT, locale);2363// Get all SHORT and LONG styles (avoid NARROW styles).2364for (int style : REST_OF_STYLES) {2365Map<String, Integer> m = calendar.getDisplayNames(field, style, locale);2366if (m != null) {2367map.putAll(m);2368}2369}2370return map;2371}23722373/**2374* After reading an object from the input stream, the format2375* pattern in the object is verified.2376* <p>2377* @exception InvalidObjectException if the pattern is invalid2378*/2379private void readObject(ObjectInputStream stream)2380throws IOException, ClassNotFoundException {2381stream.defaultReadObject();23822383try {2384compiledPattern = compile(pattern);2385} catch (Exception e) {2386throw new InvalidObjectException("invalid pattern");2387}23882389if (serialVersionOnStream < 1) {2390// didn't have defaultCenturyStart field2391initializeDefaultCentury();2392}2393else {2394// fill in dependent transient field2395parseAmbiguousDatesAsAfter(defaultCenturyStart);2396}2397serialVersionOnStream = currentSerialVersion;23982399// If the deserialized object has a SimpleTimeZone, try2400// to replace it with a ZoneInfo equivalent in order to2401// be compatible with the SimpleTimeZone-based2402// implementation as much as possible.2403TimeZone tz = getTimeZone();2404if (tz instanceof SimpleTimeZone) {2405String id = tz.getID();2406TimeZone zi = TimeZone.getTimeZone(id);2407if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) {2408setTimeZone(zi);2409}2410}2411}24122413/**2414* Analyze the negative subpattern of DecimalFormat and set/update values2415* as necessary.2416*/2417private void checkNegativeNumberExpression() {2418if ((numberFormat instanceof DecimalFormat) &&2419!numberFormat.equals(originalNumberFormat)) {2420String numberPattern = ((DecimalFormat)numberFormat).toPattern();2421if (!numberPattern.equals(originalNumberPattern)) {2422hasFollowingMinusSign = false;24232424int separatorIndex = numberPattern.indexOf(';');2425// If the negative subpattern is not absent, we have to analayze2426// it in order to check if it has a following minus sign.2427if (separatorIndex > -1) {2428int minusIndex = numberPattern.indexOf('-', separatorIndex);2429if ((minusIndex > numberPattern.lastIndexOf('0')) &&2430(minusIndex > numberPattern.lastIndexOf('#'))) {2431hasFollowingMinusSign = true;2432minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign();2433}2434}2435originalNumberPattern = numberPattern;2436}2437originalNumberFormat = numberFormat;2438}2439}24402441}244224432444