Home | History | Annotate | Download | only in text
      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      */
    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      */
    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      */
    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 
    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      */
    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      */
    354     public SimpleDateFormat(String template, Locale locale) {
    355         this(locale);
    356         validatePattern(template);
    357         pattern = template;
    358         formatData = new DateFormatSymbols(locale);
    359     }
    360 
    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      */
    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      */
    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
    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 
    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
    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
    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 
    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      */
    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.clear();
    521         }
    522 
    523         final int patternLength = pattern.length();
    524         for (int i = 0; i < patternLength; i++) {
    525             next = (pattern.charAt(i));
    526             if (next == '\'') {
    527                 if (count > 0) {
    528                     append(buffer, field, fields, (char) last, count);
    529                     count = 0;
    530                 }
    531                 if (last == next) {
    532                     buffer.append('\'');
    533                     last = -1;
    534                 } else {
    535                     last = next;
    536                 }
    537                 quote = !quote;
    538                 continue;
    539             }
    540             if (!quote
    541                     && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
    542                 if (last == next) {
    543                     count++;
    544                 } else {
    545                     if (count > 0) {
    546                         append(buffer, field, fields, (char) last, count);
    547                     }
    548                     last = next;
    549                     count = 1;
    550                 }
    551             } else {
    552                 if (count > 0) {
    553                     append(buffer, field, fields, (char) last, count);
    554                     count = 0;
    555                 }
    556                 last = -1;
    557                 buffer.append((char) next);
    558             }
    559         }
    560         if (count > 0) {
    561             append(buffer, field, fields, (char) last, count);
    562         }
    563         return buffer;
    564     }
    565 
    566     private void append(StringBuffer buffer, FieldPosition position,
    567             List<FieldPosition> fields, char format, int count) {
    568         int field = -1;
    569         int index = PATTERN_CHARS.indexOf(format);
    570         if (index == -1) {
    571             throw new IllegalArgumentException("Unknown pattern character '" + format + "'");
    572         }
    573 
    574         int beginPosition = buffer.length();
    575         Field dateFormatField = null;
    576         switch (index) {
    577             case ERA_FIELD:
    578                 dateFormatField = Field.ERA;
    579                 buffer.append(formatData.eras[calendar.get(Calendar.ERA)]);
    580                 break;
    581             case YEAR_FIELD:
    582                 dateFormatField = Field.YEAR;
    583                 int year = calendar.get(Calendar.YEAR);
    584                 /*
    585                  * For 'y' and 'yyy', we're consistent with Unicode and previous releases
    586                  * of Android. But this means we're inconsistent with the RI.
    587                  *     http://unicode.org/reports/tr35/
    588                  */
    589                 if (count == 2) {
    590                     appendNumber(buffer, 2, year % 100);
    591                 } else {
    592                     appendNumber(buffer, count, year);
    593                 }
    594                 break;
    595             case STAND_ALONE_MONTH_FIELD: // 'L'
    596                 dateFormatField = Field.MONTH;
    597                 appendMonth(buffer, count, true);
    598                 break;
    599             case MONTH_FIELD: // 'M'
    600                 dateFormatField = Field.MONTH;
    601                 appendMonth(buffer, count, false);
    602                 break;
    603             case DATE_FIELD:
    604                 dateFormatField = Field.DAY_OF_MONTH;
    605                 field = Calendar.DATE;
    606                 break;
    607             case HOUR_OF_DAY1_FIELD: // 'k'
    608                 dateFormatField = Field.HOUR_OF_DAY1;
    609                 int hour = calendar.get(Calendar.HOUR_OF_DAY);
    610                 appendNumber(buffer, count, hour == 0 ? 24 : hour);
    611                 break;
    612             case HOUR_OF_DAY0_FIELD: // 'H'
    613                 dateFormatField = Field.HOUR_OF_DAY0;
    614                 field = Calendar.HOUR_OF_DAY;
    615                 break;
    616             case MINUTE_FIELD:
    617                 dateFormatField = Field.MINUTE;
    618                 field = Calendar.MINUTE;
    619                 break;
    620             case SECOND_FIELD:
    621                 dateFormatField = Field.SECOND;
    622                 field = Calendar.SECOND;
    623                 break;
    624             case MILLISECOND_FIELD:
    625                 dateFormatField = Field.MILLISECOND;
    626                 int value = calendar.get(Calendar.MILLISECOND);
    627                 appendNumber(buffer, count, value);
    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.
    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.
    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      */
    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             if (!formatData.customZoneStrings) {
    744                 buffer.append(tz.getDisplayName(daylight, style, formatData.locale));
    745                 return;
    746             }
    747             // We can't call TimeZone.getDisplayName() because it would not use
    748             // the custom DateFormatSymbols of this SimpleDateFormat.
    749             String custom = TimeZoneNames.getDisplayName(formatData.zoneStrings, tz.getID(), daylight, style);
    750             if (custom != null) {
    751                 buffer.append(custom);
    752                 return;
    753             }
    754         }
    755         // We didn't find what we were looking for, so default to a numeric time zone.
    756         appendNumericTimeZone(buffer, count, generalTimeZone);
    757     }
    758 
    759     // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts.
    760     // @param generalTimeZone "GMT-08:00" rather than "-0800".
    761     private void appendNumericTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) {
    762         int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
    763         char sign = '+';
    764         if (offset < 0) {
    765             sign = '-';
    766             offset = -offset;
    767         }
    768         if (generalTimeZone || count == 4) {
    769             buffer.append("GMT");
    770         }
    771         buffer.append(sign);
    772         appendNumber(buffer, 2, offset / 3600000);
    773         if (generalTimeZone || count >= 4) {
    774             buffer.append(':');
    775         }
    776         appendNumber(buffer, 2, (offset % 3600000) / 60000);
    777     }
    778 
    779     private void appendNumber(StringBuffer buffer, int count, int value) {
    780         // TODO: we could avoid using the NumberFormat in most cases for a significant speedup.
    781         // The only problem is that we expose the NumberFormat to third-party code, so we'd have
    782         // some work to do to work out when the optimization is valid.
    783         int minimumIntegerDigits = numberFormat.getMinimumIntegerDigits();
    784         numberFormat.setMinimumIntegerDigits(count);
    785         numberFormat.format(Integer.valueOf(value), buffer, new FieldPosition(0));
    786         numberFormat.setMinimumIntegerDigits(minimumIntegerDigits);
    787     }
    788 
    789     private Date error(ParsePosition position, int offset, TimeZone zone) {
    790         position.setErrorIndex(offset);
    791         calendar.setTimeZone(zone);
    792         return null;
    793     }
    794 
    795     /**
    796      * Formats the specified date as a string using the pattern of this date
    797      * format and appends the string to the specified string buffer.
    798      * <p>
    799      * If the {@code field} member of {@code field} contains a value specifying
    800      * a format field, then its {@code beginIndex} and {@code endIndex} members
    801      * will be updated with the position of the first occurrence of this field
    802      * in the formatted text.
    803      *
    804      * @param date
    805      *            the date to format.
    806      * @param buffer
    807      *            the target string buffer to append the formatted date/time to.
    808      * @param fieldPos
    809      *            on input: an optional alignment field; on output: the offsets
    810      *            of the alignment field in the formatted text.
    811      * @return the string buffer.
    812      * @throws IllegalArgumentException
    813      *             if there are invalid characters in the pattern.
    814      */
    815     @Override
    816     public StringBuffer format(Date date, StringBuffer buffer, FieldPosition fieldPos) {
    817         // Harmony delegates to ICU's SimpleDateFormat, we implement it directly
    818         return formatImpl(date, buffer, fieldPos, null);
    819     }
    820 
    821     /**
    822      * Returns the date which is the start of the one hundred year period for two-digit year values.
    823      * See {@link #set2DigitYearStart} for details.
    824      */
    825     public Date get2DigitYearStart() {
    826         return (Date) defaultCenturyStart.clone();
    827     }
    828 
    829     /**
    830      * Returns the {@code DateFormatSymbols} used by this simple date format.
    831      *
    832      * @return the {@code DateFormatSymbols} object.
    833      */
    834     public DateFormatSymbols getDateFormatSymbols() {
    835         return (DateFormatSymbols) formatData.clone();
    836     }
    837 
    838     @Override
    839     public int hashCode() {
    840         return super.hashCode() + pattern.hashCode() + formatData.hashCode() + creationYear;
    841     }
    842 
    843     private int parse(String string, int offset, char format, int count) {
    844         int index = PATTERN_CHARS.indexOf(format);
    845         if (index == -1) {
    846             throw new IllegalArgumentException("Unknown pattern character '" + format + "'");
    847         }
    848         int field = -1;
    849         // TODO: what's 'absolute' for? when is 'count' negative, and why?
    850         int absolute = 0;
    851         if (count < 0) {
    852             count = -count;
    853             absolute = count;
    854         }
    855         switch (index) {
    856             case ERA_FIELD:
    857                 return parseText(string, offset, formatData.eras, Calendar.ERA);
    858             case YEAR_FIELD:
    859                 if (count >= 3) {
    860                     field = Calendar.YEAR;
    861                 } else {
    862                     ParsePosition position = new ParsePosition(offset);
    863                     Number result = parseNumber(absolute, string, position);
    864                     if (result == null) {
    865                         return -position.getErrorIndex() - 1;
    866                     }
    867                     int year = result.intValue();
    868                     // A two digit year must be exactly two digits, i.e. 01
    869                     if ((position.getIndex() - offset) == 2 && year >= 0) {
    870                         year += creationYear / 100 * 100;
    871                         if (year < creationYear) {
    872                             year += 100;
    873                         }
    874                     }
    875                     calendar.set(Calendar.YEAR, year);
    876                     return position.getIndex();
    877                 }
    878                 break;
    879             case STAND_ALONE_MONTH_FIELD: // 'L'
    880                 return parseMonth(string, offset, count, absolute, true);
    881             case MONTH_FIELD: // 'M'
    882                 return parseMonth(string, offset, count, absolute, false);
    883             case DATE_FIELD:
    884                 field = Calendar.DATE;
    885                 break;
    886             case HOUR_OF_DAY1_FIELD: // 'k'
    887                 ParsePosition position = new ParsePosition(offset);
    888                 Number result = parseNumber(absolute, string, position);
    889                 if (result == null) {
    890                     return -position.getErrorIndex() - 1;
    891                 }
    892                 int hour = result.intValue();
    893                 if (hour == 24) {
    894                     hour = 0;
    895                 }
    896                 calendar.set(Calendar.HOUR_OF_DAY, hour);
    897                 return position.getIndex();
    898             case HOUR_OF_DAY0_FIELD: // 'H'
    899                 field = Calendar.HOUR_OF_DAY;
    900                 break;
    901             case MINUTE_FIELD:
    902                 field = Calendar.MINUTE;
    903                 break;
    904             case SECOND_FIELD:
    905                 field = Calendar.SECOND;
    906                 break;
    907             case MILLISECOND_FIELD:
    908                 field = Calendar.MILLISECOND;
    909                 break;
    910             case STAND_ALONE_DAY_OF_WEEK_FIELD:
    911                 return parseDayOfWeek(string, offset, true);
    912             case DAY_OF_WEEK_FIELD:
    913                 return parseDayOfWeek(string, offset, false);
    914             case DAY_OF_YEAR_FIELD:
    915                 field = Calendar.DAY_OF_YEAR;
    916                 break;
    917             case DAY_OF_WEEK_IN_MONTH_FIELD:
    918                 field = Calendar.DAY_OF_WEEK_IN_MONTH;
    919                 break;
    920             case WEEK_OF_YEAR_FIELD:
    921                 field = Calendar.WEEK_OF_YEAR;
    922                 break;
    923             case WEEK_OF_MONTH_FIELD:
    924                 field = Calendar.WEEK_OF_MONTH;
    925                 break;
    926             case AM_PM_FIELD:
    927                 return parseText(string, offset, formatData.ampms, Calendar.AM_PM);
    928             case HOUR1_FIELD: // 'h'
    929                 position = new ParsePosition(offset);
    930                 result = parseNumber(absolute, string, position);
    931                 if (result == null) {
    932                     return -position.getErrorIndex() - 1;
    933                 }
    934                 hour = result.intValue();
    935                 if (hour == 12) {
    936                     hour = 0;
    937                 }
    938                 calendar.set(Calendar.HOUR, hour);
    939                 return position.getIndex();
    940             case HOUR0_FIELD: // 'K'
    941                 field = Calendar.HOUR;
    942                 break;
    943             case TIMEZONE_FIELD: // 'z'
    944                 return parseTimeZone(string, offset);
    945             case RFC_822_TIMEZONE_FIELD: // 'Z'
    946                 return parseTimeZone(string, offset);
    947         }
    948         if (field != -1) {
    949             return parseNumber(absolute, string, offset, field, 0);
    950         }
    951         return offset;
    952     }
    953 
    954     private int parseDayOfWeek(String string, int offset, boolean standAlone) {
    955       LocaleData ld = formatData.localeData;
    956       int index = parseText(string, offset,
    957                             standAlone ? ld.longStandAloneWeekdayNames : formatData.weekdays,
    958                             Calendar.DAY_OF_WEEK);
    959       if (index < 0) {
    960         index = parseText(string, offset,
    961                           standAlone ? ld.shortStandAloneWeekdayNames : formatData.shortWeekdays,
    962                           Calendar.DAY_OF_WEEK);
    963       }
    964       return index;
    965     }
    966 
    967     private int parseMonth(String string, int offset, int count, int absolute, boolean standAlone) {
    968       if (count <= 2) {
    969         return parseNumber(absolute, string, offset, Calendar.MONTH, -1);
    970       }
    971       LocaleData ld = formatData.localeData;
    972       int index = parseText(string, offset,
    973                             standAlone ? ld.longStandAloneMonthNames : formatData.months,
    974                             Calendar.MONTH);
    975       if (index < 0) {
    976         index = parseText(string, offset,
    977                           standAlone ? ld.shortStandAloneMonthNames : formatData.shortMonths,
    978                           Calendar.MONTH);
    979       }
    980       return index;
    981     }
    982 
    983     /**
    984      * Parses a date from the specified string starting at the index specified
    985      * by {@code position}. If the string is successfully parsed then the index
    986      * of the {@code ParsePosition} is updated to the index following the parsed
    987      * text. On error, the index is unchanged and the error index of {@code
    988      * ParsePosition} is set to the index where the error occurred.
    989      *
    990      * @param string
    991      *            the string to parse using the pattern of this simple date
    992      *            format.
    993      * @param position
    994      *            input/output parameter, specifies the start index in {@code
    995      *            string} from where to start parsing. If parsing is successful,
    996      *            it is updated with the index following the parsed text; on
    997      *            error, the index is unchanged and the error index is set to
    998      *            the index where the error occurred.
    999      * @return the date resulting from the parse, or {@code null} if there is an
   1000      *         error.
   1001      * @throws IllegalArgumentException
   1002      *             if there are invalid characters in the pattern.
   1003      */
   1004     @Override
   1005     public Date parse(String string, ParsePosition position) {
   1006         // Harmony delegates to ICU's SimpleDateFormat, we implement it directly
   1007         boolean quote = false;
   1008         int next, last = -1, count = 0, offset = position.getIndex();
   1009         int length = string.length();
   1010         calendar.clear();
   1011         TimeZone zone = calendar.getTimeZone();
   1012         final int patternLength = pattern.length();
   1013         for (int i = 0; i < patternLength; i++) {
   1014             next = pattern.charAt(i);
   1015             if (next == '\'') {
   1016                 if (count > 0) {
   1017                     if ((offset = parse(string, offset, (char) last, count)) < 0) {
   1018                         return error(position, -offset - 1, zone);
   1019                     }
   1020                     count = 0;
   1021                 }
   1022                 if (last == next) {
   1023                     if (offset >= length || string.charAt(offset) != '\'') {
   1024                         return error(position, offset, zone);
   1025                     }
   1026                     offset++;
   1027                     last = -1;
   1028                 } else {
   1029                     last = next;
   1030                 }
   1031                 quote = !quote;
   1032                 continue;
   1033             }
   1034             if (!quote
   1035                     && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
   1036                 if (last == next) {
   1037                     count++;
   1038                 } else {
   1039                     if (count > 0) {
   1040                         if ((offset = parse(string, offset, (char) last, -count)) < 0) {
   1041                             return error(position, -offset - 1, zone);
   1042                         }
   1043                     }
   1044                     last = next;
   1045                     count = 1;
   1046                 }
   1047             } else {
   1048                 if (count > 0) {
   1049                     if ((offset = parse(string, offset, (char) last, count)) < 0) {
   1050                         return error(position, -offset - 1, zone);
   1051                     }
   1052                     count = 0;
   1053                 }
   1054                 last = -1;
   1055                 if (offset >= length || string.charAt(offset) != next) {
   1056                     return error(position, offset, zone);
   1057                 }
   1058                 offset++;
   1059             }
   1060         }
   1061         if (count > 0) {
   1062             if ((offset = parse(string, offset, (char) last, count)) < 0) {
   1063                 return error(position, -offset - 1, zone);
   1064             }
   1065         }
   1066         Date date;
   1067         try {
   1068             date = calendar.getTime();
   1069         } catch (IllegalArgumentException e) {
   1070             return error(position, offset, zone);
   1071         }
   1072         position.setIndex(offset);
   1073         calendar.setTimeZone(zone);
   1074         return date;
   1075     }
   1076 
   1077     private Number parseNumber(int max, String string, ParsePosition position) {
   1078         int length = string.length();
   1079         int index = position.getIndex();
   1080         if (max > 0 && max < length - index) {
   1081             length = index + max;
   1082         }
   1083         while (index < length && (string.charAt(index) == ' ' || string.charAt(index) == '\t')) {
   1084             ++index;
   1085         }
   1086         if (max == 0) {
   1087             position.setIndex(index);
   1088             Number n = numberFormat.parse(string, position);
   1089             // In RTL locales, NumberFormat might have parsed "2012-" in an ISO date as the
   1090             // negative number -2012.
   1091             // Ideally, we wouldn't have this broken API that exposes a NumberFormat and expects
   1092             // us to use it. The next best thing would be a way to ask the NumberFormat to parse
   1093             // positive numbers only, but icu4c supports negative (BCE) years. The best we can do
   1094             // is try to recognize when icu4c has done this, and undo it.
   1095             if (n != null && n.longValue() < 0) {
   1096                 if (numberFormat instanceof DecimalFormat) {
   1097                     DecimalFormat df = (DecimalFormat) numberFormat;
   1098                     char lastChar = string.charAt(position.getIndex() - 1);
   1099                     char minusSign = df.getDecimalFormatSymbols().getMinusSign();
   1100                     if (lastChar == minusSign) {
   1101                         n = Long.valueOf(-n.longValue()); // Make the value positive.
   1102                         position.setIndex(position.getIndex() - 1); // Spit out the negative sign.
   1103                     }
   1104                 }
   1105             }
   1106             return n;
   1107         }
   1108 
   1109         int result = 0;
   1110         int digit;
   1111         while (index < length && (digit = Character.digit(string.charAt(index), 10)) != -1) {
   1112             result = result * 10 + digit;
   1113             ++index;
   1114         }
   1115         if (index == position.getIndex()) {
   1116             position.setErrorIndex(index);
   1117             return null;
   1118         }
   1119         position.setIndex(index);
   1120         return Integer.valueOf(result);
   1121     }
   1122 
   1123     private int parseNumber(int max, String string, int offset, int field, int skew) {
   1124         ParsePosition position = new ParsePosition(offset);
   1125         Number result = parseNumber(max, string, position);
   1126         if (result == null) {
   1127             return -position.getErrorIndex() - 1;
   1128         }
   1129         calendar.set(field, result.intValue() + skew);
   1130         return position.getIndex();
   1131     }
   1132 
   1133     private int parseText(String string, int offset, String[] options, int field) {
   1134         // We search for the longest match, in case some entries are substrings of others.
   1135         int bestIndex = -1;
   1136         int bestLength = -1;
   1137         for (int i = 0; i < options.length; ++i) {
   1138             String option = options[i];
   1139             int optionLength = option.length();
   1140             if (optionLength == 0) {
   1141                 continue;
   1142             }
   1143             if (string.regionMatches(true, offset, option, 0, optionLength)) {
   1144                 if (bestIndex == -1 || optionLength > bestLength) {
   1145                     bestIndex = i;
   1146                     bestLength = optionLength;
   1147                 }
   1148             } else if (option.charAt(optionLength - 1) == '.') {
   1149                 // If CLDR has abbreviated forms like "Aug.", we should accept "Aug" too.
   1150                 // https://code.google.com/p/android/issues/detail?id=59383
   1151                 if (string.regionMatches(true, offset, option, 0, optionLength - 1)) {
   1152                     if (bestIndex == -1 || optionLength - 1 > bestLength) {
   1153                         bestIndex = i;
   1154                         bestLength = optionLength - 1;
   1155                     }
   1156                 }
   1157             }
   1158         }
   1159         if (bestIndex != -1) {
   1160             calendar.set(field, bestIndex);
   1161             return offset + bestLength;
   1162         }
   1163         return -offset - 1;
   1164     }
   1165 
   1166     private int parseTimeZone(String string, int offset) {
   1167         boolean foundGMT = string.regionMatches(offset, "GMT", 0, 3);
   1168         if (foundGMT) {
   1169             offset += 3;
   1170         }
   1171         char sign;
   1172         if (offset < string.length() && ((sign = string.charAt(offset)) == '+' || sign == '-')) {
   1173             ParsePosition position = new ParsePosition(offset + 1);
   1174             Number result = numberFormat.parse(string, position);
   1175             if (result == null) {
   1176                 return -position.getErrorIndex() - 1;
   1177             }
   1178             int hour = result.intValue();
   1179             int raw = hour * 3600000;
   1180             int index = position.getIndex();
   1181             if (index < string.length() && string.charAt(index) == ':') {
   1182                 position.setIndex(index + 1);
   1183                 result = numberFormat.parse(string, position);
   1184                 if (result == null) {
   1185                     return -position.getErrorIndex() - 1;
   1186                 }
   1187                 int minute = result.intValue();
   1188                 raw += minute * 60000;
   1189             } else if (hour >= 24) {
   1190                 raw = (hour / 100 * 3600000) + (hour % 100 * 60000);
   1191             }
   1192             if (sign == '-') {
   1193                 raw = -raw;
   1194             }
   1195             calendar.setTimeZone(new SimpleTimeZone(raw, ""));
   1196             return position.getIndex();
   1197         }
   1198         if (foundGMT) {
   1199             calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
   1200             return offset;
   1201         }
   1202         for (String[] row : formatData.internalZoneStrings()) {
   1203             for (int i = TimeZoneNames.LONG_NAME; i < TimeZoneNames.NAME_COUNT; ++i) {
   1204                 if (row[i] == null) {
   1205                     // If icu4c doesn't have a name, our array contains a null. Normally we'd
   1206                     // work out the correct GMT offset, but we already handled parsing GMT offsets
   1207                     // above, so we can just ignore these cases. http://b/8128460.
   1208                     continue;
   1209                 }
   1210                 if (string.regionMatches(true, offset, row[i], 0, row[i].length())) {
   1211                     TimeZone zone = TimeZone.getTimeZone(row[TimeZoneNames.OLSON_NAME]);
   1212                     if (zone == null) {
   1213                         return -offset - 1;
   1214                     }
   1215                     int raw = zone.getRawOffset();
   1216                     if (i == TimeZoneNames.LONG_NAME_DST || i == TimeZoneNames.SHORT_NAME_DST) {
   1217                         // Not all time zones use a one-hour difference, so we need to query
   1218                         // the TimeZone. (Australia/Lord_Howe is the usual example of this.)
   1219                         int dstSavings = zone.getDSTSavings();
   1220                         // One problem with TimeZone.getDSTSavings is that it will return 0 if the
   1221                         // time zone has stopped using DST, even if we're parsing a date from
   1222                         // the past. In that case, assume the default.
   1223                         if (dstSavings == 0) {
   1224                             // TODO: we should change this to use TimeZone.getOffset(long),
   1225                             // but that requires the complete date to be parsed first.
   1226                             dstSavings = 3600000;
   1227                         }
   1228                         raw += dstSavings;
   1229                     }
   1230                     calendar.setTimeZone(new SimpleTimeZone(raw, ""));
   1231                     return offset + row[i].length();
   1232                 }
   1233             }
   1234         }
   1235         return -offset - 1;
   1236     }
   1237 
   1238     /**
   1239      * Sets the date which is the start of the one hundred year period for two-digit year values.
   1240      *
   1241      * <p>When parsing a date string using the abbreviated year pattern {@code yy}, {@code
   1242      * SimpleDateFormat} must interpret the abbreviated year relative to some
   1243      * century. It does this by adjusting dates to be within 80 years before and 20
   1244      * years after the time the {@code SimpleDateFormat} instance was created. For
   1245      * example, using a pattern of {@code MM/dd/yy}, an
   1246      * instance created on Jan 1, 1997 would interpret the string {@code "01/11/12"}
   1247      * as Jan 11, 2012 but interpret the string {@code "05/04/64"} as May 4, 1964.
   1248      * During parsing, only strings consisting of exactly two digits, as
   1249      * defined by {@link java.lang.Character#isDigit(char)}, will be parsed into the
   1250      * default century. Any other numeric string, such as a one digit string, a
   1251      * three or more digit string, or a two digit string that isn't all digits (for
   1252      * example, {@code "-1"}), is interpreted literally. So using the same pattern, both
   1253      * {@code "01/02/3"} and {@code "01/02/003"} are parsed as Jan 2, 3 AD.
   1254      * Similarly, {@code "01/02/-3"} is parsed as Jan 2, 4 BC.
   1255      *
   1256      * <p>If the year pattern does not have exactly two 'y' characters, the year is
   1257      * interpreted literally, regardless of the number of digits. So using the
   1258      * pattern {@code MM/dd/yyyy}, {@code "01/11/12"} is parsed as Jan 11, 12 A.D.
   1259      */
   1260     public void set2DigitYearStart(Date date) {
   1261         defaultCenturyStart = (Date) date.clone();
   1262         Calendar cal = new GregorianCalendar();
   1263         cal.setTime(defaultCenturyStart);
   1264         creationYear = cal.get(Calendar.YEAR);
   1265     }
   1266 
   1267     /**
   1268      * Sets the {@code DateFormatSymbols} used by this simple date format.
   1269      *
   1270      * @param value
   1271      *            the new {@code DateFormatSymbols} object.
   1272      */
   1273     public void setDateFormatSymbols(DateFormatSymbols value) {
   1274         formatData = (DateFormatSymbols) value.clone();
   1275     }
   1276 
   1277     /**
   1278      * Returns the pattern of this simple date format using localized pattern
   1279      * characters.
   1280      *
   1281      * @return the localized pattern.
   1282      */
   1283     public String toLocalizedPattern() {
   1284         return convertPattern(pattern, PATTERN_CHARS, formatData.getLocalPatternChars(), false);
   1285     }
   1286 
   1287     private static String convertPattern(String template, String fromChars, String toChars, boolean check) {
   1288         if (!check && fromChars.equals(toChars)) {
   1289             return template;
   1290         }
   1291         boolean quote = false;
   1292         StringBuilder output = new StringBuilder();
   1293         int length = template.length();
   1294         for (int i = 0; i < length; i++) {
   1295             int index;
   1296             char next = template.charAt(i);
   1297             if (next == '\'') {
   1298                 quote = !quote;
   1299             }
   1300             if (!quote && (index = fromChars.indexOf(next)) != -1) {
   1301                 output.append(toChars.charAt(index));
   1302             } else if (check && !quote && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
   1303                 throw new IllegalArgumentException("Invalid pattern character '" + next + "' in " + "'" + template + "'");
   1304             } else {
   1305                 output.append(next);
   1306             }
   1307         }
   1308         if (quote) {
   1309             throw new IllegalArgumentException("Unterminated quote");
   1310         }
   1311         return output.toString();
   1312     }
   1313 
   1314     /**
   1315      * Returns the pattern of this simple date format using non-localized
   1316      * pattern characters.
   1317      *
   1318      * @return the non-localized pattern.
   1319      */
   1320     public String toPattern() {
   1321         return pattern;
   1322     }
   1323 
   1324     private static final ObjectStreamField[] serialPersistentFields = {
   1325         new ObjectStreamField("defaultCenturyStart", Date.class),
   1326         new ObjectStreamField("formatData", DateFormatSymbols.class),
   1327         new ObjectStreamField("pattern", String.class),
   1328         new ObjectStreamField("serialVersionOnStream", int.class),
   1329     };
   1330 
   1331     private void writeObject(ObjectOutputStream stream) throws IOException {
   1332         ObjectOutputStream.PutField fields = stream.putFields();
   1333         fields.put("defaultCenturyStart", defaultCenturyStart);
   1334         fields.put("formatData", formatData);
   1335         fields.put("pattern", pattern);
   1336         fields.put("serialVersionOnStream", 1);
   1337         stream.writeFields();
   1338     }
   1339 
   1340     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
   1341         ObjectInputStream.GetField fields = stream.readFields();
   1342         int version = fields.get("serialVersionOnStream", 0);
   1343         Date date;
   1344         if (version > 0) {
   1345             date = (Date) fields.get("defaultCenturyStart", new Date());
   1346         } else {
   1347             date = new Date();
   1348         }
   1349         set2DigitYearStart(date);
   1350         formatData = (DateFormatSymbols) fields.get("formatData", null);
   1351         pattern = (String) fields.get("pattern", "");
   1352     }
   1353 }
   1354