• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 /*
28  * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
29  * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved
30  *
31  *   The original version of this source code and documentation is copyrighted
32  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
33  * materials are provided under terms of a License Agreement between Taligent
34  * and Sun. This technology is protected by multiple US and International
35  * patents. This notice and attribution to Taligent may not be removed.
36  *   Taligent is a registered trademark of Taligent, Inc.
37  *
38  */
39 
40 package java.text;
41 
42 import android.icu.text.TimeZoneNames;
43 import android.icu.util.ULocale;
44 
45 import com.android.icu.text.ExtendedTimeZoneNames;
46 import com.android.icu.text.ExtendedTimeZoneNames.Match;
47 
48 import java.io.IOException;
49 import java.io.InvalidObjectException;
50 import java.io.ObjectInputStream;
51 import java.util.Calendar;
52 import java.util.Date;
53 import java.util.GregorianCalendar;
54 import java.util.Locale;
55 import java.util.Map;
56 import java.util.NavigableMap;
57 import java.util.SimpleTimeZone;
58 import java.util.TimeZone;
59 import java.util.concurrent.ConcurrentHashMap;
60 import java.util.concurrent.ConcurrentMap;
61 import libcore.icu.LocaleData;
62 
63 import sun.util.calendar.CalendarUtils;
64 
65 import static java.text.DateFormatSymbols.*;
66 
67 // Android-changed: Added supported API level, removed unnecessary <br>
68 // Android-changed: Clarified info about X symbol time zone parsing
69 // Android-changed: Changed MMMMM to MMMM in month format example (ICU behavior).
70 // http://b/147860740
71 /**
72  * <code>SimpleDateFormat</code> is a concrete class for formatting and
73  * parsing dates in a locale-sensitive manner. It allows for formatting
74  * (date &rarr; text), parsing (text &rarr; date), and normalization.
75  *
76  * <p>
77  * <code>SimpleDateFormat</code> allows you to start by choosing
78  * any user-defined patterns for date-time formatting. However, you
79  * are encouraged to create a date-time formatter with either
80  * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
81  * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
82  * of these class methods can return a date/time formatter initialized
83  * with a default format pattern. You may modify the format pattern
84  * using the <code>applyPattern</code> methods as desired.
85  * For more information on using these methods, see
86  * {@link DateFormat}.
87  *
88  * <h3>Date and Time Patterns</h3>
89  * <p>
90  * Date and time formats are specified by <em>date and time pattern</em>
91  * strings.
92  * Within date and time pattern strings, unquoted letters from
93  * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to
94  * <code>'z'</code> are interpreted as pattern letters representing the
95  * components of a date or time string.
96  * Text can be quoted using single quotes (<code>'</code>) to avoid
97  * interpretation.
98  * <code>"''"</code> represents a single quote.
99  * All other characters are not interpreted; they're simply copied into the
100  * output string during formatting or matched against the input string
101  * during parsing.
102  * <p>
103  * The following pattern letters are defined (all other characters from
104  * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to
105  * <code>'z'</code> are reserved):
106  * <blockquote>
107  * <table border=0 cellspacing=3 cellpadding=0 summary="Chart shows pattern letters, date/time component, presentation, and examples.">
108  *     <tr style="background-color: rgb(204, 204, 255);">
109  *         <th align=left>Letter
110  *         <th align=left>Date or Time Component
111  *         <th align=left>Presentation
112  *         <th align=left>Examples
113  *         <th align=left>Supported (API Levels)
114  *     <tr>
115  *         <td><code>G</code>
116  *         <td>Era designator
117  *         <td><a href="#text">Text</a>
118  *         <td><code>AD</code>
119  *         <td>1+</td>
120  *     <tr style="background-color: rgb(238, 238, 255);">
121  *         <td><code>y</code>
122  *         <td>Year
123  *         <td><a href="#year">Year</a>
124  *         <td><code>1996</code>; <code>96</code>
125  *         <td>1+</td>
126  *     <tr>
127  *         <td><code>Y</code>
128  *         <td>Week year
129  *         <td><a href="#year">Year</a>
130  *         <td><code>2009</code>; <code>09</code>
131  *         <td>24+</td>
132  *     <tr style="background-color: rgb(238, 238, 255);">
133  *         <td><code>M</code>
134  *         <td>Month in year (context sensitive)
135  *         <td><a href="#month">Month</a>
136  *         <td><code>July</code>; <code>Jul</code>; <code>07</code>
137  *         <td>1+</td>
138  *     <tr>
139  *         <td><code>L</code>
140  *         <td>Month in year (standalone form)
141  *         <td><a href="#month">Month</a>
142  *         <td><code>July</code>; <code>Jul</code>; <code>07</code>
143  *         <td>TBD</td>
144  *     <tr style="background-color: rgb(238, 238, 255);">
145  *         <td><code>w</code>
146  *         <td>Week in year
147  *         <td><a href="#number">Number</a>
148  *         <td><code>27</code>
149  *         <td>1+</td>
150  *     <tr>
151  *         <td><code>W</code>
152  *         <td>Week in month
153  *         <td><a href="#number">Number</a>
154  *         <td><code>2</code>
155  *         <td>1+</td>
156  *     <tr style="background-color: rgb(238, 238, 255);">
157  *         <td><code>D</code>
158  *         <td>Day in year
159  *         <td><a href="#number">Number</a>
160  *         <td><code>189</code>
161  *         <td>1+</td>
162  *     <tr>
163  *         <td><code>d</code>
164  *         <td>Day in month
165  *         <td><a href="#number">Number</a>
166  *         <td><code>10</code>
167  *         <td>1+</td>
168  *     <tr style="background-color: rgb(238, 238, 255);">
169  *         <td><code>F</code>
170  *         <td>Day of week in month
171  *         <td><a href="#number">Number</a>
172  *         <td><code>2</code>
173  *         <td>1+</td>
174  *     <tr>
175  *         <td><code>E</code>
176  *         <td>Day name in week
177  *         <td><a href="#text">Text</a>
178  *         <td><code>Tuesday</code>; <code>Tue</code>
179  *         <td>1+</td>
180  *     <tr style="background-color: rgb(238, 238, 255);">
181  *         <td><code>u</code>
182  *         <td>Day number of week (1 = Monday, ..., 7 = Sunday)
183  *         <td><a href="#number">Number</a>
184  *         <td><code>1</code>
185  *         <td>24+</td>
186  *     <tr>
187  *         <td><code>a</code>
188  *         <td>Am/pm marker
189  *         <td><a href="#text">Text</a>
190  *         <td><code>PM</code>
191  *         <td>1+</td>
192  *     <tr style="background-color: rgb(238, 238, 255);">
193  *         <td><code>H</code>
194  *         <td>Hour in day (0-23)
195  *         <td><a href="#number">Number</a>
196  *         <td><code>0</code>
197  *         <td>1+</td>
198  *     <tr>
199  *         <td><code>k</code>
200  *         <td>Hour in day (1-24)
201  *         <td><a href="#number">Number</a>
202  *         <td><code>24</code>
203  *         <td>1+</td>
204  *     <tr style="background-color: rgb(238, 238, 255);">
205  *         <td><code>K</code>
206  *         <td>Hour in am/pm (0-11)
207  *         <td><a href="#number">Number</a>
208  *         <td><code>0</code>
209  *         <td>1+</td>
210  *     <tr>
211  *         <td><code>h</code>
212  *         <td>Hour in am/pm (1-12)
213  *         <td><a href="#number">Number</a>
214  *         <td><code>12</code>
215  *         <td>1+</td>
216  *     <tr style="background-color: rgb(238, 238, 255);">
217  *         <td><code>m</code>
218  *         <td>Minute in hour
219  *         <td><a href="#number">Number</a>
220  *         <td><code>30</code>
221  *         <td>1+</td>
222  *     <tr>
223  *         <td><code>s</code>
224  *         <td>Second in minute
225  *         <td><a href="#number">Number</a>
226  *         <td><code>55</code>
227  *         <td>1+</td>
228  *     <tr style="background-color: rgb(238, 238, 255);">
229  *         <td><code>S</code>
230  *         <td>Millisecond
231  *         <td><a href="#number">Number</a>
232  *         <td><code>978</code>
233  *         <td>1+</td>
234  *     <tr>
235  *         <td><code>z</code>
236  *         <td>Time zone
237  *         <td><a href="#timezone">General time zone</a>
238  *         <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code>
239  *         <td>1+</td>
240  *     <tr style="background-color: rgb(238, 238, 255);">
241  *         <td><code>Z</code>
242  *         <td>Time zone
243  *         <td><a href="#rfc822timezone">RFC 822 time zone</a>
244  *         <td><code>-0800</code>
245  *         <td>1+</td>
246  *     <tr>
247  *         <td><code>X</code>
248  *         <td>Time zone
249  *         <td><a href="#iso8601timezone">ISO 8601 time zone</a>
250  *         <td><code>-08</code>; <code>-0800</code>;  <code>-08:00</code>
251  *         <td>24+</td>
252  * </table>
253  * </blockquote>
254  * Pattern letters are usually repeated, as their number determines the
255  * exact presentation:
256  * <ul>
257  * <li><strong><a name="text">Text:</a></strong>
258  *     For formatting, if the number of pattern letters is 4 or more,
259  *     the full form is used; otherwise a short or abbreviated form
260  *     is used if available.
261  *     For parsing, both forms are accepted, independent of the number
262  *     of pattern letters.</li>
263  * <li><strong><a name="number">Number:</a></strong>
264  *     For formatting, the number of pattern letters is the minimum
265  *     number of digits, and shorter numbers are zero-padded to this amount.
266  *     For parsing, the number of pattern letters is ignored unless
267  *     it's needed to separate two adjacent fields.</li>
268  * <li><strong><a name="year">Year:</a></strong>
269  *     If the formatter's {@link #getCalendar() Calendar} is the Gregorian
270  *     calendar, the following rules are applied.
271  *     <ul>
272  *     <li>For formatting, if the number of pattern letters is 2, the year
273  *         is truncated to 2 digits; otherwise it is interpreted as a
274  *         <a href="#number">number</a>.
275  *     <li>For parsing, if the number of pattern letters is more than 2,
276  *         the year is interpreted literally, regardless of the number of
277  *         digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to
278  *         Jan 11, 12 A.D.
279  *     <li>For parsing with the abbreviated year pattern ("y" or "yy"),
280  *         <code>SimpleDateFormat</code> must interpret the abbreviated year
281  *         relative to some century.  It does this by adjusting dates to be
282  *         within 80 years before and 20 years after the time the <code>SimpleDateFormat</code>
283  *         instance is created. For example, using a pattern of "MM/dd/yy" and a
284  *         <code>SimpleDateFormat</code> instance created on Jan 1, 1997,  the string
285  *         "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
286  *         would be interpreted as May 4, 1964.
287  *         During parsing, only strings consisting of exactly two digits, as defined by
288  *         {@link Character#isDigit(char)}, will be parsed into the default century.
289  *         Any other numeric string, such as a one digit string, a three or more digit
290  *         string, or a two digit string that isn't all digits (for example, "-1"), is
291  *         interpreted literally.  So "01/02/3" or "01/02/003" are parsed, using the
292  *         same pattern, as Jan 2, 3 AD.  Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
293  *     </ul>
294  *     Otherwise, calendar system specific forms are applied.
295  *     For both formatting and parsing, if the number of pattern
296  *     letters is 4 or more, a calendar specific {@linkplain
297  *     Calendar#LONG long form} is used. Otherwise, a calendar
298  *     specific {@linkplain Calendar#SHORT short or abbreviated form}
299  *     is used.
300  *     <br>
301  *     If week year {@code 'Y'} is specified and the {@linkplain
302  *     #getCalendar() calendar} doesn't support any <a
303  *     href="../util/GregorianCalendar.html#week_year"> week
304  *     years</a>, the calendar year ({@code 'y'}) is used instead. The
305  *     support of week years can be tested with a call to {@link
306  *     DateFormat#getCalendar() getCalendar()}.{@link
307  *     java.util.Calendar#isWeekDateSupported()
308  *     isWeekDateSupported()}.</li>
309  * <li><strong><a name="month">Month:</a></strong>
310  *     If the number of pattern letters is 3 or more, the month is
311  *     interpreted as <a href="#text">text</a>; otherwise,
312  *     it is interpreted as a <a href="#number">number</a>.
313  *     <ul>
314  *     <li>Letter <em>M</em> produces context-sensitive month names, such as the
315  *         embedded form of names. If a {@code DateFormatSymbols} has been set
316  *         explicitly with constructor {@link #SimpleDateFormat(String,
317  *         DateFormatSymbols)} or method {@link
318  *         #setDateFormatSymbols(DateFormatSymbols)}, the month names given by
319  *         the {@code DateFormatSymbols} are used.</li>
320  *     <li>Letter <em>L</em> produces the standalone form of month names.</li>
321  *     </ul>
322  *     <br></li>
323  * <li><strong><a name="timezone">General time zone:</a></strong>
324  *     Time zones are interpreted as <a href="#text">text</a> if they have
325  *     names. For time zones representing a GMT offset value, the
326  *     following syntax is used:
327  *     <pre>
328  *     <a name="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a>
329  *             <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i>
330  *     <i>Sign:</i> one of
331  *             <code>+ -</code>
332  *     <i>Hours:</i>
333  *             <i>Digit</i>
334  *             <i>Digit</i> <i>Digit</i>
335  *     <i>Minutes:</i>
336  *             <i>Digit</i> <i>Digit</i>
337  *     <i>Digit:</i> one of
338  *             <code>0 1 2 3 4 5 6 7 8 9</code></pre>
339  *     <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between
340  *     00 and 59. The format is locale independent and digits must be taken
341  *     from the Basic Latin block of the Unicode standard.
342  *     <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also
343  *     accepted.</li>
344  * <li><strong><a name="rfc822timezone">RFC 822 time zone:</a></strong>
345  *     For formatting, the RFC 822 4-digit time zone format is used:
346  *
347  *     <pre>
348  *     <i>RFC822TimeZone:</i>
349  *             <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>
350  *     <i>TwoDigitHours:</i>
351  *             <i>Digit Digit</i></pre>
352  *     <i>TwoDigitHours</i> must be between 00 and 23. Other definitions
353  *     are as for <a href="#timezone">general time zones</a>.
354  *
355  *     <p>For parsing, <a href="#timezone">general time zones</a> are also
356  *     accepted.
357  * <li><strong><a name="iso8601timezone">ISO 8601 Time zone:</a></strong>
358  *     The number of pattern letters designates the format for both formatting
359  *     and parsing as follows:
360  *     <pre>
361  *     <i>ISO8601TimeZone:</i>
362  *             <i>OneLetterISO8601TimeZone</i>
363  *             <i>TwoLetterISO8601TimeZone</i>
364  *             <i>ThreeLetterISO8601TimeZone</i>
365  *     <i>OneLetterISO8601TimeZone:</i>
366  *             <i>Sign</i> <i>TwoDigitHours</i>
367  *             {@code Z}
368  *     <i>TwoLetterISO8601TimeZone:</i>
369  *             <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>
370  *             {@code Z}
371  *     <i>ThreeLetterISO8601TimeZone:</i>
372  *             <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i>
373  *             {@code Z}</pre>
374  *     Other definitions are as for <a href="#timezone">general time zones</a> or
375  *     <a href="#rfc822timezone">RFC 822 time zones</a>.
376  *
377  *     <p>For formatting, if the offset value from GMT is 0, {@code "Z"} is
378  *     produced. If the number of pattern letters is 1, any fraction of an hour
379  *     is ignored. For example, if the pattern is {@code "X"} and the time zone is
380  *     {@code "GMT+05:30"}, {@code "+05"} is produced.
381  *
382  *     <p>For parsing, the letter {@code "Z"} is parsed as the UTC time zone designator (therefore
383  *     {@code "09:30Z"} is parsed as {@code "09:30 UTC"}.
384  *     <a href="#timezone">General time zones</a> are <em>not</em> accepted.
385  *     <p>If the number of {@code "X"} pattern letters is 4 or more (e.g. {@code XXXX}), {@link
386  *     IllegalArgumentException} is thrown when constructing a {@code
387  *     SimpleDateFormat} or {@linkplain #applyPattern(String) applying a
388  *     pattern}.
389  * </ul>
390  * <code>SimpleDateFormat</code> also supports <em>localized date and time
391  * pattern</em> strings. In these strings, the pattern letters described above
392  * may be replaced with other, locale dependent, pattern letters.
393  * <code>SimpleDateFormat</code> does not deal with the localization of text
394  * other than the pattern letters; that's up to the client of the class.
395  *
396  * <h4>Examples</h4>
397  *
398  * The following examples show how date and time patterns are interpreted in
399  * the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time
400  * in the U.S. Pacific Time time zone.
401  * <blockquote>
402  * <table border=0 cellspacing=3 cellpadding=0 summary="Examples of date and time patterns interpreted in the U.S. locale">
403  *     <tr style="background-color: rgb(204, 204, 255);">
404  *         <th align=left>Date and Time Pattern
405  *         <th align=left>Result
406  *     <tr>
407  *         <td><code>"yyyy.MM.dd G 'at' HH:mm:ss z"</code>
408  *         <td><code>2001.07.04 AD at 12:08:56 PDT</code>
409  *     <tr style="background-color: rgb(238, 238, 255);">
410  *         <td><code>"EEE, MMM d, ''yy"</code>
411  *         <td><code>Wed, Jul 4, '01</code>
412  *     <tr>
413  *         <td><code>"h:mm a"</code>
414  *         <td><code>12:08 PM</code>
415  *     <tr style="background-color: rgb(238, 238, 255);">
416  *         <td><code>"hh 'o''clock' a, zzzz"</code>
417  *         <td><code>12 o'clock PM, Pacific Daylight Time</code>
418  *     <tr>
419  *         <td><code>"K:mm a, z"</code>
420  *         <td><code>0:08 PM, PDT</code>
421  *     <tr style="background-color: rgb(238, 238, 255);">
422  *         <td><code>"yyyyy.MMMM.dd GGG hh:mm aaa"</code>
423  *         <td><code>02001.July.04 AD 12:08 PM</code>
424  *     <tr>
425  *         <td><code>"EEE, d MMM yyyy HH:mm:ss Z"</code>
426  *         <td><code>Wed, 4 Jul 2001 12:08:56 -0700</code>
427  *     <tr style="background-color: rgb(238, 238, 255);">
428  *         <td><code>"yyMMddHHmmssZ"</code>
429  *         <td><code>010704120856-0700</code>
430  *     <tr>
431  *         <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSZ"</code>
432  *         <td><code>2001-07-04T12:08:56.235-0700</code>
433  *     <tr style="background-color: rgb(238, 238, 255);">
434  *         <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"</code>
435  *         <td><code>2001-07-04T12:08:56.235-07:00</code>
436  *     <tr>
437  *         <td><code>"YYYY-'W'ww-u"</code>
438  *         <td><code>2001-W27-3</code>
439  * </table>
440  * </blockquote>
441  *
442  * <h4><a name="synchronization">Synchronization</a></h4>
443  *
444  * <p>
445  * Date formats are not synchronized.
446  * It is recommended to create separate format instances for each thread.
447  * If multiple threads access a format concurrently, it must be synchronized
448  * externally.
449  *
450  * @see          <a href="https://docs.oracle.com/javase/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a>
451  * @see          java.util.Calendar
452  * @see          java.util.TimeZone
453  * @see          DateFormat
454  * @see          DateFormatSymbols
455  * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
456  */
457 public class SimpleDateFormat extends DateFormat {
458 
459     // the official serial version ID which says cryptically
460     // which version we're compatible with
461     static final long serialVersionUID = 4774881970558875024L;
462 
463     // the internal serial version which says which version was written
464     // - 0 (default) for version up to JDK 1.1.3
465     // - 1 for version from JDK 1.1.4, which includes a new field
466     static final int currentSerialVersion = 1;
467 
468     /**
469      * The version of the serialized data on the stream.  Possible values:
470      * <ul>
471      * <li><b>0</b> or not present on stream: JDK 1.1.3.  This version
472      * has no <code>defaultCenturyStart</code> on stream.
473      * <li><b>1</b> JDK 1.1.4 or later.  This version adds
474      * <code>defaultCenturyStart</code>.
475      * </ul>
476      * When streaming out this class, the most recent format
477      * and the highest allowable <code>serialVersionOnStream</code>
478      * is written.
479      * @serial
480      * @since JDK1.1.4
481      */
482     private int serialVersionOnStream = currentSerialVersion;
483 
484     /**
485      * The pattern string of this formatter.  This is always a non-localized
486      * pattern.  May not be null.  See class documentation for details.
487      * @serial
488      */
489     private String pattern;
490 
491     /**
492      * Saved numberFormat and pattern.
493      * @see SimpleDateFormat#checkNegativeNumberExpression
494      */
495     transient private NumberFormat originalNumberFormat;
496     transient private String originalNumberPattern;
497 
498     /**
499      * The minus sign to be used with format and parse.
500      */
501     transient private char minusSign = '-';
502 
503     /**
504      * True when a negative sign follows a number.
505      * (True as default in Arabic.)
506      */
507     transient private boolean hasFollowingMinusSign = false;
508 
509     // BEGIN Android-removed: App compat for formatting pattern letter M.
510     // OpenJDK forces the standalone form of month when patterns contain pattern M only.
511     // This feature is not incorporated for app compatibility and because the feature is
512     // not documented in OpenJDK or Android.
513     /*
514     /**
515      * True if standalone form needs to be used.
516      *
517     transient private boolean forceStandaloneForm = false;
518     */
519     // END Android-removed: App compat for formatting pattern letter M.
520 
521     /**
522      * The compiled pattern.
523      */
524     transient private char[] compiledPattern;
525 
526     /**
527      * Tags for the compiled pattern.
528      */
529     private final static int TAG_QUOTE_ASCII_CHAR       = 100;
530     private final static int TAG_QUOTE_CHARS            = 101;
531 
532     /**
533      * Locale dependent digit zero.
534      * @see #zeroPaddingNumber
535      * @see java.text.DecimalFormatSymbols#getZeroDigit
536      */
537     transient private char zeroDigit;
538 
539     /**
540      * The symbols used by this formatter for week names, month names,
541      * etc.  May not be null.
542      * @serial
543      * @see java.text.DateFormatSymbols
544      */
545     private DateFormatSymbols formatData;
546 
547     /**
548      * We map dates with two-digit years into the century starting at
549      * <code>defaultCenturyStart</code>, which may be any date.  May
550      * not be null.
551      * @serial
552      * @since JDK1.1.4
553      */
554     private Date defaultCenturyStart;
555 
556     transient private int defaultCenturyStartYear;
557 
558     private static final int MILLIS_PER_MINUTE = 60 * 1000;
559 
560     // For time zones that have no names, use strings GMT+minutes and
561     // GMT-minutes. For instance, in France the time zone is GMT+60.
562     private static final String GMT = "GMT";
563 
564     /**
565      * Cache NumberFormat instances with Locale key.
566      */
567     private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData
568         = new ConcurrentHashMap<>(3);
569 
570     /**
571      * The Locale used to instantiate this
572      * <code>SimpleDateFormat</code>. The value may be null if this object
573      * has been created by an older <code>SimpleDateFormat</code> and
574      * deserialized.
575      *
576      * @serial
577      * @since 1.6
578      */
579     private Locale locale;
580 
581     /**
582      * Indicates whether this <code>SimpleDateFormat</code> should use
583      * the DateFormatSymbols. If true, the format and parse methods
584      * use the DateFormatSymbols values. If false, the format and
585      * parse methods call Calendar.getDisplayName or
586      * Calendar.getDisplayNames.
587      */
588     transient boolean useDateFormatSymbols;
589 
590     // Android-added: ICU TimeZoneNames field.
591     /**
592      * ICU TimeZoneNames used to format and parse time zone names.
593      */
594     private transient ExtendedTimeZoneNames timeZoneNames;
595 
596     /**
597      * Constructs a <code>SimpleDateFormat</code> using the default pattern and
598      * date format symbols for the default
599      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
600      * <b>Note:</b> This constructor may not support all locales.
601      * For full coverage, use the factory methods in the {@link DateFormat}
602      * class.
603      */
SimpleDateFormat()604     public SimpleDateFormat() {
605         // BEGIN Android-changed: Android has no LocaleProviderAdapter. Use ICU locale data.
606         /*
607         this("", Locale.getDefault(Locale.Category.FORMAT));
608         applyPatternImpl(LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale)
609                          .getDateTimePattern(SHORT, SHORT, calendar));
610         */
611         this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT));
612         // END Android-changed: Android has no LocaleProviderAdapter. Use ICU locale data.
613     }
614 
615     // BEGIN Android-added: Ctor used by DateFormat to remove use of LocaleProviderAdapter.
616     /**
617      * Constructs a <code>SimpleDateFormat</code> using the given date and time formatting styles.
618      * @param timeStyle the given date formatting style.
619      * @param dateStyle the given time formatting style.
620      * @param locale the locale whose pattern and date format symbols should be used
621      */
SimpleDateFormat(int timeStyle, int dateStyle, Locale locale)622     SimpleDateFormat(int timeStyle, int dateStyle, Locale locale) {
623         this(getDateTimeFormat(timeStyle, dateStyle, locale), locale);
624     }
625 
getDateTimeFormat(int timeStyle, int dateStyle, Locale locale)626     private static String getDateTimeFormat(int timeStyle, int dateStyle, Locale locale) {
627         LocaleData localeData = LocaleData.get(locale);
628         if ((timeStyle >= 0) && (dateStyle >= 0)) {
629             Object[] dateTimeArgs = {
630                 localeData.getDateFormat(dateStyle),
631                 localeData.getTimeFormat(timeStyle),
632             };
633             return MessageFormat.format("{0} {1}", dateTimeArgs);
634         } else if (timeStyle >= 0) {
635             return localeData.getTimeFormat(timeStyle);
636         } else if (dateStyle >= 0) {
637             return localeData.getDateFormat(dateStyle);
638         } else {
639             throw new IllegalArgumentException("No date or time style specified");
640         }
641     }
642     // END Android-added: Ctor used by DateFormat to remove use of LocaleProviderAdapter.
643 
644     /**
645      * Constructs a <code>SimpleDateFormat</code> using the given pattern and
646      * the default date format symbols for the default
647      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
648      * <b>Note:</b> This constructor may not support all locales.
649      * For full coverage, use the factory methods in the {@link DateFormat}
650      * class.
651      * <p>This is equivalent to calling
652      * {@link #SimpleDateFormat(String, Locale)
653      *     SimpleDateFormat(pattern, Locale.getDefault(Locale.Category.FORMAT))}.
654      *
655      * @see java.util.Locale#getDefault(java.util.Locale.Category)
656      * @see java.util.Locale.Category#FORMAT
657      * @param pattern the pattern describing the date and time format
658      * @exception NullPointerException if the given pattern is null
659      * @exception IllegalArgumentException if the given pattern is invalid
660      */
SimpleDateFormat(String pattern)661     public SimpleDateFormat(String pattern)
662     {
663         this(pattern, Locale.getDefault(Locale.Category.FORMAT));
664     }
665 
666     /**
667      * Constructs a <code>SimpleDateFormat</code> using the given pattern and
668      * the default date format symbols for the given locale.
669      * <b>Note:</b> This constructor may not support all locales.
670      * For full coverage, use the factory methods in the {@link DateFormat}
671      * class.
672      *
673      * @param pattern the pattern describing the date and time format
674      * @param locale the locale whose date format symbols should be used
675      * @exception NullPointerException if the given pattern or locale is null
676      * @exception IllegalArgumentException if the given pattern is invalid
677      */
SimpleDateFormat(String pattern, Locale locale)678     public SimpleDateFormat(String pattern, Locale locale)
679     {
680         if (pattern == null || locale == null) {
681             throw new NullPointerException();
682         }
683 
684         initializeCalendar(locale);
685         this.pattern = pattern;
686         this.formatData = DateFormatSymbols.getInstanceRef(locale);
687         this.locale = locale;
688         initialize(locale);
689     }
690 
691     /**
692      * Constructs a <code>SimpleDateFormat</code> using the given pattern and
693      * date format symbols.
694      *
695      * @param pattern the pattern describing the date and time format
696      * @param formatSymbols the date format symbols to be used for formatting
697      * @exception NullPointerException if the given pattern or formatSymbols is null
698      * @exception IllegalArgumentException if the given pattern is invalid
699      */
SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)700     public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)
701     {
702         if (pattern == null || formatSymbols == null) {
703             throw new NullPointerException();
704         }
705 
706         this.pattern = pattern;
707         this.formatData = (DateFormatSymbols) formatSymbols.clone();
708         this.locale = Locale.getDefault(Locale.Category.FORMAT);
709         initializeCalendar(this.locale);
710         initialize(this.locale);
711         useDateFormatSymbols = true;
712     }
713 
714     /* Initialize compiledPattern and numberFormat fields */
initialize(Locale loc)715     private void initialize(Locale loc) {
716         // Verify and compile the given pattern.
717         compiledPattern = compile(pattern);
718 
719         /* try the cache first */
720         numberFormat = cachedNumberFormatData.get(loc);
721         if (numberFormat == null) { /* cache miss */
722             numberFormat = NumberFormat.getIntegerInstance(loc);
723             numberFormat.setGroupingUsed(false);
724 
725             /* update cache */
726             cachedNumberFormatData.putIfAbsent(loc, numberFormat);
727         }
728         numberFormat = (NumberFormat) numberFormat.clone();
729 
730         initializeDefaultCentury();
731     }
732 
initializeCalendar(Locale loc)733     private void initializeCalendar(Locale loc) {
734         if (calendar == null) {
735             assert loc != null;
736             // The format object must be constructed using the symbols for this zone.
737             // However, the calendar should use the current default TimeZone.
738             // If this is not contained in the locale zone strings, then the zone
739             // will be formatted using generic GMT+/-H:MM nomenclature.
740             calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
741         }
742     }
743 
744     /**
745      * Returns the compiled form of the given pattern. The syntax of
746      * the compiled pattern is:
747      * <blockquote>
748      * CompiledPattern:
749      *     EntryList
750      * EntryList:
751      *     Entry
752      *     EntryList Entry
753      * Entry:
754      *     TagField
755      *     TagField data
756      * TagField:
757      *     Tag Length
758      *     TaggedData
759      * Tag:
760      *     pattern_char_index
761      *     TAG_QUOTE_CHARS
762      * Length:
763      *     short_length
764      *     long_length
765      * TaggedData:
766      *     TAG_QUOTE_ASCII_CHAR ascii_char
767      *
768      * </blockquote>
769      *
770      * where `short_length' is an 8-bit unsigned integer between 0 and
771      * 254.  `long_length' is a sequence of an 8-bit integer 255 and a
772      * 32-bit signed integer value which is split into upper and lower
773      * 16-bit fields in two char's. `pattern_char_index' is an 8-bit
774      * integer between 0 and 18. `ascii_char' is an 7-bit ASCII
775      * character value. `data' depends on its Tag value.
776      * <p>
777      * If Length is short_length, Tag and short_length are packed in a
778      * single char, as illustrated below.
779      * <blockquote>
780      *     char[0] = (Tag << 8) | short_length;
781      * </blockquote>
782      *
783      * If Length is long_length, Tag and 255 are packed in the first
784      * char and a 32-bit integer, as illustrated below.
785      * <blockquote>
786      *     char[0] = (Tag << 8) | 255;
787      *     char[1] = (char) (long_length >>> 16);
788      *     char[2] = (char) (long_length & 0xffff);
789      * </blockquote>
790      * <p>
791      * If Tag is a pattern_char_index, its Length is the number of
792      * pattern characters. For example, if the given pattern is
793      * "yyyy", Tag is 1 and Length is 4, followed by no data.
794      * <p>
795      * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's
796      * following the TagField. For example, if the given pattern is
797      * "'o''clock'", Length is 7 followed by a char sequence of
798      * <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>.
799      * <p>
800      * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII
801      * character in place of Length. For example, if the given pattern
802      * is "'o'", the TaggedData entry is
803      * <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>.
804      *
805      * @exception NullPointerException if the given pattern is null
806      * @exception IllegalArgumentException if the given pattern is invalid
807      */
compile(String pattern)808     private char[] compile(String pattern) {
809         int length = pattern.length();
810         boolean inQuote = false;
811         StringBuilder compiledCode = new StringBuilder(length * 2);
812         StringBuilder tmpBuffer = null;
813         // BEGIN Android-removed: App compat for formatting pattern letter M.
814         // See forceStandaloneForm field
815         /*
816         int count = 0, tagcount = 0;
817         int lastTag = -1, prevTag = -1;
818         */
819         int count = 0;
820         int lastTag = -1;
821         // END Android-removed: App compat for formatting pattern letter M.
822 
823         for (int i = 0; i < length; i++) {
824             char c = pattern.charAt(i);
825 
826             if (c == '\'') {
827                 // '' is treated as a single quote regardless of being
828                 // in a quoted section.
829                 if ((i + 1) < length) {
830                     c = pattern.charAt(i + 1);
831                     if (c == '\'') {
832                         i++;
833                         if (count != 0) {
834                             encode(lastTag, count, compiledCode);
835                             // BEGIN Android-removed: App compat for formatting pattern letter M.
836                             // See forceStandaloneForm field
837                             /*
838                             tagcount++;
839                             prevTag = lastTag;
840                             */
841                             // END Android-removed: App compat for formatting pattern letter M.
842                             lastTag = -1;
843                             count = 0;
844                         }
845                         if (inQuote) {
846                             tmpBuffer.append(c);
847                         } else {
848                             compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
849                         }
850                         continue;
851                     }
852                 }
853                 if (!inQuote) {
854                     if (count != 0) {
855                         encode(lastTag, count, compiledCode);
856                         // BEGIN Android-removed: App compat for formatting pattern letter M.
857                         // See forceStandaloneForm field
858                         /*
859                         tagcount++;
860                         prevTag = lastTag;
861                         */
862                         // END Android-removed: App compat for formatting pattern letter M.
863                         lastTag = -1;
864                         count = 0;
865                     }
866                     if (tmpBuffer == null) {
867                         tmpBuffer = new StringBuilder(length);
868                     } else {
869                         tmpBuffer.setLength(0);
870                     }
871                     inQuote = true;
872                 } else {
873                     int len = tmpBuffer.length();
874                     if (len == 1) {
875                         char ch = tmpBuffer.charAt(0);
876                         if (ch < 128) {
877                             compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch));
878                         } else {
879                             compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | 1));
880                             compiledCode.append(ch);
881                         }
882                     } else {
883                         encode(TAG_QUOTE_CHARS, len, compiledCode);
884                         compiledCode.append(tmpBuffer);
885                     }
886                     inQuote = false;
887                 }
888                 continue;
889             }
890             if (inQuote) {
891                 tmpBuffer.append(c);
892                 continue;
893             }
894             if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
895                 if (count != 0) {
896                     encode(lastTag, count, compiledCode);
897                     // BEGIN Android-removed: App compat for formatting pattern letter M.
898                     // See forceStandaloneForm field
899                     /*
900                     tagcount++;
901                     prevTag = lastTag;
902                     */
903                     // END Android-removed: App compat for formatting pattern letter M.
904                     lastTag = -1;
905                     count = 0;
906                 }
907                 if (c < 128) {
908                     // In most cases, c would be a delimiter, such as ':'.
909                     compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
910                 } else {
911                     // Take any contiguous non-ASCII alphabet characters and
912                     // put them in a single TAG_QUOTE_CHARS.
913                     int j;
914                     for (j = i + 1; j < length; j++) {
915                         char d = pattern.charAt(j);
916                         if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
917                             break;
918                         }
919                     }
920                     compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | (j - i)));
921                     for (; i < j; i++) {
922                         compiledCode.append(pattern.charAt(i));
923                     }
924                     i--;
925                 }
926                 continue;
927             }
928 
929             int tag;
930             if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {
931                 throw new IllegalArgumentException("Illegal pattern character " +
932                                                    "'" + c + "'");
933             }
934             if (lastTag == -1 || lastTag == tag) {
935                 lastTag = tag;
936                 count++;
937                 continue;
938             }
939             encode(lastTag, count, compiledCode);
940             // BEGIN Android-removed: App compat for formatting pattern letter M.
941             // See forceStandaloneForm field
942             /*
943             tagcount++;
944             prevTag = lastTag;
945             */
946             // END Android-removed: App compat for formatting pattern letter M.
947             lastTag = tag;
948             count = 1;
949         }
950 
951         if (inQuote) {
952             throw new IllegalArgumentException("Unterminated quote");
953         }
954 
955         if (count != 0) {
956             encode(lastTag, count, compiledCode);
957             // BEGIN Android-removed: App compat for formatting pattern letter M.
958             // See forceStandaloneForm field
959             /*
960             tagcount++;
961             prevTag = lastTag;
962             */
963             // END Android-removed: App compat for formatting pattern letter M.
964         }
965 
966         // Android-removed: App compat for formatting pattern letter M.
967         // See forceStandaloneForm field
968         // forceStandaloneForm = (tagcount == 1 && prevTag == PATTERN_MONTH);
969 
970         // Copy the compiled pattern to a char array
971         int len = compiledCode.length();
972         char[] r = new char[len];
973         compiledCode.getChars(0, len, r, 0);
974         return r;
975     }
976 
977     /**
978      * Encodes the given tag and length and puts encoded char(s) into buffer.
979      */
encode(int tag, int length, StringBuilder buffer)980     private static void encode(int tag, int length, StringBuilder buffer) {
981         if (tag == PATTERN_ISO_ZONE && length >= 4) {
982             throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);
983         }
984         if (length < 255) {
985             buffer.append((char)(tag << 8 | length));
986         } else {
987             buffer.append((char)((tag << 8) | 0xff));
988             buffer.append((char)(length >>> 16));
989             buffer.append((char)(length & 0xffff));
990         }
991     }
992 
993     /* Initialize the fields we use to disambiguate ambiguous years. Separate
994      * so we can call it from readObject().
995      */
initializeDefaultCentury()996     private void initializeDefaultCentury() {
997         calendar.setTimeInMillis(System.currentTimeMillis());
998         calendar.add( Calendar.YEAR, -80 );
999         parseAmbiguousDatesAsAfter(calendar.getTime());
1000     }
1001 
1002     /* Define one-century window into which to disambiguate dates using
1003      * two-digit years.
1004      */
parseAmbiguousDatesAsAfter(Date startDate)1005     private void parseAmbiguousDatesAsAfter(Date startDate) {
1006         defaultCenturyStart = startDate;
1007         calendar.setTime(startDate);
1008         defaultCenturyStartYear = calendar.get(Calendar.YEAR);
1009     }
1010 
1011     /**
1012      * Sets the 100-year period 2-digit years will be interpreted as being in
1013      * to begin on the date the user specifies.
1014      *
1015      * @param startDate During parsing, two digit years will be placed in the range
1016      * <code>startDate</code> to <code>startDate + 100 years</code>.
1017      * @see #get2DigitYearStart
1018      * @since 1.2
1019      */
set2DigitYearStart(Date startDate)1020     public void set2DigitYearStart(Date startDate) {
1021         parseAmbiguousDatesAsAfter(new Date(startDate.getTime()));
1022     }
1023 
1024     /**
1025      * Returns the beginning date of the 100-year period 2-digit years are interpreted
1026      * as being within.
1027      *
1028      * @return the start of the 100-year period into which two digit years are
1029      * parsed
1030      * @see #set2DigitYearStart
1031      * @since 1.2
1032      */
get2DigitYearStart()1033     public Date get2DigitYearStart() {
1034         return (Date) defaultCenturyStart.clone();
1035     }
1036 
1037     /**
1038      * Formats the given <code>Date</code> into a date/time string and appends
1039      * the result to the given <code>StringBuffer</code>.
1040      *
1041      * @param date the date-time value to be formatted into a date-time string.
1042      * @param toAppendTo where the new date-time text is to be appended.
1043      * @param pos the formatting position. On input: an alignment field,
1044      * if desired. On output: the offsets of the alignment field.
1045      * @return the formatted date-time string.
1046      * @exception NullPointerException if the given {@code date} is {@code null}.
1047      */
1048     @Override
format(Date date, StringBuffer toAppendTo, FieldPosition pos)1049     public StringBuffer format(Date date, StringBuffer toAppendTo,
1050                                FieldPosition pos)
1051     {
1052         pos.beginIndex = pos.endIndex = 0;
1053         return format(date, toAppendTo, pos.getFieldDelegate());
1054     }
1055 
1056     // Called from Format after creating a FieldDelegate
format(Date date, StringBuffer toAppendTo, FieldDelegate delegate)1057     private StringBuffer format(Date date, StringBuffer toAppendTo,
1058                                 FieldDelegate delegate) {
1059         // Convert input date to time field list
1060         calendar.setTime(date);
1061 
1062         boolean useDateFormatSymbols = useDateFormatSymbols();
1063 
1064         for (int i = 0; i < compiledPattern.length; ) {
1065             int tag = compiledPattern[i] >>> 8;
1066             int count = compiledPattern[i++] & 0xff;
1067             if (count == 255) {
1068                 count = compiledPattern[i++] << 16;
1069                 count |= compiledPattern[i++];
1070             }
1071 
1072             switch (tag) {
1073             case TAG_QUOTE_ASCII_CHAR:
1074                 toAppendTo.append((char)count);
1075                 break;
1076 
1077             case TAG_QUOTE_CHARS:
1078                 toAppendTo.append(compiledPattern, i, count);
1079                 i += count;
1080                 break;
1081 
1082             default:
1083                 subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
1084                 break;
1085             }
1086         }
1087         return toAppendTo;
1088     }
1089 
1090     /**
1091      * Formats an Object producing an <code>AttributedCharacterIterator</code>.
1092      * You can use the returned <code>AttributedCharacterIterator</code>
1093      * to build the resulting String, as well as to determine information
1094      * about the resulting String.
1095      * <p>
1096      * Each attribute key of the AttributedCharacterIterator will be of type
1097      * <code>DateFormat.Field</code>, with the corresponding attribute value
1098      * being the same as the attribute key.
1099      *
1100      * @exception NullPointerException if obj is null.
1101      * @exception IllegalArgumentException if the Format cannot format the
1102      *            given object, or if the Format's pattern string is invalid.
1103      * @param obj The object to format
1104      * @return AttributedCharacterIterator describing the formatted value.
1105      * @since 1.4
1106      */
1107     @Override
formatToCharacterIterator(Object obj)1108     public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
1109         StringBuffer sb = new StringBuffer();
1110         CharacterIteratorFieldDelegate delegate = new
1111                          CharacterIteratorFieldDelegate();
1112 
1113         if (obj instanceof Date) {
1114             format((Date)obj, sb, delegate);
1115         }
1116         else if (obj instanceof Number) {
1117             format(new Date(((Number)obj).longValue()), sb, delegate);
1118         }
1119         else if (obj == null) {
1120             throw new NullPointerException(
1121                    "formatToCharacterIterator must be passed non-null object");
1122         }
1123         else {
1124             throw new IllegalArgumentException(
1125                              "Cannot format given Object as a Date");
1126         }
1127         return delegate.getIterator(sb.toString());
1128     }
1129 
1130     // Map index into pattern character string to Calendar field number
1131     private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = {
1132         Calendar.ERA,
1133         Calendar.YEAR,
1134         Calendar.MONTH,
1135         Calendar.DATE,
1136         Calendar.HOUR_OF_DAY,
1137         Calendar.HOUR_OF_DAY,
1138         Calendar.MINUTE,
1139         Calendar.SECOND,
1140         Calendar.MILLISECOND,
1141         Calendar.DAY_OF_WEEK,
1142         Calendar.DAY_OF_YEAR,
1143         Calendar.DAY_OF_WEEK_IN_MONTH,
1144         Calendar.WEEK_OF_YEAR,
1145         Calendar.WEEK_OF_MONTH,
1146         Calendar.AM_PM,
1147         Calendar.HOUR,
1148         Calendar.HOUR,
1149         Calendar.ZONE_OFFSET,
1150         Calendar.ZONE_OFFSET,
1151         CalendarBuilder.WEEK_YEAR,         // Pseudo Calendar field
1152         CalendarBuilder.ISO_DAY_OF_WEEK,   // Pseudo Calendar field
1153         Calendar.ZONE_OFFSET,
1154         Calendar.MONTH,
1155         // Android-added: 'c' for standalone day of week.
1156         Calendar.DAY_OF_WEEK,
1157         // Android-added: Support for 'b'/'B' (day period). Calendar.AM_PM is just used as a
1158         // placeholder in the absence of full support for day period.
1159         Calendar.AM_PM,
1160         Calendar.AM_PM
1161     };
1162 
1163     // Map index into pattern character string to DateFormat field number
1164     private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
1165         DateFormat.ERA_FIELD,
1166         DateFormat.YEAR_FIELD,
1167         DateFormat.MONTH_FIELD,
1168         DateFormat.DATE_FIELD,
1169         DateFormat.HOUR_OF_DAY1_FIELD,
1170         DateFormat.HOUR_OF_DAY0_FIELD,
1171         DateFormat.MINUTE_FIELD,
1172         DateFormat.SECOND_FIELD,
1173         DateFormat.MILLISECOND_FIELD,
1174         DateFormat.DAY_OF_WEEK_FIELD,
1175         DateFormat.DAY_OF_YEAR_FIELD,
1176         DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
1177         DateFormat.WEEK_OF_YEAR_FIELD,
1178         DateFormat.WEEK_OF_MONTH_FIELD,
1179         DateFormat.AM_PM_FIELD,
1180         DateFormat.HOUR1_FIELD,
1181         DateFormat.HOUR0_FIELD,
1182         DateFormat.TIMEZONE_FIELD,
1183         DateFormat.TIMEZONE_FIELD,
1184         DateFormat.YEAR_FIELD,
1185         DateFormat.DAY_OF_WEEK_FIELD,
1186         DateFormat.TIMEZONE_FIELD,
1187         DateFormat.MONTH_FIELD,
1188         // Android-added: 'c' for standalone day of week.
1189         DateFormat.DAY_OF_WEEK_FIELD,
1190         // Android-added: Support for 'b'/'B' (day period). DateFormat.AM_PM_FIELD is just used as a
1191         // placeholder in the absence of full support for day period.
1192         DateFormat.AM_PM_FIELD,
1193         DateFormat.AM_PM_FIELD
1194     };
1195 
1196     // Maps from DecimalFormatSymbols index to Field constant
1197     private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = {
1198         Field.ERA,
1199         Field.YEAR,
1200         Field.MONTH,
1201         Field.DAY_OF_MONTH,
1202         Field.HOUR_OF_DAY1,
1203         Field.HOUR_OF_DAY0,
1204         Field.MINUTE,
1205         Field.SECOND,
1206         Field.MILLISECOND,
1207         Field.DAY_OF_WEEK,
1208         Field.DAY_OF_YEAR,
1209         Field.DAY_OF_WEEK_IN_MONTH,
1210         Field.WEEK_OF_YEAR,
1211         Field.WEEK_OF_MONTH,
1212         Field.AM_PM,
1213         Field.HOUR1,
1214         Field.HOUR0,
1215         Field.TIME_ZONE,
1216         Field.TIME_ZONE,
1217         Field.YEAR,
1218         Field.DAY_OF_WEEK,
1219         Field.TIME_ZONE,
1220         Field.MONTH,
1221         // Android-added: 'c' for standalone day of week.
1222         Field.DAY_OF_WEEK,
1223         // Android-added: Support for 'b'/'B' (day period). Field.AM_PM is just used as a
1224         // placeholder in the absence of full support for day period.
1225         Field.AM_PM,
1226         Field.AM_PM
1227     };
1228 
1229     /**
1230      * Private member function that does the real date/time formatting.
1231      */
subFormat(int patternCharIndex, int count, FieldDelegate delegate, StringBuffer buffer, boolean useDateFormatSymbols)1232     private void subFormat(int patternCharIndex, int count,
1233                            FieldDelegate delegate, StringBuffer buffer,
1234                            boolean useDateFormatSymbols)
1235     {
1236         int     maxIntCount = Integer.MAX_VALUE;
1237         String  current = null;
1238         int     beginOffset = buffer.length();
1239 
1240         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1241         int value;
1242         if (field == CalendarBuilder.WEEK_YEAR) {
1243             if (calendar.isWeekDateSupported()) {
1244                 value = calendar.getWeekYear();
1245             } else {
1246                 // use calendar year 'y' instead
1247                 patternCharIndex = PATTERN_YEAR;
1248                 field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1249                 value = calendar.get(field);
1250             }
1251         } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
1252             value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
1253         } else {
1254             value = calendar.get(field);
1255         }
1256 
1257         int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
1258         if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) {
1259             current = calendar.getDisplayName(field, style, locale);
1260         }
1261 
1262         // Note: zeroPaddingNumber() assumes that maxDigits is either
1263         // 2 or maxIntCount. If we make any changes to this,
1264         // zeroPaddingNumber() must be fixed.
1265 
1266         switch (patternCharIndex) {
1267         case PATTERN_ERA: // 'G'
1268             if (useDateFormatSymbols) {
1269                 String[] eras = formatData.getEras();
1270                 if (value < eras.length) {
1271                     current = eras[value];
1272                 }
1273             }
1274             if (current == null) {
1275                 current = "";
1276             }
1277             break;
1278 
1279         case PATTERN_WEEK_YEAR: // 'Y'
1280         case PATTERN_YEAR:      // 'y'
1281             if (calendar instanceof GregorianCalendar) {
1282                 if (count != 2) {
1283                     zeroPaddingNumber(value, count, maxIntCount, buffer);
1284                 } else {
1285                     zeroPaddingNumber(value, 2, 2, buffer);
1286                 } // clip 1996 to 96
1287             } else {
1288                 if (current == null) {
1289                     zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count,
1290                                       maxIntCount, buffer);
1291                 }
1292             }
1293             break;
1294 
1295         case PATTERN_MONTH:            // 'M' (context seinsive)
1296             // BEGIN Android-changed: formatMonth() method to format using ICU data.
1297             /*
1298             if (useDateFormatSymbols) {
1299                 String[] months;
1300                 if (count >= 4) {
1301                     months = formatData.getMonths();
1302                     current = months[value];
1303                 } else if (count == 3) {
1304                     months = formatData.getShortMonths();
1305                     current = months[value];
1306                 }
1307             } else {
1308                 if (count < 3) {
1309                     current = null;
1310                 } else if (forceStandaloneForm) {
1311                     current = calendar.getDisplayName(field, style | 0x8000, locale);
1312                     if (current == null) {
1313                         current = calendar.getDisplayName(field, style, locale);
1314                     }
1315                 }
1316             }
1317             if (current == null) {
1318                 zeroPaddingNumber(value+1, count, maxIntCount, buffer);
1319             }
1320             */
1321             current = formatMonth(count, value, maxIntCount, buffer, useDateFormatSymbols,
1322                 false /* standalone */, field, style);
1323             // END Android-changed: formatMonth() method to format using ICU data.
1324             break;
1325 
1326         case PATTERN_MONTH_STANDALONE: // 'L'
1327             // BEGIN Android-changed: formatMonth() method to format using ICU data.
1328             /*
1329             assert current == null;
1330             if (locale == null) {
1331                 String[] months;
1332                 if (count >= 4) {
1333                     months = formatData.getMonths();
1334                     current = months[value];
1335                 } else if (count == 3) {
1336                     months = formatData.getShortMonths();
1337                     current = months[value];
1338                 }
1339             } else {
1340                 if (count >= 3) {
1341                     current = calendar.getDisplayName(field, style | 0x8000, locale);
1342                 }
1343             }
1344             if (current == null) {
1345                 zeroPaddingNumber(value+1, count, maxIntCount, buffer);
1346             }
1347             */
1348             current = formatMonth(count, value, maxIntCount, buffer, useDateFormatSymbols,
1349                    true /* standalone */, field, style);
1350             // END Android-changed: formatMonth() method to format using ICU data.
1351             break;
1352 
1353         case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
1354             if (current == null) {
1355                 if (value == 0) {
1356                     zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1,
1357                                       count, maxIntCount, buffer);
1358                 } else {
1359                     zeroPaddingNumber(value, count, maxIntCount, buffer);
1360                 }
1361             }
1362             break;
1363 
1364         case PATTERN_DAY_OF_WEEK: // 'E'
1365             // BEGIN Android-removed: App compat for formatting pattern letter M.
1366             // See forceStandaloneForm field
1367             /*
1368             if (useDateFormatSymbols) {
1369                 String[] weekdays;
1370                 if (count >= 4) {
1371                     weekdays = formatData.getWeekdays();
1372                     current = weekdays[value];
1373                 } else { // count < 4, use abbreviated form if exists
1374                     weekdays = formatData.getShortWeekdays();
1375                     current = weekdays[value];
1376                 }
1377             }
1378             */
1379             if (current == null) {
1380                 current = formatWeekday(count, value, useDateFormatSymbols, false /* standalone */);
1381             }
1382             // END Android-removed: App compat for formatting pattern letter M.
1383             break;
1384 
1385         // BEGIN Android-added: support for 'c' (standalone day of week).
1386         case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
1387             if (current == null) {
1388                 current = formatWeekday(count, value, useDateFormatSymbols, true /* standalone */);
1389             }
1390             break;
1391         // END Android-added: support for 'c' (standalone day of week).
1392 
1393         case PATTERN_AM_PM:    // 'a'
1394             if (useDateFormatSymbols) {
1395                 String[] ampm = formatData.getAmPmStrings();
1396                 current = ampm[value];
1397             }
1398             break;
1399 
1400         // Android-added: Ignore 'b' and 'B' introduced in CLDR 32+ pattern data. http://b/68139386
1401         // Not currently supported here.
1402         case PATTERN_DAY_PERIOD:
1403         case PATTERN_FLEXIBLE_DAY_PERIOD:
1404             current = "";
1405             break;
1406 
1407         case PATTERN_HOUR1:    // 'h' 1-based.  eg, 11PM + 1 hour =>> 12 AM
1408             if (current == null) {
1409                 if (value == 0) {
1410                     zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1,
1411                                       count, maxIntCount, buffer);
1412                 } else {
1413                     zeroPaddingNumber(value, count, maxIntCount, buffer);
1414                 }
1415             }
1416             break;
1417 
1418         case PATTERN_ZONE_NAME: // 'z'
1419             if (current == null) {
1420                 // BEGIN Android-changed: Format time zone name using ICU.
1421                 /*
1422                 if (formatData.locale == null || formatData.isZoneStringsSet) {
1423                     int zoneIndex =
1424                         formatData.getZoneIndex(calendar.getTimeZone().getID());
1425                     if (zoneIndex == -1) {
1426                         value = calendar.get(Calendar.ZONE_OFFSET) +
1427                             calendar.get(Calendar.DST_OFFSET);
1428                         buffer.append(ZoneInfoFile.toCustomID(value));
1429                     } else {
1430                         int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3;
1431                         if (count < 4) {
1432                             // Use the short name
1433                             index++;
1434                         }
1435                         String[][] zoneStrings = formatData.getZoneStringsWrapper();
1436                         buffer.append(zoneStrings[zoneIndex][index]);
1437                     }
1438                 } else {
1439                     TimeZone tz = calendar.getTimeZone();
1440                     boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
1441                     int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG);
1442                     buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale));
1443                 }
1444                 */
1445                 TimeZone tz = calendar.getTimeZone();
1446                 boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
1447                 String zoneString;
1448                 if (formatData.isZoneStringsSet) {
1449                     // DateFormatSymbols.setZoneStrings() has be used, use those values instead of
1450                     // ICU code.
1451                     int tzstyle = count < 4 ? TimeZone.SHORT : TimeZone.LONG;
1452                     zoneString = libcore.icu.TimeZoneNames.getDisplayName(
1453                             formatData.getZoneStringsWrapper(), tz.getID(), daylight, tzstyle);
1454                 } else {
1455                     TimeZoneNames.NameType nameType;
1456                     if (count < 4) {
1457                         nameType = daylight
1458                                 ? TimeZoneNames.NameType.SHORT_DAYLIGHT
1459                                 : TimeZoneNames.NameType.SHORT_STANDARD;
1460                     } else {
1461                         nameType = daylight
1462                                 ? TimeZoneNames.NameType.LONG_DAYLIGHT
1463                                 : TimeZoneNames.NameType.LONG_STANDARD;
1464                     }
1465                     String canonicalID = android.icu.util.TimeZone.getCanonicalID(tz.getID());
1466                     zoneString = getTimeZoneNames()
1467                             .getDisplayName(canonicalID, nameType, calendar.getTimeInMillis());
1468                 }
1469                 if (zoneString != null) {
1470                     buffer.append(zoneString);
1471                 } else {
1472                     int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) +
1473                         calendar.get(Calendar.DST_OFFSET);
1474                     buffer.append(TimeZone.createGmtOffsetString(true, true, offsetMillis));
1475                 }
1476                 // END Android-changed: Format time zone name using ICU.
1477             }
1478             break;
1479 
1480         case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)
1481             // BEGIN Android-changed: Use shared code in TimeZone for zone offset string.
1482             /*
1483             value = (calendar.get(Calendar.ZONE_OFFSET) +
1484                      calendar.get(Calendar.DST_OFFSET)) / 60000;
1485 
1486             int width = 4;
1487             if (value >= 0) {
1488                 buffer.append('+');
1489             } else {
1490                 width++;
1491             }
1492 
1493             int num = (value / 60) * 100 + (value % 60);
1494             CalendarUtils.sprintf0d(buffer, num, width);
1495             */
1496             value = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1497             final boolean includeSeparator = (count >= 4);
1498             final boolean includeGmt = (count == 4);
1499             buffer.append(TimeZone.createGmtOffsetString(includeGmt, includeSeparator, value));
1500 
1501             break;
1502             // END Android-changed: Use shared code in TimeZone for zone offset string.
1503 
1504         case PATTERN_ISO_ZONE:   // 'X'
1505             value = calendar.get(Calendar.ZONE_OFFSET)
1506                     + calendar.get(Calendar.DST_OFFSET);
1507 
1508             if (value == 0) {
1509                 buffer.append('Z');
1510                 break;
1511             }
1512 
1513             value /=  60000;
1514             if (value >= 0) {
1515                 buffer.append('+');
1516             } else {
1517                 buffer.append('-');
1518                 value = -value;
1519             }
1520 
1521             CalendarUtils.sprintf0d(buffer, value / 60, 2);
1522             if (count == 1) {
1523                 break;
1524             }
1525 
1526             if (count == 3) {
1527                 buffer.append(':');
1528             }
1529             CalendarUtils.sprintf0d(buffer, value % 60, 2);
1530             break;
1531         // BEGIN Android-added: Better UTS#35 conformity for fractional seconds.
1532         case PATTERN_MILLISECOND: // 'S'
1533             // Fractional seconds must be treated specially. We must always convert the parsed
1534             // value into a fractional second [0, 1) and then widen it out to the appropriate
1535             // formatted size. For example, an initial value of 789 will be converted
1536             // 0.789 and then become ".7" (S) or ".78" (SS) or "0.789" (SSS) or "0.7890" (SSSS)
1537             // in the resulting formatted output.
1538             if (current == null) {
1539                 value = (int) (((double) value / 1000) * Math.pow(10, count));
1540                 zeroPaddingNumber(value, count, count, buffer);
1541             }
1542             break;
1543         // END Android-added: Better UTS#35 conformity for fractional seconds.
1544 
1545         default:
1546      // case PATTERN_DAY_OF_MONTH:         // 'd'
1547      // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
1548      // case PATTERN_MINUTE:               // 'm'
1549      // case PATTERN_SECOND:               // 's'
1550      // Android-removed: PATTERN_MILLISECONDS handled in an explicit case above.
1551      //// case PATTERN_MILLISECOND:          // 'S'
1552      // case PATTERN_DAY_OF_YEAR:          // 'D'
1553      // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
1554      // case PATTERN_WEEK_OF_YEAR:         // 'w'
1555      // case PATTERN_WEEK_OF_MONTH:        // 'W'
1556      // case PATTERN_HOUR0:                // 'K' eg, 11PM + 1 hour =>> 0 AM
1557      // case PATTERN_ISO_DAY_OF_WEEK:      // 'u' pseudo field, Monday = 1, ..., Sunday = 7
1558             if (current == null) {
1559                 zeroPaddingNumber(value, count, maxIntCount, buffer);
1560             }
1561             break;
1562         } // switch (patternCharIndex)
1563 
1564         if (current != null) {
1565             buffer.append(current);
1566         }
1567 
1568         int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];
1569         Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];
1570 
1571         delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
1572     }
1573 
1574     // BEGIN Android-added: formatWeekday() and formatMonth() methods to format using ICU data.
formatWeekday(int count, int value, boolean useDateFormatSymbols, boolean standalone)1575     private String formatWeekday(int count, int value, boolean useDateFormatSymbols,
1576                                  boolean standalone) {
1577         if (useDateFormatSymbols) {
1578             final String[] weekdays;
1579             if (count == 4) {
1580                 weekdays = standalone ? formatData.getStandAloneWeekdays() : formatData.getWeekdays();
1581             } else if (count == 5) {
1582                 weekdays =
1583                         standalone ? formatData.getTinyStandAloneWeekdays() : formatData.getTinyWeekdays();
1584 
1585             } else { // count < 4, use abbreviated form if exists
1586                 weekdays = standalone ? formatData.getShortStandAloneWeekdays() : formatData.getShortWeekdays();
1587             }
1588 
1589             return weekdays[value];
1590         }
1591 
1592         return null;
1593     }
1594 
formatMonth(int count, int value, int maxIntCount, StringBuffer buffer, boolean useDateFormatSymbols, boolean standalone, int field, int style)1595     private String formatMonth(int count, int value, int maxIntCount, StringBuffer buffer,
1596                                boolean useDateFormatSymbols, boolean standalone,
1597                                int field, int style) {
1598         String current = null;
1599         if (useDateFormatSymbols) {
1600             final String[] months;
1601             if (count == 4) {
1602                 months = standalone ? formatData.getStandAloneMonths() : formatData.getMonths();
1603             } else if (count == 5) {
1604                 months = standalone ? formatData.getTinyStandAloneMonths() : formatData.getTinyMonths();
1605             } else if (count == 3) {
1606                 months = standalone ? formatData.getShortStandAloneMonths() : formatData.getShortMonths();
1607             } else {
1608                 months = null;
1609             }
1610 
1611             if (months != null) {
1612                 current = months[value];
1613             }
1614         } else {
1615             if (count < 3) {
1616                 current = null;
1617             } else {
1618                 if (standalone) {
1619                     style = Calendar.toStandaloneStyle(style);
1620                 }
1621                 current = calendar.getDisplayName(field, style, locale);
1622             }
1623         }
1624 
1625         if (current == null) {
1626             zeroPaddingNumber(value+1, count, maxIntCount, buffer);
1627         }
1628 
1629         return current;
1630     }
1631     // END Android-added: formatWeekday() and formatMonth() methods to format using ICU data.
1632 
1633     /**
1634      * Formats a number with the specified minimum and maximum number of digits.
1635      */
zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)1636     private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)
1637     {
1638         // Optimization for 1, 2 and 4 digit numbers. This should
1639         // cover most cases of formatting date/time related items.
1640         // Note: This optimization code assumes that maxDigits is
1641         // either 2 or Integer.MAX_VALUE (maxIntCount in format()).
1642         try {
1643             if (zeroDigit == 0) {
1644                 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
1645             }
1646             if (value >= 0) {
1647                 if (value < 100 && minDigits >= 1 && minDigits <= 2) {
1648                     if (value < 10) {
1649                         if (minDigits == 2) {
1650                             buffer.append(zeroDigit);
1651                         }
1652                         buffer.append((char)(zeroDigit + value));
1653                     } else {
1654                         buffer.append((char)(zeroDigit + value / 10));
1655                         buffer.append((char)(zeroDigit + value % 10));
1656                     }
1657                     return;
1658                 } else if (value >= 1000 && value < 10000) {
1659                     if (minDigits == 4) {
1660                         buffer.append((char)(zeroDigit + value / 1000));
1661                         value %= 1000;
1662                         buffer.append((char)(zeroDigit + value / 100));
1663                         value %= 100;
1664                         buffer.append((char)(zeroDigit + value / 10));
1665                         buffer.append((char)(zeroDigit + value % 10));
1666                         return;
1667                     }
1668                     if (minDigits == 2 && maxDigits == 2) {
1669                         zeroPaddingNumber(value % 100, 2, 2, buffer);
1670                         return;
1671                     }
1672                 }
1673             }
1674         } catch (Exception e) {
1675         }
1676 
1677         numberFormat.setMinimumIntegerDigits(minDigits);
1678         numberFormat.setMaximumIntegerDigits(maxDigits);
1679         numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE);
1680     }
1681 
1682 
1683     /**
1684      * Parses text from a string to produce a <code>Date</code>.
1685      * <p>
1686      * The method attempts to parse text starting at the index given by
1687      * <code>pos</code>.
1688      * If parsing succeeds, then the index of <code>pos</code> is updated
1689      * to the index after the last character used (parsing does not necessarily
1690      * use all characters up to the end of the string), and the parsed
1691      * date is returned. The updated <code>pos</code> can be used to
1692      * indicate the starting point for the next call to this method.
1693      * If an error occurs, then the index of <code>pos</code> is not
1694      * changed, the error index of <code>pos</code> is set to the index of
1695      * the character where the error occurred, and null is returned.
1696      *
1697      * <p>This parsing operation uses the {@link DateFormat#calendar
1698      * calendar} to produce a {@code Date}. All of the {@code
1699      * calendar}'s date-time fields are {@linkplain Calendar#clear()
1700      * cleared} before parsing, and the {@code calendar}'s default
1701      * values of the date-time fields are used for any missing
1702      * date-time information. For example, the year value of the
1703      * parsed {@code Date} is 1970 with {@link GregorianCalendar} if
1704      * no year value is given from the parsing operation.  The {@code
1705      * TimeZone} value may be overwritten, depending on the given
1706      * pattern and the time zone value in {@code text}. Any {@code
1707      * TimeZone} value that has previously been set by a call to
1708      * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need
1709      * to be restored for further operations.
1710      *
1711      * @param text  A <code>String</code>, part of which should be parsed.
1712      * @param pos   A <code>ParsePosition</code> object with index and error
1713      *              index information as described above.
1714      * @return A <code>Date</code> parsed from the string. In case of
1715      *         error, returns null.
1716      * @exception NullPointerException if <code>text</code> or <code>pos</code> is null.
1717      */
1718     @Override
parse(String text, ParsePosition pos)1719     public Date parse(String text, ParsePosition pos) {
1720         // BEGIN Android-changed: extract parseInternal() and avoid modifying timezone during parse.
1721         // Make sure the timezone associated with this dateformat instance (set via
1722         // {@code setTimeZone} isn't change as a side-effect of parsing a date.
1723         final TimeZone tz = getTimeZone();
1724         try {
1725             return parseInternal(text, pos);
1726         } finally {
1727             setTimeZone(tz);
1728         }
1729     }
1730 
parseInternal(String text, ParsePosition pos)1731     private Date parseInternal(String text, ParsePosition pos)
1732     {
1733         // END Android-changed: extract parseInternal() and avoid modifying timezone during parse.
1734         checkNegativeNumberExpression();
1735 
1736         int start = pos.index;
1737         int oldStart = start;
1738         int textLength = text.length();
1739 
1740         boolean[] ambiguousYear = {false};
1741 
1742         CalendarBuilder calb = new CalendarBuilder();
1743 
1744         for (int i = 0; i < compiledPattern.length; ) {
1745             int tag = compiledPattern[i] >>> 8;
1746             int count = compiledPattern[i++] & 0xff;
1747             if (count == 255) {
1748                 count = compiledPattern[i++] << 16;
1749                 count |= compiledPattern[i++];
1750             }
1751 
1752             switch (tag) {
1753             case TAG_QUOTE_ASCII_CHAR:
1754                 if (start >= textLength || text.charAt(start) != (char)count) {
1755                     pos.index = oldStart;
1756                     pos.errorIndex = start;
1757                     return null;
1758                 }
1759                 start++;
1760                 break;
1761 
1762             case TAG_QUOTE_CHARS:
1763                 while (count-- > 0) {
1764                     if (start >= textLength || text.charAt(start) != compiledPattern[i++]) {
1765                         pos.index = oldStart;
1766                         pos.errorIndex = start;
1767                         return null;
1768                     }
1769                     start++;
1770                 }
1771                 break;
1772 
1773             default:
1774                 // Peek the next pattern to determine if we need to
1775                 // obey the number of pattern letters for
1776                 // parsing. It's required when parsing contiguous
1777                 // digit text (e.g., "20010704") with a pattern which
1778                 // has no delimiters between fields, like "yyyyMMdd".
1779                 boolean obeyCount = false;
1780 
1781                 // In Arabic, a minus sign for a negative number is put after
1782                 // the number. Even in another locale, a minus sign can be
1783                 // put after a number using DateFormat.setNumberFormat().
1784                 // If both the minus sign and the field-delimiter are '-',
1785                 // subParse() needs to determine whether a '-' after a number
1786                 // in the given text is a delimiter or is a minus sign for the
1787                 // preceding number. We give subParse() a clue based on the
1788                 // information in compiledPattern.
1789                 boolean useFollowingMinusSignAsDelimiter = false;
1790 
1791                 if (i < compiledPattern.length) {
1792                     int nextTag = compiledPattern[i] >>> 8;
1793                     if (!(nextTag == TAG_QUOTE_ASCII_CHAR ||
1794                           nextTag == TAG_QUOTE_CHARS)) {
1795                         obeyCount = true;
1796                     }
1797 
1798                     if (hasFollowingMinusSign &&
1799                         (nextTag == TAG_QUOTE_ASCII_CHAR ||
1800                          nextTag == TAG_QUOTE_CHARS)) {
1801                         int c;
1802                         if (nextTag == TAG_QUOTE_ASCII_CHAR) {
1803                             c = compiledPattern[i] & 0xff;
1804                         } else {
1805                             c = compiledPattern[i+1];
1806                         }
1807 
1808                         if (c == minusSign) {
1809                             useFollowingMinusSignAsDelimiter = true;
1810                         }
1811                     }
1812                 }
1813                 start = subParse(text, start, tag, count, obeyCount,
1814                                  ambiguousYear, pos,
1815                                  useFollowingMinusSignAsDelimiter, calb);
1816                 if (start < 0) {
1817                     pos.index = oldStart;
1818                     return null;
1819                 }
1820             }
1821         }
1822 
1823         // At this point the fields of Calendar have been set.  Calendar
1824         // will fill in default values for missing fields when the time
1825         // is computed.
1826 
1827         pos.index = start;
1828 
1829         Date parsedDate;
1830         try {
1831             parsedDate = calb.establish(calendar).getTime();
1832             // If the year value is ambiguous,
1833             // then the two-digit year == the default start year
1834             if (ambiguousYear[0]) {
1835                 if (parsedDate.before(defaultCenturyStart)) {
1836                     parsedDate = calb.addYear(100).establish(calendar).getTime();
1837                 }
1838             }
1839         }
1840         // An IllegalArgumentException will be thrown by Calendar.getTime()
1841         // if any fields are out of range, e.g., MONTH == 17.
1842         catch (IllegalArgumentException e) {
1843             pos.errorIndex = start;
1844             pos.index = oldStart;
1845             return null;
1846         }
1847 
1848         return parsedDate;
1849     }
1850 
1851     /**
1852      * Private code-size reduction function used by subParse.
1853      * @param text the time text being parsed.
1854      * @param start where to start parsing.
1855      * @param field the date field being parsed.
1856      * @param data the string array to parsed.
1857      * @return the new start position if matching succeeded; a negative number
1858      * indicating matching failure, otherwise.
1859      */
matchString(String text, int start, int field, String[] data, CalendarBuilder calb)1860     private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)
1861     {
1862         int i = 0;
1863         int count = data.length;
1864 
1865         if (field == Calendar.DAY_OF_WEEK) {
1866             i = 1;
1867         }
1868 
1869         // There may be multiple strings in the data[] array which begin with
1870         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1871         // We keep track of the longest match, and return that.  Note that this
1872         // unfortunately requires us to test all array elements.
1873         int bestMatchLength = 0, bestMatch = -1;
1874         for (; i<count; ++i)
1875         {
1876             int length = data[i].length();
1877             // Always compare if we have no match yet; otherwise only compare
1878             // against potentially better matches (longer strings).
1879             if (length > bestMatchLength &&
1880                 text.regionMatches(true, start, data[i], 0, length))
1881             {
1882                 bestMatch = i;
1883                 bestMatchLength = length;
1884             }
1885 
1886             // BEGIN Android-changed: Handle abbreviated fields that end with a '.'.
1887             // When the input option ends with a period (usually an abbreviated form), attempt
1888             // to match all chars up to that period.
1889             if ((data[i].charAt(length - 1) == '.') &&
1890                     ((length - 1) > bestMatchLength) &&
1891                     text.regionMatches(true, start, data[i], 0, length - 1)) {
1892                 bestMatch = i;
1893                 bestMatchLength = (length - 1);
1894             }
1895             // END Android-changed: Handle abbreviated fields that end with a '.'.
1896         }
1897         if (bestMatch >= 0)
1898         {
1899             calb.set(field, bestMatch);
1900             return start + bestMatchLength;
1901         }
1902         return -start;
1903     }
1904 
1905     /**
1906      * Performs the same thing as matchString(String, int, int,
1907      * String[]). This method takes a Map<String, Integer> instead of
1908      * String[].
1909      */
matchString(String text, int start, int field, Map<String,Integer> data, CalendarBuilder calb)1910     private int matchString(String text, int start, int field,
1911                             Map<String,Integer> data, CalendarBuilder calb) {
1912         if (data != null) {
1913             // TODO: make this default when it's in the spec.
1914             // BEGIN Android-changed: SortedMap instance lookup optimization in matchString().
1915             // RI returns not the longest match as matchString(String[]) does. http://b/119913354
1916             /*
1917             if (data instanceof SortedMap) {
1918                 for (String name : data.keySet()) {
1919             */
1920             if (data instanceof NavigableMap && ((NavigableMap) data).comparator() == null) {
1921                 for (String name : ((NavigableMap<String, Integer>) data).descendingKeySet()) {
1922             // END Android-changed: SortedMap instance lookup optimization in matchString().
1923                     if (text.regionMatches(true, start, name, 0, name.length())) {
1924                         calb.set(field, data.get(name));
1925                         return start + name.length();
1926                     }
1927                 }
1928                 return -start;
1929             }
1930 
1931             String bestMatch = null;
1932 
1933             for (String name : data.keySet()) {
1934                 int length = name.length();
1935                 if (bestMatch == null || length > bestMatch.length()) {
1936                     if (text.regionMatches(true, start, name, 0, length)) {
1937                         bestMatch = name;
1938                     }
1939                 }
1940             }
1941 
1942             if (bestMatch != null) {
1943                 calb.set(field, data.get(bestMatch));
1944                 return start + bestMatch.length();
1945             }
1946         }
1947         return -start;
1948     }
1949 
matchZoneString(String text, int start, String[] zoneNames)1950     private int matchZoneString(String text, int start, String[] zoneNames) {
1951         for (int i = 1; i <= 4; ++i) {
1952             // Checking long and short zones [1 & 2],
1953             // and long and short daylight [3 & 4].
1954             String zoneName = zoneNames[i];
1955             if (text.regionMatches(true, start,
1956                                    zoneName, 0, zoneName.length())) {
1957                 return i;
1958             }
1959         }
1960         return -1;
1961     }
1962 
1963     // BEGIN Android-removed: Unused private method matchDSTString.
1964     /*
1965     private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex,
1966                                    String[][] zoneStrings) {
1967         int index = standardIndex + 2;
1968         String zoneName  = zoneStrings[zoneIndex][index];
1969         if (text.regionMatches(true, start,
1970                                zoneName, 0, zoneName.length())) {
1971             return true;
1972         }
1973         return false;
1974     }
1975     */
1976     // END Android-removed: Unused private method matchDSTString.
1977 
1978     // BEGIN Android-changed: Parse time zone strings using ICU TimeZoneNames.
1979     // Note that this change falls back to the upstream zone names parsing code if the zoneStrings
1980     // for the formatData field has been set by the user. The original code of subParseZoneString
1981     // can be found in subParseZoneStringFromSymbols().
1982     /**
1983      * Parses the string in {@code text} (starting at {@code start}), interpreting it as a time zone
1984      * name. If a time zone is found, the internal calendar is set to that timezone and the index of
1985      * the first character after the time zone name is returned. Otherwise, returns {@code 0}.
1986      * @return the index of the next character to parse or {@code 0} on error.
1987      */
subParseZoneString(String text, int start, CalendarBuilder calb)1988     private int subParseZoneString(String text, int start, CalendarBuilder calb) {
1989         if (formatData.isZoneStringsSet) {
1990             // DateFormatSymbols.setZoneStrings() has be used, use those values instead of ICU code.
1991             return subParseZoneStringFromSymbols(text, start, calb);
1992         } else {
1993             return subParseZoneStringFromICU(text, start, calb);
1994         }
1995     }
1996 
getExtendedTimeZoneNames()1997     private ExtendedTimeZoneNames getExtendedTimeZoneNames() {
1998         if (timeZoneNames == null) {
1999             timeZoneNames = ExtendedTimeZoneNames.getInstance(ULocale.forLocale(locale));
2000         }
2001         return timeZoneNames;
2002     }
2003 
getTimeZoneNames()2004     private TimeZoneNames getTimeZoneNames() {
2005         return getExtendedTimeZoneNames().getTimeZoneNames();
2006     }
2007 
2008     /**
2009      * Parses the time zone string using the ICU4J class {@link TimeZoneNames}.
2010      */
subParseZoneStringFromICU(String text, int start, CalendarBuilder calb)2011     private int subParseZoneStringFromICU(String text, int start, CalendarBuilder calb) {
2012         String currentTimeZoneID = android.icu.util.TimeZone.getCanonicalID(getTimeZone().getID());
2013 
2014         Match matchedName = getExtendedTimeZoneNames().matchName(text, start, currentTimeZoneID);
2015         if (matchedName == null) {
2016             // No match found, return error.
2017             return -start;
2018         }
2019 
2020         String tzId = matchedName.getTzId();
2021         TimeZone newTimeZone = TimeZone.getTimeZone(tzId);
2022         if (!currentTimeZoneID.equals(tzId)) {
2023             setTimeZone(newTimeZone);
2024         }
2025 
2026         // Same logic as in subParseZoneStringFromSymbols, see below for details.
2027         boolean isDst = matchedName.isDst();
2028         int dstAmount = isDst ? newTimeZone.getDSTSavings() : 0;
2029         if (!isDst || dstAmount != 0) {
2030             calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount);
2031         }
2032 
2033         return matchedName.getMatchLength() + start;
2034     }
2035 
2036     /**
2037      * Parses the time zone string using the information in {@link #formatData}.
2038      */
subParseZoneStringFromSymbols(String text, int start, CalendarBuilder calb)2039     private int subParseZoneStringFromSymbols(String text, int start, CalendarBuilder calb) {
2040         // END Android-changed: Parse time zone strings using ICU TimeZoneNames.
2041         boolean useSameName = false; // true if standard and daylight time use the same abbreviation.
2042         TimeZone currentTimeZone = getTimeZone();
2043 
2044         // At this point, check for named time zones by looking through
2045         // the locale data from the TimeZoneNames strings.
2046         // Want to be able to parse both short and long forms.
2047         int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID());
2048         TimeZone tz = null;
2049         String[][] zoneStrings = formatData.getZoneStringsWrapper();
2050         String[] zoneNames = null;
2051         int nameIndex = 0;
2052         if (zoneIndex != -1) {
2053             zoneNames = zoneStrings[zoneIndex];
2054             if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
2055                 if (nameIndex <= 2) {
2056                     // Check if the standard name (abbr) and the daylight name are the same.
2057                     useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
2058                 }
2059                 tz = TimeZone.getTimeZone(zoneNames[0]);
2060             }
2061         }
2062         if (tz == null) {
2063             zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID());
2064             if (zoneIndex != -1) {
2065                 zoneNames = zoneStrings[zoneIndex];
2066                 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
2067                     if (nameIndex <= 2) {
2068                         useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
2069                     }
2070                     tz = TimeZone.getTimeZone(zoneNames[0]);
2071                 }
2072             }
2073         }
2074 
2075         if (tz == null) {
2076             int len = zoneStrings.length;
2077             for (int i = 0; i < len; i++) {
2078                 zoneNames = zoneStrings[i];
2079                 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
2080                     if (nameIndex <= 2) {
2081                         useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
2082                     }
2083                     tz = TimeZone.getTimeZone(zoneNames[0]);
2084                     break;
2085                 }
2086             }
2087         }
2088         if (tz != null) { // Matched any ?
2089             if (!tz.equals(currentTimeZone)) {
2090                 setTimeZone(tz);
2091             }
2092             // If the time zone matched uses the same name
2093             // (abbreviation) for both standard and daylight time,
2094             // let the time zone in the Calendar decide which one.
2095             //
2096             // Also if tz.getDSTSaving() returns 0 for DST, use tz to
2097             // determine the local time. (6645292)
2098             int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0;
2099             if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) {
2100                 calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount);
2101             }
2102             return (start + zoneNames[nameIndex].length());
2103         }
2104         return -start;
2105     }
2106 
2107     /**
2108      * Parses numeric forms of time zone offset, such as "hh:mm", and
2109      * sets calb to the parsed value.
2110      *
2111      * @param text  the text to be parsed
2112      * @param start the character position to start parsing
2113      * @param sign  1: positive; -1: negative
2114      * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's
2115      * @param colon true - colon required between hh and mm; false - no colon required
2116      * @param calb  a CalendarBuilder in which the parsed value is stored
2117      * @return updated parsed position, or its negative value to indicate a parsing error
2118      */
subParseNumericZone(String text, int start, int sign, int count, boolean colon, CalendarBuilder calb)2119     private int subParseNumericZone(String text, int start, int sign, int count,
2120                                     boolean colon, CalendarBuilder calb) {
2121         int index = start;
2122 
2123       parse:
2124         try {
2125             char c = text.charAt(index++);
2126             // Parse hh
2127             int hours;
2128             if (!isDigit(c)) {
2129                 break parse;
2130             }
2131             hours = c - '0';
2132             c = text.charAt(index++);
2133             if (isDigit(c)) {
2134                 hours = hours * 10 + (c - '0');
2135             } else {
2136                 // BEGIN Android-removed: Be more tolerant of colon. b/26426526
2137                 /*
2138                 // If no colon in RFC 822 or 'X' (ISO), two digits are
2139                 // required.
2140                 if (count > 0 || !colon) {
2141                     break parse;
2142                 }
2143                 */
2144                 // END Android-removed: Be more tolerant of colon. b/26426526
2145                 --index;
2146             }
2147             if (hours > 23) {
2148                 break parse;
2149             }
2150             int minutes = 0;
2151             if (count != 1) {
2152                 // Proceed with parsing mm
2153                 c = text.charAt(index++);
2154                 // BEGIN Android-changed: Be more tolerant of colon. b/26426526
2155                 // OpenJDK will return an error code if a : is found and colonRequired is false,
2156                 // this will return an error code if a : is not found and colonRequired is true.
2157                 //
2158                 //   colon       | c == ':' | OpenJDK | this
2159                 //   false       |  false   |   ok    |  ok
2160                 //   false       |  true    |  error  |  ok
2161                 //   true        |  false   |   ok    | error
2162                 //   true        |  true    |   ok    |  ok
2163                 /*
2164                 if (colon) {
2165                     if (c != ':') {
2166                         break parse;
2167                     }
2168                 */
2169                 if (c == ':') {
2170                     c = text.charAt(index++);
2171                 } else if (colon) {
2172                     break parse;
2173                 }
2174                 // END Android-changed: Be more tolerant of colon. b/26426526
2175                 if (!isDigit(c)) {
2176                     break parse;
2177                 }
2178                 minutes = c - '0';
2179                 c = text.charAt(index++);
2180                 if (!isDigit(c)) {
2181                     break parse;
2182                 }
2183                 minutes = minutes * 10 + (c - '0');
2184                 if (minutes > 59) {
2185                     break parse;
2186                 }
2187             }
2188             minutes += hours * 60;
2189             calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign)
2190                 .set(Calendar.DST_OFFSET, 0);
2191             return index;
2192         } catch (IndexOutOfBoundsException e) {
2193         }
2194         return  1 - index; // -(index - 1)
2195     }
2196 
isDigit(char c)2197     private boolean isDigit(char c) {
2198         return c >= '0' && c <= '9';
2199     }
2200 
2201     /**
2202      * Private member function that converts the parsed date strings into
2203      * timeFields. Returns -start (for ParsePosition) if failed.
2204      * @param text the time text to be parsed.
2205      * @param start where to start parsing.
2206      * @param patternCharIndex the index of the pattern character.
2207      * @param count the count of a pattern character.
2208      * @param obeyCount if true, then the next field directly abuts this one,
2209      * and we should use the count to know when to stop parsing.
2210      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
2211      * is true, then a two-digit year was parsed and may need to be readjusted.
2212      * @param origPos origPos.errorIndex is used to return an error index
2213      * at which a parse error occurred, if matching failure occurs.
2214      * @return the new start position if matching succeeded; -1 indicating
2215      * matching failure, otherwise. In case matching failure occurred,
2216      * an error index is set to origPos.errorIndex.
2217      */
subParse(String text, int start, int patternCharIndex, int count, boolean obeyCount, boolean[] ambiguousYear, ParsePosition origPos, boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb)2218     private int subParse(String text, int start, int patternCharIndex, int count,
2219                          boolean obeyCount, boolean[] ambiguousYear,
2220                          ParsePosition origPos,
2221                          boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {
2222         Number number;
2223         int value = 0;
2224         ParsePosition pos = new ParsePosition(0);
2225         pos.index = start;
2226         if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {
2227             // use calendar year 'y' instead
2228             patternCharIndex = PATTERN_YEAR;
2229         }
2230         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
2231 
2232         // If there are any spaces here, skip over them.  If we hit the end
2233         // of the string, then fail.
2234         for (;;) {
2235             if (pos.index >= text.length()) {
2236                 origPos.errorIndex = start;
2237                 return -1;
2238             }
2239             char c = text.charAt(pos.index);
2240             if (c != ' ' && c != '\t') {
2241                 break;
2242             }
2243             ++pos.index;
2244         }
2245         // Remember the actual start index
2246         int actualStart = pos.index;
2247 
2248       parsing:
2249         {
2250             // We handle a few special cases here where we need to parse
2251             // a number value.  We handle further, more generic cases below.  We need
2252             // to handle some of them here because some fields require extra processing on
2253             // the parsed value.
2254             if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||
2255                 patternCharIndex == PATTERN_HOUR1 ||
2256                 (patternCharIndex == PATTERN_MONTH && count <= 2) ||
2257                 patternCharIndex == PATTERN_YEAR ||
2258                 patternCharIndex == PATTERN_WEEK_YEAR) {
2259                 // It would be good to unify this with the obeyCount logic below,
2260                 // but that's going to be difficult.
2261                 if (obeyCount) {
2262                     if ((start+count) > text.length()) {
2263                         break parsing;
2264                     }
2265                     number = numberFormat.parse(text.substring(0, start+count), pos);
2266                 } else {
2267                     number = numberFormat.parse(text, pos);
2268                 }
2269                 if (number == null) {
2270                     if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) {
2271                         break parsing;
2272                     }
2273                 } else {
2274                     value = number.intValue();
2275 
2276                     if (useFollowingMinusSignAsDelimiter && (value < 0) &&
2277                         (((pos.index < text.length()) &&
2278                          (text.charAt(pos.index) != minusSign)) ||
2279                          ((pos.index == text.length()) &&
2280                           (text.charAt(pos.index-1) == minusSign)))) {
2281                         value = -value;
2282                         pos.index--;
2283                     }
2284                 }
2285             }
2286 
2287             boolean useDateFormatSymbols = useDateFormatSymbols();
2288 
2289             int index;
2290             switch (patternCharIndex) {
2291             case PATTERN_ERA: // 'G'
2292                 if (useDateFormatSymbols) {
2293                     if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) {
2294                         return index;
2295                     }
2296                 } else {
2297                     Map<String, Integer> map = getDisplayNamesMap(field, locale);
2298                     if ((index = matchString(text, start, field, map, calb)) > 0) {
2299                         return index;
2300                     }
2301                 }
2302                 break parsing;
2303 
2304             case PATTERN_WEEK_YEAR: // 'Y'
2305             case PATTERN_YEAR:      // 'y'
2306                 if (!(calendar instanceof GregorianCalendar)) {
2307                     // calendar might have text representations for year values,
2308                     // such as "\u5143" in JapaneseImperialCalendar.
2309                     int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
2310                     Map<String, Integer> map = calendar.getDisplayNames(field, style, locale);
2311                     if (map != null) {
2312                         if ((index = matchString(text, start, field, map, calb)) > 0) {
2313                             return index;
2314                         }
2315                     }
2316                     calb.set(field, value);
2317                     return pos.index;
2318                 }
2319 
2320                 // If there are 3 or more YEAR pattern characters, this indicates
2321                 // that the year value is to be treated literally, without any
2322                 // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
2323                 // we made adjustments to place the 2-digit year in the proper
2324                 // century, for parsed strings from "00" to "99".  Any other string
2325                 // is treated literally:  "2250", "-1", "1", "002".
2326                 if (count <= 2 && (pos.index - actualStart) == 2
2327                     && Character.isDigit(text.charAt(actualStart))
2328                     && Character.isDigit(text.charAt(actualStart + 1))) {
2329                     // Assume for example that the defaultCenturyStart is 6/18/1903.
2330                     // This means that two-digit years will be forced into the range
2331                     // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
2332                     // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
2333                     // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
2334                     // other fields specify a date before 6/18, or 1903 if they specify a
2335                     // date afterwards.  As a result, 03 is an ambiguous year.  All other
2336                     // two-digit years are unambiguous.
2337                     int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
2338                     ambiguousYear[0] = value == ambiguousTwoDigitYear;
2339                     value += (defaultCenturyStartYear/100)*100 +
2340                         (value < ambiguousTwoDigitYear ? 100 : 0);
2341                 }
2342                 calb.set(field, value);
2343                 return pos.index;
2344 
2345             case PATTERN_MONTH: // 'M'
2346             // BEGIN Android-changed: extract parseMonth method.
2347             /*
2348                 if (count <= 2) // i.e., M or MM.
2349                 {
2350                     // Don't want to parse the month if it is a string
2351                     // while pattern uses numeric style: M or MM.
2352                     // [We computed 'value' above.]
2353                     calb.set(Calendar.MONTH, value - 1);
2354                     return pos.index;
2355                 }
2356 
2357                 if (useDateFormatSymbols) {
2358                     // count >= 3 // i.e., MMM or MMMM
2359                     // Want to be able to parse both short and long forms.
2360                     // Try count == 4 first:
2361                     int newStart;
2362                     if ((newStart = matchString(text, start, Calendar.MONTH,
2363                                                 formatData.getMonths(), calb)) > 0) {
2364                         return newStart;
2365                     }
2366                     // count == 4 failed, now try count == 3
2367                     if ((index = matchString(text, start, Calendar.MONTH,
2368                                              formatData.getShortMonths(), calb)) > 0) {
2369                         return index;
2370                     }
2371                 } else {
2372                     Map<String, Integer> map = getDisplayNamesMap(field, locale);
2373                     if ((index = matchString(text, start, field, map, calb)) > 0) {
2374                         return index;
2375                     }
2376                 }
2377             */
2378             {
2379                 final int idx = parseMonth(text, count, value, start, field, pos,
2380                         useDateFormatSymbols, false /* isStandalone */, calb);
2381                 if (idx > 0) {
2382                     return idx;
2383                 }
2384 
2385                 break parsing;
2386             }
2387 
2388             case PATTERN_MONTH_STANDALONE: // 'L'.
2389             {
2390                 final int idx = parseMonth(text, count, value, start, field, pos,
2391                         useDateFormatSymbols, true /* isStandalone */, calb);
2392                 if (idx > 0) {
2393                     return idx;
2394                 }
2395                 break parsing;
2396             }
2397             // END Android-changed: extract parseMonth method.
2398 
2399             case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
2400                 if (!isLenient()) {
2401                     // Validate the hour value in non-lenient
2402                     if (value < 1 || value > 24) {
2403                         break parsing;
2404                     }
2405                 }
2406                 // [We computed 'value' above.]
2407                 if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) {
2408                     value = 0;
2409                 }
2410                 calb.set(Calendar.HOUR_OF_DAY, value);
2411                 return pos.index;
2412 
2413             case PATTERN_DAY_OF_WEEK:  // 'E'
2414             // BEGIN Android-changed: extract parseWeekday method.
2415             /*
2416                 {
2417                     if (useDateFormatSymbols) {
2418                         // Want to be able to parse both short and long forms.
2419                         // Try count == 4 (DDDD) first:
2420                         int newStart;
2421                         if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
2422                                                   formatData.getWeekdays(), calb)) > 0) {
2423                             return newStart;
2424                         }
2425                         // DDDD failed, now try DDD
2426                         if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,
2427                                                  formatData.getShortWeekdays(), calb)) > 0) {
2428                             return index;
2429                         }
2430                     } else {
2431                         int[] styles = { Calendar.LONG, Calendar.SHORT };
2432                         for (int style : styles) {
2433                             Map<String,Integer> map = calendar.getDisplayNames(field, style, locale);
2434                             if ((index = matchString(text, start, field, map, calb)) > 0) {
2435                                 return index;
2436                             }
2437                         }
2438                     }
2439                 }
2440             */
2441             {
2442                 final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
2443                         false /* standalone */, calb);
2444                 if (idx > 0) {
2445                     return idx;
2446                 }
2447                 break parsing;
2448             }
2449             // END Android-changed: extract parseWeekday method.
2450 
2451             // BEGIN Android-added: support for 'c' (standalone day of week).
2452             case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
2453             {
2454                 final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
2455                         true /* standalone */, calb);
2456                 if (idx > 0) {
2457                     return idx;
2458                 }
2459 
2460                 break parsing;
2461             }
2462             // END Android-added: support for 'c' (standalone day of week).
2463 
2464             case PATTERN_AM_PM:    // 'a'
2465                 if (useDateFormatSymbols) {
2466                     if ((index = matchString(text, start, Calendar.AM_PM,
2467                                              formatData.getAmPmStrings(), calb)) > 0) {
2468                         return index;
2469                     }
2470                 } else {
2471                     Map<String,Integer> map = getDisplayNamesMap(field, locale);
2472                     if ((index = matchString(text, start, field, map, calb)) > 0) {
2473                         return index;
2474                     }
2475                 }
2476                 break parsing;
2477 
2478             case PATTERN_HOUR1: // 'h' 1-based.  eg, 11PM + 1 hour =>> 12 AM
2479                 if (!isLenient()) {
2480                     // Validate the hour value in non-lenient
2481                     if (value < 1 || value > 12) {
2482                         break parsing;
2483                     }
2484                 }
2485                 // [We computed 'value' above.]
2486                 if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) {
2487                     value = 0;
2488                 }
2489                 calb.set(Calendar.HOUR, value);
2490                 return pos.index;
2491 
2492             case PATTERN_ZONE_NAME:  // 'z'
2493             case PATTERN_ZONE_VALUE: // 'Z'
2494                 {
2495                     int sign = 0;
2496                     try {
2497                         char c = text.charAt(pos.index);
2498                         if (c == '+') {
2499                             sign = 1;
2500                         } else if (c == '-') {
2501                             sign = -1;
2502                         }
2503                         if (sign == 0) {
2504                             // Try parsing a custom time zone "GMT+hh:mm" or "GMT".
2505                             if ((c == 'G' || c == 'g')
2506                                 && (text.length() - start) >= GMT.length()
2507                                 && text.regionMatches(true, start, GMT, 0, GMT.length())) {
2508                                 pos.index = start + GMT.length();
2509 
2510                                 if ((text.length() - pos.index) > 0) {
2511                                     c = text.charAt(pos.index);
2512                                     if (c == '+') {
2513                                         sign = 1;
2514                                     } else if (c == '-') {
2515                                         sign = -1;
2516                                     }
2517                                 }
2518 
2519                                 if (sign == 0) {    /* "GMT" without offset */
2520                                     calb.set(Calendar.ZONE_OFFSET, 0)
2521                                         .set(Calendar.DST_OFFSET, 0);
2522                                     return pos.index;
2523                                 }
2524 
2525                                 // BEGIN Android-changed: Be more tolerant of colon. b/26426526
2526                                 /*
2527                                 // Parse the rest as "hh:mm"
2528                                 int i = subParseNumericZone(text, ++pos.index,
2529                                                             sign, 0, true, calb);
2530                                 */
2531                                 // Parse the rest as "hh[:]?mm"
2532                                 int i = subParseNumericZone(text, ++pos.index, sign, 0,
2533                                         false, calb);
2534                                 // END Android-changed: Be more tolerant of colon. b/26426526
2535                                 if (i > 0) {
2536                                     return i;
2537                                 }
2538                                 pos.index = -i;
2539                             } else {
2540                                 // Try parsing the text as a time zone
2541                                 // name or abbreviation.
2542                                 int i = subParseZoneString(text, pos.index, calb);
2543                                 if (i > 0) {
2544                                     return i;
2545                                 }
2546                                 pos.index = -i;
2547                             }
2548                         } else {
2549                             // BEGIN Android-changed: Be more tolerant of colon. b/26426526
2550                             // Parse the rest as "hh[:]?mm" (RFC 822)
2551                             /*
2552                             // Parse the rest as "hhmm" (RFC 822)
2553                             int i = subParseNumericZone(text, ++pos.index,
2554                                                         sign, 0, false, calb);
2555                             */
2556                             int i = subParseNumericZone(text, ++pos.index, sign, 0,
2557                                     false, calb);
2558                             // END Android-changed: Be more tolerant of colon. b/26426526
2559                             if (i > 0) {
2560                                 return i;
2561                             }
2562                             pos.index = -i;
2563                         }
2564                     } catch (IndexOutOfBoundsException e) {
2565                     }
2566                 }
2567                 break parsing;
2568 
2569             case PATTERN_ISO_ZONE:   // 'X'
2570                 {
2571                     if ((text.length() - pos.index) <= 0) {
2572                         break parsing;
2573                     }
2574 
2575                     int sign;
2576                     char c = text.charAt(pos.index);
2577                     if (c == 'Z') {
2578                         calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0);
2579                         return ++pos.index;
2580                     }
2581 
2582                     // parse text as "+/-hh[[:]mm]" based on count
2583                     if (c == '+') {
2584                         sign = 1;
2585                     } else if (c == '-') {
2586                         sign = -1;
2587                     } else {
2588                         ++pos.index;
2589                         break parsing;
2590                     }
2591                     int i = subParseNumericZone(text, ++pos.index, sign, count,
2592                                                 count == 3, calb);
2593                     if (i > 0) {
2594                         return i;
2595                     }
2596                     pos.index = -i;
2597                 }
2598                 break parsing;
2599 
2600             default:
2601          // case PATTERN_DAY_OF_MONTH:         // 'd'
2602          // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
2603          // case PATTERN_MINUTE:               // 'm'
2604          // case PATTERN_SECOND:               // 's'
2605          // case PATTERN_MILLISECOND:          // 'S'
2606          // case PATTERN_DAY_OF_YEAR:          // 'D'
2607          // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
2608          // case PATTERN_WEEK_OF_YEAR:         // 'w'
2609          // case PATTERN_WEEK_OF_MONTH:        // 'W'
2610          // case PATTERN_HOUR0:                // 'K' 0-based.  eg, 11PM + 1 hour =>> 0 AM
2611          // case PATTERN_ISO_DAY_OF_WEEK:      // 'u' (pseudo field);
2612 
2613                 // Handle "generic" fields
2614                 // BEGIN Android-changed: Better UTS#35 conformity for fractional seconds.
2615                 // http://b/25863120
2616                 int parseStart = pos.getIndex();
2617                 // END Android-changed: Better UTS#35 conformity for fractional seconds.
2618                 if (obeyCount) {
2619                     if ((start+count) > text.length()) {
2620                         break parsing;
2621                     }
2622                     number = numberFormat.parse(text.substring(0, start+count), pos);
2623                 } else {
2624                     number = numberFormat.parse(text, pos);
2625                 }
2626                 if (number != null) {
2627                     // BEGIN Android-changed: Better UTS#35 conformity for fractional seconds.
2628                     /*
2629                     value = number.intValue();
2630                     */
2631                     if (patternCharIndex == PATTERN_MILLISECOND) {
2632                         // Fractional seconds must be treated specially. We must always
2633                         // normalize them to their fractional second value [0, 1) before we attempt
2634                         // to parse them.
2635                         //
2636                         // Case 1: 11.78 seconds is 11 seconds and 780 (not 78) milliseconds.
2637                         // Case 2: 11.7890567 seconds is 11 seconds and 789 (not 7890567) milliseconds.
2638                         double doubleValue = number.doubleValue();
2639                         int width = pos.getIndex() - parseStart;
2640                         final double divisor = Math.pow(10, width);
2641                         value = (int) ((doubleValue / divisor) * 1000);
2642                     } else {
2643                         value = number.intValue();
2644                     }
2645                     // END Android-changed: Better UTS#35 conformity for fractional seconds.
2646 
2647                     if (useFollowingMinusSignAsDelimiter && (value < 0) &&
2648                         (((pos.index < text.length()) &&
2649                          (text.charAt(pos.index) != minusSign)) ||
2650                          ((pos.index == text.length()) &&
2651                           (text.charAt(pos.index-1) == minusSign)))) {
2652                         value = -value;
2653                         pos.index--;
2654                     }
2655 
2656                     calb.set(field, value);
2657                     return pos.index;
2658                 }
2659                 break parsing;
2660             }
2661         }
2662 
2663         // Parsing failed.
2664         origPos.errorIndex = pos.index;
2665         return -1;
2666     }
2667 
2668     // BEGIN Android-added: parseMonth and parseWeekday methods to parse using ICU data.
parseMonth(String text, int count, int value, int start, int field, ParsePosition pos, boolean useDateFormatSymbols, boolean standalone, CalendarBuilder out)2669     private int parseMonth(String text, int count, int value, int start,
2670                            int field, ParsePosition pos, boolean useDateFormatSymbols,
2671                            boolean standalone,
2672                            CalendarBuilder out) {
2673         if (count <= 2) // i.e., M or MM.
2674         {
2675             // Don't want to parse the month if it is a string
2676             // while pattern uses numeric style: M or MM.
2677             // [We computed 'value' above.]
2678             out.set(Calendar.MONTH, value - 1);
2679             return pos.index;
2680         }
2681 
2682         int index = -1;
2683         if (useDateFormatSymbols) {
2684             // count >= 3 // i.e., MMM or MMMM
2685             // Want to be able to parse both short and long forms.
2686             // Try count == 4 first:
2687             if ((index = matchString(
2688                     text, start, Calendar.MONTH,
2689                     standalone ? formatData.getStandAloneMonths() : formatData.getMonths(),
2690                     out)) > 0) {
2691                 return index;
2692             }
2693             // count == 4 failed, now try count == 3
2694             if ((index = matchString(
2695                     text, start, Calendar.MONTH,
2696                     standalone ? formatData.getShortStandAloneMonths() : formatData.getShortMonths(),
2697                     out)) > 0) {
2698                 return index;
2699             }
2700         } else {
2701             Map<String, Integer> map = getDisplayNamesMap(field, locale);
2702             if ((index = matchString(text, start, field, map, out)) > 0) {
2703                 return index;
2704             }
2705         }
2706 
2707         return index;
2708     }
2709 
parseWeekday(String text, int start, int field, boolean useDateFormatSymbols, boolean standalone, CalendarBuilder out)2710     private int parseWeekday(String text, int start, int field, boolean useDateFormatSymbols,
2711                              boolean standalone, CalendarBuilder out) {
2712         int index = -1;
2713         if (useDateFormatSymbols) {
2714             // Want to be able to parse both short and long forms.
2715             // Try count == 4 (DDDD) first:
2716             if ((index=matchString(
2717                     text, start, Calendar.DAY_OF_WEEK,
2718                     standalone ? formatData.getStandAloneWeekdays() : formatData.getWeekdays(),
2719                     out)) > 0) {
2720                 return index;
2721             }
2722 
2723             // DDDD failed, now try DDD
2724             if ((index = matchString(
2725                     text, start, Calendar.DAY_OF_WEEK,
2726                     standalone ? formatData.getShortStandAloneWeekdays() : formatData.getShortWeekdays(),
2727                     out)) > 0) {
2728                 return index;
2729             }
2730         } else {
2731             int[] styles = { Calendar.LONG, Calendar.SHORT };
2732             for (int style : styles) {
2733                 Map<String,Integer> map = calendar.getDisplayNames(field, style, locale);
2734                 if ((index = matchString(text, start, field, map, out)) > 0) {
2735                     return index;
2736                 }
2737             }
2738         }
2739 
2740         return index;
2741     }
2742     // END Android-added: parseMonth and parseWeekday methods to parse using ICU data.
2743 
2744     // Android-changed: Always useDateFormatSymbols() for GregorianCalendar.
2745     /**
2746      * Returns true if the DateFormatSymbols has been set explicitly or locale
2747      * is null or calendar is Gregorian.
2748      */
useDateFormatSymbols()2749     private boolean useDateFormatSymbols() {
2750         // Android-changed: Always useDateFormatSymbols() for GregorianCalendar.
2751         // This is for app compat. http://b/66411240#comment14
2752         // return useDateFormatSymbols || locale == null;
2753       return useDateFormatSymbols
2754           || "java.util.GregorianCalendar".equals(calendar.getClass().getName())
2755           || locale == null;
2756     }
2757 
2758     /**
2759      * Translates a pattern, mapping each character in the from string to the
2760      * corresponding character in the to string.
2761      *
2762      * @exception IllegalArgumentException if the given pattern is invalid
2763      */
translatePattern(String pattern, String from, String to)2764     private String translatePattern(String pattern, String from, String to) {
2765         StringBuilder result = new StringBuilder();
2766         boolean inQuote = false;
2767         for (int i = 0; i < pattern.length(); ++i) {
2768             char c = pattern.charAt(i);
2769             if (inQuote) {
2770                 if (c == '\'') {
2771                     inQuote = false;
2772                 }
2773             }
2774             else {
2775                 if (c == '\'') {
2776                     inQuote = true;
2777                 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
2778                     int ci = from.indexOf(c);
2779                     if (ci >= 0) {
2780                         // patternChars is longer than localPatternChars due
2781                         // to serialization compatibility. The pattern letters
2782                         // unsupported by localPatternChars pass through.
2783                         if (ci < to.length()) {
2784                             c = to.charAt(ci);
2785                         }
2786                     } else {
2787                         throw new IllegalArgumentException("Illegal pattern " +
2788                                                            " character '" +
2789                                                            c + "'");
2790                     }
2791                 }
2792             }
2793             result.append(c);
2794         }
2795         if (inQuote) {
2796             throw new IllegalArgumentException("Unfinished quote in pattern");
2797         }
2798         return result.toString();
2799     }
2800 
2801     /**
2802      * Returns a pattern string describing this date format.
2803      *
2804      * @return a pattern string describing this date format.
2805      */
toPattern()2806     public String toPattern() {
2807         return pattern;
2808     }
2809 
2810     /**
2811      * Returns a localized pattern string describing this date format.
2812      *
2813      * @return a localized pattern string describing this date format.
2814      */
toLocalizedPattern()2815     public String toLocalizedPattern() {
2816         return translatePattern(pattern,
2817                                 DateFormatSymbols.patternChars,
2818                                 formatData.getLocalPatternChars());
2819     }
2820 
2821     /**
2822      * Applies the given pattern string to this date format.
2823      *
2824      * @param pattern the new date and time pattern for this date format
2825      * @exception NullPointerException if the given pattern is null
2826      * @exception IllegalArgumentException if the given pattern is invalid
2827      */
applyPattern(String pattern)2828     public void applyPattern(String pattern)
2829     {
2830         applyPatternImpl(pattern);
2831     }
2832 
applyPatternImpl(String pattern)2833     private void applyPatternImpl(String pattern) {
2834         compiledPattern = compile(pattern);
2835         this.pattern = pattern;
2836     }
2837 
2838     /**
2839      * Applies the given localized pattern string to this date format.
2840      *
2841      * @param pattern a String to be mapped to the new date and time format
2842      *        pattern for this format
2843      * @exception NullPointerException if the given pattern is null
2844      * @exception IllegalArgumentException if the given pattern is invalid
2845      */
applyLocalizedPattern(String pattern)2846     public void applyLocalizedPattern(String pattern) {
2847          String p = translatePattern(pattern,
2848                                      formatData.getLocalPatternChars(),
2849                                      DateFormatSymbols.patternChars);
2850          compiledPattern = compile(p);
2851          this.pattern = p;
2852     }
2853 
2854     /**
2855      * Gets a copy of the date and time format symbols of this date format.
2856      *
2857      * @return the date and time format symbols of this date format
2858      * @see #setDateFormatSymbols
2859      */
getDateFormatSymbols()2860     public DateFormatSymbols getDateFormatSymbols()
2861     {
2862         return (DateFormatSymbols)formatData.clone();
2863     }
2864 
2865     /**
2866      * Sets the date and time format symbols of this date format.
2867      *
2868      * @param newFormatSymbols the new date and time format symbols
2869      * @exception NullPointerException if the given newFormatSymbols is null
2870      * @see #getDateFormatSymbols
2871      */
setDateFormatSymbols(DateFormatSymbols newFormatSymbols)2872     public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
2873     {
2874         this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
2875         useDateFormatSymbols = true;
2876     }
2877 
2878     /**
2879      * Creates a copy of this <code>SimpleDateFormat</code>. This also
2880      * clones the format's date format symbols.
2881      *
2882      * @return a clone of this <code>SimpleDateFormat</code>
2883      */
2884     @Override
clone()2885     public Object clone() {
2886         SimpleDateFormat other = (SimpleDateFormat) super.clone();
2887         other.formatData = (DateFormatSymbols) formatData.clone();
2888         return other;
2889     }
2890 
2891     /**
2892      * Returns the hash code value for this <code>SimpleDateFormat</code> object.
2893      *
2894      * @return the hash code value for this <code>SimpleDateFormat</code> object.
2895      */
2896     @Override
hashCode()2897     public int hashCode()
2898     {
2899         return pattern.hashCode();
2900         // just enough fields for a reasonable distribution
2901     }
2902 
2903     /**
2904      * Compares the given object with this <code>SimpleDateFormat</code> for
2905      * equality.
2906      *
2907      * @return true if the given object is equal to this
2908      * <code>SimpleDateFormat</code>
2909      */
2910     @Override
equals(Object obj)2911     public boolean equals(Object obj)
2912     {
2913         if (!super.equals(obj)) {
2914             return false; // super does class check
2915         }
2916         SimpleDateFormat that = (SimpleDateFormat) obj;
2917         return (pattern.equals(that.pattern)
2918                 && formatData.equals(that.formatData));
2919     }
2920 
2921     private static final int[] REST_OF_STYLES = {
2922         Calendar.SHORT_STANDALONE, Calendar.LONG_FORMAT, Calendar.LONG_STANDALONE,
2923     };
getDisplayNamesMap(int field, Locale locale)2924     private Map<String, Integer> getDisplayNamesMap(int field, Locale locale) {
2925         Map<String, Integer> map = calendar.getDisplayNames(field, Calendar.SHORT_FORMAT, locale);
2926         // Get all SHORT and LONG styles (avoid NARROW styles).
2927         for (int style : REST_OF_STYLES) {
2928             Map<String, Integer> m = calendar.getDisplayNames(field, style, locale);
2929             if (m != null) {
2930                 map.putAll(m);
2931             }
2932         }
2933         return map;
2934     }
2935 
2936     /**
2937      * After reading an object from the input stream, the format
2938      * pattern in the object is verified.
2939      * <p>
2940      * @exception InvalidObjectException if the pattern is invalid
2941      */
readObject(ObjectInputStream stream)2942     private void readObject(ObjectInputStream stream)
2943                          throws IOException, ClassNotFoundException {
2944         stream.defaultReadObject();
2945 
2946         try {
2947             compiledPattern = compile(pattern);
2948         } catch (Exception e) {
2949             throw new InvalidObjectException("invalid pattern");
2950         }
2951 
2952         if (serialVersionOnStream < 1) {
2953             // didn't have defaultCenturyStart field
2954             initializeDefaultCentury();
2955         }
2956         else {
2957             // fill in dependent transient field
2958             parseAmbiguousDatesAsAfter(defaultCenturyStart);
2959         }
2960         serialVersionOnStream = currentSerialVersion;
2961 
2962         // If the deserialized object has a SimpleTimeZone, try
2963         // to replace it with a ZoneInfo equivalent in order to
2964         // be compatible with the SimpleTimeZone-based
2965         // implementation as much as possible.
2966         TimeZone tz = getTimeZone();
2967         if (tz instanceof SimpleTimeZone) {
2968             String id = tz.getID();
2969             TimeZone zi = TimeZone.getTimeZone(id);
2970             if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) {
2971                 setTimeZone(zi);
2972             }
2973         }
2974     }
2975 
2976     /**
2977      * Analyze the negative subpattern of DecimalFormat and set/update values
2978      * as necessary.
2979      */
checkNegativeNumberExpression()2980     private void checkNegativeNumberExpression() {
2981         if ((numberFormat instanceof DecimalFormat) &&
2982             !numberFormat.equals(originalNumberFormat)) {
2983             String numberPattern = ((DecimalFormat)numberFormat).toPattern();
2984             if (!numberPattern.equals(originalNumberPattern)) {
2985                 hasFollowingMinusSign = false;
2986 
2987                 int separatorIndex = numberPattern.indexOf(';');
2988                 // If the negative subpattern is not absent, we have to analayze
2989                 // it in order to check if it has a following minus sign.
2990                 if (separatorIndex > -1) {
2991                     int minusIndex = numberPattern.indexOf('-', separatorIndex);
2992                     if ((minusIndex > numberPattern.lastIndexOf('0')) &&
2993                         (minusIndex > numberPattern.lastIndexOf('#'))) {
2994                         hasFollowingMinusSign = true;
2995                         minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign();
2996                     }
2997                 }
2998                 originalNumberPattern = numberPattern;
2999             }
3000             originalNumberFormat = numberFormat;
3001         }
3002     }
3003 
3004 }
3005