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