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