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