• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     /**
1718      * Parses text from a string to produce a {@code Date}.
1719      * <p>
1720      * The method attempts to parse text starting at the index given by
1721      * {@code pos}.
1722      * If parsing succeeds, then the index of {@code pos} is updated
1723      * to the index after the last character used (parsing does not necessarily
1724      * use all characters up to the end of the string), and the parsed
1725      * date is returned. The updated {@code pos} can be used to
1726      * indicate the starting point for the next call to this method.
1727      * If an error occurs, then the index of {@code pos} is not
1728      * changed, the error index of {@code pos} is set to the index of
1729      * the character where the error occurred, and null is returned.
1730      *
1731      * <p>This parsing operation uses the {@link DateFormat#calendar
1732      * calendar} to produce a {@code Date}. All of the {@code
1733      * calendar}'s date-time fields are {@linkplain Calendar#clear()
1734      * cleared} before parsing, and the {@code calendar}'s default
1735      * values of the date-time fields are used for any missing
1736      * date-time information. For example, the year value of the
1737      * parsed {@code Date} is 1970 with {@link GregorianCalendar} if
1738      * no year value is given from the parsing operation.  The {@code
1739      * TimeZone} value may be overwritten, depending on the given
1740      * pattern and the time zone value in {@code text}. Any {@code
1741      * TimeZone} value that has previously been set by a call to
1742      * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need
1743      * to be restored for further operations.
1744      *
1745      * @param text  A {@code String}, part of which should be parsed.
1746      * @param pos   A {@code ParsePosition} object with index and error
1747      *              index information as described above.
1748      * @return A {@code Date} parsed from the string. In case of
1749      *         error, returns null.
1750      * @throws    NullPointerException if {@code text} or {@code pos} is null.
1751      */
1752     @Override
parse(String text, ParsePosition pos)1753     public Date parse(String text, ParsePosition pos) {
1754         // BEGIN Android-changed: extract parseInternal() and avoid modifying timezone during parse.
1755         // Make sure the timezone associated with this dateformat instance (set via
1756         // {@code setTimeZone} isn't change as a side-effect of parsing a date.
1757         final TimeZone tz = getTimeZone();
1758         try {
1759             return parseInternal(text, pos);
1760         } finally {
1761             setTimeZone(tz);
1762         }
1763     }
1764 
parseInternal(String text, ParsePosition pos)1765     private Date parseInternal(String text, ParsePosition pos)
1766     {
1767         // END Android-changed: extract parseInternal() and avoid modifying timezone during parse.
1768         checkNegativeNumberExpression();
1769 
1770         int start = pos.index;
1771         int oldStart = start;
1772         int textLength = text.length();
1773 
1774         boolean[] ambiguousYear = {false};
1775 
1776         CalendarBuilder calb = new CalendarBuilder();
1777 
1778         for (int i = 0; i < compiledPattern.length; ) {
1779             int tag = compiledPattern[i] >>> 8;
1780             int count = compiledPattern[i++] & 0xff;
1781             if (count == 255) {
1782                 count = compiledPattern[i++] << 16;
1783                 count |= compiledPattern[i++];
1784             }
1785 
1786             switch (tag) {
1787             case TAG_QUOTE_ASCII_CHAR:
1788                 if (start >= textLength || text.charAt(start) != (char)count) {
1789                     pos.index = oldStart;
1790                     pos.errorIndex = start;
1791                     return null;
1792                 }
1793                 start++;
1794                 break;
1795 
1796             case TAG_QUOTE_CHARS:
1797                 while (count-- > 0) {
1798                     if (start >= textLength || text.charAt(start) != compiledPattern[i++]) {
1799                         pos.index = oldStart;
1800                         pos.errorIndex = start;
1801                         return null;
1802                     }
1803                     start++;
1804                 }
1805                 break;
1806 
1807             default:
1808                 // Peek the next pattern to determine if we need to
1809                 // obey the number of pattern letters for
1810                 // parsing. It's required when parsing contiguous
1811                 // digit text (e.g., "20010704") with a pattern which
1812                 // has no delimiters between fields, like "yyyyMMdd".
1813                 boolean obeyCount = false;
1814 
1815                 // In Arabic, a minus sign for a negative number is put after
1816                 // the number. Even in another locale, a minus sign can be
1817                 // put after a number using DateFormat.setNumberFormat().
1818                 // If both the minus sign and the field-delimiter are '-',
1819                 // subParse() needs to determine whether a '-' after a number
1820                 // in the given text is a delimiter or is a minus sign for the
1821                 // preceding number. We give subParse() a clue based on the
1822                 // information in compiledPattern.
1823                 boolean useFollowingMinusSignAsDelimiter = false;
1824 
1825                 if (i < compiledPattern.length) {
1826                     int nextTag = compiledPattern[i] >>> 8;
1827                     int nextCount = compiledPattern[i] & 0xff;
1828                     obeyCount = shouldObeyCount(nextTag, nextCount);
1829 
1830                     if (hasFollowingMinusSign &&
1831                         (nextTag == TAG_QUOTE_ASCII_CHAR ||
1832                          nextTag == TAG_QUOTE_CHARS)) {
1833 
1834                         if (nextTag != TAG_QUOTE_ASCII_CHAR) {
1835                             nextCount = compiledPattern[i+1];
1836                         }
1837 
1838                         if (nextCount == minusSign) {
1839                             useFollowingMinusSignAsDelimiter = true;
1840                         }
1841                     }
1842                 }
1843                 start = subParse(text, start, tag, count, obeyCount,
1844                                  ambiguousYear, pos,
1845                                  useFollowingMinusSignAsDelimiter, calb);
1846                 if (start < 0) {
1847                     pos.index = oldStart;
1848                     return null;
1849                 }
1850             }
1851         }
1852 
1853         // At this point the fields of Calendar have been set.  Calendar
1854         // will fill in default values for missing fields when the time
1855         // is computed.
1856 
1857         pos.index = start;
1858 
1859         Date parsedDate;
1860         try {
1861             parsedDate = calb.establish(calendar).getTime();
1862             // If the year value is ambiguous,
1863             // then the two-digit year == the default start year
1864             if (ambiguousYear[0]) {
1865                 if (parsedDate.before(defaultCenturyStart)) {
1866                     parsedDate = calb.addYear(100).establish(calendar).getTime();
1867                 }
1868             }
1869         }
1870         // An IllegalArgumentException will be thrown by Calendar.getTime()
1871         // if any fields are out of range, e.g., MONTH == 17.
1872         catch (IllegalArgumentException e) {
1873             pos.errorIndex = start;
1874             pos.index = oldStart;
1875             return null;
1876         }
1877 
1878         return parsedDate;
1879     }
1880 
1881     /* If the next tag/pattern is a <Numeric_Field> then the parser
1882      * should consider the count of digits while parsing the contigous digits
1883      * for the current tag/pattern
1884      */
shouldObeyCount(int tag, int count)1885     private boolean shouldObeyCount(int tag, int count) {
1886         switch (tag) {
1887             case PATTERN_MONTH:
1888             case PATTERN_MONTH_STANDALONE:
1889                 return count <= 2;
1890             case PATTERN_YEAR:
1891             case PATTERN_DAY_OF_MONTH:
1892             case PATTERN_HOUR_OF_DAY1:
1893             case PATTERN_HOUR_OF_DAY0:
1894             case PATTERN_MINUTE:
1895             case PATTERN_SECOND:
1896             case PATTERN_MILLISECOND:
1897             case PATTERN_DAY_OF_YEAR:
1898             case PATTERN_DAY_OF_WEEK_IN_MONTH:
1899             case PATTERN_WEEK_OF_YEAR:
1900             case PATTERN_WEEK_OF_MONTH:
1901             case PATTERN_HOUR1:
1902             case PATTERN_HOUR0:
1903             case PATTERN_WEEK_YEAR:
1904             case PATTERN_ISO_DAY_OF_WEEK:
1905                 return true;
1906             default:
1907                 return false;
1908         }
1909     }
1910 
1911     /**
1912      * Private code-size reduction function used by subParse.
1913      * @param text the time text being parsed.
1914      * @param start where to start parsing.
1915      * @param field the date field being parsed.
1916      * @param data the string array to parsed.
1917      * @return the new start position if matching succeeded; a negative number
1918      * indicating matching failure, otherwise.
1919      */
matchString(String text, int start, int field, String[] data, CalendarBuilder calb)1920     private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)
1921     {
1922         int i = 0;
1923         int count = data.length;
1924 
1925         if (field == Calendar.DAY_OF_WEEK) {
1926             i = 1;
1927         }
1928 
1929         // There may be multiple strings in the data[] array which begin with
1930         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1931         // We keep track of the longest match, and return that.  Note that this
1932         // unfortunately requires us to test all array elements.
1933         int bestMatchLength = 0, bestMatch = -1;
1934         for (; i<count; ++i)
1935         {
1936             int length = data[i].length();
1937             // Always compare if we have no match yet; otherwise only compare
1938             // against potentially better matches (longer strings).
1939             if (length > bestMatchLength &&
1940                 text.regionMatches(true, start, data[i], 0, length))
1941             {
1942                 bestMatch = i;
1943                 bestMatchLength = length;
1944             }
1945 
1946             // BEGIN Android-changed: Handle abbreviated fields that end with a '.'.
1947             // When the input option ends with a period (usually an abbreviated form), attempt
1948             // to match all chars up to that period.
1949             if ((data[i].charAt(length - 1) == '.') &&
1950                     ((length - 1) > bestMatchLength) &&
1951                     text.regionMatches(true, start, data[i], 0, length - 1)) {
1952                 bestMatch = i;
1953                 bestMatchLength = (length - 1);
1954             }
1955             // END Android-changed: Handle abbreviated fields that end with a '.'.
1956         }
1957         if (bestMatch >= 0)
1958         {
1959             calb.set(field, bestMatch);
1960             return start + bestMatchLength;
1961         }
1962         return -start;
1963     }
1964 
1965     /**
1966      * Performs the same thing as matchString(String, int, int,
1967      * String[]). This method takes a Map<String, Integer> instead of
1968      * String[].
1969      */
matchString(String text, int start, int field, Map<String,Integer> data, CalendarBuilder calb)1970     private int matchString(String text, int start, int field,
1971                             Map<String,Integer> data, CalendarBuilder calb) {
1972         if (data != null) {
1973             // TODO: make this default when it's in the spec.
1974             // BEGIN Android-changed: SortedMap instance lookup optimization in matchString().
1975             // RI returns not the longest match as matchString(String[]) does. http://b/119913354
1976             /*
1977             if (data instanceof SortedMap) {
1978                 for (String name : data.keySet()) {
1979             */
1980             if (data instanceof NavigableMap && ((NavigableMap) data).comparator() == null) {
1981                 for (String name : ((NavigableMap<String, Integer>) data).descendingKeySet()) {
1982             // END Android-changed: SortedMap instance lookup optimization in matchString().
1983                     if (text.regionMatches(true, start, name, 0, name.length())) {
1984                         calb.set(field, data.get(name));
1985                         return start + name.length();
1986                     }
1987                 }
1988                 return -start;
1989             }
1990 
1991             String bestMatch = null;
1992 
1993             for (String name : data.keySet()) {
1994                 int length = name.length();
1995                 if (bestMatch == null || length > bestMatch.length()) {
1996                     if (text.regionMatches(true, start, name, 0, length)) {
1997                         bestMatch = name;
1998                     }
1999                 }
2000             }
2001 
2002             if (bestMatch != null) {
2003                 calb.set(field, data.get(bestMatch));
2004                 return start + bestMatch.length();
2005             }
2006         }
2007         return -start;
2008     }
2009 
matchZoneString(String text, int start, String[] zoneNames)2010     private int matchZoneString(String text, int start, String[] zoneNames) {
2011         for (int i = 1; i <= 4; ++i) {
2012             // Checking long and short zones [1 & 2],
2013             // and long and short daylight [3 & 4].
2014             String zoneName = zoneNames[i];
2015             // Android-removed: App compat. Don't retrieve more data when data missing in DFS.
2016             //  It may have risk of app compat issue, and no significant benefit on Android because
2017             //  the DFS and TimeZoneNameUtility will both come from ICU / CLDR on Android.
2018             /*
2019             if (zoneName.isEmpty()) {
2020                 // fill in by retrieving single name
2021                 zoneName = TimeZoneNameUtility.retrieveDisplayName(
2022                                 zoneNames[0], i >= 3, i % 2, locale);
2023                 zoneNames[i] = zoneName;
2024             }
2025             */
2026             if (text.regionMatches(true, start,
2027                                    zoneName, 0, zoneName.length())) {
2028                 return i;
2029             }
2030         }
2031         return -1;
2032     }
2033 
2034     // BEGIN Android-removed: Unused private method matchDSTString.
2035     /*
2036     private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex,
2037                                    String[][] zoneStrings) {
2038         int index = standardIndex + 2;
2039         String zoneName  = zoneStrings[zoneIndex][index];
2040         if (text.regionMatches(true, start,
2041                                zoneName, 0, zoneName.length())) {
2042             return true;
2043         }
2044         return false;
2045     }
2046     */
2047     // END Android-removed: Unused private method matchDSTString.
2048 
2049     // BEGIN Android-changed: Parse time zone strings using ICU TimeZoneNames.
2050     // Note that this change falls back to the upstream zone names parsing code if the zoneStrings
2051     // for the formatData field has been set by the user. The original code of subParseZoneString
2052     // can be found in subParseZoneStringFromSymbols().
2053     /**
2054      * Parses the string in {@code text} (starting at {@code start}), interpreting it as a time zone
2055      * name. If a time zone is found, the internal calendar is set to that timezone and the index of
2056      * the first character after the time zone name is returned. Otherwise, returns {@code 0}.
2057      * @return the index of the next character to parse or {@code 0} on error.
2058      */
subParseZoneString(String text, int start, CalendarBuilder calb)2059     private int subParseZoneString(String text, int start, CalendarBuilder calb) {
2060         if (formatData.isZoneStringsSet) {
2061             // DateFormatSymbols.setZoneStrings() has be used, use those values instead of ICU code.
2062             return subParseZoneStringFromSymbols(text, start, calb);
2063         } else {
2064             return subParseZoneStringFromICU(text, start, calb);
2065         }
2066     }
2067 
getExtendedTimeZoneNames()2068     private ExtendedTimeZoneNames getExtendedTimeZoneNames() {
2069         if (timeZoneNames == null) {
2070             timeZoneNames = ExtendedTimeZoneNames.getInstance(ULocale.forLocale(locale));
2071         }
2072         return timeZoneNames;
2073     }
2074 
getTimeZoneNames()2075     private TimeZoneNames getTimeZoneNames() {
2076         return getExtendedTimeZoneNames().getTimeZoneNames();
2077     }
2078 
2079     /**
2080      * Parses the time zone string using the ICU4J class {@link TimeZoneNames}.
2081      */
subParseZoneStringFromICU(String text, int start, CalendarBuilder calb)2082     private int subParseZoneStringFromICU(String text, int start, CalendarBuilder calb) {
2083         String currentTimeZoneID = android.icu.util.TimeZone.getCanonicalID(getTimeZone().getID());
2084 
2085         Match matchedName = getExtendedTimeZoneNames().matchName(text, start, currentTimeZoneID);
2086         if (matchedName == null) {
2087             // No match found, return error.
2088             return -start;
2089         }
2090 
2091         String tzId = matchedName.getTzId();
2092         TimeZone newTimeZone = TimeZone.getTimeZone(tzId);
2093         if (!currentTimeZoneID.equals(tzId)) {
2094             setTimeZone(newTimeZone);
2095         }
2096 
2097         // Same logic as in subParseZoneStringFromSymbols, see below for details.
2098         boolean isDst = matchedName.isDst();
2099         int dstAmount = isDst ? newTimeZone.getDSTSavings() : 0;
2100         if (!isDst || dstAmount != 0) {
2101             calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount);
2102         }
2103 
2104         return matchedName.getMatchLength() + start;
2105     }
2106 
2107     /**
2108      * Parses the time zone string using the information in {@link #formatData}.
2109      */
subParseZoneStringFromSymbols(String text, int start, CalendarBuilder calb)2110     private int subParseZoneStringFromSymbols(String text, int start, CalendarBuilder calb) {
2111         // END Android-changed: Parse time zone strings using ICU TimeZoneNames.
2112         boolean useSameName = false; // true if standard and daylight time use the same abbreviation.
2113         TimeZone currentTimeZone = getTimeZone();
2114 
2115         // At this point, check for named time zones by looking through
2116         // the locale data from the TimeZoneNames strings.
2117         // Want to be able to parse both short and long forms.
2118         int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID());
2119         TimeZone tz = null;
2120         String[][] zoneStrings = formatData.getZoneStringsWrapper();
2121         String[] zoneNames = null;
2122         int nameIndex = 0;
2123         if (zoneIndex != -1) {
2124             zoneNames = zoneStrings[zoneIndex];
2125             if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
2126                 if (nameIndex <= 2) {
2127                     // Check if the standard name (abbr) and the daylight name are the same.
2128                     useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
2129                 }
2130                 tz = TimeZone.getTimeZone(zoneNames[0]);
2131             }
2132         }
2133         if (tz == null) {
2134             zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID());
2135             if (zoneIndex != -1) {
2136                 zoneNames = zoneStrings[zoneIndex];
2137                 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
2138                     if (nameIndex <= 2) {
2139                         useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
2140                     }
2141                     tz = TimeZone.getTimeZone(zoneNames[0]);
2142                 }
2143             }
2144         }
2145 
2146         if (tz == null) {
2147             int len = zoneStrings.length;
2148             for (int i = 0; i < len; i++) {
2149                 zoneNames = zoneStrings[i];
2150                 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
2151                     if (nameIndex <= 2) {
2152                         useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
2153                     }
2154                     tz = TimeZone.getTimeZone(zoneNames[0]);
2155                     break;
2156                 }
2157             }
2158         }
2159         if (tz != null) { // Matched any ?
2160             if (!tz.equals(currentTimeZone)) {
2161                 setTimeZone(tz);
2162             }
2163             // If the time zone matched uses the same name
2164             // (abbreviation) for both standard and daylight time,
2165             // let the time zone in the Calendar decide which one.
2166             //
2167             // Also if tz.getDSTSaving() returns 0 for DST, use tz to
2168             // determine the local time. (6645292)
2169             int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0;
2170             if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) {
2171                 calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount);
2172             }
2173             return (start + zoneNames[nameIndex].length());
2174         }
2175         return -start;
2176     }
2177 
2178     /**
2179      * Parses numeric forms of time zone offset, such as "hh:mm", and
2180      * sets calb to the parsed value.
2181      *
2182      * @param text  the text to be parsed
2183      * @param start the character position to start parsing
2184      * @param sign  1: positive; -1: negative
2185      * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's
2186      * @param colon true - colon required between hh and mm; false - no colon required
2187      * @param calb  a CalendarBuilder in which the parsed value is stored
2188      * @return updated parsed position, or its negative value to indicate a parsing error
2189      */
subParseNumericZone(String text, int start, int sign, int count, boolean colon, CalendarBuilder calb)2190     private int subParseNumericZone(String text, int start, int sign, int count,
2191                                     boolean colon, CalendarBuilder calb) {
2192         int index = start;
2193 
2194       parse:
2195         try {
2196             char c = text.charAt(index++);
2197             // Parse hh
2198             int hours;
2199             if (!isDigit(c)) {
2200                 break parse;
2201             }
2202             hours = c - '0';
2203             c = text.charAt(index++);
2204             if (isDigit(c)) {
2205                 hours = hours * 10 + (c - '0');
2206             } else {
2207                 // BEGIN Android-removed: Be more tolerant of colon. b/26426526
2208                 /*
2209                 // If no colon in RFC 822 or 'X' (ISO), two digits are
2210                 // required.
2211                 if (count > 0 || !colon) {
2212                     break parse;
2213                 }
2214                 */
2215                 // END Android-removed: Be more tolerant of colon. b/26426526
2216                 --index;
2217             }
2218             if (hours > 23) {
2219                 break parse;
2220             }
2221             int minutes = 0;
2222             if (count != 1) {
2223                 // Proceed with parsing mm
2224                 c = text.charAt(index++);
2225                 // BEGIN Android-changed: Be more tolerant of colon. b/26426526
2226                 // OpenJDK will return an error code if a : is found and colonRequired is false,
2227                 // this will return an error code if a : is not found and colonRequired is true.
2228                 //
2229                 //   colon       | c == ':' | OpenJDK | this
2230                 //   false       |  false   |   ok    |  ok
2231                 //   false       |  true    |  error  |  ok
2232                 //   true        |  false   |   ok    | error
2233                 //   true        |  true    |   ok    |  ok
2234                 /*
2235                 if (colon) {
2236                     if (c != ':') {
2237                         break parse;
2238                     }
2239                 */
2240                 if (c == ':') {
2241                     c = text.charAt(index++);
2242                 } else if (colon) {
2243                     break parse;
2244                 }
2245                 // END Android-changed: Be more tolerant of colon. b/26426526
2246                 if (!isDigit(c)) {
2247                     break parse;
2248                 }
2249                 minutes = c - '0';
2250                 c = text.charAt(index++);
2251                 if (!isDigit(c)) {
2252                     break parse;
2253                 }
2254                 minutes = minutes * 10 + (c - '0');
2255                 if (minutes > 59) {
2256                     break parse;
2257                 }
2258             }
2259             minutes += hours * 60;
2260             calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign)
2261                 .set(Calendar.DST_OFFSET, 0);
2262             return index;
2263         } catch (IndexOutOfBoundsException e) {
2264         }
2265         return  1 - index; // -(index - 1)
2266     }
2267 
isDigit(char c)2268     private boolean isDigit(char c) {
2269         return c >= '0' && c <= '9';
2270     }
2271 
2272     /**
2273      * Private member function that converts the parsed date strings into
2274      * timeFields. Returns -start (for ParsePosition) if failed.
2275      * @param text the time text to be parsed.
2276      * @param start where to start parsing.
2277      * @param patternCharIndex the index of the pattern character.
2278      * @param count the count of a pattern character.
2279      * @param obeyCount if true, then the next field directly abuts this one,
2280      * and we should use the count to know when to stop parsing.
2281      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
2282      * is true, then a two-digit year was parsed and may need to be readjusted.
2283      * @param origPos origPos.errorIndex is used to return an error index
2284      * at which a parse error occurred, if matching failure occurs.
2285      * @return the new start position if matching succeeded; -1 indicating
2286      * matching failure, otherwise. In case matching failure occurred,
2287      * an error index is set to origPos.errorIndex.
2288      */
subParse(String text, int start, int patternCharIndex, int count, boolean obeyCount, boolean[] ambiguousYear, ParsePosition origPos, boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb)2289     private int subParse(String text, int start, int patternCharIndex, int count,
2290                          boolean obeyCount, boolean[] ambiguousYear,
2291                          ParsePosition origPos,
2292                          boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {
2293         Number number;
2294         int value = 0;
2295         ParsePosition pos = new ParsePosition(0);
2296         pos.index = start;
2297         if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {
2298             // use calendar year 'y' instead
2299             patternCharIndex = PATTERN_YEAR;
2300         }
2301         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
2302 
2303         // If there are any spaces here, skip over them.  If we hit the end
2304         // of the string, then fail.
2305         for (;;) {
2306             if (pos.index >= text.length()) {
2307                 origPos.errorIndex = start;
2308                 return -1;
2309             }
2310             char c = text.charAt(pos.index);
2311             if (c != ' ' && c != '\t') {
2312                 break;
2313             }
2314             ++pos.index;
2315         }
2316         // Remember the actual start index
2317         int actualStart = pos.index;
2318 
2319       parsing:
2320         {
2321             // We handle a few special cases here where we need to parse
2322             // a number value.  We handle further, more generic cases below.  We need
2323             // to handle some of them here because some fields require extra processing on
2324             // the parsed value.
2325             if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||
2326                 patternCharIndex == PATTERN_HOUR1 ||
2327                 (patternCharIndex == PATTERN_MONTH && count <= 2) ||
2328                 (patternCharIndex == PATTERN_MONTH_STANDALONE && count <= 2) ||
2329                 patternCharIndex == PATTERN_YEAR ||
2330                 patternCharIndex == PATTERN_WEEK_YEAR) {
2331                 // It would be good to unify this with the obeyCount logic below,
2332                 // but that's going to be difficult.
2333                 if (obeyCount) {
2334                     if ((start+count) > text.length()) {
2335                         break parsing;
2336                     }
2337                     number = numberFormat.parse(text.substring(0, start+count), pos);
2338                 } else {
2339                     number = numberFormat.parse(text, pos);
2340                 }
2341                 if (number == null) {
2342                     if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) {
2343                         break parsing;
2344                     }
2345                 } else {
2346                     value = number.intValue();
2347 
2348                     if (useFollowingMinusSignAsDelimiter && (value < 0) &&
2349                         (((pos.index < text.length()) &&
2350                          (text.charAt(pos.index) != minusSign)) ||
2351                          ((pos.index == text.length()) &&
2352                           (text.charAt(pos.index-1) == minusSign)))) {
2353                         value = -value;
2354                         pos.index--;
2355                     }
2356                 }
2357             }
2358 
2359             boolean useDateFormatSymbols = useDateFormatSymbols();
2360 
2361             int index;
2362             switch (patternCharIndex) {
2363             case PATTERN_ERA: // 'G'
2364                 if (useDateFormatSymbols) {
2365                     if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) {
2366                         return index;
2367                     }
2368                 } else {
2369                     Map<String, Integer> map = getDisplayNamesMap(field, locale);
2370                     if ((index = matchString(text, start, field, map, calb)) > 0) {
2371                         return index;
2372                     }
2373                 }
2374                 break parsing;
2375 
2376             case PATTERN_WEEK_YEAR: // 'Y'
2377             case PATTERN_YEAR:      // 'y'
2378                 if (!(calendar instanceof GregorianCalendar)) {
2379                     // calendar might have text representations for year values,
2380                     // such as "\u5143" in JapaneseImperialCalendar.
2381                     int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
2382                     Map<String, Integer> map = calendar.getDisplayNames(field, style, locale);
2383                     if (map != null) {
2384                         if ((index = matchString(text, start, field, map, calb)) > 0) {
2385                             return index;
2386                         }
2387                     }
2388                     calb.set(field, value);
2389                     return pos.index;
2390                 }
2391 
2392                 // If there are 3 or more YEAR pattern characters, this indicates
2393                 // that the year value is to be treated literally, without any
2394                 // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
2395                 // we made adjustments to place the 2-digit year in the proper
2396                 // century, for parsed strings from "00" to "99".  Any other string
2397                 // is treated literally:  "2250", "-1", "1", "002".
2398                 if (count <= 2 && (pos.index - actualStart) == 2
2399                     && Character.isDigit(text.charAt(actualStart))
2400                     && Character.isDigit(text.charAt(actualStart + 1))) {
2401                     // Assume for example that the defaultCenturyStart is 6/18/1903.
2402                     // This means that two-digit years will be forced into the range
2403                     // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
2404                     // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
2405                     // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
2406                     // other fields specify a date before 6/18, or 1903 if they specify a
2407                     // date afterwards.  As a result, 03 is an ambiguous year.  All other
2408                     // two-digit years are unambiguous.
2409                     int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
2410                     ambiguousYear[0] = value == ambiguousTwoDigitYear;
2411                     value += (defaultCenturyStartYear/100)*100 +
2412                         (value < ambiguousTwoDigitYear ? 100 : 0);
2413                 }
2414                 calb.set(field, value);
2415                 return pos.index;
2416 
2417             case PATTERN_MONTH: // 'M'
2418             // BEGIN Android-changed: extract parseMonth method.
2419             /*
2420                 if (count <= 2) // i.e., M or MM.
2421                 {
2422                     // Don't want to parse the month if it is a string
2423                     // while pattern uses numeric style: M or MM.
2424                     // [We computed 'value' above.]
2425                     calb.set(Calendar.MONTH, value - 1);
2426                     return pos.index;
2427                 }
2428 
2429                 if (useDateFormatSymbols) {
2430                     // count >= 3 // i.e., MMM or MMMM
2431                     // Want to be able to parse both short and long forms.
2432                     // Try count == 4 first:
2433                     int newStart;
2434                     if ((newStart = matchString(text, start, Calendar.MONTH,
2435                                                 formatData.getMonths(), calb)) > 0) {
2436                         return newStart;
2437                     }
2438                     // count == 4 failed, now try count == 3
2439                     if ((index = matchString(text, start, Calendar.MONTH,
2440                                              formatData.getShortMonths(), calb)) > 0) {
2441                         return index;
2442                     }
2443                 } else {
2444                     Map<String, Integer> map = getDisplayContextNamesMap(field, locale);
2445                     if ((index = matchString(text, start, field, map, calb)) > 0) {
2446                         return index;
2447                     }
2448                 }
2449                 break parsing;
2450 
2451             case PATTERN_MONTH_STANDALONE: // 'L'
2452                 if (count <= 2) {
2453                     // Don't want to parse the month if it is a string
2454                     // while pattern uses numeric style: L or LL
2455                     //[we computed 'value' above.]
2456                     calb.set(Calendar.MONTH, value - 1);
2457                     return pos.index;
2458                 }
2459                 Map<String, Integer> maps = getDisplayNamesMap(field, locale);
2460                 if ((index = matchString(text, start, field, maps, calb)) > 0) {
2461                     return index;
2462                 }
2463                 break parsing;
2464             */
2465             {
2466                 final int idx = parseMonth(text, count, value, start, field, pos,
2467                         useDateFormatSymbols, false /* isStandalone */, calb);
2468                 if (idx > 0) {
2469                     return idx;
2470                 }
2471 
2472                 break parsing;
2473             }
2474 
2475             case PATTERN_MONTH_STANDALONE: // 'L'.
2476             {
2477                 final int idx = parseMonth(text, count, value, start, field, pos,
2478                         useDateFormatSymbols, true /* isStandalone */, calb);
2479                 if (idx > 0) {
2480                     return idx;
2481                 }
2482                 break parsing;
2483             }
2484             // END Android-changed: extract parseMonth method.
2485 
2486             case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
2487                 if (!isLenient()) {
2488                     // Validate the hour value in non-lenient
2489                     if (value < 1 || value > 24) {
2490                         break parsing;
2491                     }
2492                 }
2493                 // [We computed 'value' above.]
2494                 if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) {
2495                     value = 0;
2496                 }
2497                 calb.set(Calendar.HOUR_OF_DAY, value);
2498                 return pos.index;
2499 
2500             case PATTERN_DAY_OF_WEEK:  // 'E'
2501             // BEGIN Android-changed: extract parseWeekday method.
2502             /*
2503                 {
2504                     if (useDateFormatSymbols) {
2505                         // Want to be able to parse both short and long forms.
2506                         // Try count == 4 (DDDD) first:
2507                         int newStart;
2508                         if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
2509                                                   formatData.getWeekdays(), calb)) > 0) {
2510                             return newStart;
2511                         }
2512                         // DDDD failed, now try DDD
2513                         if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,
2514                                                  formatData.getShortWeekdays(), calb)) > 0) {
2515                             return index;
2516                         }
2517                     } else {
2518                         int[] styles = { Calendar.LONG, Calendar.SHORT };
2519                         for (int style : styles) {
2520                             Map<String,Integer> map = calendar.getDisplayNames(field, style, locale);
2521                             if ((index = matchString(text, start, field, map, calb)) > 0) {
2522                                 return index;
2523                             }
2524                         }
2525                     }
2526                 }
2527             */
2528             {
2529                 final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
2530                         false /* standalone */, calb);
2531                 if (idx > 0) {
2532                     return idx;
2533                 }
2534                 break parsing;
2535             }
2536             // END Android-changed: extract parseWeekday method.
2537 
2538             // BEGIN Android-added: support for 'c' (standalone day of week).
2539             case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
2540             {
2541                 final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
2542                         true /* standalone */, calb);
2543                 if (idx > 0) {
2544                     return idx;
2545                 }
2546 
2547                 break parsing;
2548             }
2549             // END Android-added: support for 'c' (standalone day of week).
2550 
2551             case PATTERN_AM_PM:    // 'a'
2552                 if (useDateFormatSymbols) {
2553                     if ((index = matchString(text, start, Calendar.AM_PM,
2554                                              formatData.getAmPmStrings(), calb)) > 0) {
2555                         return index;
2556                     }
2557                 } else {
2558                     Map<String,Integer> map = getDisplayNamesMap(field, locale);
2559                     if ((index = matchString(text, start, field, map, calb)) > 0) {
2560                         return index;
2561                     }
2562                 }
2563                 break parsing;
2564 
2565             case PATTERN_HOUR1: // 'h' 1-based.  eg, 11PM + 1 hour =>> 12 AM
2566                 if (!isLenient()) {
2567                     // Validate the hour value in non-lenient
2568                     if (value < 1 || value > 12) {
2569                         break parsing;
2570                     }
2571                 }
2572                 // [We computed 'value' above.]
2573                 if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) {
2574                     value = 0;
2575                 }
2576                 calb.set(Calendar.HOUR, value);
2577                 return pos.index;
2578 
2579             case PATTERN_ZONE_NAME:  // 'z'
2580             case PATTERN_ZONE_VALUE: // 'Z'
2581                 {
2582                     int sign = 0;
2583                     try {
2584                         char c = text.charAt(pos.index);
2585                         if (c == '+') {
2586                             sign = 1;
2587                         } else if (c == '-') {
2588                             sign = -1;
2589                         }
2590                         if (sign == 0) {
2591                             // Try parsing a custom time zone "GMT+hh:mm" or "GMT".
2592                             if ((c == 'G' || c == 'g')
2593                                 && (text.length() - start) >= GMT.length()
2594                                 && text.regionMatches(true, start, GMT, 0, GMT.length())) {
2595                                 pos.index = start + GMT.length();
2596 
2597                                 if ((text.length() - pos.index) > 0) {
2598                                     c = text.charAt(pos.index);
2599                                     if (c == '+') {
2600                                         sign = 1;
2601                                     } else if (c == '-') {
2602                                         sign = -1;
2603                                     }
2604                                 }
2605 
2606                                 if (sign == 0) {    /* "GMT" without offset */
2607                                     calb.set(Calendar.ZONE_OFFSET, 0)
2608                                         .set(Calendar.DST_OFFSET, 0);
2609                                     return pos.index;
2610                                 }
2611 
2612                                 // BEGIN Android-changed: Be more tolerant of colon. b/26426526
2613                                 /*
2614                                 // Parse the rest as "hh:mm"
2615                                 int i = subParseNumericZone(text, ++pos.index,
2616                                                             sign, 0, true, calb);
2617                                 */
2618                                 // Parse the rest as "hh[:]?mm"
2619                                 int i = subParseNumericZone(text, ++pos.index, sign, 0,
2620                                         false, calb);
2621                                 // END Android-changed: Be more tolerant of colon. b/26426526
2622                                 if (i > 0) {
2623                                     return i;
2624                                 }
2625                                 pos.index = -i;
2626                             } else {
2627                                 // Try parsing the text as a time zone
2628                                 // name or abbreviation.
2629                                 int i = subParseZoneString(text, pos.index, calb);
2630                                 if (i > 0) {
2631                                     return i;
2632                                 }
2633                                 pos.index = -i;
2634                             }
2635                         } else {
2636                             // BEGIN Android-changed: Be more tolerant of colon. b/26426526
2637                             // Parse the rest as "hh[:]?mm" (RFC 822)
2638                             /*
2639                             // Parse the rest as "hhmm" (RFC 822)
2640                             int i = subParseNumericZone(text, ++pos.index,
2641                                                         sign, 0, false, calb);
2642                             */
2643                             int i = subParseNumericZone(text, ++pos.index, sign, 0,
2644                                     false, calb);
2645                             // END Android-changed: Be more tolerant of colon. b/26426526
2646                             if (i > 0) {
2647                                 return i;
2648                             }
2649                             pos.index = -i;
2650                         }
2651                     } catch (IndexOutOfBoundsException e) {
2652                     }
2653                 }
2654                 break parsing;
2655 
2656             case PATTERN_ISO_ZONE:   // 'X'
2657                 {
2658                     if ((text.length() - pos.index) <= 0) {
2659                         break parsing;
2660                     }
2661 
2662                     int sign;
2663                     char c = text.charAt(pos.index);
2664                     if (c == 'Z') {
2665                         calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0);
2666                         return ++pos.index;
2667                     }
2668 
2669                     // parse text as "+/-hh[[:]mm]" based on count
2670                     if (c == '+') {
2671                         sign = 1;
2672                     } else if (c == '-') {
2673                         sign = -1;
2674                     } else {
2675                         ++pos.index;
2676                         break parsing;
2677                     }
2678                     int i = subParseNumericZone(text, ++pos.index, sign, count,
2679                                                 count == 3, calb);
2680                     if (i > 0) {
2681                         return i;
2682                     }
2683                     pos.index = -i;
2684                 }
2685                 break parsing;
2686 
2687             default:
2688          // case PATTERN_DAY_OF_MONTH:         // 'd'
2689          // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
2690          // case PATTERN_MINUTE:               // 'm'
2691          // case PATTERN_SECOND:               // 's'
2692          // case PATTERN_MILLISECOND:          // 'S'
2693          // case PATTERN_DAY_OF_YEAR:          // 'D'
2694          // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
2695          // case PATTERN_WEEK_OF_YEAR:         // 'w'
2696          // case PATTERN_WEEK_OF_MONTH:        // 'W'
2697          // case PATTERN_HOUR0:                // 'K' 0-based.  eg, 11PM + 1 hour =>> 0 AM
2698          // case PATTERN_ISO_DAY_OF_WEEK:      // 'u' (pseudo field);
2699 
2700                 // Handle "generic" fields
2701                 // BEGIN Android-changed: Better UTS#35 conformity for fractional seconds.
2702                 // http://b/25863120
2703                 int parseStart = pos.getIndex();
2704                 // END Android-changed: Better UTS#35 conformity for fractional seconds.
2705                 if (obeyCount) {
2706                     if ((start+count) > text.length()) {
2707                         break parsing;
2708                     }
2709                     number = numberFormat.parse(text.substring(0, start+count), pos);
2710                 } else {
2711                     number = numberFormat.parse(text, pos);
2712                 }
2713                 if (number != null) {
2714                     // BEGIN Android-changed: Better UTS#35 conformity for fractional seconds.
2715                     /*
2716                     value = number.intValue();
2717                     */
2718                     if (patternCharIndex == PATTERN_MILLISECOND) {
2719                         // Fractional seconds must be treated specially. We must always
2720                         // normalize them to their fractional second value [0, 1) before we attempt
2721                         // to parse them.
2722                         //
2723                         // Case 1: 11.78 seconds is 11 seconds and 780 (not 78) milliseconds.
2724                         // Case 2: 11.7890567 seconds is 11 seconds and 789 (not 7890567) milliseconds.
2725                         double doubleValue = number.doubleValue();
2726                         int width = pos.getIndex() - parseStart;
2727                         final double divisor = Math.pow(10, width);
2728                         value = (int) ((doubleValue / divisor) * 1000);
2729                     } else {
2730                         value = number.intValue();
2731                     }
2732                     // END Android-changed: Better UTS#35 conformity for fractional seconds.
2733 
2734                     if (useFollowingMinusSignAsDelimiter && (value < 0) &&
2735                         (((pos.index < text.length()) &&
2736                          (text.charAt(pos.index) != minusSign)) ||
2737                          ((pos.index == text.length()) &&
2738                           (text.charAt(pos.index-1) == minusSign)))) {
2739                         value = -value;
2740                         pos.index--;
2741                     }
2742 
2743                     calb.set(field, value);
2744                     return pos.index;
2745                 }
2746                 break parsing;
2747             }
2748         }
2749 
2750         // Parsing failed.
2751         origPos.errorIndex = pos.index;
2752         return -1;
2753     }
2754 
2755     // 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)2756     private int parseMonth(String text, int count, int value, int start,
2757                            int field, ParsePosition pos, boolean useDateFormatSymbols,
2758                            boolean standalone,
2759                            CalendarBuilder out) {
2760         if (count <= 2) // i.e., M or MM.
2761         {
2762             // Don't want to parse the month if it is a string
2763             // while pattern uses numeric style: M or MM.
2764             // [We computed 'value' above.]
2765             out.set(Calendar.MONTH, value - 1);
2766             return pos.index;
2767         }
2768 
2769         int index = -1;
2770         if (useDateFormatSymbols) {
2771             // count >= 3 // i.e., MMM or MMMM
2772             // Want to be able to parse both short and long forms.
2773             // Try count == 4 first:
2774             if ((index = matchString(
2775                     text, start, Calendar.MONTH,
2776                     standalone ? formatData.getStandAloneMonths() : formatData.getMonths(),
2777                     out)) > 0) {
2778                 return index;
2779             }
2780             // count == 4 failed, now try count == 3
2781             if ((index = matchString(
2782                     text, start, Calendar.MONTH,
2783                     standalone ? formatData.getShortStandAloneMonths() : formatData.getShortMonths(),
2784                     out)) > 0) {
2785                 return index;
2786             }
2787         } else {
2788             Map<String, Integer> map = getDisplayNamesMap(field, locale);
2789             if ((index = matchString(text, start, field, map, out)) > 0) {
2790                 return index;
2791             }
2792         }
2793 
2794         return index;
2795     }
2796 
parseWeekday(String text, int start, int field, boolean useDateFormatSymbols, boolean standalone, CalendarBuilder out)2797     private int parseWeekday(String text, int start, int field, boolean useDateFormatSymbols,
2798                              boolean standalone, CalendarBuilder out) {
2799         int index = -1;
2800         if (useDateFormatSymbols) {
2801             // Want to be able to parse both short and long forms.
2802             // Try count == 4 (DDDD) first:
2803             if ((index=matchString(
2804                     text, start, Calendar.DAY_OF_WEEK,
2805                     standalone ? formatData.getStandAloneWeekdays() : formatData.getWeekdays(),
2806                     out)) > 0) {
2807                 return index;
2808             }
2809 
2810             // DDDD failed, now try DDD
2811             if ((index = matchString(
2812                     text, start, Calendar.DAY_OF_WEEK,
2813                     standalone ? formatData.getShortStandAloneWeekdays() : formatData.getShortWeekdays(),
2814                     out)) > 0) {
2815                 return index;
2816             }
2817         } else {
2818             int[] styles = { Calendar.LONG, Calendar.SHORT };
2819             for (int style : styles) {
2820                 Map<String,Integer> map = calendar.getDisplayNames(field, style, locale);
2821                 if ((index = matchString(text, start, field, map, out)) > 0) {
2822                     return index;
2823                 }
2824             }
2825         }
2826 
2827         return index;
2828     }
2829     // END Android-added: parseMonth and parseWeekday methods to parse using ICU data.
2830 
2831     // Android-changed: Always useDateFormatSymbols() for GregorianCalendar.
2832     /**
2833      * Returns true if the DateFormatSymbols has been set explicitly or locale
2834      * is null or calendar is Gregorian.
2835      */
useDateFormatSymbols()2836     private boolean useDateFormatSymbols() {
2837         // Android-changed: Always useDateFormatSymbols() for GregorianCalendar.
2838         // This is for app compat. http://b/66411240#comment14
2839         // return useDateFormatSymbols || locale == null;
2840       return useDateFormatSymbols
2841           || "java.util.GregorianCalendar".equals(calendar.getClass().getName())
2842           || locale == null;
2843     }
2844 
2845     /**
2846      * Translates a pattern, mapping each character in the from string to the
2847      * corresponding character in the to string.
2848      *
2849      * @throws    IllegalArgumentException if the given pattern is invalid
2850      */
translatePattern(String pattern, String from, String to)2851     private String translatePattern(String pattern, String from, String to) {
2852         StringBuilder result = new StringBuilder();
2853         boolean inQuote = false;
2854         for (int i = 0; i < pattern.length(); ++i) {
2855             char c = pattern.charAt(i);
2856             if (inQuote) {
2857                 if (c == '\'') {
2858                     inQuote = false;
2859                 }
2860             }
2861             else {
2862                 if (c == '\'') {
2863                     inQuote = true;
2864                 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
2865                     int ci = from.indexOf(c);
2866                     if (ci >= 0) {
2867                         // patternChars is longer than localPatternChars due
2868                         // to serialization compatibility. The pattern letters
2869                         // unsupported by localPatternChars pass through.
2870                         if (ci < to.length()) {
2871                             c = to.charAt(ci);
2872                         }
2873                     } else {
2874                         throw new IllegalArgumentException("Illegal pattern " +
2875                                                            " character '" +
2876                                                            c + "'");
2877                     }
2878                 }
2879             }
2880             result.append(c);
2881         }
2882         if (inQuote) {
2883             throw new IllegalArgumentException("Unfinished quote in pattern");
2884         }
2885         return result.toString();
2886     }
2887 
2888     /**
2889      * Returns a pattern string describing this date format.
2890      *
2891      * @return a pattern string describing this date format.
2892      */
toPattern()2893     public String toPattern() {
2894         return pattern;
2895     }
2896 
2897     /**
2898      * Returns a localized pattern string describing this date format.
2899      *
2900      * @return a localized pattern string describing this date format.
2901      */
toLocalizedPattern()2902     public String toLocalizedPattern() {
2903         return translatePattern(pattern,
2904                                 DateFormatSymbols.patternChars,
2905                                 formatData.getLocalPatternChars());
2906     }
2907 
2908     /**
2909      * Applies the given pattern string to this date format.
2910      *
2911      * @param pattern the new date and time pattern for this date format
2912      * @throws    NullPointerException if the given pattern is null
2913      * @throws    IllegalArgumentException if the given pattern is invalid
2914      */
applyPattern(String pattern)2915     public void applyPattern(String pattern)
2916     {
2917         applyPatternImpl(pattern);
2918     }
2919 
applyPatternImpl(String pattern)2920     private void applyPatternImpl(String pattern) {
2921         compiledPattern = compile(pattern);
2922         this.pattern = pattern;
2923     }
2924 
2925     /**
2926      * Applies the given localized pattern string to this date format.
2927      *
2928      * @param pattern a String to be mapped to the new date and time format
2929      *        pattern for this format
2930      * @throws    NullPointerException if the given pattern is null
2931      * @throws    IllegalArgumentException if the given pattern is invalid
2932      */
applyLocalizedPattern(String pattern)2933     public void applyLocalizedPattern(String pattern) {
2934          String p = translatePattern(pattern,
2935                                      formatData.getLocalPatternChars(),
2936                                      DateFormatSymbols.patternChars);
2937          compiledPattern = compile(p);
2938          this.pattern = p;
2939     }
2940 
2941     /**
2942      * Gets a copy of the date and time format symbols of this date format.
2943      *
2944      * @return the date and time format symbols of this date format
2945      * @see #setDateFormatSymbols
2946      */
getDateFormatSymbols()2947     public DateFormatSymbols getDateFormatSymbols()
2948     {
2949         return (DateFormatSymbols)formatData.clone();
2950     }
2951 
2952     /**
2953      * Sets the date and time format symbols of this date format.
2954      *
2955      * @param newFormatSymbols the new date and time format symbols
2956      * @throws    NullPointerException if the given newFormatSymbols is null
2957      * @see #getDateFormatSymbols
2958      */
setDateFormatSymbols(DateFormatSymbols newFormatSymbols)2959     public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
2960     {
2961         this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
2962         useDateFormatSymbols = true;
2963     }
2964 
2965     /**
2966      * Creates a copy of this {@code SimpleDateFormat}. This also
2967      * clones the format's date format symbols.
2968      *
2969      * @return a clone of this {@code SimpleDateFormat}
2970      */
2971     @Override
clone()2972     public Object clone() {
2973         SimpleDateFormat other = (SimpleDateFormat) super.clone();
2974         other.formatData = (DateFormatSymbols) formatData.clone();
2975         return other;
2976     }
2977 
2978     /**
2979      * Returns the hash code value for this {@code SimpleDateFormat} object.
2980      *
2981      * @return the hash code value for this {@code SimpleDateFormat} object.
2982      */
2983     @Override
hashCode()2984     public int hashCode()
2985     {
2986         return pattern.hashCode();
2987         // just enough fields for a reasonable distribution
2988     }
2989 
2990     /**
2991      * Compares the given object with this {@code SimpleDateFormat} for
2992      * equality.
2993      *
2994      * @return true if the given object is equal to this
2995      * {@code SimpleDateFormat}
2996      */
2997     @Override
equals(Object obj)2998     public boolean equals(Object obj)
2999     {
3000         if (!super.equals(obj)) {
3001             return false; // super does class check
3002         }
3003         SimpleDateFormat that = (SimpleDateFormat) obj;
3004         return (pattern.equals(that.pattern)
3005                 && formatData.equals(that.formatData));
3006     }
3007 
3008     private static final int[] REST_OF_STYLES = {
3009         Calendar.SHORT_STANDALONE, Calendar.LONG_FORMAT, Calendar.LONG_STANDALONE,
3010     };
getDisplayNamesMap(int field, Locale locale)3011     private Map<String, Integer> getDisplayNamesMap(int field, Locale locale) {
3012         Map<String, Integer> map = calendar.getDisplayNames(field, Calendar.SHORT_FORMAT, locale);
3013         // Get all SHORT and LONG styles (avoid NARROW styles).
3014         for (int style : REST_OF_STYLES) {
3015             Map<String, Integer> m = calendar.getDisplayNames(field, style, locale);
3016             if (m != null) {
3017                 map.putAll(m);
3018             }
3019         }
3020         return map;
3021     }
3022 
3023     // Android-removed: Remove unused getDisplayContextNamesMap() for month parsing.
3024     /*
3025      * Obtains display names map, taking the context into account. Currently only
3026      * the month name pattern 'M' is context dependent.
3027      *
3028     private Map<String, Integer> getDisplayContextNamesMap(int field, Locale locale) {
3029         Map<String, Integer> map = calendar.getDisplayNames(field,
3030             forceStandaloneForm ? Calendar.SHORT_STANDALONE : Calendar.SHORT_FORMAT, locale);
3031         // Get the LONG style
3032         Map<String, Integer> m = calendar.getDisplayNames(field,
3033             forceStandaloneForm ? Calendar.LONG_STANDALONE : Calendar.LONG_FORMAT, locale);
3034         if (m != null) {
3035             map.putAll(m);
3036         }
3037         return map;
3038     }
3039     */
3040 
3041     /**
3042      * After reading an object from the input stream, the format
3043      * pattern in the object is verified.
3044      *
3045      * @throws    InvalidObjectException if the pattern is invalid
3046      */
3047     @java.io.Serial
readObject(ObjectInputStream stream)3048     private void readObject(ObjectInputStream stream)
3049                          throws IOException, ClassNotFoundException {
3050         stream.defaultReadObject();
3051 
3052         try {
3053             compiledPattern = compile(pattern);
3054         } catch (Exception e) {
3055             throw new InvalidObjectException("invalid pattern");
3056         }
3057 
3058         if (serialVersionOnStream < 1) {
3059             // didn't have defaultCenturyStart field
3060             initializeDefaultCentury();
3061         }
3062         else {
3063             // fill in dependent transient field
3064             parseAmbiguousDatesAsAfter(defaultCenturyStart);
3065         }
3066         serialVersionOnStream = currentSerialVersion;
3067 
3068         // If the deserialized object has a SimpleTimeZone, try
3069         // to replace it with a ZoneInfo equivalent in order to
3070         // be compatible with the SimpleTimeZone-based
3071         // implementation as much as possible.
3072         TimeZone tz = getTimeZone();
3073         if (tz instanceof SimpleTimeZone) {
3074             String id = tz.getID();
3075             TimeZone zi = TimeZone.getTimeZone(id);
3076             if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) {
3077                 setTimeZone(zi);
3078             }
3079         }
3080     }
3081 
3082     /**
3083      * Analyze the negative subpattern of DecimalFormat and set/update values
3084      * as necessary.
3085      */
checkNegativeNumberExpression()3086     private void checkNegativeNumberExpression() {
3087         if ((numberFormat instanceof DecimalFormat) &&
3088             !numberFormat.equals(originalNumberFormat)) {
3089             String numberPattern = ((DecimalFormat)numberFormat).toPattern();
3090             if (!numberPattern.equals(originalNumberPattern)) {
3091                 hasFollowingMinusSign = false;
3092 
3093                 int separatorIndex = numberPattern.indexOf(';');
3094                 // If the negative subpattern is not absent, we have to analayze
3095                 // it in order to check if it has a following minus sign.
3096                 if (separatorIndex > -1) {
3097                     int minusIndex = numberPattern.indexOf('-', separatorIndex);
3098                     if ((minusIndex > numberPattern.lastIndexOf('0')) &&
3099                         (minusIndex > numberPattern.lastIndexOf('#'))) {
3100                         hasFollowingMinusSign = true;
3101                         minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign();
3102                     }
3103                 }
3104                 originalNumberPattern = numberPattern;
3105             }
3106             originalNumberFormat = numberFormat;
3107         }
3108     }
3109 
3110 }
3111