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