1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package java.text;
19 
20 import java.io.IOException;
21 import java.io.ObjectInputStream;
22 import java.io.ObjectOutputStream;
23 import java.io.ObjectStreamField;
24 import java.util.ArrayList;
25 import java.util.Calendar;
26 import java.util.Date;
27 import java.util.GregorianCalendar;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.SimpleTimeZone;
31 import java.util.TimeZone;
32 import libcore.icu.LocaleData;
33 import libcore.icu.TimeZoneNames;
34 
35 /**
36  * Formats and parses dates in a locale-sensitive manner. Formatting turns a {@link Date} into
37  * a {@link String}, and parsing turns a {@code String} into a {@code Date}.
38  *
39  * <h4>Time Pattern Syntax</h4>
40  * <p>You can supply a Unicode <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a>
41  * pattern describing what strings are produced/accepted, but almost all
42  * callers should use {@link DateFormat#getDateInstance}, {@link DateFormat#getDateTimeInstance},
43  * or {@link DateFormat#getTimeInstance} to get a ready-made instance suitable for the user's
44  * locale. In cases where the system does not provide a suitable pattern, see
45  * {@link android.text.format.DateFormat#getBestDateTimePattern} which lets you specify
46  * the elements you'd like in a pattern and get back a pattern suitable for any given locale.
47  *
48  * <p>The main reason you'd create an instance this class directly is because you need to
49  * format/parse a specific machine-readable format, in which case you almost certainly want
50  * to explicitly ask for {@link Locale#US} to ensure that you get ASCII digits (rather than,
51  * say, Arabic digits).
52  * (See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".)
53  * The most useful non-localized pattern is {@code "yyyy-MM-dd HH:mm:ss.SSSZ"}, which corresponds
54  * to the ISO 8601 international standard date format.
55  *
56  * <p>To specify the time format, use a <i>time pattern</i> string. In this
57  * string, any character from {@code 'A'} to {@code 'Z'} or {@code 'a'} to {@code 'z'} is
58  * treated specially. All other characters are passed through verbatim. The interpretation of each
59  * of the ASCII letters is given in the table below. ASCII letters not appearing in the table are
60  * reserved for future use, and it is an error to attempt to use them.
61  *
62  * <p>The number of consecutive copies (the "count") of a pattern character further influences
63  * the format, as shown in the table. For fields of kind "number", the count is the minimum number
64  * of digits; shorter values are zero-padded to the given width and longer values overflow it.
65  *
66  * <p><table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY="">
67  * <tr BGCOLOR="#CCCCFF" CLASS="TableHeadingColor">
68  *      <td><B>Symbol</B></td> <td><B>Meaning</B></td> <td><B>Kind</B></td> <td><B>Example</B></td> </tr>
69  * <tr> <td>{@code D}</td> <td>day in year</td>             <td>(Number)</td>      <td>189</td> </tr>
70  * <tr> <td>{@code E}</td> <td>day of week</td>             <td>(Text)</td>        <td>{@code E}/{@code EE}/{@code EEE}:Tue, {@code EEEE}:Tuesday, {@code EEEEE}:T</td> </tr>
71  * <tr> <td>{@code F}</td> <td>day of week in month</td>    <td>(Number)</td>      <td>2 <i>(2nd Wed in July)</i></td> </tr>
72  * <tr> <td>{@code G}</td> <td>era designator</td>          <td>(Text)</td>        <td>AD</td> </tr>
73  * <tr> <td>{@code H}</td> <td>hour in day (0-23)</td>      <td>(Number)</td>      <td>0</td> </tr>
74  * <tr> <td>{@code K}</td> <td>hour in am/pm (0-11)</td>    <td>(Number)</td>      <td>0</td> </tr>
75  * <tr> <td>{@code L}</td> <td>stand-alone month</td>       <td>(Text)</td>        <td>{@code L}:1 {@code LL}:01 {@code LLL}:Jan {@code LLLL}:January {@code LLLLL}:J</td> </tr>
76  * <tr> <td>{@code M}</td> <td>month in year</td>           <td>(Text)</td>        <td>{@code M}:1 {@code MM}:01 {@code MMM}:Jan {@code MMMM}:January {@code MMMMM}:J</td> </tr>
77  * <tr> <td>{@code S}</td> <td>fractional seconds</td>      <td>(Number)</td>      <td>978</td> </tr>
78  * <tr> <td>{@code W}</td> <td>week in month</td>           <td>(Number)</td>      <td>2</td> </tr>
79  * <tr> <td>{@code Z}</td> <td>time zone (RFC 822)</td>     <td>(Time Zone)</td>   <td>{@code Z}/{@code ZZ}/{@code ZZZ}:-0800 {@code ZZZZ}:GMT-08:00 {@code ZZZZZ}:-08:00</td> </tr>
80  * <tr> <td>{@code a}</td> <td>am/pm marker</td>            <td>(Text)</td>        <td>PM</td> </tr>
81  * <tr> <td>{@code c}</td> <td>stand-alone day of week</td> <td>(Text)</td>        <td>{@code c}/{@code cc}/{@code ccc}:Tue, {@code cccc}:Tuesday, {@code ccccc}:T</td> </tr>
82  * <tr> <td>{@code d}</td> <td>day in month</td>            <td>(Number)</td>      <td>10</td> </tr>
83  * <tr> <td>{@code h}</td> <td>hour in am/pm (1-12)</td>    <td>(Number)</td>      <td>12</td> </tr>
84  * <tr> <td>{@code k}</td> <td>hour in day (1-24)</td>      <td>(Number)</td>      <td>24</td> </tr>
85  * <tr> <td>{@code m}</td> <td>minute in hour</td>          <td>(Number)</td>      <td>30</td> </tr>
86  * <tr> <td>{@code s}</td> <td>second in minute</td>        <td>(Number)</td>      <td>55</td> </tr>
87  * <tr> <td>{@code w}</td> <td>week in year</td>            <td>(Number)</td>      <td>27</td> </tr>
88  * <tr> <td>{@code y}</td> <td>year</td>                    <td>(Number)</td>      <td>{@code yy}:10 {@code y}/{@code yyy}/{@code yyyy}:2010</td> </tr>
89  * <tr> <td>{@code z}</td> <td>time zone</td>               <td>(Time Zone)</td>   <td>{@code z}/{@code zz}/{@code zzz}:PST {@code zzzz}:Pacific Standard Time</td> </tr>
90  * <tr> <td>{@code '}</td> <td>escape for text</td>         <td>(Delimiter)</td>   <td>{@code 'Date='}:Date=</td> </tr>
91  * <tr> <td>{@code ''}</td> <td>single quote</td>           <td>(Literal)</td>     <td>{@code 'o''clock'}:o'clock</td> </tr>
92  * </table>
93  *
94  * <p>Fractional seconds are handled specially: they're zero-padded on the <i>right</i>.
95  *
96  * <p>The two pattern characters {@code L} and {@code c} are ICU-compatible extensions, not
97  * available in the RI or in Android before Android 2.3 (Gingerbread, API level 9). These
98  * extensions are necessary for correct localization in languages such as Russian
99  * that make a grammatical distinction between, say, the word "June" in the sentence "June" and
100  * in the sentence "June 10th"; the former is the stand-alone form, the latter the regular
101  * form (because the usual case is to format a complete date). The relationship between {@code E}
102  * and {@code c} is equivalent, but for weekday names.
103  *
104  * <p>Five-count patterns (such as "MMMMM") used for the shortest non-numeric
105  * representation of a field were introduced in Android 4.3 (Jelly Bean MR2, API level 18).
106  *
107  * <p>When two numeric fields are directly adjacent with no intervening delimiter
108  * characters, they constitute a run of adjacent numeric fields. Such runs are
109  * parsed specially. For example, the format "HHmmss" parses the input text
110  * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
111  * parse "1234". In other words, the leftmost field of the run is flexible,
112  * while the others keep a fixed width. If the parse fails anywhere in the run,
113  * then the leftmost field is shortened by one character, and the entire run is
114  * parsed again. This is repeated until either the parse succeeds or the
115  * leftmost field is one character in length. If the parse still fails at that
116  * point, the parse of the run fails.
117  *
118  * <p>See {@link #set2DigitYearStart} for more about handling two-digit years.
119  *
120  * <h4>Sample Code</h4>
121  * <p>If you're formatting for human use, you should use an instance returned from
122  * {@link DateFormat} as described above. This code:
123  * <pre>
124  * DateFormat[] formats = new DateFormat[] {
125  *   DateFormat.getDateInstance(),
126  *   DateFormat.getDateTimeInstance(),
127  *   DateFormat.getTimeInstance(),
128  * };
129  * for (DateFormat df : formats) {
130  *   System.out.println(df.format(new Date(0)));
131  * }
132  * </pre>
133  *
134  * <p>Produces this output when run on an {@code en_US} device in the America/Los_Angeles time zone:
135  * <pre>
136  * Dec 31, 1969
137  * Dec 31, 1969 4:00:00 PM
138  * 4:00:00 PM
139  * </pre>
140  * And will produce similarly appropriate localized human-readable output on any user's system.
141  *
142  * <p>If you're formatting for machine use, consider this code:
143  * <pre>
144  * String[] formats = new String[] {
145  *   "yyyy-MM-dd",
146  *   "yyyy-MM-dd HH:mm",
147  *   "yyyy-MM-dd HH:mmZ",
148  *   "yyyy-MM-dd HH:mm:ss.SSSZ",
149  *   "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
150  * };
151  * for (String format : formats) {
152  *   SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US);
153  *   System.out.format("%30s %s\n", format, sdf.format(new Date(0)));
154  *   sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
155  *   System.out.format("%30s %s\n", format, sdf.format(new Date(0)));
156  * }
157  * </pre>
158  *
159  * <p>Which produces this output when run in the America/Los_Angeles time zone:
160  * <pre>
161  *                     yyyy-MM-dd 1969-12-31
162  *                     yyyy-MM-dd 1970-01-01
163  *               yyyy-MM-dd HH:mm 1969-12-31 16:00
164  *               yyyy-MM-dd HH:mm 1970-01-01 00:00
165  *              yyyy-MM-dd HH:mmZ 1969-12-31 16:00-0800
166  *              yyyy-MM-dd HH:mmZ 1970-01-01 00:00+0000
167  *       yyyy-MM-dd HH:mm:ss.SSSZ 1969-12-31 16:00:00.000-0800
168  *       yyyy-MM-dd HH:mm:ss.SSSZ 1970-01-01 00:00:00.000+0000
169  *     yyyy-MM-dd'T'HH:mm:ss.SSSZ 1969-12-31T16:00:00.000-0800
170  *     yyyy-MM-dd'T'HH:mm:ss.SSSZ 1970-01-01T00:00:00.000+0000
171  * </pre>
172  *
173  * <p>As this example shows, each {@code SimpleDateFormat} instance has a {@link TimeZone}.
174  * This is because it's called upon to format instances of {@code Date}, which represents an
175  * absolute time in UTC. That is, {@code Date} does not carry time zone information.
176  * By default, {@code SimpleDateFormat} will use the system's default time zone. This is
177  * appropriate for human-readable output (for which, see the previous sample instead), but
178  * generally inappropriate for machine-readable output, where ambiguity is a problem. Note that
179  * in this example, the output that included a time but no time zone cannot be parsed back into
180  * the original {@code Date}. For this
181  * reason it is almost always necessary and desirable to include the timezone in the output.
182  * It may also be desirable to set the formatter's time zone to UTC (to ease comparison, or to
183  * make logs more readable, for example). It is often best to avoid formatting completely when
184  * writing dates/times in machine-readable form. Simply sending the "Unix time" as a {@code long}
185  * or as the string corresponding to the long is cheaper and unambiguous, and can be formatted any
186  * way the recipient deems appropriate.
187  *
188  * <h4>Synchronization</h4>
189  * {@code SimpleDateFormat} is not thread-safe. Users should create a separate instance for
190  * each thread.
191  *
192  * @see java.util.Calendar
193  * @see java.util.Date
194  * @see java.util.TimeZone
195  * @see java.text.DateFormat
196  */
197 public class SimpleDateFormat extends DateFormat {
198 
199     private static final long serialVersionUID = 4774881970558875024L;
200 
201     // 'L' and 'c' are ICU-compatible extensions for stand-alone month and stand-alone weekday.
202     static final String PATTERN_CHARS = "GyMdkHmsSEDFwWahKzZLc";
203 
204     // The index of 'Z' in the PATTERN_CHARS string. This pattern character is supported by the RI,
205     // but has no corresponding public constant.
206     private static final int RFC_822_TIMEZONE_FIELD = 18;
207 
208     // The index of 'L' (cf. 'M') in the PATTERN_CHARS string. This is an ICU-compatible extension
209     // necessary for correct localization in various languages (http://b/2633414).
210     private static final int STAND_ALONE_MONTH_FIELD = 19;
211     // The index of 'c' (cf. 'E') in the PATTERN_CHARS string. This is an ICU-compatible extension
212     // necessary for correct localization in various languages (http://b/2633414).
213     private static final int STAND_ALONE_DAY_OF_WEEK_FIELD = 20;
214 
215     private String pattern;
216 
217     private DateFormatSymbols formatData;
218 
219     transient private int creationYear;
220 
221     private Date defaultCenturyStart;
222 
223     /**
224      * Constructs a new {@code SimpleDateFormat} for formatting and parsing
225      * dates and times in the {@code SHORT} style for the user's default locale.
226      * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
227      */
SimpleDateFormat()228     public SimpleDateFormat() {
229         this(Locale.getDefault());
230         this.pattern = defaultPattern();
231         this.formatData = new DateFormatSymbols(Locale.getDefault());
232     }
233 
234     /**
235      * Constructs a new {@code SimpleDateFormat} using the specified
236      * non-localized pattern and the {@code DateFormatSymbols} and {@code
237      * Calendar} for the user's default locale.
238      * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
239      *
240      * @param pattern
241      *            the pattern.
242      * @throws NullPointerException
243      *            if the pattern is {@code null}.
244      * @throws IllegalArgumentException
245      *            if {@code pattern} is not considered to be usable by this
246      *            formatter.
247      */
SimpleDateFormat(String pattern)248     public SimpleDateFormat(String pattern) {
249         this(pattern, Locale.getDefault());
250     }
251 
252     /**
253      * Validates the pattern.
254      *
255      * @param template
256      *            the pattern to validate.
257      *
258      * @throws NullPointerException
259      *             if the pattern is null
260      * @throws IllegalArgumentException
261      *             if the pattern is invalid
262      */
validatePattern(String template)263     private void validatePattern(String template) {
264         boolean quote = false;
265         int next, last = -1, count = 0;
266 
267         final int patternLength = template.length();
268         for (int i = 0; i < patternLength; i++) {
269             next = (template.charAt(i));
270             if (next == '\'') {
271                 if (count > 0) {
272                     validatePatternCharacter((char) last);
273                     count = 0;
274                 }
275                 if (last == next) {
276                     last = -1;
277                 } else {
278                     last = next;
279                 }
280                 quote = !quote;
281                 continue;
282             }
283             if (!quote
284                     && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
285                 if (last == next) {
286                     count++;
287                 } else {
288                     if (count > 0) {
289                         validatePatternCharacter((char) last);
290                     }
291                     last = next;
292                     count = 1;
293                 }
294             } else {
295                 if (count > 0) {
296                     validatePatternCharacter((char) last);
297                     count = 0;
298                 }
299                 last = -1;
300             }
301         }
302         if (count > 0) {
303             validatePatternCharacter((char) last);
304         }
305 
306         if (quote) {
307             throw new IllegalArgumentException("Unterminated quote");
308         }
309     }
310 
validatePatternCharacter(char format)311     private void validatePatternCharacter(char format) {
312         int index = PATTERN_CHARS.indexOf(format);
313         if (index == -1) {
314             throw new IllegalArgumentException("Unknown pattern character '" + format + "'");
315         }
316     }
317 
318     /**
319      * Constructs a new {@code SimpleDateFormat} using the specified
320      * non-localized pattern and {@code DateFormatSymbols} and the {@code
321      * Calendar} for the user's default locale.
322      * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
323      *
324      * @param template
325      *            the pattern.
326      * @param value
327      *            the DateFormatSymbols.
328      * @throws NullPointerException
329      *            if the pattern is {@code null}.
330      * @throws IllegalArgumentException
331      *            if the pattern is invalid.
332      */
SimpleDateFormat(String template, DateFormatSymbols value)333     public SimpleDateFormat(String template, DateFormatSymbols value) {
334         this(Locale.getDefault());
335         validatePattern(template);
336         pattern = template;
337         formatData = (DateFormatSymbols) value.clone();
338     }
339 
340     /**
341      * Constructs a new {@code SimpleDateFormat} using the specified
342      * non-localized pattern and the {@code DateFormatSymbols} and {@code
343      * Calendar} for the specified locale.
344      *
345      * @param template
346      *            the pattern.
347      * @param locale
348      *            the locale.
349      * @throws NullPointerException
350      *            if the pattern is {@code null}.
351      * @throws IllegalArgumentException
352      *            if the pattern is invalid.
353      */
SimpleDateFormat(String template, Locale locale)354     public SimpleDateFormat(String template, Locale locale) {
355         this(locale);
356         validatePattern(template);
357         pattern = template;
358         formatData = new DateFormatSymbols(locale);
359     }
360 
SimpleDateFormat(Locale locale)361     private SimpleDateFormat(Locale locale) {
362         numberFormat = NumberFormat.getInstance(locale);
363         numberFormat.setParseIntegerOnly(true);
364         numberFormat.setGroupingUsed(false);
365         calendar = new GregorianCalendar(locale);
366         calendar.add(Calendar.YEAR, -80);
367         creationYear = calendar.get(Calendar.YEAR);
368         defaultCenturyStart = calendar.getTime();
369     }
370 
371     /**
372      * Changes the pattern of this simple date format to the specified pattern
373      * which uses localized pattern characters.
374      *
375      * @param template
376      *            the localized pattern.
377      */
applyLocalizedPattern(String template)378     public void applyLocalizedPattern(String template) {
379         pattern = convertPattern(template, formatData.getLocalPatternChars(), PATTERN_CHARS, true);
380     }
381 
382     /**
383      * Changes the pattern of this simple date format to the specified pattern
384      * which uses non-localized pattern characters.
385      *
386      * @param template
387      *            the non-localized pattern.
388      * @throws NullPointerException
389      *                if the pattern is {@code null}.
390      * @throws IllegalArgumentException
391      *                if the pattern is invalid.
392      */
applyPattern(String template)393     public void applyPattern(String template) {
394         validatePattern(template);
395         pattern = template;
396     }
397 
398     /**
399      * Returns a new {@code SimpleDateFormat} with the same pattern and
400      * properties as this simple date format.
401      */
402     @Override
clone()403     public Object clone() {
404         SimpleDateFormat clone = (SimpleDateFormat) super.clone();
405         clone.formatData = (DateFormatSymbols) formatData.clone();
406         clone.defaultCenturyStart = new Date(defaultCenturyStart.getTime());
407         return clone;
408     }
409 
defaultPattern()410     private static String defaultPattern() {
411         LocaleData localeData = LocaleData.get(Locale.getDefault());
412         return localeData.getDateFormat(SHORT) + " " + localeData.getTimeFormat(SHORT);
413     }
414 
415     /**
416      * Compares the specified object with this simple date format and indicates
417      * if they are equal. In order to be equal, {@code object} must be an
418      * instance of {@code SimpleDateFormat} and have the same {@code DateFormat}
419      * properties, pattern, {@code DateFormatSymbols} and creation year.
420      *
421      * @param object
422      *            the object to compare with this object.
423      * @return {@code true} if the specified object is equal to this simple date
424      *         format; {@code false} otherwise.
425      * @see #hashCode
426      */
427     @Override
equals(Object object)428     public boolean equals(Object object) {
429         if (this == object) {
430             return true;
431         }
432         if (!(object instanceof SimpleDateFormat)) {
433             return false;
434         }
435         SimpleDateFormat simple = (SimpleDateFormat) object;
436         return super.equals(object) && pattern.equals(simple.pattern)
437                 && formatData.equals(simple.formatData);
438     }
439 
440     /**
441      * Formats the specified object using the rules of this simple date format
442      * and returns an {@code AttributedCharacterIterator} with the formatted
443      * date and attributes.
444      *
445      * @param object
446      *            the object to format.
447      * @return an {@code AttributedCharacterIterator} with the formatted date
448      *         and attributes.
449      * @throws NullPointerException
450      *            if the object is {@code null}.
451      * @throws IllegalArgumentException
452      *            if the object cannot be formatted by this simple date
453      *            format.
454      */
455     @Override
formatToCharacterIterator(Object object)456     public AttributedCharacterIterator formatToCharacterIterator(Object object) {
457         if (object == null) {
458             throw new NullPointerException("object == null");
459         }
460         if (object instanceof Date) {
461             return formatToCharacterIteratorImpl((Date) object);
462         }
463         if (object instanceof Number) {
464             return formatToCharacterIteratorImpl(new Date(((Number) object).longValue()));
465         }
466         throw new IllegalArgumentException("Bad class: " + object.getClass());
467     }
468 
formatToCharacterIteratorImpl(Date date)469     private AttributedCharacterIterator formatToCharacterIteratorImpl(Date date) {
470         StringBuffer buffer = new StringBuffer();
471         ArrayList<FieldPosition> fields = new ArrayList<FieldPosition>();
472 
473         // format the date, and find fields
474         formatImpl(date, buffer, null, fields);
475 
476         // create and AttributedString with the formatted buffer
477         AttributedString as = new AttributedString(buffer.toString());
478 
479         // add DateFormat field attributes to the AttributedString
480         for (FieldPosition pos : fields) {
481             Format.Field attribute = pos.getFieldAttribute();
482             as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos.getEndIndex());
483         }
484 
485         // return the CharacterIterator from AttributedString
486         return as.getIterator();
487     }
488 
489     /**
490      * Formats the date.
491      * <p>
492      * If the FieldPosition {@code field} is not null, and the field
493      * specified by this FieldPosition is formatted, set the begin and end index
494      * of the formatted field in the FieldPosition.
495      * <p>
496      * If the list {@code fields} is not null, find fields of this
497      * date, set FieldPositions with these fields, and add them to the fields
498      * vector.
499      *
500      * @param date
501      *            Date to Format
502      * @param buffer
503      *            StringBuffer to store the resulting formatted String
504      * @param field
505      *            FieldPosition to set begin and end index of the field
506      *            specified, if it is part of the format for this date
507      * @param fields
508      *            list used to store the FieldPositions for each field in this
509      *            date
510      * @return the formatted Date
511      * @throws IllegalArgumentException
512      *            if the object cannot be formatted by this Format.
513      */
formatImpl(Date date, StringBuffer buffer, FieldPosition field, List<FieldPosition> fields)514     private StringBuffer formatImpl(Date date, StringBuffer buffer,
515                                     FieldPosition field, List<FieldPosition> fields) {
516         boolean quote = false;
517         int next, last = -1, count = 0;
518         calendar.setTime(date);
519         if (field != null) {
520             field.setBeginIndex(0);
521             field.setEndIndex(0);
522         }
523 
524         final int patternLength = pattern.length();
525         for (int i = 0; i < patternLength; i++) {
526             next = (pattern.charAt(i));
527             if (next == '\'') {
528                 if (count > 0) {
529                     append(buffer, field, fields, (char) last, count);
530                     count = 0;
531                 }
532                 if (last == next) {
533                     buffer.append('\'');
534                     last = -1;
535                 } else {
536                     last = next;
537                 }
538                 quote = !quote;
539                 continue;
540             }
541             if (!quote
542                     && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
543                 if (last == next) {
544                     count++;
545                 } else {
546                     if (count > 0) {
547                         append(buffer, field, fields, (char) last, count);
548                     }
549                     last = next;
550                     count = 1;
551                 }
552             } else {
553                 if (count > 0) {
554                     append(buffer, field, fields, (char) last, count);
555                     count = 0;
556                 }
557                 last = -1;
558                 buffer.append((char) next);
559             }
560         }
561         if (count > 0) {
562             append(buffer, field, fields, (char) last, count);
563         }
564         return buffer;
565     }
566 
append(StringBuffer buffer, FieldPosition position, List<FieldPosition> fields, char format, int count)567     private void append(StringBuffer buffer, FieldPosition position,
568             List<FieldPosition> fields, char format, int count) {
569         int field = -1;
570         int index = PATTERN_CHARS.indexOf(format);
571         if (index == -1) {
572             throw new IllegalArgumentException("Unknown pattern character '" + format + "'");
573         }
574 
575         int beginPosition = buffer.length();
576         Field dateFormatField = null;
577         switch (index) {
578             case ERA_FIELD:
579                 dateFormatField = Field.ERA;
580                 buffer.append(formatData.eras[calendar.get(Calendar.ERA)]);
581                 break;
582             case YEAR_FIELD:
583                 dateFormatField = Field.YEAR;
584                 int year = calendar.get(Calendar.YEAR);
585                 /*
586                  * For 'y' and 'yyy', we're consistent with Unicode and previous releases
587                  * of Android. But this means we're inconsistent with the RI.
588                  *     http://unicode.org/reports/tr35/
589                  */
590                 if (count == 2) {
591                     appendNumber(buffer, 2, year % 100);
592                 } else {
593                     appendNumber(buffer, count, year);
594                 }
595                 break;
596             case STAND_ALONE_MONTH_FIELD: // 'L'
597                 dateFormatField = Field.MONTH;
598                 appendMonth(buffer, count, true);
599                 break;
600             case MONTH_FIELD: // 'M'
601                 dateFormatField = Field.MONTH;
602                 appendMonth(buffer, count, false);
603                 break;
604             case DATE_FIELD:
605                 dateFormatField = Field.DAY_OF_MONTH;
606                 field = Calendar.DATE;
607                 break;
608             case HOUR_OF_DAY1_FIELD: // 'k'
609                 dateFormatField = Field.HOUR_OF_DAY1;
610                 int hour = calendar.get(Calendar.HOUR_OF_DAY);
611                 appendNumber(buffer, count, hour == 0 ? 24 : hour);
612                 break;
613             case HOUR_OF_DAY0_FIELD: // 'H'
614                 dateFormatField = Field.HOUR_OF_DAY0;
615                 field = Calendar.HOUR_OF_DAY;
616                 break;
617             case MINUTE_FIELD:
618                 dateFormatField = Field.MINUTE;
619                 field = Calendar.MINUTE;
620                 break;
621             case SECOND_FIELD:
622                 dateFormatField = Field.SECOND;
623                 field = Calendar.SECOND;
624                 break;
625             case MILLISECOND_FIELD:
626                 dateFormatField = Field.MILLISECOND;
627                 appendMilliseconds(buffer, count, calendar.get(Calendar.MILLISECOND));
628                 break;
629             case STAND_ALONE_DAY_OF_WEEK_FIELD:
630                 dateFormatField = Field.DAY_OF_WEEK;
631                 appendDayOfWeek(buffer, count, true);
632                 break;
633             case DAY_OF_WEEK_FIELD:
634                 dateFormatField = Field.DAY_OF_WEEK;
635                 appendDayOfWeek(buffer, count, false);
636                 break;
637             case DAY_OF_YEAR_FIELD:
638                 dateFormatField = Field.DAY_OF_YEAR;
639                 field = Calendar.DAY_OF_YEAR;
640                 break;
641             case DAY_OF_WEEK_IN_MONTH_FIELD:
642                 dateFormatField = Field.DAY_OF_WEEK_IN_MONTH;
643                 field = Calendar.DAY_OF_WEEK_IN_MONTH;
644                 break;
645             case WEEK_OF_YEAR_FIELD:
646                 dateFormatField = Field.WEEK_OF_YEAR;
647                 field = Calendar.WEEK_OF_YEAR;
648                 break;
649             case WEEK_OF_MONTH_FIELD:
650                 dateFormatField = Field.WEEK_OF_MONTH;
651                 field = Calendar.WEEK_OF_MONTH;
652                 break;
653             case AM_PM_FIELD:
654                 dateFormatField = Field.AM_PM;
655                 buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]);
656                 break;
657             case HOUR1_FIELD: // 'h'
658                 dateFormatField = Field.HOUR1;
659                 hour = calendar.get(Calendar.HOUR);
660                 appendNumber(buffer, count, hour == 0 ? 12 : hour);
661                 break;
662             case HOUR0_FIELD: // 'K'
663                 dateFormatField = Field.HOUR0;
664                 field = Calendar.HOUR;
665                 break;
666             case TIMEZONE_FIELD: // 'z'
667                 dateFormatField = Field.TIME_ZONE;
668                 appendTimeZone(buffer, count, true);
669                 break;
670             case RFC_822_TIMEZONE_FIELD: // 'Z'
671                 dateFormatField = Field.TIME_ZONE;
672                 appendNumericTimeZone(buffer, count, false);
673                 break;
674         }
675         if (field != -1) {
676             appendNumber(buffer, count, calendar.get(field));
677         }
678 
679         if (fields != null) {
680             position = new FieldPosition(dateFormatField);
681             position.setBeginIndex(beginPosition);
682             position.setEndIndex(buffer.length());
683             fields.add(position);
684         } else {
685             // Set to the first occurrence
686             if ((position.getFieldAttribute() == dateFormatField || (position
687                     .getFieldAttribute() == null && position.getField() == index))
688                     && position.getEndIndex() == 0) {
689                 position.setBeginIndex(beginPosition);
690                 position.setEndIndex(buffer.length());
691             }
692         }
693     }
694 
695     // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
appendDayOfWeek(StringBuffer buffer, int count, boolean standAlone)696     private void appendDayOfWeek(StringBuffer buffer, int count, boolean standAlone) {
697       String[] days;
698       LocaleData ld = formatData.localeData;
699       if (count == 4) {
700         days = standAlone ? ld.longStandAloneWeekdayNames : formatData.weekdays;
701       } else if (count == 5) {
702         days = standAlone ? ld.tinyStandAloneWeekdayNames : formatData.localeData.tinyWeekdayNames;
703       } else {
704         days = standAlone ? ld.shortStandAloneWeekdayNames : formatData.shortWeekdays;
705       }
706       buffer.append(days[calendar.get(Calendar.DAY_OF_WEEK)]);
707     }
708 
709     // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
appendMonth(StringBuffer buffer, int count, boolean standAlone)710     private void appendMonth(StringBuffer buffer, int count, boolean standAlone) {
711       int month = calendar.get(Calendar.MONTH);
712       if (count <= 2) {
713         appendNumber(buffer, count, month + 1);
714         return;
715       }
716 
717       String[] months;
718       LocaleData ld = formatData.localeData;
719       if (count == 4) {
720         months = standAlone ? ld.longStandAloneMonthNames : formatData.months;
721       } else if (count == 5) {
722         months = standAlone ? ld.tinyStandAloneMonthNames : ld.tinyMonthNames;
723       } else {
724         months = standAlone ? ld.shortStandAloneMonthNames : formatData.shortMonths;
725       }
726       buffer.append(months[month]);
727     }
728 
729     /**
730      * Append a representation of the time zone of 'calendar' to 'buffer'.
731      *
732      * @param count the number of z or Z characters in the format string; "zzz" would be 3,
733      * for example.
734      * @param generalTimeZone true if we should use a display name ("PDT") if available;
735      * false implies that we should use RFC 822 format ("-0800") instead. This corresponds to 'z'
736      * versus 'Z' in the format string.
737      */
appendTimeZone(StringBuffer buffer, int count, boolean generalTimeZone)738     private void appendTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) {
739         if (generalTimeZone) {
740             TimeZone tz = calendar.getTimeZone();
741             boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
742             int style = count < 4 ? TimeZone.SHORT : TimeZone.LONG;
743             String zoneString = formatData.getTimeZoneDisplayName(tz, daylight, style);
744             if (zoneString != null) {
745                 buffer.append(zoneString);
746                 return;
747             }
748         }
749         // We didn't find what we were looking for, so default to a numeric time zone.
750         appendNumericTimeZone(buffer, count, generalTimeZone);
751     }
752 
753     // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
754     // @param generalTimeZone "GMT-08:00" rather than "-0800".
755     private void appendNumericTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) {
756         int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
757         boolean includeGmt = generalTimeZone || count == 4;
758         boolean includeMinuteSeparator = generalTimeZone || count >= 4;
759         buffer.append(TimeZone.createGmtOffsetString(includeGmt,  includeMinuteSeparator,
760                 offsetMillis));
761     }
762 
appendMilliseconds(StringBuffer buffer, int count, int value)763     private void appendMilliseconds(StringBuffer buffer, int count, int value) {
764         // Unlike other fields, milliseconds are truncated by count. So 361 formatted SS is "36".
765         numberFormat.setMinimumIntegerDigits((count > 3) ? 3 : count);
766         numberFormat.setMaximumIntegerDigits(10);
767         // We need to left-justify.
768         if (count == 1) {
769             value /= 100;
770         } else if (count == 2) {
771             value /= 10;
772         }
773         FieldPosition p = new FieldPosition(0);
774         numberFormat.format(Integer.valueOf(value), buffer, p);
775         if (count > 3) {
776             numberFormat.setMinimumIntegerDigits(count - 3);
777             numberFormat.format(Integer.valueOf(0), buffer, p);
778         }
779     }
780 
appendNumber(StringBuffer buffer, int count, int value)781     private void appendNumber(StringBuffer buffer, int count, int value) {
782         // TODO: we could avoid using the NumberFormat in most cases for a significant speedup.
783         // The only problem is that we expose the NumberFormat to third-party code, so we'd have
784         // some work to do to work out when the optimization is valid.
785         int minimumIntegerDigits = numberFormat.getMinimumIntegerDigits();
786         numberFormat.setMinimumIntegerDigits(count);
787         numberFormat.format(Integer.valueOf(value), buffer, new FieldPosition(0));
788         numberFormat.setMinimumIntegerDigits(minimumIntegerDigits);
789     }
790 
error(ParsePosition position, int offset, TimeZone zone)791     private Date error(ParsePosition position, int offset, TimeZone zone) {
792         position.setErrorIndex(offset);
793         calendar.setTimeZone(zone);
794         return null;
795     }
796 
797     /**
798      * Formats the specified date as a string using the pattern of this date
799      * format and appends the string to the specified string buffer.
800      * <p>
801      * If the {@code field} member of {@code field} contains a value specifying
802      * a format field, then its {@code beginIndex} and {@code endIndex} members
803      * will be updated with the position of the first occurrence of this field
804      * in the formatted text.
805      *
806      * @param date
807      *            the date to format.
808      * @param buffer
809      *            the target string buffer to append the formatted date/time to.
810      * @param fieldPos
811      *            on input: an optional alignment field; on output: the offsets
812      *            of the alignment field in the formatted text.
813      * @return the string buffer.
814      * @throws IllegalArgumentException
815      *             if there are invalid characters in the pattern.
816      */
817     @Override
format(Date date, StringBuffer buffer, FieldPosition fieldPos)818     public StringBuffer format(Date date, StringBuffer buffer, FieldPosition fieldPos) {
819         // Harmony delegates to ICU's SimpleDateFormat, we implement it directly
820         return formatImpl(date, buffer, fieldPos, null);
821     }
822 
823     /**
824      * Returns the date which is the start of the one hundred year period for two-digit year values.
825      * See {@link #set2DigitYearStart} for details.
826      */
get2DigitYearStart()827     public Date get2DigitYearStart() {
828         return (Date) defaultCenturyStart.clone();
829     }
830 
831     /**
832      * Returns the {@code DateFormatSymbols} used by this simple date format.
833      *
834      * @return the {@code DateFormatSymbols} object.
835      */
getDateFormatSymbols()836     public DateFormatSymbols getDateFormatSymbols() {
837         return (DateFormatSymbols) formatData.clone();
838     }
839 
840     @Override
hashCode()841     public int hashCode() {
842         return super.hashCode() + pattern.hashCode() + formatData.hashCode() + creationYear;
843     }
844 
parse(String string, int offset, char format, int count)845     private int parse(String string, int offset, char format, int count) {
846         int index = PATTERN_CHARS.indexOf(format);
847         if (index == -1) {
848             throw new IllegalArgumentException("Unknown pattern character '" + format + "'");
849         }
850         int field = -1;
851         // TODO: what's 'absolute' for? when is 'count' negative, and why?
852         int absolute = 0;
853         if (count < 0) {
854             count = -count;
855             absolute = count;
856         }
857         switch (index) {
858             case ERA_FIELD:
859                 return parseText(string, offset, formatData.eras, Calendar.ERA);
860             case YEAR_FIELD:
861                 if (count >= 3) {
862                     field = Calendar.YEAR;
863                 } else {
864                     ParsePosition position = new ParsePosition(offset);
865                     Number result = parseNumber(absolute, string, position);
866                     if (result == null) {
867                         return -position.getErrorIndex() - 1;
868                     }
869                     int year = result.intValue();
870                     // A two digit year must be exactly two digits, i.e. 01
871                     if ((position.getIndex() - offset) == 2 && year >= 0) {
872                         year += creationYear / 100 * 100;
873                         if (year < creationYear) {
874                             year += 100;
875                         }
876                     }
877                     calendar.set(Calendar.YEAR, year);
878                     return position.getIndex();
879                 }
880                 break;
881             case STAND_ALONE_MONTH_FIELD: // 'L'
882                 return parseMonth(string, offset, count, absolute, true);
883             case MONTH_FIELD: // 'M'
884                 return parseMonth(string, offset, count, absolute, false);
885             case DATE_FIELD:
886                 field = Calendar.DATE;
887                 break;
888             case HOUR_OF_DAY1_FIELD: // 'k'
889                 ParsePosition position = new ParsePosition(offset);
890                 Number result = parseNumber(absolute, string, position);
891                 if (result == null) {
892                     return -position.getErrorIndex() - 1;
893                 }
894                 int hour = result.intValue();
895                 if (hour == 24) {
896                     hour = 0;
897                 }
898                 calendar.set(Calendar.HOUR_OF_DAY, hour);
899                 return position.getIndex();
900             case HOUR_OF_DAY0_FIELD: // 'H'
901                 field = Calendar.HOUR_OF_DAY;
902                 break;
903             case MINUTE_FIELD:
904                 field = Calendar.MINUTE;
905                 break;
906             case SECOND_FIELD:
907                 field = Calendar.SECOND;
908                 break;
909             case MILLISECOND_FIELD:
910                 return parseFractionalSeconds(string, offset, absolute);
911             case STAND_ALONE_DAY_OF_WEEK_FIELD:
912                 return parseDayOfWeek(string, offset, true);
913             case DAY_OF_WEEK_FIELD:
914                 return parseDayOfWeek(string, offset, false);
915             case DAY_OF_YEAR_FIELD:
916                 field = Calendar.DAY_OF_YEAR;
917                 break;
918             case DAY_OF_WEEK_IN_MONTH_FIELD:
919                 field = Calendar.DAY_OF_WEEK_IN_MONTH;
920                 break;
921             case WEEK_OF_YEAR_FIELD:
922                 field = Calendar.WEEK_OF_YEAR;
923                 break;
924             case WEEK_OF_MONTH_FIELD:
925                 field = Calendar.WEEK_OF_MONTH;
926                 break;
927             case AM_PM_FIELD:
928                 return parseText(string, offset, formatData.ampms, Calendar.AM_PM);
929             case HOUR1_FIELD: // 'h'
930                 position = new ParsePosition(offset);
931                 result = parseNumber(absolute, string, position);
932                 if (result == null) {
933                     return -position.getErrorIndex() - 1;
934                 }
935                 hour = result.intValue();
936                 if (hour == 12) {
937                     hour = 0;
938                 }
939                 calendar.set(Calendar.HOUR, hour);
940                 return position.getIndex();
941             case HOUR0_FIELD: // 'K'
942                 field = Calendar.HOUR;
943                 break;
944             case TIMEZONE_FIELD: // 'z'
945                 return parseTimeZone(string, offset);
946             case RFC_822_TIMEZONE_FIELD: // 'Z'
947                 return parseTimeZone(string, offset);
948         }
949         if (field != -1) {
950             return parseNumber(absolute, string, offset, field, 0);
951         }
952         return offset;
953     }
954 
955     /**
956      * Parses the fractional seconds section of a formatted date and assigns
957      * it to the {@code Calendar.MILLISECOND} field. Note that fractional seconds
958      * are somewhat unique, because they are zero suffixed.
959      */
parseFractionalSeconds(String string, int offset, int count)960     private int parseFractionalSeconds(String string, int offset, int count) {
961         final ParsePosition parsePosition = new ParsePosition(offset);
962         final Number fractionalSeconds = parseNumber(count, string, parsePosition);
963         if (fractionalSeconds == null) {
964             return -parsePosition.getErrorIndex() - 1;
965         }
966 
967         // NOTE: We could've done this using two parses instead. The first parse
968         // looking at |count| digits (to verify the date matched the format), and
969         // then a second parse that consumed just the first three digits. That
970         // would've avoided the floating point arithmetic, but would've demanded
971         // that we round values ourselves.
972         final double result = fractionalSeconds.doubleValue();
973         final int numDigitsParsed = parsePosition.getIndex() - offset;
974         final double divisor = Math.pow(10, numDigitsParsed);
975 
976         calendar.set(Calendar.MILLISECOND, (int) ((result / divisor) * 1000));
977         return parsePosition.getIndex();
978     }
979 
parseDayOfWeek(String string, int offset, boolean standAlone)980     private int parseDayOfWeek(String string, int offset, boolean standAlone) {
981       LocaleData ld = formatData.localeData;
982       int index = parseText(string, offset,
983                             standAlone ? ld.longStandAloneWeekdayNames : formatData.weekdays,
984                             Calendar.DAY_OF_WEEK);
985       if (index < 0) {
986         index = parseText(string, offset,
987                           standAlone ? ld.shortStandAloneWeekdayNames : formatData.shortWeekdays,
988                           Calendar.DAY_OF_WEEK);
989       }
990       return index;
991     }
992 
parseMonth(String string, int offset, int count, int absolute, boolean standAlone)993     private int parseMonth(String string, int offset, int count, int absolute, boolean standAlone) {
994       if (count <= 2) {
995         return parseNumber(absolute, string, offset, Calendar.MONTH, -1);
996       }
997       LocaleData ld = formatData.localeData;
998       int index = parseText(string, offset,
999                             standAlone ? ld.longStandAloneMonthNames : formatData.months,
1000                             Calendar.MONTH);
1001       if (index < 0) {
1002         index = parseText(string, offset,
1003                           standAlone ? ld.shortStandAloneMonthNames : formatData.shortMonths,
1004                           Calendar.MONTH);
1005       }
1006       return index;
1007     }
1008 
1009     /**
1010      * Parses a date from the specified string starting at the index specified
1011      * by {@code position}. If the string is successfully parsed then the index
1012      * of the {@code ParsePosition} is updated to the index following the parsed
1013      * text. On error, the index is unchanged and the error index of {@code
1014      * ParsePosition} is set to the index where the error occurred.
1015      *
1016      * @param string
1017      *            the string to parse using the pattern of this simple date
1018      *            format.
1019      * @param position
1020      *            input/output parameter, specifies the start index in {@code
1021      *            string} from where to start parsing. If parsing is successful,
1022      *            it is updated with the index following the parsed text; on
1023      *            error, the index is unchanged and the error index is set to
1024      *            the index where the error occurred.
1025      * @return the date resulting from the parse, or {@code null} if there is an
1026      *         error.
1027      * @throws IllegalArgumentException
1028      *             if there are invalid characters in the pattern.
1029      */
1030     @Override
parse(String string, ParsePosition position)1031     public Date parse(String string, ParsePosition position) {
1032         // Harmony delegates to ICU's SimpleDateFormat, we implement it directly
1033         boolean quote = false;
1034         int next, last = -1, count = 0, offset = position.getIndex();
1035         int length = string.length();
1036         calendar.clear();
1037         TimeZone zone = calendar.getTimeZone();
1038         final int patternLength = pattern.length();
1039         for (int i = 0; i < patternLength; i++) {
1040             next = pattern.charAt(i);
1041             if (next == '\'') {
1042                 if (count > 0) {
1043                     if ((offset = parse(string, offset, (char) last, count)) < 0) {
1044                         return error(position, -offset - 1, zone);
1045                     }
1046                     count = 0;
1047                 }
1048                 if (last == next) {
1049                     if (offset >= length || string.charAt(offset) != '\'') {
1050                         return error(position, offset, zone);
1051                     }
1052                     offset++;
1053                     last = -1;
1054                 } else {
1055                     last = next;
1056                 }
1057                 quote = !quote;
1058                 continue;
1059             }
1060             if (!quote
1061                     && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
1062                 if (last == next) {
1063                     count++;
1064                 } else {
1065                     if (count > 0) {
1066                         if ((offset = parse(string, offset, (char) last, -count)) < 0) {
1067                             return error(position, -offset - 1, zone);
1068                         }
1069                     }
1070                     last = next;
1071                     count = 1;
1072                 }
1073             } else {
1074                 if (count > 0) {
1075                     if ((offset = parse(string, offset, (char) last, count)) < 0) {
1076                         return error(position, -offset - 1, zone);
1077                     }
1078                     count = 0;
1079                 }
1080                 last = -1;
1081                 if (offset >= length || string.charAt(offset) != next) {
1082                     return error(position, offset, zone);
1083                 }
1084                 offset++;
1085             }
1086         }
1087         if (count > 0) {
1088             if ((offset = parse(string, offset, (char) last, count)) < 0) {
1089                 return error(position, -offset - 1, zone);
1090             }
1091         }
1092         Date date;
1093         try {
1094             date = calendar.getTime();
1095         } catch (IllegalArgumentException e) {
1096             return error(position, offset, zone);
1097         }
1098         position.setIndex(offset);
1099         calendar.setTimeZone(zone);
1100         return date;
1101     }
1102 
parseNumber(int max, String string, ParsePosition position)1103     private Number parseNumber(int max, String string, ParsePosition position) {
1104         int length = string.length();
1105         int index = position.getIndex();
1106         if (max > 0 && max < length - index) {
1107             length = index + max;
1108         }
1109         while (index < length && (string.charAt(index) == ' ' || string.charAt(index) == '\t')) {
1110             ++index;
1111         }
1112         if (max == 0) {
1113             position.setIndex(index);
1114             return numberFormat.parse(string, position);
1115         }
1116 
1117         int result = 0;
1118         int digit;
1119         while (index < length && (digit = Character.digit(string.charAt(index), 10)) != -1) {
1120             result = result * 10 + digit;
1121             ++index;
1122         }
1123         if (index == position.getIndex()) {
1124             position.setErrorIndex(index);
1125             return null;
1126         }
1127         position.setIndex(index);
1128         return Integer.valueOf(result);
1129     }
1130 
parseNumber(int max, String string, int offset, int field, int skew)1131     private int parseNumber(int max, String string, int offset, int field, int skew) {
1132         ParsePosition position = new ParsePosition(offset);
1133         Number result = parseNumber(max, string, position);
1134         if (result == null) {
1135             return -position.getErrorIndex() - 1;
1136         }
1137         calendar.set(field, result.intValue() + skew);
1138         return position.getIndex();
1139     }
1140 
parseText(String string, int offset, String[] options, int field)1141     private int parseText(String string, int offset, String[] options, int field) {
1142         // We search for the longest match, in case some entries are substrings of others.
1143         int bestIndex = -1;
1144         int bestLength = -1;
1145         for (int i = 0; i < options.length; ++i) {
1146             String option = options[i];
1147             int optionLength = option.length();
1148             if (optionLength == 0) {
1149                 continue;
1150             }
1151             if (string.regionMatches(true, offset, option, 0, optionLength)) {
1152                 if (bestIndex == -1 || optionLength > bestLength) {
1153                     bestIndex = i;
1154                     bestLength = optionLength;
1155                 }
1156             } else if (option.charAt(optionLength - 1) == '.') {
1157                 // If CLDR has abbreviated forms like "Aug.", we should accept "Aug" too.
1158                 // https://code.google.com/p/android/issues/detail?id=59383
1159                 if (string.regionMatches(true, offset, option, 0, optionLength - 1)) {
1160                     if (bestIndex == -1 || optionLength - 1 > bestLength) {
1161                         bestIndex = i;
1162                         bestLength = optionLength - 1;
1163                     }
1164                 }
1165             }
1166         }
1167         if (bestIndex != -1) {
1168             calendar.set(field, bestIndex);
1169             return offset + bestLength;
1170         }
1171         return -offset - 1;
1172     }
1173 
parseTimeZone(String string, int offset)1174     private int parseTimeZone(String string, int offset) {
1175         boolean foundGMT = string.regionMatches(offset, "GMT", 0, 3);
1176         if (foundGMT) {
1177             offset += 3;
1178         }
1179 
1180         // Check for an offset, which may have been preceded by "GMT"
1181         char sign;
1182         if (offset < string.length() && ((sign = string.charAt(offset)) == '+' || sign == '-')) {
1183             ParsePosition position = new ParsePosition(offset + 1);
1184             Number result = numberFormat.parse(string, position);
1185             if (result == null) {
1186                 return -position.getErrorIndex() - 1;
1187             }
1188             int hour = result.intValue();
1189             int raw = hour * 3600000;
1190             int index = position.getIndex();
1191             if (index < string.length() && string.charAt(index) == ':') {
1192                 position.setIndex(index + 1);
1193                 result = numberFormat.parse(string, position);
1194                 if (result == null) {
1195                     return -position.getErrorIndex() - 1;
1196                 }
1197                 int minute = result.intValue();
1198                 raw += minute * 60000;
1199             } else if (hour >= 24) {
1200                 raw = (hour / 100 * 3600000) + (hour % 100 * 60000);
1201             }
1202             if (sign == '-') {
1203                 raw = -raw;
1204             }
1205             calendar.setTimeZone(new SimpleTimeZone(raw, ""));
1206             return position.getIndex();
1207         }
1208 
1209         // If there was "GMT" but no offset.
1210         if (foundGMT) {
1211             calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
1212             return offset;
1213         }
1214 
1215         // Exhaustively look for the string in this DateFormat's localized time zone strings.
1216         for (String[] row : formatData.internalZoneStrings()) {
1217             for (int i = TimeZoneNames.LONG_NAME; i < TimeZoneNames.NAME_COUNT; ++i) {
1218                 if (row[i] == null) {
1219                     // If icu4c doesn't have a name, our array contains a null. Normally we'd
1220                     // work out the correct GMT offset, but we already handled parsing GMT offsets
1221                     // above, so we can just ignore these cases. http://b/8128460.
1222                     continue;
1223                 }
1224                 if (string.regionMatches(true, offset, row[i], 0, row[i].length())) {
1225                     TimeZone zone = TimeZone.getTimeZone(row[TimeZoneNames.OLSON_NAME]);
1226                     if (zone == null) {
1227                         return -offset - 1;
1228                     }
1229                     int raw = zone.getRawOffset();
1230                     if (i == TimeZoneNames.LONG_NAME_DST || i == TimeZoneNames.SHORT_NAME_DST) {
1231                         // Not all time zones use a one-hour difference, so we need to query
1232                         // the TimeZone. (Australia/Lord_Howe is the usual example of this.)
1233                         int dstSavings = zone.getDSTSavings();
1234                         // One problem with TimeZone.getDSTSavings is that it will return 0 if the
1235                         // time zone has stopped using DST, even if we're parsing a date from
1236                         // the past. In that case, assume the default.
1237                         if (dstSavings == 0) {
1238                             // TODO: we should change this to use TimeZone.getOffset(long),
1239                             // but that requires the complete date to be parsed first.
1240                             dstSavings = 3600000;
1241                         }
1242                         raw += dstSavings;
1243                     }
1244                     calendar.setTimeZone(new SimpleTimeZone(raw, ""));
1245                     return offset + row[i].length();
1246                 }
1247             }
1248         }
1249         return -offset - 1;
1250     }
1251 
1252     /**
1253      * Sets the date which is the start of the one hundred year period for two-digit year values.
1254      *
1255      * <p>When parsing a date string using the abbreviated year pattern {@code yy}, {@code
1256      * SimpleDateFormat} must interpret the abbreviated year relative to some
1257      * century. It does this by adjusting dates to be within 80 years before and 20
1258      * years after the time the {@code SimpleDateFormat} instance was created. For
1259      * example, using a pattern of {@code MM/dd/yy}, an
1260      * instance created on Jan 1, 1997 would interpret the string {@code "01/11/12"}
1261      * as Jan 11, 2012 but interpret the string {@code "05/04/64"} as May 4, 1964.
1262      * During parsing, only strings consisting of exactly two digits, as
1263      * defined by {@link java.lang.Character#isDigit(char)}, will be parsed into the
1264      * default century. Any other numeric string, such as a one digit string, a
1265      * three or more digit string, or a two digit string that isn't all digits (for
1266      * example, {@code "-1"}), is interpreted literally. So using the same pattern, both
1267      * {@code "01/02/3"} and {@code "01/02/003"} are parsed as Jan 2, 3 AD.
1268      * Similarly, {@code "01/02/-3"} is parsed as Jan 2, 4 BC.
1269      *
1270      * <p>If the year pattern does not have exactly two 'y' characters, the year is
1271      * interpreted literally, regardless of the number of digits. So using the
1272      * pattern {@code MM/dd/yyyy}, {@code "01/11/12"} is parsed as Jan 11, 12 A.D.
1273      */
set2DigitYearStart(Date date)1274     public void set2DigitYearStart(Date date) {
1275         defaultCenturyStart = (Date) date.clone();
1276         Calendar cal = new GregorianCalendar();
1277         cal.setTime(defaultCenturyStart);
1278         creationYear = cal.get(Calendar.YEAR);
1279     }
1280 
1281     /**
1282      * Sets the {@code DateFormatSymbols} used by this simple date format.
1283      *
1284      * @param value
1285      *            the new {@code DateFormatSymbols} object.
1286      */
setDateFormatSymbols(DateFormatSymbols value)1287     public void setDateFormatSymbols(DateFormatSymbols value) {
1288         formatData = (DateFormatSymbols) value.clone();
1289     }
1290 
1291     /**
1292      * Returns the pattern of this simple date format using localized pattern
1293      * characters.
1294      *
1295      * @return the localized pattern.
1296      */
toLocalizedPattern()1297     public String toLocalizedPattern() {
1298         return convertPattern(pattern, PATTERN_CHARS, formatData.getLocalPatternChars(), false);
1299     }
1300 
convertPattern(String template, String fromChars, String toChars, boolean check)1301     private static String convertPattern(String template, String fromChars, String toChars, boolean check) {
1302         if (!check && fromChars.equals(toChars)) {
1303             return template;
1304         }
1305         boolean quote = false;
1306         StringBuilder output = new StringBuilder();
1307         int length = template.length();
1308         for (int i = 0; i < length; i++) {
1309             int index;
1310             char next = template.charAt(i);
1311             if (next == '\'') {
1312                 quote = !quote;
1313             }
1314             if (!quote && (index = fromChars.indexOf(next)) != -1) {
1315                 output.append(toChars.charAt(index));
1316             } else if (check && !quote && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
1317                 throw new IllegalArgumentException("Invalid pattern character '" + next + "' in " + "'" + template + "'");
1318             } else {
1319                 output.append(next);
1320             }
1321         }
1322         if (quote) {
1323             throw new IllegalArgumentException("Unterminated quote");
1324         }
1325         return output.toString();
1326     }
1327 
1328     /**
1329      * Returns the pattern of this simple date format using non-localized
1330      * pattern characters.
1331      *
1332      * @return the non-localized pattern.
1333      */
toPattern()1334     public String toPattern() {
1335         return pattern;
1336     }
1337 
1338     private static final ObjectStreamField[] serialPersistentFields = {
1339         new ObjectStreamField("defaultCenturyStart", Date.class),
1340         new ObjectStreamField("formatData", DateFormatSymbols.class),
1341         new ObjectStreamField("pattern", String.class),
1342         new ObjectStreamField("serialVersionOnStream", int.class),
1343     };
1344 
writeObject(ObjectOutputStream stream)1345     private void writeObject(ObjectOutputStream stream) throws IOException {
1346         ObjectOutputStream.PutField fields = stream.putFields();
1347         fields.put("defaultCenturyStart", defaultCenturyStart);
1348         fields.put("formatData", formatData);
1349         fields.put("pattern", pattern);
1350         fields.put("serialVersionOnStream", 1);
1351         stream.writeFields();
1352     }
1353 
readObject(ObjectInputStream stream)1354     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
1355         ObjectInputStream.GetField fields = stream.readFields();
1356         int version = fields.get("serialVersionOnStream", 0);
1357         Date date;
1358         if (version > 0) {
1359             date = (Date) fields.get("defaultCenturyStart", new Date());
1360         } else {
1361             date = new Date();
1362         }
1363         set2DigitYearStart(date);
1364         formatData = (DateFormatSymbols) fields.get("formatData", null);
1365         pattern = (String) fields.get("pattern", "");
1366     }
1367 }
1368