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