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