Home | History | Annotate | Download | only in text
      1 /* GENERATED SOURCE. DO NOT MODIFY. */
      2 //  2016 and later: Unicode, Inc. and others.
      3 // License & terms of use: http://www.unicode.org/copyright.html#License
      4 /*
      5  *******************************************************************************
      6  * Copyright (C) 1996-2016, International Business Machines Corporation and
      7  * others. All Rights Reserved.
      8  *******************************************************************************
      9  */
     10 
     11 package android.icu.text;
     12 
     13 import java.io.IOException;
     14 import java.io.ObjectInputStream;
     15 import java.io.ObjectOutputStream;
     16 import java.text.AttributedCharacterIterator;
     17 import java.text.AttributedString;
     18 import java.text.FieldPosition;
     19 import java.text.Format;
     20 import java.text.ParsePosition;
     21 import java.util.ArrayList;
     22 import java.util.Date;
     23 import java.util.HashMap;
     24 import java.util.List;
     25 import java.util.Locale;
     26 import java.util.MissingResourceException;
     27 import java.util.UUID;
     28 
     29 import android.icu.impl.DateNumberFormat;
     30 import android.icu.impl.DayPeriodRules;
     31 import android.icu.impl.ICUCache;
     32 import android.icu.impl.ICUData;
     33 import android.icu.impl.ICUResourceBundle;
     34 import android.icu.impl.PatternProps;
     35 import android.icu.impl.SimpleCache;
     36 import android.icu.impl.SimpleFormatterImpl;
     37 import android.icu.lang.UCharacter;
     38 import android.icu.text.TimeZoneFormat.Style;
     39 import android.icu.text.TimeZoneFormat.TimeType;
     40 import android.icu.util.BasicTimeZone;
     41 import android.icu.util.Calendar;
     42 import android.icu.util.HebrewCalendar;
     43 import android.icu.util.Output;
     44 import android.icu.util.TimeZone;
     45 import android.icu.util.TimeZoneTransition;
     46 import android.icu.util.ULocale;
     47 import android.icu.util.ULocale.Category;
     48 import android.icu.util.UResourceBundle;
     49 
     50 
     51 
     52 /**
     53  * <strong>[icu enhancement]</strong> ICU's replacement for {@link java.text.SimpleDateFormat}.&nbsp;Methods, fields, and other functionality specific to ICU are labeled '<strong>[icu]</strong>'.
     54  *
     55  * <p><code>SimpleDateFormat</code> is a concrete class for formatting and
     56  * parsing dates in a locale-sensitive manner. It allows for formatting
     57  * (date -&gt; text), parsing (text -&gt; date), and normalization.
     58  *
     59  * <p>
     60  * <code>SimpleDateFormat</code> allows you to start by choosing
     61  * any user-defined patterns for date-time formatting. However, you
     62  * are encouraged to create a date-time formatter with either
     63  * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
     64  * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
     65  * of these class methods can return a date/time formatter initialized
     66  * with a default format pattern. You may modify the format pattern
     67  * using the <code>applyPattern</code> methods as desired.
     68  * For more information on using these methods, see
     69  * {@link DateFormat}.
     70  *
     71  * <p><strong>Date and Time Patterns:</strong></p>
     72  *
     73  * <p>Date and time formats are specified by <em>date and time pattern</em> strings.
     74  * Within date and time pattern strings, all unquoted ASCII letters [A-Za-z] are reserved
     75  * as pattern letters representing calendar fields. <code>SimpleDateFormat</code> supports
     76  * the date and time formatting algorithm and pattern letters defined by <a href="http://www.unicode.org/reports/tr35/">UTS#35
     77  * Unicode Locale Data Markup Language (LDML)</a>. The following pattern letters are
     78  * currently available (note that the actual values depend on CLDR and may change from the
     79  * examples shown here):</p>
     80  * <blockquote>
     81  * <table border="1">
     82  *     <tr>
     83  *         <th>Field</th>
     84  *         <th style="text-align: center">Sym.</th>
     85  *         <th style="text-align: center">No.</th>
     86  *         <th>Example</th>
     87  *         <th>Description</th>
     88  *     </tr>
     89  *     <tr>
     90  *         <th rowspan="3">era</th>
     91  *         <td style="text-align: center" rowspan="3">G</td>
     92  *         <td style="text-align: center">1..3</td>
     93  *         <td>AD</td>
     94  *         <td rowspan="3">Era - Replaced with the Era string for the current date. One to three letters for the
     95  *         abbreviated form, four letters for the long (wide) form, five for the narrow form.</td>
     96  *     </tr>
     97  *     <tr>
     98  *         <td style="text-align: center">4</td>
     99  *         <td>Anno Domini</td>
    100  *     </tr>
    101  *     <tr>
    102  *         <td style="text-align: center">5</td>
    103  *         <td>A</td>
    104  *     </tr>
    105  *     <tr>
    106  *         <th rowspan="6">year</th>
    107  *         <td style="text-align: center">y</td>
    108  *         <td style="text-align: center">1..n</td>
    109  *         <td>1996</td>
    110  *         <td>Year. Normally the length specifies the padding, but for two letters it also specifies the maximum
    111  *         length. Example:<div style="text-align: center">
    112  *             <center>
    113  *             <table border="1" cellpadding="2" cellspacing="0">
    114  *                 <tr>
    115  *                     <th>Year</th>
    116  *                     <th style="text-align: right">y</th>
    117  *                     <th style="text-align: right">yy</th>
    118  *                     <th style="text-align: right">yyy</th>
    119  *                     <th style="text-align: right">yyyy</th>
    120  *                     <th style="text-align: right">yyyyy</th>
    121  *                 </tr>
    122  *                 <tr>
    123  *                     <td>AD 1</td>
    124  *                     <td style="text-align: right">1</td>
    125  *                     <td style="text-align: right">01</td>
    126  *                     <td style="text-align: right">001</td>
    127  *                     <td style="text-align: right">0001</td>
    128  *                     <td style="text-align: right">00001</td>
    129  *                 </tr>
    130  *                 <tr>
    131  *                     <td>AD 12</td>
    132  *                     <td style="text-align: right">12</td>
    133  *                     <td style="text-align: right">12</td>
    134  *                     <td style="text-align: right">012</td>
    135  *                     <td style="text-align: right">0012</td>
    136  *                     <td style="text-align: right">00012</td>
    137  *                 </tr>
    138  *                 <tr>
    139  *                     <td>AD 123</td>
    140  *                     <td style="text-align: right">123</td>
    141  *                     <td style="text-align: right">23</td>
    142  *                     <td style="text-align: right">123</td>
    143  *                     <td style="text-align: right">0123</td>
    144  *                     <td style="text-align: right">00123</td>
    145  *                 </tr>
    146  *                 <tr>
    147  *                     <td>AD 1234</td>
    148  *                     <td style="text-align: right">1234</td>
    149  *                     <td style="text-align: right">34</td>
    150  *                     <td style="text-align: right">1234</td>
    151  *                     <td style="text-align: right">1234</td>
    152  *                     <td style="text-align: right">01234</td>
    153  *                 </tr>
    154  *                 <tr>
    155  *                     <td>AD 12345</td>
    156  *                     <td style="text-align: right">12345</td>
    157  *                     <td style="text-align: right">45</td>
    158  *                     <td style="text-align: right">12345</td>
    159  *                     <td style="text-align: right">12345</td>
    160  *                     <td style="text-align: right">12345</td>
    161  *                 </tr>
    162  *             </table>
    163  *             </center></div>
    164  *         </td>
    165  *     </tr>
    166  *     <tr>
    167  *         <td style="text-align: center">Y</td>
    168  *         <td style="text-align: center">1..n</td>
    169  *         <td>1997</td>
    170  *         <td>Year (in "Week of Year" based calendars). Normally the length specifies the padding,
    171  *         but for two letters it also specifies the maximum length. This year designation is used in ISO
    172  *         year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems
    173  *         where week date processing is desired. May not always be the same value as calendar year.</td>
    174  *     </tr>
    175  *     <tr>
    176  *         <td style="text-align: center">u</td>
    177  *         <td style="text-align: center">1..n</td>
    178  *         <td>4601</td>
    179  *         <td>Extended year. This is a single number designating the year of this calendar system, encompassing
    180  *         all supra-year fields. For example, for the Julian calendar system, year numbers are positive, with an
    181  *         era of BCE or CE. An extended year value for the Julian calendar system assigns positive values to CE
    182  *         years and negative values to BCE years, with 1 BCE being year 0.</td>
    183  *     </tr>
    184  *     <tr>
    185  *         <td style="text-align: center" rowspan="3">U</td>
    186  *         <td style="text-align: center">1..3</td>
    187  *         <td></td>
    188  *         <td rowspan="3">Cyclic year name. Calendars such as the Chinese lunar calendar (and related calendars)
    189  *         and the Hindu calendars use 60-year cycles of year names. Use one through three letters for the abbreviated
    190  *         name, four for the full (wide) name, or five for the narrow name (currently the data only provides abbreviated names,
    191  *         which will be used for all requested name widths). If the calendar does not provide cyclic year name data,
    192  *         or if the year value to be formatted is out of the range of years for which cyclic name data is provided,
    193  *         then numeric formatting is used (behaves like 'y').</td>
    194  *     </tr>
    195  *     <tr>
    196  *         <td style="text-align: center">4</td>
    197  *         <td>(currently also )</td>
    198  *     </tr>
    199  *     <tr>
    200  *         <td style="text-align: center">5</td>
    201  *         <td>(currently also )</td>
    202  *     </tr>
    203  *     <tr>
    204  *         <th rowspan="6">quarter</th>
    205  *         <td rowspan="3" style="text-align: center">Q</td>
    206  *         <td style="text-align: center">1..2</td>
    207  *         <td>02</td>
    208  *         <td rowspan="3">Quarter - Use one or two for the numerical quarter, three for the abbreviation, or four
    209  *         for the full (wide) name (five for the narrow name is not yet supported).</td>
    210  *     </tr>
    211  *     <tr>
    212  *         <td style="text-align: center">3</td>
    213  *         <td>Q2</td>
    214  *     </tr>
    215  *     <tr>
    216  *         <td style="text-align: center">4</td>
    217  *         <td>2nd quarter</td>
    218  *     </tr>
    219  *     <tr>
    220  *         <td rowspan="3" style="text-align: center">q</td>
    221  *         <td style="text-align: center">1..2</td>
    222  *         <td>02</td>
    223  *         <td rowspan="3"><b>Stand-Alone</b> Quarter - Use one or two for the numerical quarter, three for the abbreviation,
    224  *         or four for the full name (five for the narrow name is not yet supported).</td>
    225  *     </tr>
    226  *     <tr>
    227  *         <td style="text-align: center">3</td>
    228  *         <td>Q2</td>
    229  *     </tr>
    230  *     <tr>
    231  *         <td style="text-align: center">4</td>
    232  *         <td>2nd quarter</td>
    233  *     </tr>
    234  *     <tr>
    235  *         <th rowspan="8">month</th>
    236  *         <td rowspan="4" style="text-align: center">M</td>
    237  *         <td style="text-align: center">1..2</td>
    238  *         <td>09</td>
    239  *         <td rowspan="4">Month - Use one or two for the numerical month, three for the abbreviation, four for
    240  *         the full (wide) name, or five for the narrow name. With two ("MM"), the month number is zero-padded
    241  *         if necessary (e.g. "08").</td>
    242  *     </tr>
    243  *     <tr>
    244  *         <td style="text-align: center">3</td>
    245  *         <td>Sep</td>
    246  *     </tr>
    247  *     <tr>
    248  *         <td style="text-align: center">4</td>
    249  *         <td>September</td>
    250  *     </tr>
    251  *     <tr>
    252  *         <td style="text-align: center">5</td>
    253  *         <td>S</td>
    254  *     </tr>
    255  *     <tr>
    256  *         <td rowspan="4" style="text-align: center">L</td>
    257  *         <td style="text-align: center">1..2</td>
    258  *         <td>09</td>
    259  *         <td rowspan="4"><b>Stand-Alone</b> Month - Use one or two for the numerical month, three for the abbreviation,
    260  *         four for the full (wide) name, or 5 for the narrow name. With two ("LL"), the month number is zero-padded if
    261  *         necessary (e.g. "08").</td>
    262  *     </tr>
    263  *     <tr>
    264  *         <td style="text-align: center">3</td>
    265  *         <td>Sep</td>
    266  *     </tr>
    267  *     <tr>
    268  *         <td style="text-align: center">4</td>
    269  *         <td>September</td>
    270  *     </tr>
    271  *     <tr>
    272  *         <td style="text-align: center">5</td>
    273  *         <td>S</td>
    274  *     </tr>
    275  *     <tr>
    276  *         <th rowspan="2">week</th>
    277  *         <td style="text-align: center">w</td>
    278  *         <td style="text-align: center">1..2</td>
    279  *         <td>27</td>
    280  *         <td>Week of Year. Use "w" to show the minimum number of digits, or "ww" to always show two digits
    281  *         (zero-padding if necessary, e.g. "08").</td>
    282  *     </tr>
    283  *     <tr>
    284  *         <td style="text-align: center">W</td>
    285  *         <td style="text-align: center">1</td>
    286  *         <td>3</td>
    287  *         <td>Week of Month</td>
    288  *     </tr>
    289  *     <tr>
    290  *         <th rowspan="4">day</th>
    291  *         <td style="text-align: center">d</td>
    292  *         <td style="text-align: center">1..2</td>
    293  *         <td>1</td>
    294  *         <td>Date - Day of the month. Use "d" to show the minimum number of digits, or "dd" to always show
    295  *         two digits (zero-padding if necessary, e.g. "08").</td>
    296  *     </tr>
    297  *     <tr>
    298  *         <td style="text-align: center">D</td>
    299  *         <td style="text-align: center">1..3</td>
    300  *         <td>345</td>
    301  *         <td>Day of year</td>
    302  *     </tr>
    303  *     <tr>
    304  *         <td style="text-align: center">F</td>
    305  *         <td style="text-align: center">1</td>
    306  *         <td>2</td>
    307  *         <td>Day of Week in Month. The example is for the 2nd Wed in July</td>
    308  *     </tr>
    309  *     <tr>
    310  *         <td style="text-align: center">g</td>
    311  *         <td style="text-align: center">1..n</td>
    312  *         <td>2451334</td>
    313  *         <td>Modified Julian day. This is different from the conventional Julian day number in two regards.
    314  *         First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number;
    315  *         that is, it depends on the local time zone. It can be thought of as a single number that encompasses
    316  *         all the date-related fields.</td>
    317  *     </tr>
    318  *     <tr>
    319  *         <th rowspan="14">week<br>
    320  *         day</th>
    321  *         <td rowspan="4" style="text-align: center">E</td>
    322  *         <td style="text-align: center">1..3</td>
    323  *         <td>Tue</td>
    324  *         <td rowspan="4">Day of week - Use one through three letters for the short day, four for the full (wide) name,
    325  *         five for the narrow name, or six for the short name.</td>
    326  *     </tr>
    327  *     <tr>
    328  *         <td style="text-align: center">4</td>
    329  *         <td>Tuesday</td>
    330  *     </tr>
    331  *     <tr>
    332  *         <td style="text-align: center">5</td>
    333  *         <td>T</td>
    334  *     </tr>
    335  *     <tr>
    336  *         <td style="text-align: center">6</td>
    337  *         <td>Tu</td>
    338  *     </tr>
    339  *     <tr>
    340  *         <td rowspan="5" style="text-align: center">e</td>
    341  *         <td style="text-align: center">1..2</td>
    342  *         <td>2</td>
    343  *         <td rowspan="5">Local day of week. Same as E except adds a numeric value that will depend on the local
    344  *         starting day of the week, using one or two letters. For this example, Monday is the first day of the week.</td>
    345  *     </tr>
    346  *     <tr>
    347  *         <td style="text-align: center">3</td>
    348  *         <td>Tue</td>
    349  *     </tr>
    350  *     <tr>
    351  *         <td style="text-align: center">4</td>
    352  *         <td>Tuesday</td>
    353  *     </tr>
    354  *     <tr>
    355  *         <td style="text-align: center">5</td>
    356  *         <td>T</td>
    357  *     </tr>
    358  *     <tr>
    359  *         <td style="text-align: center">6</td>
    360  *         <td>Tu</td>
    361  *     </tr>
    362  *     <tr>
    363  *         <td rowspan="5" style="text-align: center">c</td>
    364  *         <td style="text-align: center">1</td>
    365  *         <td>2</td>
    366  *         <td rowspan="5"><b>Stand-Alone</b> local day of week - Use one letter for the local numeric value (same
    367  *         as 'e'), three for the short day, four for the full (wide) name, five for the narrow name, or six for
    368  *         the short name.</td>
    369  *     </tr>
    370  *     <tr>
    371  *         <td style="text-align: center">3</td>
    372  *         <td>Tue</td>
    373  *     </tr>
    374  *     <tr>
    375  *         <td style="text-align: center">4</td>
    376  *         <td>Tuesday</td>
    377  *     </tr>
    378  *     <tr>
    379  *         <td style="text-align: center">5</td>
    380  *         <td>T</td>
    381  *     </tr>
    382  *     <tr>
    383  *         <td style="text-align: center">6</td>
    384  *         <td>Tu</td>
    385  *     </tr>
    386  *     <tr>
    387  *         <th>period</th>
    388  *         <td style="text-align: center">a</td>
    389  *         <td style="text-align: center">1</td>
    390  *         <td>AM</td>
    391  *         <td>AM or PM</td>
    392  *     </tr>
    393  *     <tr>
    394  *         <th rowspan="4">hour</th>
    395  *         <td style="text-align: center">h</td>
    396  *         <td style="text-align: center">1..2</td>
    397  *         <td>11</td>
    398  *         <td>Hour [1-12]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern
    399  *         generation, it should match the 12-hour-cycle format preferred by the locale (h or K); it should not match
    400  *         a 24-hour-cycle format (H or k). Use hh for zero padding.</td>
    401  *     </tr>
    402  *     <tr>
    403  *         <td style="text-align: center">H</td>
    404  *         <td style="text-align: center">1..2</td>
    405  *         <td>13</td>
    406  *         <td>Hour [0-23]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern
    407  *         generation, it should match the 24-hour-cycle format preferred by the locale (H or k); it should not match a
    408  *         12-hour-cycle format (h or K). Use HH for zero padding.</td>
    409  *     </tr>
    410  *     <tr>
    411  *         <td style="text-align: center">K</td>
    412  *         <td style="text-align: center">1..2</td>
    413  *         <td>0</td>
    414  *         <td>Hour [0-11]. When used in a skeleton, only matches K or h, see above. Use KK for zero padding.</td>
    415  *     </tr>
    416  *     <tr>
    417  *         <td style="text-align: center">k</td>
    418  *         <td style="text-align: center">1..2</td>
    419  *         <td>24</td>
    420  *         <td>Hour [1-24]. When used in a skeleton, only matches k or H, see above. Use kk for zero padding.</td>
    421  *     </tr>
    422  *     <tr>
    423  *         <th>minute</th>
    424  *         <td style="text-align: center">m</td>
    425  *         <td style="text-align: center">1..2</td>
    426  *         <td>59</td>
    427  *         <td>Minute. Use "m" to show the minimum number of digits, or "mm" to always show two digits
    428  *         (zero-padding if necessary, e.g. "08")..</td>
    429  *     </tr>
    430  *     <tr>
    431  *         <th rowspan="3">second</th>
    432  *         <td style="text-align: center">s</td>
    433  *         <td style="text-align: center">1..2</td>
    434  *         <td>12</td>
    435  *         <td>Second. Use "s" to show the minimum number of digits, or "ss" to always show two digits
    436  *         (zero-padding if necessary, e.g. "08").</td>
    437  *     </tr>
    438  *     <tr>
    439  *         <td style="text-align: center">S</td>
    440  *         <td style="text-align: center">1..n</td>
    441  *         <td>3450</td>
    442  *         <td>Fractional Second - truncates (like other time fields) to the count of letters when formatting. Appends zeros if more than 3 letters specified. Truncates at three significant digits when parsing.
    443  *         (example shows display using pattern SSSS for seconds value 12.34567)</td>
    444  *     </tr>
    445  *     <tr>
    446  *         <td style="text-align: center">A</td>
    447  *         <td style="text-align: center">1..n</td>
    448  *         <td>69540000</td>
    449  *         <td>Milliseconds in day. This field behaves <i>exactly</i> like a composite of all time-related fields,
    450  *         not including the zone fields. As such, it also reflects discontinuities of those fields on DST transition
    451  *         days. On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. This
    452  *         reflects the fact that is must be combined with the offset field to obtain a unique local time value.</td>
    453  *     </tr>
    454  *     <tr>
    455  *         <th rowspan="23">zone</th>
    456  *         <td rowspan="2" style="text-align: center">z</td>
    457  *         <td style="text-align: center">1..3</td>
    458  *         <td>PDT</td>
    459  *         <td>The <i>short specific non-location format</i>.
    460  *         Where that is unavailable, falls back to the <i>short localized GMT format</i> ("O").</td>
    461  *     </tr>
    462  *     <tr>
    463  *         <td style="text-align: center">4</td>
    464  *         <td>Pacific Daylight Time</td>
    465  *         <td>The <i>long specific non-location format</i>.
    466  *         Where that is unavailable, falls back to the <i>long localized GMT format</i> ("OOOO").</td>
    467  *     </tr>
    468  *     <tr>
    469  *         <td rowspan="3" style="text-align: center">Z</td>
    470  *         <td style="text-align: center">1..3</td>
    471  *         <td>-0800</td>
    472  *         <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
    473  *         The format is equivalent to RFC 822 zone format (when optional seconds field is absent).
    474  *         This is equivalent to the "xxxx" specifier.</td>
    475  *     </tr>
    476  *     <tr>
    477  *         <td style="text-align: center">4</td>
    478  *         <td>GMT-8:00</td>
    479  *         <td>The <i>long localized GMT format</i>.
    480  *         This is equivalent to the "OOOO" specifier.</td>
    481  *     </tr>
    482  *     <tr>
    483  *         <td style="text-align: center">5</td>
    484  *         <td>-08:00<br>
    485  *         -07:52:58</td>
    486  *         <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
    487  *         The ISO8601 UTC indicator "Z" is used when local time offset is 0.
    488  *         This is equivalent to the "XXXXX" specifier.</td>
    489  *     </tr>
    490  *     <tr>
    491  *         <td rowspan="2" style="text-align: center">O</td>
    492  *         <td style="text-align: center">1</td>
    493  *         <td>GMT-8</td>
    494  *         <td>The <i>short localized GMT format</i>.</td>
    495  *     </tr>
    496  *     <tr>
    497  *         <td style="text-align: center">4</td>
    498  *         <td>GMT-08:00</td>
    499  *         <td>The <i>long localized GMT format</i>.</td>
    500  *     </tr>
    501  *     <tr>
    502  *         <td rowspan="2" style="text-align: center">v</td>
    503  *         <td style="text-align: center">1</td>
    504  *         <td>PT</td>
    505  *         <td>The <i>short generic non-location format</i>.
    506  *         Where that is unavailable, falls back to the <i>generic location format</i> ("VVVV"),
    507  *         then the <i>short localized GMT format</i> as the final fallback.</td>
    508  *     </tr>
    509  *     <tr>
    510  *         <td style="text-align: center">4</td>
    511  *         <td>Pacific Time</td>
    512  *         <td>The <i>long generic non-location format</i>.
    513  *         Where that is unavailable, falls back to <i>generic location format</i> ("VVVV").
    514  *     </tr>
    515  *     <tr>
    516  *         <td rowspan="4" style="text-align: center">V</td>
    517  *         <td style="text-align: center">1</td>
    518  *         <td>uslax</td>
    519  *         <td>The short time zone ID.
    520  *         Where that is unavailable, the special short time zone ID <i>unk</i> (Unknown Zone) is used.<br>
    521  *         <i><b>Note</b>: This specifier was originally used for a variant of the short specific non-location format,
    522  *         but it was deprecated in the later version of the LDML specification. In CLDR 23/ICU 51, the definition of
    523  *         the specifier was changed to designate a short time zone ID.</i></td>
    524  *     </tr>
    525  *     <tr>
    526  *         <td style="text-align: center">2</td>
    527  *         <td>America/Los_Angeles</td>
    528  *         <td>The long time zone ID.</td>
    529  *     </tr>
    530  *     <tr>
    531  *         <td style="text-align: center">3</td>
    532  *         <td>Los Angeles</td>
    533  *         <td>The exemplar city (location) for the time zone.
    534  *         Where that is unavailable, the localized exemplar city name for the special zone <i>Etc/Unknown</i> is used
    535  *         as the fallback (for example, "Unknown City"). </td>
    536  *     </tr>
    537  *     <tr>
    538  *         <td style="text-align: center">4</td>
    539  *         <td>Los Angeles Time</td>
    540  *         <td>The <i>generic location format</i>.
    541  *         Where that is unavailable, falls back to the <i>long localized GMT format</i> ("OOOO";
    542  *         Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.)<br>
    543  *         This is especially useful when presenting possible timezone choices for user selection,
    544  *         since the naming is more uniform than the "v" format.</td>
    545  *     </tr>
    546  *     <tr>
    547  *         <td rowspan="5" style="text-align: center">X</td>
    548  *         <td style="text-align: center">1</td>
    549  *         <td>-08<br>
    550  *         +0530<br>
    551  *         Z</td>
    552  *         <td>The <i>ISO8601 basic format</i> with hours field and optional minutes field.
    553  *         The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
    554  *     </tr>
    555  *     <tr>
    556  *         <td style="text-align: center">2</td>
    557  *         <td>-0800<br>
    558  *         Z</td>
    559  *         <td>The <i>ISO8601 basic format</i> with hours and minutes fields.
    560  *         The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
    561  *     </tr>
    562  *     <tr>
    563  *         <td style="text-align: center">3</td>
    564  *         <td>-08:00<br>
    565  *         Z</td>
    566  *         <td>The <i>ISO8601 extended format</i> with hours and minutes fields.
    567  *         The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
    568  *     </tr>
    569  *     <tr>
    570  *         <td style="text-align: center">4</td>
    571  *         <td>-0800<br>
    572  *         -075258<br>
    573  *         Z</td>
    574  *         <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
    575  *         (Note: The seconds field is not supported by the ISO8601 specification.)
    576  *         The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
    577  *     </tr>
    578  *     <tr>
    579  *         <td style="text-align: center">5</td>
    580  *         <td>-08:00<br>
    581  *         -07:52:58<br>
    582  *         Z</td>
    583  *         <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
    584  *         (Note: The seconds field is not supported by the ISO8601 specification.)
    585  *         The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
    586  *     </tr>
    587  *     <tr>
    588  *         <td rowspan="5" style="text-align: center">x</td>
    589  *         <td style="text-align: center">1</td>
    590  *         <td>-08<br>
    591  *         +0530</td>
    592  *         <td>The <i>ISO8601 basic format</i> with hours field and optional minutes field.</td>
    593  *     </tr>
    594  *     <tr>
    595  *         <td style="text-align: center">2</td>
    596  *         <td>-0800</td>
    597  *         <td>The <i>ISO8601 basic format</i> with hours and minutes fields.</td>
    598  *     </tr>
    599  *     <tr>
    600  *         <td style="text-align: center">3</td>
    601  *         <td>-08:00</td>
    602  *         <td>The <i>ISO8601 extended format</i> with hours and minutes fields.</td>
    603  *     </tr>
    604  *     <tr>
    605  *         <td style="text-align: center">4</td>
    606  *         <td>-0800<br>
    607  *         -075258</td>
    608  *         <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
    609  *         (Note: The seconds field is not supported by the ISO8601 specification.)</td>
    610  *     </tr>
    611  *     <tr>
    612  *         <td style="text-align: center">5</td>
    613  *         <td>-08:00<br>
    614  *         -07:52:58</td>
    615  *         <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
    616  *         (Note: The seconds field is not supported by the ISO8601 specification.)</td>
    617  *     </tr>
    618  * </table>
    619  *
    620  * </blockquote>
    621  * <p>
    622  * Any characters in the pattern that are not in the ranges of ['a'..'z']
    623  * and ['A'..'Z'] will be treated as quoted text. For instance, characters
    624  * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
    625  * even they are not embraced within single quotes.
    626  * <p>
    627  * A pattern containing any invalid pattern letter will result in a thrown
    628  * exception during formatting or parsing.
    629  *
    630  * <p>
    631  * <strong>Examples Using the US Locale:</strong>
    632  * <blockquote>
    633  * <pre>
    634  * Format Pattern                         Result
    635  * --------------                         -------
    636  * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" -&gt;&gt;  1996.07.10 AD at 15:08:56 Pacific Time
    637  * "EEE, MMM d, ''yy"                -&gt;&gt;  Wed, July 10, '96
    638  * "h:mm a"                          -&gt;&gt;  12:08 PM
    639  * "hh 'o''clock' a, zzzz"           -&gt;&gt;  12 o'clock PM, Pacific Daylight Time
    640  * "K:mm a, vvv"                     -&gt;&gt;  0:00 PM, PT
    641  * "yyyyy.MMMMM.dd GGG hh:mm aaa"    -&gt;&gt;  01996.July.10 AD 12:08 PM
    642  * </pre>
    643  * </blockquote>
    644  * <strong>Code Sample:</strong>
    645  * <blockquote>
    646  * <pre>
    647  * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
    648  * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
    649  * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
    650  * <br>
    651  * // Format the current time.
    652  * SimpleDateFormat formatter
    653  *     = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
    654  * Date currentTime_1 = new Date();
    655  * String dateString = formatter.format(currentTime_1);
    656  * <br>
    657  * // Parse the previous string back into a Date.
    658  * ParsePosition pos = new ParsePosition(0);
    659  * Date currentTime_2 = formatter.parse(dateString, pos);
    660  * </pre>
    661  * </blockquote>
    662  * In the example, the time value <code>currentTime_2</code> obtained from
    663  * parsing will be equal to <code>currentTime_1</code>. However, they may not be
    664  * equal if the am/pm marker 'a' is left out from the format pattern while
    665  * the "hour in am/pm" pattern symbol is used. This information loss can
    666  * happen when formatting the time in PM.
    667  *
    668  * <p>When parsing a date string using the abbreviated year pattern ("yy"),
    669  * SimpleDateFormat must interpret the abbreviated year
    670  * relative to some century.  It does this by adjusting dates to be
    671  * within 80 years before and 20 years after the time the SimpleDateFormat
    672  * instance is created. For example, using a pattern of "MM/dd/yy" and a
    673  * SimpleDateFormat instance created on Jan 1, 1997,  the string
    674  * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
    675  * would be interpreted as May 4, 1964.
    676  * During parsing, only strings consisting of exactly two digits, as defined by
    677  * {@link android.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default
    678  * century.
    679  * Any other numeric string, such as a one digit string, a three or more digit
    680  * string, or a two digit string that isn't all digits (for example, "-1"), is
    681  * interpreted literally.  So "01/02/3" or "01/02/003" are parsed, using the
    682  * same pattern, as Jan 2, 3 AD.  Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
    683  *
    684  * <p>If the year pattern does not have exactly two 'y' characters, the year is
    685  * interpreted literally, regardless of the number of digits.  So using the
    686  * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
    687  *
    688  * <p>When numeric fields abut one another directly, with no intervening delimiter
    689  * characters, they constitute a run of abutting numeric fields.  Such runs are
    690  * parsed specially.  For example, the format "HHmmss" parses the input text
    691  * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
    692  * parse "1234".  In other words, the leftmost field of the run is flexible,
    693  * while the others keep a fixed width.  If the parse fails anywhere in the run,
    694  * then the leftmost field is shortened by one character, and the entire run is
    695  * parsed again. This is repeated until either the parse succeeds or the
    696  * leftmost field is one character in length.  If the parse still fails at that
    697  * point, the parse of the run fails.
    698  *
    699  * <p>For time zones that have no names, use strings GMT+hours:minutes or
    700  * GMT-hours:minutes.
    701  *
    702  * <p>The calendar defines what is the first day of the week, the first week
    703  * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
    704  * time zone. There is one common decimal format to handle all the numbers;
    705  * the digit count is handled programmatically according to the pattern.
    706  *
    707  * <h3>Synchronization</h3>
    708  *
    709  * Date formats are not synchronized. It is recommended to create separate
    710  * format instances for each thread. If multiple threads access a format
    711  * concurrently, it must be synchronized externally.
    712  *
    713  * @see          android.icu.util.Calendar
    714  * @see          android.icu.util.GregorianCalendar
    715  * @see          android.icu.util.TimeZone
    716  * @see          DateFormat
    717  * @see          DateFormatSymbols
    718  * @see          DecimalFormat
    719  * @see          TimeZoneFormat
    720  * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
    721  */
    722 public class SimpleDateFormat extends DateFormat {
    723 
    724     // the official serial version ID which says cryptically
    725     // which version we're compatible with
    726     private static final long serialVersionUID = 4774881970558875024L;
    727 
    728     // the internal serial version which says which version was written
    729     // - 0 (default) for version up to JDK 1.1.3
    730     // - 1 for version from JDK 1.1.4, which includes a new field
    731     // - 2 we write additional int for capitalizationSetting
    732     static final int currentSerialVersion = 2;
    733 
    734     static boolean DelayedHebrewMonthCheck = false;
    735 
    736     /*
    737      * From calendar field to its level.
    738      * Used to order calendar field.
    739      * For example, calendar fields can be defined in the following order:
    740      * year >  month > date > am-pm > hour >  minute
    741      * YEAR --> 10, MONTH -->20, DATE --> 30;
    742      * AM_PM -->40, HOUR --> 50, MINUTE -->60
    743      */
    744     private static final int[] CALENDAR_FIELD_TO_LEVEL =
    745     {
    746         /*GyM*/ 0, 10, 20,
    747         /*wW*/ 20, 30,
    748         /*dDEF*/ 30, 20, 30, 30,
    749         /*ahHm*/ 40, 50, 50, 60,
    750         /*sS*/ 70, 80,
    751         /*z?Y*/ 0, 0, 10,
    752         /*eug*/ 30, 10, 0,
    753         /*A?*/ 40, 0, 0
    754     };
    755 
    756     /*
    757      * From calendar field letter to its level.
    758      * Used to order calendar field.
    759      * For example, calendar fields can be defined in the following order:
    760      * year >  month > date > am-pm > hour >  minute
    761      * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60
    762      */
    763     private static final int[] PATTERN_CHAR_TO_LEVEL =
    764     {
    765         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    766     //
    767         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    768     //       !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /
    769         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    770     //   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
    771         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    772     //   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
    773         -1, 40, -1, -1, 20, 30, 30,  0, 50, -1, -1, 50, 20, 20, -1,  0,
    774     //   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
    775         -1, 20, -1, 80, -1, 10,  0, 30,  0, 10,  0, -1, -1, -1, -1, -1,
    776     //   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
    777         -1, 40, -1, 30, 30, 30, -1,  0, 50, -1, -1, 50, -1, 60, -1, -1,
    778     //   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~
    779         -1, 20, 10, 70, -1, 10,  0, 20,  0, 10,  0, -1, -1, -1, -1, -1,
    780     };
    781 
    782     /**
    783      * Map calendar field letter into calendar field level.
    784      */
    785     private static int getLevelFromChar(char ch) {
    786         return ch < PATTERN_CHAR_TO_LEVEL.length ? PATTERN_CHAR_TO_LEVEL[ch & 0xff] : -1;
    787     }
    788 
    789     private static final boolean[] PATTERN_CHAR_IS_SYNTAX =
    790     {
    791         //
    792         false, false, false, false, false, false, false, false,
    793         //
    794         false, false, false, false, false, false, false, false,
    795         //
    796         false, false, false, false, false, false, false, false,
    797         //
    798         false, false, false, false, false, false, false, false,
    799         //         !      "      #      $      %      &      '
    800         false, false, false, false, false, false, false, false,
    801         //  (      )      *      +      ,      -      .      /
    802         false, false, false, false, false, false, false, false,
    803         //  0      1      2      3      4      5      6      7
    804         false, false, false, false, false, false, false, false,
    805         //  8      9      :      ;      <      =      >      ?
    806         false, false, false, false, false, false, false, false,
    807         //  @      A      B      C      D      E      F      G
    808         false,  true,  true,  true,  true,  true,  true,  true,
    809         //  H      I      J      K      L      M      N      O
    810          true,  true,  true,  true,  true,  true,  true,  true,
    811         //  P      Q      R      S      T      U      V      W
    812          true,  true,  true,  true,  true,  true,  true,  true,
    813         //  X      Y      Z      [      \      ]      ^      _
    814          true,  true,  true, false, false, false, false, false,
    815         //  `      a      b      c      d      e      f      g
    816         false,  true,  true,  true,  true,  true,  true,  true,
    817         //  h      i      j      k      l      m      n      o
    818          true,  true,  true,  true,  true,  true,  true,  true,
    819         //  p      q      r      s      t      u      v      w
    820          true,  true,  true,  true,  true,  true,  true,  true,
    821         //  x      y      z      {      |      }      ~
    822          true,  true,  true, false, false, false, false, false,
    823     };
    824 
    825     /**
    826      * Tell if a character can be used to define a field in a format string.
    827      */
    828     private static boolean isSyntaxChar(char ch) {
    829         return ch < PATTERN_CHAR_IS_SYNTAX.length ? PATTERN_CHAR_IS_SYNTAX[ch & 0xff] : false;
    830     }
    831 
    832     // When calendar uses hebr numbering (i.e. he@calendar=hebrew),
    833     // offset the years within the current millenium down to 1-999
    834     private static final int HEBREW_CAL_CUR_MILLENIUM_START_YEAR = 5000;
    835     private static final int HEBREW_CAL_CUR_MILLENIUM_END_YEAR = 6000;
    836 
    837     /**
    838      * The version of the serialized data on the stream.  Possible values:
    839      * <ul>
    840      * <li><b>0</b> or not present on stream: JDK 1.1.3.  This version
    841      * has no <code>defaultCenturyStart</code> on stream.
    842      * <li><b>1</b> JDK 1.1.4 or later.  This version adds
    843      * <code>defaultCenturyStart</code>.
    844      * <li><b>2</b> This version writes an additional int for
    845      * <code>capitalizationSetting</code>.
    846      * </ul>
    847      * When streaming out this class, the most recent format
    848      * and the highest allowable <code>serialVersionOnStream</code>
    849      * is written.
    850      * @serial
    851      */
    852     private int serialVersionOnStream = currentSerialVersion;
    853 
    854     /**
    855      * The pattern string of this formatter.  This is always a non-localized
    856      * pattern.  May not be null.  See class documentation for details.
    857      * @serial
    858      */
    859     private String pattern;
    860 
    861     /**
    862      * The override string of this formatter.  Used to override the
    863      * numbering system for one or more fields.
    864      * @serial
    865      */
    866     private String override;
    867 
    868     /**
    869      * The hash map used for number format overrides.
    870      * @serial
    871      */
    872     private HashMap<String, NumberFormat> numberFormatters;
    873 
    874     /**
    875      * The hash map used for number format overrides.
    876      * @serial
    877      */
    878     private HashMap<Character, String> overrideMap;
    879 
    880     /**
    881      * The symbols used by this formatter for week names, month names,
    882      * etc.  May not be null.
    883      * @serial
    884      * @see DateFormatSymbols
    885      */
    886     private DateFormatSymbols formatData;
    887 
    888     private transient ULocale locale;
    889 
    890     /**
    891      * We map dates with two-digit years into the century starting at
    892      * <code>defaultCenturyStart</code>, which may be any date.  May
    893      * not be null.
    894      * @serial
    895      */
    896     private Date defaultCenturyStart;
    897 
    898     private transient int defaultCenturyStartYear;
    899 
    900     // defaultCenturyBase is set when an instance is created
    901     // and may be used for calculating defaultCenturyStart when needed.
    902     private transient long defaultCenturyBase;
    903 
    904     private static final int millisPerHour = 60 * 60 * 1000;
    905 
    906     // When possessing ISO format, the ERA may be ommitted is the
    907     // year specifier is a negative number.
    908     private static final int ISOSpecialEra = -32000;
    909 
    910     // This prefix is designed to NEVER MATCH real text, in order to
    911     // suppress the parsing of negative numbers.  Adjust as needed (if
    912     // this becomes valid Unicode).
    913     private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
    914 
    915     /**
    916      * If true, this object supports fast formatting using the
    917      * subFormat variant that takes a StringBuffer.
    918      */
    919     private transient boolean useFastFormat;
    920 
    921     /*
    922      *  The time zone sub-formatter, introduced in ICU 4.8
    923      */
    924     private volatile TimeZoneFormat tzFormat;
    925 
    926     /**
    927      * BreakIterator to use for capitalization
    928      */
    929     private transient BreakIterator capitalizationBrkIter = null;
    930 
    931     /**
    932      * DateFormat pattern contains the minute field.
    933      */
    934     private transient boolean hasMinute;
    935 
    936     /**
    937      * DateFormat pattern contains the second field.
    938      */
    939     private transient boolean hasSecond;
    940 
    941     /*
    942      *  Capitalization setting, introduced in ICU 50
    943      *  Special serialization, see writeObject & readObject below
    944      *
    945      *  Hoisted to DateFormat in ICU 53, get value with
    946      *  getContext(DisplayContext.Type.CAPITALIZATION)
    947      */
    948     // private transient DisplayContext capitalizationSetting;
    949 
    950     /*
    951      *  Old defaultCapitalizationContext field
    952      *  from ICU 49.1:
    953      */
    954     //private ContextValue defaultCapitalizationContext;
    955     /**
    956      *  Old ContextValue enum, preserved only to avoid
    957      *  deserialization errs from ICU 49.1.
    958      */
    959     @SuppressWarnings("unused")
    960     private enum ContextValue {
    961         UNKNOWN,
    962         CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,
    963         CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE,
    964         CAPITALIZATION_FOR_UI_LIST_OR_MENU,
    965         CAPITALIZATION_FOR_STANDALONE
    966     }
    967 
    968     /**
    969      * Constructs a SimpleDateFormat using the default pattern for the default <code>FORMAT</code>
    970      * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
    971      * generality, use the factory methods in the DateFormat class.
    972      *
    973      * @see DateFormat
    974      * @see Category#FORMAT
    975      */
    976     public SimpleDateFormat() {
    977         this(getDefaultPattern(), null, null, null, null, true, null);
    978     }
    979 
    980     /**
    981      * Constructs a SimpleDateFormat using the given pattern in the default <code>FORMAT</code>
    982      * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
    983      * generality, use the factory methods in the DateFormat class.
    984      * @see Category#FORMAT
    985      */
    986     public SimpleDateFormat(String pattern)
    987     {
    988         this(pattern, null, null, null, null, true, null);
    989     }
    990 
    991     /**
    992      * Constructs a SimpleDateFormat using the given pattern and locale.
    993      * <b>Note:</b> Not all locales support SimpleDateFormat; for full
    994      * generality, use the factory methods in the DateFormat class.
    995      */
    996     public SimpleDateFormat(String pattern, Locale loc)
    997     {
    998         this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
    999     }
   1000 
   1001     /**
   1002      * Constructs a SimpleDateFormat using the given pattern and locale.
   1003      * <b>Note:</b> Not all locales support SimpleDateFormat; for full
   1004      * generality, use the factory methods in the DateFormat class.
   1005      */
   1006     public SimpleDateFormat(String pattern, ULocale loc)
   1007     {
   1008         this(pattern, null, null, null, loc, true, null);
   1009     }
   1010 
   1011     /**
   1012      * Constructs a SimpleDateFormat using the given pattern , override and locale.
   1013      * @param pattern The pattern to be used
   1014      * @param override The override string.  A numbering system override string can take one of the following forms:
   1015      *     1). If just a numbering system name is specified, it applies to all numeric fields in the date format pattern.
   1016      *     2). To specify an alternate numbering system on a field by field basis, use the field letters from the pattern
   1017      *         followed by an = sign, followed by the numbering system name.  For example, to specify that just the year
   1018      *         be formatted using Hebrew digits, use the override "y=hebr".  Multiple overrides can be specified in a single
   1019      *         string by separating them with a semi-colon. For example, the override string "m=thai;y=deva" would format using
   1020      *         Thai digits for the month and Devanagari digits for the year.
   1021      * @param loc The locale to be used
   1022      */
   1023     public SimpleDateFormat(String pattern, String override, ULocale loc)
   1024     {
   1025         this(pattern, null, null, null, loc, false,override);
   1026     }
   1027 
   1028     /**
   1029      * Constructs a SimpleDateFormat using the given pattern and
   1030      * locale-specific symbol data.
   1031      * Warning: uses default <code>FORMAT</code> locale for digits!
   1032      */
   1033     public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
   1034     {
   1035         this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
   1036     }
   1037 
   1038     /**
   1039      * @deprecated This API is ICU internal only.
   1040      * @hide original deprecated declaration
   1041      * @hide draft / provisional / internal are hidden on Android
   1042      */
   1043     @Deprecated
   1044     public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
   1045     {
   1046         this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
   1047     }
   1048 
   1049     /**
   1050      * Package-private constructor that allows a subclass to specify
   1051      * whether it supports fast formatting.
   1052      *
   1053      * TODO make this API public.
   1054      */
   1055     SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
   1056                      boolean useFastFormat, String override) {
   1057         this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);
   1058     }
   1059 
   1060     /*
   1061      * The constructor called from all other SimpleDateFormat constructors
   1062      */
   1063     private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,
   1064             NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {
   1065         this.pattern = pattern;
   1066         this.formatData = formatData;
   1067         this.calendar = calendar;
   1068         this.numberFormat = numberFormat;
   1069         this.locale = locale; // time zone formatting
   1070         this.useFastFormat = useFastFormat;
   1071         this.override = override;
   1072         initialize();
   1073     }
   1074 
   1075     /**
   1076      * Creates an instance of SimpleDateFormat for the given format configuration
   1077      * @param formatConfig the format configuration
   1078      * @return A SimpleDateFormat instance
   1079      * @deprecated This API is ICU internal only.
   1080      * @hide original deprecated declaration
   1081      * @hide draft / provisional / internal are hidden on Android
   1082      */
   1083     @Deprecated
   1084     public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
   1085 
   1086         String ostr = formatConfig.getOverrideString();
   1087         boolean useFast = ( ostr != null && ostr.length() > 0 );
   1088 
   1089         return new SimpleDateFormat(formatConfig.getPatternString(),
   1090                     formatConfig.getDateFormatSymbols(),
   1091                     formatConfig.getCalendar(),
   1092                     null,
   1093                     formatConfig.getLocale(),
   1094                     useFast,
   1095                     formatConfig.getOverrideString());
   1096     }
   1097 
   1098     /*
   1099      * Initialized fields
   1100      */
   1101     private void initialize() {
   1102         if (locale == null) {
   1103             locale = ULocale.getDefault(Category.FORMAT);
   1104         }
   1105         if (formatData == null) {
   1106             formatData = new DateFormatSymbols(locale);
   1107         }
   1108         if (calendar == null) {
   1109             calendar = Calendar.getInstance(locale);
   1110         }
   1111         if (numberFormat == null) {
   1112             NumberingSystem ns = NumberingSystem.getInstance(locale);
   1113             String digitString = ns.getDescription();
   1114             // DateNumberFormat does not support non-BMP digits at this moment.
   1115             if (ns.isAlgorithmic() || digitString.length() != 10) {
   1116                 numberFormat = NumberFormat.getInstance(locale);
   1117             } else {
   1118                 String nsName = ns.getName();
   1119                 // Use a NumberFormat optimized for date formatting
   1120                 numberFormat = new DateNumberFormat(locale, digitString, nsName);
   1121             }
   1122         }
   1123         if (numberFormat instanceof DecimalFormat) {
   1124             fixNumberFormatForDates(numberFormat);
   1125         }
   1126 
   1127         // Note: deferring calendar calculation until when we really need it.
   1128         // Instead, we just record time of construction for backward compatibility.
   1129         defaultCenturyBase = System.currentTimeMillis();
   1130 
   1131         setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
   1132         initLocalZeroPaddingNumberFormat();
   1133 
   1134         if (override != null) {
   1135            initNumberFormatters(locale);
   1136         }
   1137 
   1138         parsePattern();
   1139     }
   1140 
   1141     /**
   1142      * Private method lazily instantiate the TimeZoneFormat field
   1143      * @param bForceUpdate when true, check if tzFormat is synchronized with
   1144      * the current numberFormat and update its digits if necessary. When false,
   1145      * this check is skipped.
   1146      */
   1147     private synchronized void initializeTimeZoneFormat(boolean bForceUpdate) {
   1148         if (bForceUpdate || tzFormat == null) {
   1149             tzFormat = TimeZoneFormat.getInstance(locale);
   1150 
   1151             String digits = null;
   1152             if (numberFormat instanceof DecimalFormat) {
   1153                 DecimalFormatSymbols decsym = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
   1154                 String[] strDigits = decsym.getDigitStringsLocal();
   1155                 // Note: TimeZoneFormat#setGMTOffsetDigits() does not support string array,
   1156                 // so we need to concatenate digits to make a single string.
   1157                 StringBuilder digitsBuf = new StringBuilder();
   1158                 for (String digit : strDigits) {
   1159                     digitsBuf.append(digit);
   1160                 }
   1161                 digits = digitsBuf.toString();
   1162             } else if (numberFormat instanceof DateNumberFormat) {
   1163                 digits = new String(((DateNumberFormat)numberFormat).getDigits());
   1164             }
   1165 
   1166             if (digits != null) {
   1167                 if (!tzFormat.getGMTOffsetDigits().equals(digits)) {
   1168                     if (tzFormat.isFrozen()) {
   1169                         tzFormat = tzFormat.cloneAsThawed();
   1170                     }
   1171                     tzFormat.setGMTOffsetDigits(digits);
   1172                 }
   1173             }
   1174         }
   1175     }
   1176 
   1177     /**
   1178      * Private method, returns non-null TimeZoneFormat.
   1179      * @return the TimeZoneFormat used by this formatter.
   1180      */
   1181     private TimeZoneFormat tzFormat() {
   1182         if (tzFormat == null) {
   1183             initializeTimeZoneFormat(false);
   1184         }
   1185         return tzFormat;
   1186     }
   1187 
   1188     // privates for the default pattern
   1189     private static ULocale cachedDefaultLocale = null;
   1190     private static String cachedDefaultPattern = null;
   1191     private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
   1192 
   1193     /*
   1194      * Returns the default date and time pattern (SHORT) for the default locale.
   1195      * This method is only used by the default SimpleDateFormat constructor.
   1196      */
   1197     private static synchronized String getDefaultPattern() {
   1198         ULocale defaultLocale = ULocale.getDefault(Category.FORMAT);
   1199         if (!defaultLocale.equals(cachedDefaultLocale)) {
   1200             cachedDefaultLocale = defaultLocale;
   1201             Calendar cal = Calendar.getInstance(cachedDefaultLocale);
   1202 
   1203             try {
   1204                 // Load the calendar data directly.
   1205                 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(
   1206                         ICUData.ICU_BASE_NAME, cachedDefaultLocale);
   1207                 String resourcePath = "calendar/" + cal.getType() + "/DateTimePatterns";
   1208                 ICUResourceBundle patternsRb= rb.findWithFallback(resourcePath);
   1209 
   1210                 if (patternsRb == null) {
   1211                     patternsRb = rb.findWithFallback("calendar/gregorian/DateTimePatterns");
   1212                 }
   1213                 if (patternsRb == null || patternsRb.getSize() < 9) {
   1214                     cachedDefaultPattern = FALLBACKPATTERN;
   1215                 } else {
   1216                     int defaultIndex = 8;
   1217                     if (patternsRb.getSize() >= 13) {
   1218                         defaultIndex += (SHORT + 1);
   1219                     }
   1220                     String basePattern = patternsRb.getString(defaultIndex);
   1221 
   1222                     cachedDefaultPattern = SimpleFormatterImpl.formatRawPattern(
   1223                             basePattern, 2, 2,
   1224                             patternsRb.getString(SHORT), patternsRb.getString(SHORT + 4));
   1225                 }
   1226             } catch (MissingResourceException e) {
   1227                 cachedDefaultPattern = FALLBACKPATTERN;
   1228             }
   1229         }
   1230         return cachedDefaultPattern;
   1231     }
   1232 
   1233     /* Define one-century window into which to disambiguate dates using
   1234      * two-digit years.
   1235      */
   1236     private void parseAmbiguousDatesAsAfter(Date startDate) {
   1237         defaultCenturyStart = startDate;
   1238         calendar.setTime(startDate);
   1239         defaultCenturyStartYear = calendar.get(Calendar.YEAR);
   1240     }
   1241 
   1242     /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
   1243      * The default start time is 80 years before the creation time of this object.
   1244      */
   1245     private void initializeDefaultCenturyStart(long baseTime) {
   1246         defaultCenturyBase = baseTime;
   1247         // clone to avoid messing up date stored in calendar object
   1248         // when this method is called while parsing
   1249         Calendar tmpCal = (Calendar)calendar.clone();
   1250         tmpCal.setTimeInMillis(baseTime);
   1251         tmpCal.add(Calendar.YEAR, -80);
   1252         defaultCenturyStart = tmpCal.getTime();
   1253         defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
   1254     }
   1255 
   1256     /* Gets the default century start date for this object */
   1257     private Date getDefaultCenturyStart() {
   1258         if (defaultCenturyStart == null) {
   1259             // not yet initialized
   1260             initializeDefaultCenturyStart(defaultCenturyBase);
   1261         }
   1262         return defaultCenturyStart;
   1263     }
   1264 
   1265     /* Gets the default century start year for this object */
   1266     private int getDefaultCenturyStartYear() {
   1267         if (defaultCenturyStart == null) {
   1268             // not yet initialized
   1269             initializeDefaultCenturyStart(defaultCenturyBase);
   1270         }
   1271         return defaultCenturyStartYear;
   1272     }
   1273 
   1274     /**
   1275      * Sets the 100-year period 2-digit years will be interpreted as being in
   1276      * to begin on the date the user specifies.
   1277      * @param startDate During parsing, two digit years will be placed in the range
   1278      * <code>startDate</code> to <code>startDate + 100 years</code>.
   1279      */
   1280     public void set2DigitYearStart(Date startDate) {
   1281         parseAmbiguousDatesAsAfter(startDate);
   1282     }
   1283 
   1284     /**
   1285      * Returns the beginning date of the 100-year period 2-digit years are interpreted
   1286      * as being within.
   1287      * @return the start of the 100-year period into which two digit years are
   1288      * parsed
   1289      */
   1290     public Date get2DigitYearStart() {
   1291         return getDefaultCenturyStart();
   1292     }
   1293 
   1294     /**
   1295      * <strong>[icu]</strong> Set a particular DisplayContext value in the formatter,
   1296      * such as CAPITALIZATION_FOR_STANDALONE. Note: For getContext, see
   1297      * DateFormat.
   1298      *
   1299      * @param context The DisplayContext value to set.
   1300      */
   1301     // Here we override the DateFormat implementation in order to lazily initialize relevant items
   1302     @Override
   1303     public void setContext(DisplayContext context) {
   1304         super.setContext(context);
   1305         if (capitalizationBrkIter == null && (context==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
   1306               context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU ||
   1307               context==DisplayContext.CAPITALIZATION_FOR_STANDALONE)) {
   1308             capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
   1309         }
   1310     }
   1311 
   1312     /**
   1313      * Formats a date or time, which is the standard millis
   1314      * since January 1, 1970, 00:00:00 GMT.
   1315      * <p>Example: using the US locale:
   1316      * "yyyy.MM.dd G 'at' HH:mm:ss zzz" -&gt;&gt; 1996.07.10 AD at 15:08:56 PDT
   1317      * @param cal the calendar whose date-time value is to be formatted into a date-time string
   1318      * @param toAppendTo where the new date-time text is to be appended
   1319      * @param pos the formatting position. On input: an alignment field,
   1320      * if desired. On output: the offsets of the alignment field.
   1321      * @return the formatted date-time string.
   1322      * @see DateFormat
   1323      */
   1324     @Override
   1325     public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
   1326                                FieldPosition pos) {
   1327         TimeZone backupTZ = null;
   1328         if (cal != calendar && !cal.getType().equals(calendar.getType())) {
   1329             // Different calendar type
   1330             // We use the time and time zone from the input calendar, but
   1331             // do not use the input calendar for field calculation.
   1332             calendar.setTimeInMillis(cal.getTimeInMillis());
   1333             backupTZ = calendar.getTimeZone();
   1334             calendar.setTimeZone(cal.getTimeZone());
   1335             cal = calendar;
   1336         }
   1337         StringBuffer result = format(cal, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, null);
   1338         if (backupTZ != null) {
   1339             // Restore the original time zone
   1340             calendar.setTimeZone(backupTZ);
   1341         }
   1342         return result;
   1343     }
   1344 
   1345     // The actual method to format date. If List attributes is not null,
   1346     // then attribute information will be recorded.
   1347     private StringBuffer format(Calendar cal, DisplayContext capitalizationContext,
   1348             StringBuffer toAppendTo, FieldPosition pos, List<FieldPosition> attributes) {
   1349         // Initialize
   1350         pos.setBeginIndex(0);
   1351         pos.setEndIndex(0);
   1352 
   1353         // Careful: For best performance, minimize the number of calls
   1354         // to StringBuffer.append() by consolidating appends when
   1355         // possible.
   1356 
   1357         Object[] items = getPatternItems();
   1358         for (int i = 0; i < items.length; i++) {
   1359             if (items[i] instanceof String) {
   1360                 toAppendTo.append((String)items[i]);
   1361             } else {
   1362                 PatternItem item = (PatternItem)items[i];
   1363                 int start = 0;
   1364                 if (attributes != null) {
   1365                     // Save the current length
   1366                     start = toAppendTo.length();
   1367                 }
   1368                 if (useFastFormat) {
   1369                     subFormat(toAppendTo, item.type, item.length, toAppendTo.length(),
   1370                               i, capitalizationContext, pos, cal);
   1371                 } else {
   1372                     toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(),
   1373                                                 i, capitalizationContext, pos, cal));
   1374                 }
   1375                 if (attributes != null) {
   1376                     // Check the sub format length
   1377                     int end = toAppendTo.length();
   1378                     if (end - start > 0) {
   1379                         // Append the attribute to the list
   1380                         DateFormat.Field attr = patternCharToDateFormatField(item.type);
   1381                         FieldPosition fp = new FieldPosition(attr);
   1382                         fp.setBeginIndex(start);
   1383                         fp.setEndIndex(end);
   1384                         attributes.add(fp);
   1385                     }
   1386                 }
   1387             }
   1388         }
   1389         return toAppendTo;
   1390 
   1391     }
   1392 
   1393     // Map pattern character to index
   1394     private static final int[] PATTERN_CHAR_TO_INDEX =
   1395     {
   1396         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   1397     //
   1398         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   1399     //       !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /
   1400         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   1401     //   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
   1402         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   1403     //   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
   1404         -1, 22, 36, -1, 10,  9, 11,  0,  5, -1, -1, 16, 26,  2, -1, 31,
   1405     //   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
   1406         -1, 27, -1,  8, -1, 30, 29, 13, 32, 18, 23, -1, -1, -1, -1, -1,
   1407     //   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
   1408         -1, 14, 35, 25,  3, 19, -1, 21, 15, -1, -1,  4, -1,  6, -1, -1,
   1409     //   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~
   1410         -1, 28, 34,  7, -1, 20, 24, 12, 33,  1, 17, -1, -1, -1, -1, -1,
   1411     };
   1412 
   1413     private static int getIndexFromChar(char ch) {
   1414         return ch < PATTERN_CHAR_TO_INDEX.length ? PATTERN_CHAR_TO_INDEX[ch & 0xff] : -1;
   1415     }
   1416 
   1417     // Map pattern character index to Calendar field number
   1418     private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
   1419     {
   1420         /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
   1421         /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
   1422         /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
   1423         /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
   1424         /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
   1425         /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
   1426         /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
   1427         /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1428         /*v*/   Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1429         /*c*/   Calendar.DOW_LOCAL,
   1430         /*L*/   Calendar.MONTH,
   1431         /*Qq*/  Calendar.MONTH, Calendar.MONTH,
   1432         /*V*/   Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1433         /*U*/   Calendar.YEAR,
   1434         /*O*/   Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1435         /*Xx*/  Calendar.ZONE_OFFSET /* also DST_OFFSET */, Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1436         /*r*/   Calendar.EXTENDED_YEAR /* not an exact match */,
   1437         /*bB*/  -1, -1 /* am/pm/midnight/noon and flexible day period fields; no mapping to calendar fields */
   1438         /*:*/   -1, /* => no useful mapping to any calendar field, can't use protected Calendar.BASE_FIELD_COUNT */
   1439     };
   1440 
   1441     // Map pattern character index to DateFormat field number
   1442     private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
   1443         /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
   1444         /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
   1445         /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
   1446         /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
   1447         /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
   1448         /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
   1449         /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
   1450         /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
   1451         /*v*/   DateFormat.TIMEZONE_GENERIC_FIELD,
   1452         /*c*/   DateFormat.STANDALONE_DAY_FIELD,
   1453         /*L*/   DateFormat.STANDALONE_MONTH_FIELD,
   1454         /*Qq*/  DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
   1455         /*V*/   DateFormat.TIMEZONE_SPECIAL_FIELD,
   1456         /*U*/   DateFormat.YEAR_NAME_FIELD,
   1457         /*O*/   DateFormat.TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD,
   1458         /*Xx*/  DateFormat.TIMEZONE_ISO_FIELD, DateFormat.TIMEZONE_ISO_LOCAL_FIELD,
   1459         /*r*/   DateFormat.RELATED_YEAR,
   1460         /*bB*/  DateFormat.AM_PM_MIDNIGHT_NOON_FIELD, DateFormat.FLEXIBLE_DAY_PERIOD_FIELD,
   1461         /*(no pattern character defined for this)*/   DateFormat.TIME_SEPARATOR,
   1462     };
   1463 
   1464     // Map pattern character index to DateFormat.Field
   1465     private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
   1466         /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
   1467         /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
   1468         /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
   1469         /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
   1470         /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
   1471         /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
   1472         /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
   1473         /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
   1474         /*v*/   DateFormat.Field.TIME_ZONE,
   1475         /*c*/   DateFormat.Field.DAY_OF_WEEK,
   1476         /*L*/   DateFormat.Field.MONTH,
   1477         /*Qq*/  DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
   1478         /*V*/   DateFormat.Field.TIME_ZONE,
   1479         /*U*/   DateFormat.Field.YEAR,
   1480         /*O*/   DateFormat.Field.TIME_ZONE,
   1481         /*Xx*/  DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE,
   1482         /*r*/   DateFormat.Field.RELATED_YEAR,
   1483         /*bB*/  DateFormat.Field.AM_PM_MIDNIGHT_NOON, DateFormat.Field.FLEXIBLE_DAY_PERIOD,
   1484         /*(no pattern character defined for this)*/   DateFormat.Field.TIME_SEPARATOR,
   1485     };
   1486 
   1487     /**
   1488      * Returns a DateFormat.Field constant associated with the specified format pattern
   1489      * character.
   1490      *
   1491      * @param ch The pattern character
   1492      * @return DateFormat.Field associated with the pattern character
   1493      */
   1494     protected DateFormat.Field patternCharToDateFormatField(char ch) {
   1495         int patternCharIndex = getIndexFromChar(ch);
   1496         if (patternCharIndex != -1) {
   1497             return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
   1498         }
   1499         return null;
   1500     }
   1501 
   1502     /**
   1503      * Formats a single field, given its pattern character.  Subclasses may
   1504      * override this method in order to modify or add formatting
   1505      * capabilities.
   1506      * @param ch the pattern character
   1507      * @param count the number of times ch is repeated in the pattern
   1508      * @param beginOffset the offset of the output string at the start of
   1509      * this field; used to set pos when appropriate
   1510      * @param pos receives the position of a field, when appropriate
   1511      * @param fmtData the symbols for this formatter
   1512      */
   1513     protected String subFormat(char ch, int count, int beginOffset,
   1514                                FieldPosition pos, DateFormatSymbols fmtData,
   1515                                Calendar cal)
   1516         throws IllegalArgumentException
   1517     {
   1518         // Note: formatData is ignored
   1519         return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal);
   1520     }
   1521 
   1522      /**
   1523      * Formats a single field. This is the version called internally; it
   1524      * adds fieldNum and capitalizationContext parameters.
   1525      *
   1526      * @deprecated This API is ICU internal only.
   1527      * @hide original deprecated declaration
   1528      * @hide draft / provisional / internal are hidden on Android
   1529      */
   1530     @Deprecated
   1531     protected String subFormat(char ch, int count, int beginOffset,
   1532                                int fieldNum, DisplayContext capitalizationContext,
   1533                                FieldPosition pos,
   1534                                Calendar cal)
   1535     {
   1536         StringBuffer buf = new StringBuffer();
   1537         subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
   1538         return buf.toString();
   1539     }
   1540 
   1541    /**
   1542      * Formats a single field; useFastFormat variant.  Reuses a
   1543      * StringBuffer for results instead of creating a String on the
   1544      * heap for each call.
   1545      *
   1546      * NOTE We don't really need the beginOffset parameter, EXCEPT for
   1547      * the need to support the slow subFormat variant (above) which
   1548      * has to pass it in to us.
   1549      *
   1550      * @deprecated This API is ICU internal only.
   1551  * @hide original deprecated declaration
   1552  * @hide draft / provisional / internal are hidden on Android
   1553      */
   1554     @Deprecated
   1555     @SuppressWarnings("fallthrough")
   1556     protected void subFormat(StringBuffer buf,
   1557                              char ch, int count, int beginOffset,
   1558                              int fieldNum, DisplayContext capitalizationContext,
   1559                              FieldPosition pos,
   1560                              Calendar cal) {
   1561 
   1562         final int maxIntCount = Integer.MAX_VALUE;
   1563         final int bufstart = buf.length();
   1564         TimeZone tz = cal.getTimeZone();
   1565         long date = cal.getTimeInMillis();
   1566         String result = null;
   1567 
   1568         int patternCharIndex = getIndexFromChar(ch);
   1569         if (patternCharIndex == -1) {
   1570             if (ch == 'l') { // (SMALL LETTER L) deprecated placeholder for leap month marker, ignore
   1571                 return;
   1572             } else {
   1573                 throw new IllegalArgumentException("Illegal pattern character " +
   1574                                                    "'" + ch + "' in \"" +
   1575                                                    pattern + '"');
   1576             }
   1577         }
   1578 
   1579         final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
   1580         int value = 0;
   1581         // Don't get value unless it is useful
   1582         if (field >= 0) {
   1583             value = (patternCharIndex != DateFormat.RELATED_YEAR)? cal.get(field): cal.getRelatedYear();
   1584         }
   1585 
   1586         NumberFormat currentNumberFormat = getNumberFormat(ch);
   1587         DateFormatSymbols.CapitalizationContextUsage capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.OTHER;
   1588 
   1589         switch (patternCharIndex) {
   1590         case 0: // 'G' - ERA
   1591             if ( cal.getType().equals("chinese") || cal.getType().equals("dangi") ) {
   1592                 // moved from ChineseDateFormat
   1593                 zeroPaddingNumber(currentNumberFormat, buf, value, 1, 9);
   1594             } else {
   1595                 if (count == 5) {
   1596                     safeAppend(formatData.narrowEras, value, buf);
   1597                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_NARROW;
   1598                 } else if (count == 4) {
   1599                     safeAppend(formatData.eraNames, value, buf);
   1600                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_WIDE;
   1601                 } else {
   1602                     safeAppend(formatData.eras, value, buf);
   1603                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_ABBREV;
   1604                 }
   1605             }
   1606             break;
   1607         case 30: // 'U' - YEAR_NAME_FIELD
   1608             if (formatData.shortYearNames != null && value <= formatData.shortYearNames.length) {
   1609                 safeAppend(formatData.shortYearNames, value-1, buf);
   1610                 break;
   1611             }
   1612             // else fall through to numeric year handling, do not break here
   1613         case 1: // 'y' - YEAR
   1614         case 18: // 'Y' - YEAR_WOY
   1615             if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) &&
   1616                     value > HEBREW_CAL_CUR_MILLENIUM_START_YEAR && value < HEBREW_CAL_CUR_MILLENIUM_END_YEAR ) {
   1617                 value -= HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
   1618             }
   1619             /* According to the specification, if the number of pattern letters ('y') is 2,
   1620              * the year is truncated to 2 digits; otherwise it is interpreted as a number.
   1621              * But the original code process 'y', 'yy', 'yyy' in the same way. and process
   1622              * patterns with 4 or more than 4 'y' characters in the same way.
   1623              * So I change the codes to meet the specification. [Richard/GCl]
   1624              */
   1625             if (count == 2) {
   1626                 zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
   1627             } else { //count = 1 or count > 2
   1628                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   1629             }
   1630             break;
   1631         case 2: // 'M' - MONTH
   1632         case 26: // 'L' - STANDALONE MONTH
   1633             if ( cal.getType().equals("hebrew")) {
   1634                 boolean isLeap = HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR));
   1635                 if (isLeap && value == 6 && count >= 3 ) {
   1636                     value = 13; // Show alternate form for Adar II in leap years in Hebrew calendar.
   1637                 }
   1638                 if (!isLeap && value >= 6 && count < 3 ) {
   1639                     value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7.
   1640                 }
   1641             }
   1642             int isLeapMonth = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT)?
   1643                      cal.get(Calendar.IS_LEAP_MONTH): 0;
   1644             // should consolidate the next section by using arrays of pointers & counts for the right symbols...
   1645             if (count == 5) {
   1646                 if (patternCharIndex == 2) {
   1647                     safeAppendWithMonthPattern(formatData.narrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_NARROW]: null);
   1648                 } else {
   1649                     safeAppendWithMonthPattern(formatData.standaloneNarrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW]: null);
   1650                 }
   1651                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_NARROW;
   1652             } else if (count == 4) {
   1653                 if (patternCharIndex == 2) {
   1654                     safeAppendWithMonthPattern(formatData.months, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null);
   1655                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
   1656                 } else {
   1657                     safeAppendWithMonthPattern(formatData.standaloneMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null);
   1658                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
   1659                 }
   1660             } else if (count == 3) {
   1661                 if (patternCharIndex == 2) {
   1662                     safeAppendWithMonthPattern(formatData.shortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null);
   1663                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
   1664                 } else {
   1665                     safeAppendWithMonthPattern(formatData.standaloneShortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null);
   1666                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
   1667                 }
   1668             } else {
   1669                 StringBuffer monthNumber = new StringBuffer();
   1670                 zeroPaddingNumber(currentNumberFormat, monthNumber, value+1, count, maxIntCount);
   1671                 String[] monthNumberStrings = new String[1];
   1672                 monthNumberStrings[0] = monthNumber.toString();
   1673                 safeAppendWithMonthPattern(monthNumberStrings, 0, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC]: null);
   1674             }
   1675             break;
   1676         case 4: // 'k' - HOUR_OF_DAY (1..24)
   1677             if (value == 0) {
   1678                 zeroPaddingNumber(currentNumberFormat,buf,
   1679                                   cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
   1680                                   count, maxIntCount);
   1681             } else {
   1682                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   1683             }
   1684             break;
   1685         case 8: // 'S' - FRACTIONAL_SECOND
   1686             // Fractional seconds left-justify
   1687             {
   1688                 numberFormat.setMinimumIntegerDigits(Math.min(3, count));
   1689                 numberFormat.setMaximumIntegerDigits(maxIntCount);
   1690                 if (count == 1) {
   1691                     value /= 100;
   1692                 } else if (count == 2) {
   1693                     value /= 10;
   1694                 }
   1695                 FieldPosition p = new FieldPosition(-1);
   1696                 numberFormat.format(value, buf, p);
   1697                 if (count > 3) {
   1698                     numberFormat.setMinimumIntegerDigits(count - 3);
   1699                     numberFormat.format(0L, buf, p);
   1700                 }
   1701             }
   1702             break;
   1703         case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
   1704             if (count < 3) {
   1705                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   1706                 break;
   1707             }
   1708             // For alpha day-of-week, we don't want DOW_LOCAL,
   1709             // we need the standard DAY_OF_WEEK.
   1710             value = cal.get(Calendar.DAY_OF_WEEK);
   1711             // fall through, do not break here
   1712         case 9: // 'E' - DAY_OF_WEEK
   1713             if (count == 5) {
   1714                 safeAppend(formatData.narrowWeekdays, value, buf);
   1715                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
   1716             } else if (count == 4) {
   1717                 safeAppend(formatData.weekdays, value, buf);
   1718                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
   1719             } else if (count == 6 && formatData.shorterWeekdays != null) {
   1720                 safeAppend(formatData.shorterWeekdays, value, buf);
   1721                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
   1722             } else {// count <= 3, use abbreviated form if exists
   1723                 safeAppend(formatData.shortWeekdays, value, buf);
   1724                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
   1725             }
   1726             break;
   1727         case 14: // 'a' - AM_PM
   1728             // formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version
   1729             if (count < 5 || formatData.ampmsNarrow == null) {
   1730                 safeAppend(formatData.ampms, value, buf);
   1731             } else {
   1732                 safeAppend(formatData.ampmsNarrow, value, buf);
   1733             }
   1734             break;
   1735         case 15: // 'h' - HOUR (1..12)
   1736             if (value == 0) {
   1737                 zeroPaddingNumber(currentNumberFormat,buf,
   1738                                   cal.getLeastMaximum(Calendar.HOUR)+1,
   1739                                   count, maxIntCount);
   1740             } else {
   1741                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   1742             }
   1743             break;
   1744 
   1745         case 17: // 'z' - TIMEZONE_FIELD
   1746             if (count < 4) {
   1747                 // "z", "zz", "zzz"
   1748                 result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date);
   1749                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
   1750             } else {
   1751                 result = tzFormat().format(Style.SPECIFIC_LONG, tz, date);
   1752                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
   1753             }
   1754             buf.append(result);
   1755             break;
   1756         case 23: // 'Z' - TIMEZONE_RFC_FIELD
   1757             if (count < 4) {
   1758                 // RFC822 format - equivalent to ISO 8601 local offset fixed width format
   1759                 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
   1760             } else if (count == 5) {
   1761                 // ISO 8601 extended format
   1762                 result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
   1763             } else {
   1764                 // long form, localized GMT pattern
   1765                 result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
   1766             }
   1767                 buf.append(result);
   1768             break;
   1769         case 24: // 'v' - TIMEZONE_GENERIC_FIELD
   1770             if (count == 1) {
   1771                 // "v"
   1772                 result = tzFormat().format(Style.GENERIC_SHORT, tz, date);
   1773                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
   1774             } else if (count == 4) {
   1775                 // "vvvv"
   1776                 result = tzFormat().format(Style.GENERIC_LONG, tz, date);
   1777                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
   1778             }
   1779             buf.append(result);
   1780             break;
   1781         case 29: // 'V' - TIMEZONE_SPECIAL_FIELD
   1782             if (count == 1) {
   1783                 // "V"
   1784                 result = tzFormat().format(Style.ZONE_ID_SHORT, tz, date);
   1785             } else if (count == 2) {
   1786                 // "VV"
   1787                 result = tzFormat().format(Style.ZONE_ID, tz, date);
   1788             } else if (count == 3) {
   1789                 // "VVV"
   1790                 result = tzFormat().format(Style.EXEMPLAR_LOCATION, tz, date);
   1791             } else if (count == 4) {
   1792                 // "VVVV"
   1793                 result = tzFormat().format(Style.GENERIC_LOCATION, tz, date);
   1794                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ZONE_LONG;
   1795             }
   1796             buf.append(result);
   1797             break;
   1798         case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
   1799             if (count == 1) {
   1800                 // "O" - Short Localized GMT format
   1801                 result = tzFormat().format(Style.LOCALIZED_GMT_SHORT, tz, date);
   1802             } else if (count == 4) {
   1803                 // "OOOO" - Localized GMT format
   1804                 result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
   1805             }
   1806             buf.append(result);
   1807             break;
   1808         case 32: // 'X' - TIMEZONE_ISO_FIELD
   1809             if (count == 1) {
   1810                 // "X" - ISO Basic/Short
   1811                 result = tzFormat().format(Style.ISO_BASIC_SHORT, tz, date);
   1812             } else if (count == 2) {
   1813                 // "XX" - ISO Basic/Fixed
   1814                 result = tzFormat().format(Style.ISO_BASIC_FIXED, tz, date);
   1815             } else if (count == 3) {
   1816                 // "XXX" - ISO Extended/Fixed
   1817                 result = tzFormat().format(Style.ISO_EXTENDED_FIXED, tz, date);
   1818             } else if (count == 4) {
   1819                 // "XXXX" - ISO Basic/Optional second field
   1820                 result = tzFormat().format(Style.ISO_BASIC_FULL, tz, date);
   1821             } else if (count == 5) {
   1822                 // "XXXXX" - ISO Extended/Optional second field
   1823                 result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
   1824             }
   1825             buf.append(result);
   1826             break;
   1827         case 33: // 'x' - TIMEZONE_ISO_LOCAL_FIELD
   1828             if (count == 1) {
   1829                 // "x" - ISO Local Basic/Short
   1830                 result = tzFormat().format(Style.ISO_BASIC_LOCAL_SHORT, tz, date);
   1831             } else if (count == 2) {
   1832                 // "x" - ISO Local Basic/Fixed
   1833                 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FIXED, tz, date);
   1834             } else if (count == 3) {
   1835                 // "xxx" - ISO Local Extended/Fixed
   1836                 result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FIXED, tz, date);
   1837             } else if (count == 4) {
   1838                 // "xxxx" - ISO Local Basic/Optional second field
   1839                 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
   1840             } else if (count == 5) {
   1841                 // "xxxxx" - ISO Local Extended/Optional second field
   1842                 result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FULL, tz, date);
   1843             }
   1844             buf.append(result);
   1845             break;
   1846 
   1847         case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone)
   1848             if (count < 3) {
   1849                 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
   1850                 break;
   1851             }
   1852             // For alpha day-of-week, we don't want DOW_LOCAL,
   1853             // we need the standard DAY_OF_WEEK.
   1854             value = cal.get(Calendar.DAY_OF_WEEK);
   1855             if (count == 5) {
   1856                 safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
   1857                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
   1858             } else if (count == 4) {
   1859                 safeAppend(formatData.standaloneWeekdays, value, buf);
   1860                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
   1861             } else if (count == 6 && formatData.standaloneShorterWeekdays != null) {
   1862                 safeAppend(formatData.standaloneShorterWeekdays, value, buf);
   1863                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
   1864             } else { // count == 3
   1865                 safeAppend(formatData.standaloneShortWeekdays, value, buf);
   1866                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
   1867             }
   1868             break;
   1869         case 27: // 'Q' - QUARTER
   1870             if (count >= 4) {
   1871                 safeAppend(formatData.quarters, value/3, buf);
   1872             } else if (count == 3) {
   1873                 safeAppend(formatData.shortQuarters, value/3, buf);
   1874             } else {
   1875                 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
   1876             }
   1877             break;
   1878         case 28: // 'q' - STANDALONE QUARTER
   1879             if (count >= 4) {
   1880                 safeAppend(formatData.standaloneQuarters, value/3, buf);
   1881             } else if (count == 3) {
   1882                 safeAppend(formatData.standaloneShortQuarters, value/3, buf);
   1883             } else {
   1884                 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
   1885             }
   1886             break;
   1887         case 35: // 'b' - am/pm/noon/midnight
   1888         {
   1889             // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day.
   1890             // For ICU 57 output of "midnight" is temporarily suppressed.
   1891 
   1892             int hour = cal.get(Calendar.HOUR_OF_DAY);
   1893             String toAppend = null;
   1894 
   1895             // For "midnight" and "noon":
   1896             // Time, as displayed, must be exactly noon or midnight.
   1897             // This means minutes and seconds, if present, must be zero.
   1898             if ((/*hour == 0 ||*/ hour == 12) &&
   1899                     (!hasMinute || cal.get(Calendar.MINUTE) == 0) &&
   1900                     (!hasSecond || cal.get(Calendar.SECOND) == 0)) {
   1901                 // Stealing am/pm value to use as our array index.
   1902                 // It works out: am/midnight are both 0, pm/noon are both 1,
   1903                 // 12 am is 12 midnight, and 12 pm is 12 noon.
   1904                 value = cal.get(Calendar.AM_PM);
   1905 
   1906                 if (count <= 3) {
   1907                     toAppend = formatData.abbreviatedDayPeriods[value];
   1908                 } else if (count == 4 || count > 5) {
   1909                     toAppend = formatData.wideDayPeriods[value];
   1910                 } else { // count == 5
   1911                     toAppend = formatData.narrowDayPeriods[value];
   1912                 }
   1913             }
   1914 
   1915             if (toAppend == null) {
   1916                 // Time isn't exactly midnight or noon (as displayed) or localized string doesn't
   1917                 // exist for requested period. Fall back to am/pm instead.
   1918                 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
   1919             } else {
   1920                 buf.append(toAppend);
   1921             }
   1922 
   1923             break;
   1924         }
   1925         case 36: // 'B' - flexible day period
   1926         {
   1927             // TODO: Maybe fetch the DayperiodRules during initialization (instead of at the first
   1928             // loading of an instance) if a relevant pattern character (b or B) is used.
   1929             DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale());
   1930             if (ruleSet == null) {
   1931                 // Data doesn't exist for the locale we're looking for.
   1932                 // Fall back to am/pm.
   1933                 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
   1934                 break;
   1935             }
   1936 
   1937             // Get current display time.
   1938             int hour = cal.get(Calendar.HOUR_OF_DAY);
   1939             int minute = 0;
   1940             int second = 0;
   1941             if (hasMinute) { minute = cal.get(Calendar.MINUTE); }
   1942             if (hasSecond) { second = cal.get(Calendar.SECOND); }
   1943 
   1944             // Determine day period.
   1945             DayPeriodRules.DayPeriod periodType;
   1946             if (hour == 0 && minute == 0 && second == 0 && ruleSet.hasMidnight()) {
   1947                 periodType = DayPeriodRules.DayPeriod.MIDNIGHT;
   1948             } else if (hour == 12 && minute == 0 && second == 0 && ruleSet.hasNoon()) {
   1949                 periodType = DayPeriodRules.DayPeriod.NOON;
   1950             } else {
   1951                 periodType = ruleSet.getDayPeriodForHour(hour);
   1952             }
   1953 
   1954             // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day.
   1955             // For ICU 57 output of "midnight" is temporarily suppressed.
   1956 
   1957             // Rule set exists, therefore periodType can't be null.
   1958             // Get localized string.
   1959             assert(periodType != null);
   1960             String toAppend = null;
   1961             int index;
   1962 
   1963             if (periodType != DayPeriodRules.DayPeriod.AM && periodType != DayPeriodRules.DayPeriod.PM &&
   1964                     periodType != DayPeriodRules.DayPeriod.MIDNIGHT) {
   1965                 index = periodType.ordinal();
   1966                 if (count <= 3) {
   1967                     toAppend = formatData.abbreviatedDayPeriods[index];  // i.e. short
   1968                 } else if (count == 4 || count > 5) {
   1969                     toAppend = formatData.wideDayPeriods[index];
   1970                 } else {  // count == 5
   1971                     toAppend = formatData.narrowDayPeriods[index];
   1972                 }
   1973             }
   1974 
   1975 
   1976             // Fallback schedule:
   1977             // Midnight/Noon -> General Periods -> AM/PM.
   1978 
   1979             // Midnight/Noon -> General Periods.
   1980             if (toAppend == null &&
   1981                     (periodType == DayPeriodRules.DayPeriod.MIDNIGHT ||
   1982                      periodType == DayPeriodRules.DayPeriod.NOON)) {
   1983                 periodType = ruleSet.getDayPeriodForHour(hour);
   1984                 index = periodType.ordinal();
   1985 
   1986                 if (count <= 3) {
   1987                     toAppend = formatData.abbreviatedDayPeriods[index];  // i.e. short
   1988                 } else if (count == 4 || count > 5) {
   1989                     toAppend = formatData.wideDayPeriods[index];
   1990                 } else {  // count == 5
   1991                     toAppend = formatData.narrowDayPeriods[index];
   1992                 }
   1993             }
   1994 
   1995             // General Periods -> AM/PM.
   1996             if (periodType == DayPeriodRules.DayPeriod.AM ||
   1997                     periodType == DayPeriodRules.DayPeriod.PM ||
   1998                     toAppend == null) {
   1999                 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
   2000             }
   2001             else {
   2002                 buf.append(toAppend);
   2003             }
   2004 
   2005             break;
   2006         }
   2007         case 37: // TIME SEPARATOR (no pattern character currently defined, we should
   2008                  // not get here but leave support in for future definition.
   2009             buf.append(formatData.getTimeSeparatorString());
   2010             break;
   2011         default:
   2012             // case 3: // 'd' - DATE
   2013             // case 5: // 'H' - HOUR_OF_DAY (0..23)
   2014             // case 6: // 'm' - MINUTE
   2015             // case 7: // 's' - SECOND
   2016             // case 10: // 'D' - DAY_OF_YEAR
   2017             // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
   2018             // case 12: // 'w' - WEEK_OF_YEAR
   2019             // case 13: // 'W' - WEEK_OF_MONTH
   2020             // case 16: // 'K' - HOUR (0..11)
   2021             // case 20: // 'u' - EXTENDED_YEAR
   2022             // case 21: // 'g' - JULIAN_DAY
   2023             // case 22: // 'A' - MILLISECONDS_IN_DAY
   2024 
   2025             zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   2026             break;
   2027         } // switch (patternCharIndex)
   2028 
   2029         if (fieldNum == 0 && capitalizationContext != null && UCharacter.isLowerCase(buf.codePointAt(bufstart))) {
   2030             boolean titlecase = false;
   2031             switch (capitalizationContext) {
   2032                 case CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE:
   2033                     titlecase = true;
   2034                     break;
   2035                 case CAPITALIZATION_FOR_UI_LIST_OR_MENU:
   2036                 case CAPITALIZATION_FOR_STANDALONE:
   2037                     if (formatData.capitalization != null) {
   2038                         boolean[] transforms = formatData.capitalization.get(capContextUsageType);
   2039                         titlecase = (capitalizationContext==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)?
   2040                                     transforms[0]: transforms[1];
   2041                     }
   2042                     break;
   2043                 default:
   2044                    break;
   2045             }
   2046             if (titlecase) {
   2047                 if (capitalizationBrkIter == null) {
   2048                     // should only happen when deserializing, etc.
   2049                     capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
   2050                 }
   2051                 String firstField = buf.substring(bufstart); // bufstart or beginOffset, should be the same
   2052                 String firstFieldTitleCase = UCharacter.toTitleCase(locale, firstField, capitalizationBrkIter,
   2053                                                      UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
   2054                 buf.replace(bufstart, buf.length(), firstFieldTitleCase);
   2055             }
   2056         }
   2057 
   2058         // Set the FieldPosition (for the first occurrence only)
   2059         if (pos.getBeginIndex() == pos.getEndIndex()) {
   2060             if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
   2061                 pos.setBeginIndex(beginOffset);
   2062                 pos.setEndIndex(beginOffset + buf.length() - bufstart);
   2063             } else if (pos.getFieldAttribute() ==
   2064                        PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
   2065                 pos.setBeginIndex(beginOffset);
   2066                 pos.setEndIndex(beginOffset + buf.length() - bufstart);
   2067             }
   2068         }
   2069     }
   2070 
   2071     private static void safeAppend(String[] array, int value, StringBuffer appendTo) {
   2072         if (array != null && value >= 0 && value < array.length) {
   2073             appendTo.append(array[value]);
   2074         }
   2075     }
   2076 
   2077     private static void safeAppendWithMonthPattern(String[] array, int value, StringBuffer appendTo, String monthPattern) {
   2078         if (array != null && value >= 0 && value < array.length) {
   2079             if (monthPattern == null) {
   2080                 appendTo.append(array[value]);
   2081             } else {
   2082                 String s = SimpleFormatterImpl.formatRawPattern(monthPattern, 1, 1, array[value]);
   2083                 appendTo.append(s);
   2084             }
   2085         }
   2086     }
   2087 
   2088     /*
   2089      * PatternItem store parsed date/time field pattern information.
   2090      */
   2091     private static class PatternItem {
   2092         final char type;
   2093         final int length;
   2094         final boolean isNumeric;
   2095 
   2096         PatternItem(char type, int length) {
   2097             this.type = type;
   2098             this.length = length;
   2099             isNumeric = isNumeric(type, length);
   2100         }
   2101     }
   2102 
   2103     private static ICUCache<String, Object[]> PARSED_PATTERN_CACHE =
   2104         new SimpleCache<String, Object[]>();
   2105     private transient Object[] patternItems;
   2106 
   2107     /*
   2108      * Returns parsed pattern items.  Each item is either String or
   2109      * PatternItem.
   2110      */
   2111     private Object[] getPatternItems() {
   2112         if (patternItems != null) {
   2113             return patternItems;
   2114         }
   2115 
   2116         patternItems = PARSED_PATTERN_CACHE.get(pattern);
   2117         if (patternItems != null) {
   2118             return patternItems;
   2119         }
   2120 
   2121         boolean isPrevQuote = false;
   2122         boolean inQuote = false;
   2123         StringBuilder text = new StringBuilder();
   2124         char itemType = 0;  // 0 for string literal, otherwise date/time pattern character
   2125         int itemLength = 1;
   2126 
   2127         List<Object> items = new ArrayList<Object>();
   2128 
   2129         for (int i = 0; i < pattern.length(); i++) {
   2130             char ch = pattern.charAt(i);
   2131             if (ch == '\'') {
   2132                 if (isPrevQuote) {
   2133                     text.append('\'');
   2134                     isPrevQuote = false;
   2135                 } else {
   2136                     isPrevQuote = true;
   2137                     if (itemType != 0) {
   2138                         items.add(new PatternItem(itemType, itemLength));
   2139                         itemType = 0;
   2140                     }
   2141                 }
   2142                 inQuote = !inQuote;
   2143             } else {
   2144                 isPrevQuote = false;
   2145                 if (inQuote) {
   2146                     text.append(ch);
   2147                 } else {
   2148                     if (isSyntaxChar(ch)) {
   2149                         // a date/time pattern character
   2150                         if (ch == itemType) {
   2151                             itemLength++;
   2152                         } else {
   2153                             if (itemType == 0) {
   2154                                 if (text.length() > 0) {
   2155                                     items.add(text.toString());
   2156                                     text.setLength(0);
   2157                                 }
   2158                             } else {
   2159                                 items.add(new PatternItem(itemType, itemLength));
   2160                             }
   2161                             itemType = ch;
   2162                             itemLength = 1;
   2163                         }
   2164                     } else {
   2165                         // a string literal
   2166                         if (itemType != 0) {
   2167                             items.add(new PatternItem(itemType, itemLength));
   2168                             itemType = 0;
   2169                         }
   2170                         text.append(ch);
   2171                     }
   2172                 }
   2173             }
   2174         }
   2175         // handle last item
   2176         if (itemType == 0) {
   2177             if (text.length() > 0) {
   2178                 items.add(text.toString());
   2179                 text.setLength(0);
   2180             }
   2181         } else {
   2182             items.add(new PatternItem(itemType, itemLength));
   2183         }
   2184 
   2185         patternItems = items.toArray(new Object[items.size()]);
   2186 
   2187         PARSED_PATTERN_CACHE.put(pattern, patternItems);
   2188 
   2189         return patternItems;
   2190     }
   2191 
   2192     /**
   2193      * Internal high-speed method.  Reuses a StringBuffer for results
   2194      * instead of creating a String on the heap for each call.
   2195      * @deprecated This API is ICU internal only.
   2196      * @hide original deprecated declaration
   2197      * @hide draft / provisional / internal are hidden on Android
   2198      */
   2199     @Deprecated
   2200     protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
   2201                                      int minDigits, int maxDigits) {
   2202         // Note: Indian calendar uses negative value for a calendar
   2203         // field. fastZeroPaddingNumber cannot handle negative numbers.
   2204         // BTW, it looks like a design bug in the Indian calendar...
   2205         if (useLocalZeroPaddingNumberFormat && value >= 0) {
   2206             fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
   2207         } else {
   2208             nf.setMinimumIntegerDigits(minDigits);
   2209             nf.setMaximumIntegerDigits(maxDigits);
   2210             nf.format(value, buf, new FieldPosition(-1));
   2211         }
   2212     }
   2213 
   2214     /**
   2215      * Overrides superclass method and
   2216      * This method also clears per field NumberFormat instances
   2217      * previously set by {@link #setNumberFormat(String, NumberFormat)}
   2218      */
   2219     @Override
   2220     public void setNumberFormat(NumberFormat newNumberFormat) {
   2221         // Override this method to update local zero padding number formatter
   2222         super.setNumberFormat(newNumberFormat);
   2223         initLocalZeroPaddingNumberFormat();
   2224         initializeTimeZoneFormat(true);
   2225 
   2226         if (numberFormatters != null) {
   2227             numberFormatters = null;
   2228         }
   2229         if (overrideMap != null) {
   2230             overrideMap = null;
   2231         }
   2232     }
   2233 
   2234     /*
   2235      * Initializes transient fields for fast simple numeric formatting
   2236      * code. This method should be called whenever number format is updated.
   2237      */
   2238     private void initLocalZeroPaddingNumberFormat() {
   2239         if (numberFormat instanceof DecimalFormat) {
   2240             DecimalFormatSymbols tmpDecfs = ((DecimalFormat)numberFormat).getDecimalFormatSymbols();
   2241             String[] tmpDigits = tmpDecfs.getDigitStringsLocal();
   2242             useLocalZeroPaddingNumberFormat = true;
   2243             decDigits = new char[10];
   2244             for (int i = 0; i < 10; i++) {
   2245                 if (tmpDigits[i].length() > 1) {
   2246                     useLocalZeroPaddingNumberFormat = false;
   2247                     break;
   2248                 }
   2249                 decDigits[i] = tmpDigits[i].charAt(0);
   2250             }
   2251         } else if (numberFormat instanceof DateNumberFormat) {
   2252             decDigits = ((DateNumberFormat)numberFormat).getDigits();
   2253             useLocalZeroPaddingNumberFormat = true;
   2254         } else {
   2255             useLocalZeroPaddingNumberFormat = false;
   2256         }
   2257 
   2258         if (useLocalZeroPaddingNumberFormat) {
   2259             decimalBuf = new char[DECIMAL_BUF_SIZE];
   2260         }
   2261     }
   2262 
   2263     // If true, use local version of zero padding number format
   2264     private transient boolean useLocalZeroPaddingNumberFormat;
   2265     private transient char[] decDigits;     // read-only - can be shared by multiple instances
   2266     private transient char[] decimalBuf;    // mutable - one per instance
   2267     private static final int DECIMAL_BUF_SIZE = 10; // sufficient for int numbers
   2268 
   2269     /*
   2270      * Lightweight zero padding integer number format function.
   2271      *
   2272      * Note: This implementation is almost equivalent to format method in DateNumberFormat.
   2273      * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
   2274      * but, it does not help IBM J9's JIT to optimize the performance much.  In simple repeative
   2275      * date format test case, having local implementation is ~10% faster than using one in
   2276      * DateNumberFormat on IBM J9 VM.  On Sun Hotspot VM, I do not see such difference.
   2277      *
   2278      * -Yoshito
   2279      */
   2280     private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {
   2281         int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
   2282         int index = limit - 1;
   2283         while (true) {
   2284             decimalBuf[index] = decDigits[(value % 10)];
   2285             value /= 10;
   2286             if (index == 0 || value == 0) {
   2287                 break;
   2288             }
   2289             index--;
   2290         }
   2291         int padding = minDigits - (limit - index);
   2292         while (padding > 0 && index > 0) {
   2293             decimalBuf[--index] = decDigits[0];
   2294             padding--;
   2295         }
   2296         while (padding > 0) {
   2297             // when pattern width is longer than decimalBuf, need extra
   2298             // leading zeros - ticke#7595
   2299             buf.append(decDigits[0]);
   2300             padding--;
   2301         }
   2302         buf.append(decimalBuf, index, limit - index);
   2303     }
   2304 
   2305     /**
   2306      * Formats a number with the specified minimum and maximum number of digits.
   2307      */
   2308     protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
   2309     {
   2310         numberFormat.setMinimumIntegerDigits(minDigits);
   2311         numberFormat.setMaximumIntegerDigits(maxDigits);
   2312         return numberFormat.format(value);
   2313     }
   2314 
   2315     /**
   2316      * Format characters that indicate numeric fields always.
   2317      */
   2318     private static final String NUMERIC_FORMAT_CHARS = "ADdFgHhKkmrSsuWwYy";
   2319 
   2320     /**
   2321      * Format characters that indicate numeric fields when pattern lengh
   2322      * is up to 2.
   2323      */
   2324     private static final String NUMERIC_FORMAT_CHARS2 = "ceLMQq";
   2325 
   2326     /**
   2327      * Return true if the given format character, occuring count
   2328      * times, represents a numeric field.
   2329      */
   2330     private static final boolean isNumeric(char formatChar, int count) {
   2331         return NUMERIC_FORMAT_CHARS.indexOf(formatChar) >= 0
   2332                 || (count <= 2 && NUMERIC_FORMAT_CHARS2.indexOf(formatChar) >= 0);
   2333     }
   2334 
   2335     /**
   2336      * Overrides DateFormat
   2337      * @see DateFormat
   2338      */
   2339     @Override
   2340     public void parse(String text, Calendar cal, ParsePosition parsePos)
   2341     {
   2342         TimeZone backupTZ = null;
   2343         Calendar resultCal = null;
   2344         if (cal != calendar && !cal.getType().equals(calendar.getType())) {
   2345             // Different calendar type
   2346             // We use the time/zone from the input calendar, but
   2347             // do not use the input calendar for field calculation.
   2348             calendar.setTimeInMillis(cal.getTimeInMillis());
   2349             backupTZ = calendar.getTimeZone();
   2350             calendar.setTimeZone(cal.getTimeZone());
   2351             resultCal = cal;
   2352             cal = calendar;
   2353         }
   2354 
   2355         int pos = parsePos.getIndex();
   2356         if(pos < 0) {
   2357             parsePos.setErrorIndex(0);
   2358             return;
   2359         }
   2360         int start = pos;
   2361 
   2362         // Hold the day period until everything else is parsed, because we need
   2363         // the hour to interpret time correctly.
   2364         // Using an one-element array for output parameter.
   2365         Output<DayPeriodRules.DayPeriod> dayPeriod = new Output<DayPeriodRules.DayPeriod>(null);
   2366 
   2367         Output<TimeType> tzTimeType = new Output<TimeType>(TimeType.UNKNOWN);
   2368         boolean[] ambiguousYear = { false };
   2369 
   2370         // item index for the first numeric field within a contiguous numeric run
   2371         int numericFieldStart = -1;
   2372         // item length for the first numeric field within a contiguous numeric run
   2373         int numericFieldLength = 0;
   2374         // start index of numeric text run in the input text
   2375         int numericStartPos = 0;
   2376 
   2377         MessageFormat numericLeapMonthFormatter = null;
   2378         if (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT) {
   2379             numericLeapMonthFormatter = new MessageFormat(formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC], locale);
   2380         }
   2381 
   2382         Object[] items = getPatternItems();
   2383         int i = 0;
   2384         while (i < items.length) {
   2385             if (items[i] instanceof PatternItem) {
   2386                 // Handle pattern field
   2387                 PatternItem field = (PatternItem)items[i];
   2388                 if (field.isNumeric) {
   2389                     // Handle fields within a run of abutting numeric fields.  Take
   2390                     // the pattern "HHmmss" as an example. We will try to parse
   2391                     // 2/2/2 characters of the input text, then if that fails,
   2392                     // 1/2/2.  We only adjust the width of the leftmost field; the
   2393                     // others remain fixed.  This allows "123456" => 12:34:56, but
   2394                     // "12345" => 1:23:45.  Likewise, for the pattern "yyyyMMdd" we
   2395                     // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
   2396                     if (numericFieldStart == -1) {
   2397                         // check if this field is followed by abutting another numeric field
   2398                         if ((i + 1) < items.length
   2399                                 && (items[i + 1] instanceof PatternItem)
   2400                                 && ((PatternItem)items[i + 1]).isNumeric) {
   2401                             // record the first numeric field within a numeric text run
   2402                             numericFieldStart = i;
   2403                             numericFieldLength = field.length;
   2404                             numericStartPos = pos;
   2405                         }
   2406                     }
   2407                 }
   2408                 if (numericFieldStart != -1) {
   2409                     // Handle a numeric field within abutting numeric fields
   2410                     int len = field.length;
   2411                     if (numericFieldStart == i) {
   2412                         len = numericFieldLength;
   2413                     }
   2414 
   2415                     // Parse a numeric field
   2416                     pos = subParse(text, pos, field.type, len,
   2417                             true, false, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType);
   2418 
   2419                     if (pos < 0) {
   2420                         // If the parse fails anywhere in the numeric run, back up to the
   2421                         // start of the run and use shorter pattern length for the first
   2422                         // numeric field.
   2423                         --numericFieldLength;
   2424                         if (numericFieldLength == 0) {
   2425                             // can not make shorter any more
   2426                             parsePos.setIndex(start);
   2427                             parsePos.setErrorIndex(pos);
   2428                             if (backupTZ != null) {
   2429                                 calendar.setTimeZone(backupTZ);
   2430                             }
   2431                             return;
   2432                         }
   2433                         i = numericFieldStart;
   2434                         pos = numericStartPos;
   2435                         continue;
   2436                     }
   2437 
   2438                 } else if (field.type != 'l') { // (SMALL LETTER L) obsolete pattern char just gets ignored
   2439                     // Handle a non-numeric field or a non-abutting numeric field
   2440                     numericFieldStart = -1;
   2441 
   2442                     int s = pos;
   2443                     pos = subParse(text, pos, field.type, field.length,
   2444                             false, true, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType, dayPeriod);
   2445 
   2446                     if (pos < 0) {
   2447                         if (pos == ISOSpecialEra) {
   2448                             // era not present, in special cases allow this to continue
   2449                             pos = s;
   2450 
   2451                             if (i+1 < items.length) {
   2452 
   2453                                 String patl = null;
   2454                                 // if it will cause a class cast exception to String, we can't use it
   2455                                 try {
   2456                                     patl = (String)items[i+1];
   2457                                 } catch(ClassCastException cce) {
   2458                                     parsePos.setIndex(start);
   2459                                     parsePos.setErrorIndex(s);
   2460                                     if (backupTZ != null) {
   2461                                         calendar.setTimeZone(backupTZ);
   2462                                     }
   2463                                     return;
   2464                                 }
   2465 
   2466                                 // get next item in pattern
   2467                                 if(patl == null)
   2468                                     patl = (String)items[i+1];
   2469                                 int plen = patl.length();
   2470                                 int idx=0;
   2471 
   2472                                 // White space characters found in pattern.
   2473                                 // Skip contiguous white spaces.
   2474                                 while (idx < plen) {
   2475 
   2476                                     char pch = patl.charAt(idx);
   2477                                     if (PatternProps.isWhiteSpace(pch))
   2478                                         idx++;
   2479                                     else
   2480                                         break;
   2481                                 }
   2482 
   2483                                 // if next item in pattern is all whitespace, skip it
   2484                                 if (idx == plen) {
   2485                                     i++;
   2486                                 }
   2487 
   2488                             }
   2489                         } else {
   2490                             parsePos.setIndex(start);
   2491                             parsePos.setErrorIndex(s);
   2492                             if (backupTZ != null) {
   2493                                 calendar.setTimeZone(backupTZ);
   2494                             }
   2495                             return;
   2496                         }
   2497                     }
   2498 
   2499                 }
   2500             } else {
   2501                 // Handle literal pattern text literal
   2502                 numericFieldStart = -1;
   2503                 boolean[] complete = new boolean[1];
   2504                 pos = matchLiteral(text, pos, items, i, complete);
   2505                 if (!complete[0]) {
   2506                     // Set the position of mismatch
   2507                     parsePos.setIndex(start);
   2508                     parsePos.setErrorIndex(pos);
   2509                     if (backupTZ != null) {
   2510                         calendar.setTimeZone(backupTZ);
   2511                     }
   2512                     return;
   2513                 }
   2514             }
   2515             ++i;
   2516         }
   2517 
   2518         // Special hack for trailing "." after non-numeric field.
   2519         if (pos < text.length()) {
   2520             char extra = text.charAt(pos);
   2521             if (extra == '.' && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && items.length != 0) {
   2522                 // only do if the last field is not numeric
   2523                 Object lastItem = items[items.length - 1];
   2524                 if (lastItem instanceof PatternItem && !((PatternItem)lastItem).isNumeric) {
   2525                     pos++; // skip the extra "."
   2526                 }
   2527             }
   2528         }
   2529 
   2530         // If dayPeriod is set, use it in conjunction with hour-of-day to determine am/pm.
   2531         if (dayPeriod.value != null) {
   2532             DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale());
   2533 
   2534             if (!cal.isSet(Calendar.HOUR) && !cal.isSet(Calendar.HOUR_OF_DAY)) {
   2535                 // If hour is not set, set time to the midpoint of current day period, overwriting
   2536                 // minutes if it's set.
   2537                 double midPoint = ruleSet.getMidPointForDayPeriod(dayPeriod.value);
   2538 
   2539                 // Truncate midPoint toward zero to get the hour.
   2540                 // Any leftover means it was a half-hour.
   2541                 int midPointHour = (int) midPoint;
   2542                 int midPointMinute = (midPoint - midPointHour) > 0 ? 30 : 0;
   2543 
   2544                 // No need to set am/pm because hour-of-day is set last therefore takes precedence.
   2545                 cal.set(Calendar.HOUR_OF_DAY, midPointHour);
   2546                 cal.set(Calendar.MINUTE, midPointMinute);
   2547             } else {
   2548                 int hourOfDay;
   2549 
   2550                 if (cal.isSet(Calendar.HOUR_OF_DAY)) {  // Hour is parsed in 24-hour format.
   2551                     hourOfDay = cal.get(Calendar.HOUR_OF_DAY);
   2552                 } else {  // Hour is parsed in 12-hour format.
   2553                     hourOfDay = cal.get(Calendar.HOUR);
   2554                     // cal.get() turns 12 to 0 for 12-hour time; change 0 to 12
   2555                     // so 0 unambiguously means a 24-hour time from above.
   2556                     if (hourOfDay == 0) { hourOfDay = 12; }
   2557                 }
   2558                 assert(0 <= hourOfDay && hourOfDay <= 23);
   2559 
   2560 
   2561                 // If hour-of-day is 0 or 13 thru 23 then input time in unambiguously in 24-hour format.
   2562                 if (hourOfDay == 0 || (13 <= hourOfDay && hourOfDay <= 23)) {
   2563                     // Make hour-of-day take precedence over (hour + am/pm) by setting it again.
   2564                     cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
   2565                 } else {
   2566                     // We have a 12-hour time and need to choose between am and pm.
   2567                     // Behave as if dayPeriod spanned 6 hours each way from its center point.
   2568                     // This will parse correctly for consistent time + period (e.g. 10 at night) as
   2569                     // well as provide a reasonable recovery for inconsistent time + period (e.g.
   2570                     // 9 in the afternoon).
   2571 
   2572                     // Assume current time is in the AM.
   2573                     // - Change 12 back to 0 for easier handling of 12am.
   2574                     // - Append minutes as fractional hours because e.g. 8:15 and 8:45 could be parsed
   2575                     // into different half-days if center of dayPeriod is at 14:30.
   2576                     // - cal.get(MINUTE) will return 0 if MINUTE is unset, which works.
   2577                     if (hourOfDay == 12) { hourOfDay = 0; }
   2578                     double currentHour = hourOfDay + cal.get(Calendar.MINUTE) / 60.0;
   2579                     double midPointHour = ruleSet.getMidPointForDayPeriod(dayPeriod.value);
   2580 
   2581                     double hoursAheadMidPoint = currentHour - midPointHour;
   2582 
   2583                     // Assume current time is in the AM.
   2584                     if (-6 <= hoursAheadMidPoint && hoursAheadMidPoint < 6) {
   2585                         // Assumption holds; set time as such.
   2586                         cal.set(Calendar.AM_PM, 0);
   2587                     } else {
   2588                         cal.set(Calendar.AM_PM, 1);
   2589                     }
   2590                 }
   2591             }
   2592         }
   2593 
   2594         // At this point the fields of Calendar have been set.  Calendar
   2595         // will fill in default values for missing fields when the time
   2596         // is computed.
   2597 
   2598         parsePos.setIndex(pos);
   2599 
   2600         // This part is a problem:  When we call parsedDate.after, we compute the time.
   2601         // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year
   2602         // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.
   2603         // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am
   2604         // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
   2605         // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we
   2606         // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is
   2607         // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
   2608         /*
   2609           Date parsedDate = cal.getTime();
   2610           if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
   2611           cal.add(Calendar.YEAR, 100);
   2612           parsedDate = cal.getTime();
   2613           }
   2614         */
   2615         // Because of the above condition, save off the fields in case we need to readjust.
   2616         // The procedure we use here is not particularly efficient, but there is no other
   2617         // way to do this given the API restrictions present in Calendar.  We minimize
   2618         // inefficiency by only performing this computation when it might apply, that is,
   2619         // when the two-digit year is equal to the start year, and thus might fall at the
   2620         // front or the back of the default century.  This only works because we adjust
   2621         // the year correctly to start with in other cases -- see subParse().
   2622         try {
   2623             TimeType tztype = tzTimeType.value;
   2624             if (ambiguousYear[0] || tztype != TimeType.UNKNOWN) {
   2625                 // We need a copy of the fields, and we need to avoid triggering a call to
   2626                 // complete(), which will recalculate the fields.  Since we can't access
   2627                 // the fields[] array in Calendar, we clone the entire object.  This will
   2628                 // stop working if Calendar.clone() is ever rewritten to call complete().
   2629                 Calendar copy;
   2630                 if (ambiguousYear[0]) { // the two-digit year == the default start year
   2631                     copy = (Calendar)cal.clone();
   2632                     Date parsedDate = copy.getTime();
   2633                     if (parsedDate.before(getDefaultCenturyStart())) {
   2634                         // We can't use add here because that does a complete() first.
   2635                         cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
   2636                     }
   2637                 }
   2638                 if (tztype != TimeType.UNKNOWN) {
   2639                     copy = (Calendar)cal.clone();
   2640                     TimeZone tz = copy.getTimeZone();
   2641                     BasicTimeZone btz = null;
   2642                     if (tz instanceof BasicTimeZone) {
   2643                         btz = (BasicTimeZone)tz;
   2644                     }
   2645 
   2646                     // Get local millis
   2647                     copy.set(Calendar.ZONE_OFFSET, 0);
   2648                     copy.set(Calendar.DST_OFFSET, 0);
   2649                     long localMillis = copy.getTimeInMillis();
   2650 
   2651                     // Make sure parsed time zone type (Standard or Daylight)
   2652                     // matches the rule used by the parsed time zone.
   2653                     int[] offsets = new int[2];
   2654                     if (btz != null) {
   2655                         if (tztype == TimeType.STANDARD) {
   2656                             btz.getOffsetFromLocal(localMillis,
   2657                                     BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
   2658                         } else {
   2659                             btz.getOffsetFromLocal(localMillis,
   2660                                     BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
   2661                         }
   2662                     } else {
   2663                         // No good way to resolve ambiguous time at transition,
   2664                         // but following code work in most case.
   2665                         tz.getOffset(localMillis, true, offsets);
   2666 
   2667                         if (tztype == TimeType.STANDARD && offsets[1] != 0
   2668                             || tztype == TimeType.DAYLIGHT && offsets[1] == 0) {
   2669                             // Roll back one day and try it again.
   2670                             // Note: This code assumes 1. timezone transition only happens
   2671                             // once within 24 hours at max
   2672                             // 2. the difference of local offsets at the transition is
   2673                             // less than 24 hours.
   2674                             tz.getOffset(localMillis - (24*60*60*1000), true, offsets);
   2675                         }
   2676                     }
   2677 
   2678                     // Now, compare the results with parsed type, either standard or
   2679                     // daylight saving time
   2680                     int resolvedSavings = offsets[1];
   2681                     if (tztype == TimeType.STANDARD) {
   2682                         if (offsets[1] != 0) {
   2683                             // Override DST_OFFSET = 0 in the result calendar
   2684                             resolvedSavings = 0;
   2685                         }
   2686                     } else { // tztype == TZTYPE_DST
   2687                         if (offsets[1] == 0) {
   2688                             if (btz != null) {
   2689                                 long time = localMillis + offsets[0];
   2690                                 // We use the nearest daylight saving time rule.
   2691                                 TimeZoneTransition beforeTrs, afterTrs;
   2692                                 long beforeT = time, afterT = time;
   2693                                 int beforeSav = 0, afterSav = 0;
   2694 
   2695                                 // Search for DST rule before or on the time
   2696                                 while (true) {
   2697                                     beforeTrs = btz.getPreviousTransition(beforeT, true);
   2698                                     if (beforeTrs == null) {
   2699                                         break;
   2700                                     }
   2701                                     beforeT = beforeTrs.getTime() - 1;
   2702                                     beforeSav = beforeTrs.getFrom().getDSTSavings();
   2703                                     if (beforeSav != 0) {
   2704                                         break;
   2705                                     }
   2706                                 }
   2707 
   2708                                 // Search for DST rule after the time
   2709                                 while (true) {
   2710                                     afterTrs = btz.getNextTransition(afterT, false);
   2711                                     if (afterTrs == null) {
   2712                                         break;
   2713                                     }
   2714                                     afterT = afterTrs.getTime();
   2715                                     afterSav = afterTrs.getTo().getDSTSavings();
   2716                                     if (afterSav != 0) {
   2717                                         break;
   2718                                     }
   2719                                 }
   2720 
   2721                                 if (beforeTrs != null && afterTrs != null) {
   2722                                     if (time - beforeT > afterT - time) {
   2723                                         resolvedSavings = afterSav;
   2724                                     } else {
   2725                                         resolvedSavings = beforeSav;
   2726                                     }
   2727                                 } else if (beforeTrs != null && beforeSav != 0) {
   2728                                     resolvedSavings = beforeSav;
   2729                                 } else if (afterTrs != null && afterSav != 0) {
   2730                                     resolvedSavings = afterSav;
   2731                                 } else {
   2732                                     resolvedSavings = btz.getDSTSavings();
   2733                                 }
   2734                             } else {
   2735                                 resolvedSavings = tz.getDSTSavings();
   2736                             }
   2737                             if (resolvedSavings == 0) {
   2738                                 // Final fallback
   2739                                 resolvedSavings = millisPerHour;
   2740                             }
   2741                         }
   2742                     }
   2743                     cal.set(Calendar.ZONE_OFFSET, offsets[0]);
   2744                     cal.set(Calendar.DST_OFFSET, resolvedSavings);
   2745                 }
   2746             }
   2747         }
   2748         // An IllegalArgumentException will be thrown by Calendar.getTime()
   2749         // if any fields are out of range, e.g., MONTH == 17.
   2750         catch (IllegalArgumentException e) {
   2751             parsePos.setErrorIndex(pos);
   2752             parsePos.setIndex(start);
   2753             if (backupTZ != null) {
   2754                 calendar.setTimeZone(backupTZ);
   2755             }
   2756             return;
   2757         }
   2758         // Set the parsed result if local calendar is used
   2759         // instead of the input calendar
   2760         if (resultCal != null) {
   2761             resultCal.setTimeZone(cal.getTimeZone());
   2762             resultCal.setTimeInMillis(cal.getTimeInMillis());
   2763         }
   2764         // Restore the original time zone if required
   2765         if (backupTZ != null) {
   2766             calendar.setTimeZone(backupTZ);
   2767         }
   2768     }
   2769 
   2770     /**
   2771      * Matches text (starting at pos) with patl. Returns the new pos, and sets complete[0]
   2772      * if it matched the entire text. Whitespace sequences are treated as singletons.
   2773      * <p>If isLenient and if we fail to match the first time, some special hacks are put into place.
   2774      * <ul><li>we are between date and time fields, then one or more whitespace characters
   2775      * in the text are accepted instead.</li>
   2776      * <ul><li>we are after a non-numeric field, and the text starts with a ".", we skip it.</li>
   2777      * </ul>
   2778      */
   2779     private int matchLiteral(String text, int pos, Object[] items, int itemIndex, boolean[] complete) {
   2780         int originalPos = pos;
   2781         String patternLiteral = (String)items[itemIndex];
   2782         int plen = patternLiteral.length();
   2783         int tlen = text.length();
   2784         int idx = 0;
   2785         while (idx < plen && pos < tlen) {
   2786             char pch = patternLiteral.charAt(idx);
   2787             char ich = text.charAt(pos);
   2788             if (PatternProps.isWhiteSpace(pch)
   2789                 && PatternProps.isWhiteSpace(ich)) {
   2790                 // White space characters found in both patten and input.
   2791                 // Skip contiguous white spaces.
   2792                 while ((idx + 1) < plen &&
   2793                         PatternProps.isWhiteSpace(patternLiteral.charAt(idx + 1))) {
   2794                      ++idx;
   2795                 }
   2796                 while ((pos + 1) < tlen &&
   2797                         PatternProps.isWhiteSpace(text.charAt(pos + 1))) {
   2798                      ++pos;
   2799                 }
   2800             } else if (pch != ich) {
   2801                 if (ich == '.' && pos == originalPos && 0 < itemIndex && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
   2802                     Object before = items[itemIndex-1];
   2803                     if (before instanceof PatternItem) {
   2804                         boolean isNumeric = ((PatternItem) before).isNumeric;
   2805                         if (!isNumeric) {
   2806                             ++pos; // just update pos
   2807                             continue;
   2808                         }
   2809                     }
   2810                 } else if ((pch == ' ' || pch == '.') && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
   2811                     ++idx;
   2812                     continue;
   2813                 } else if (pos != originalPos && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH)) {
   2814                     ++idx;
   2815                     continue;
   2816                 }
   2817                 break;
   2818             }
   2819             ++idx;
   2820             ++pos;
   2821         }
   2822         complete[0] = idx == plen;
   2823         if (complete[0] == false && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && 0 < itemIndex && itemIndex < items.length - 1) {
   2824             // If fully lenient, accept " "* for any text between a date and a time field
   2825             // We don't go more lenient, because we don't want to accept "12/31" for "12:31".
   2826             // People may be trying to parse for a date, then for a time.
   2827             if (originalPos < tlen) {
   2828                 Object before = items[itemIndex-1];
   2829                 Object after = items[itemIndex+1];
   2830                 if (before instanceof PatternItem && after instanceof PatternItem) {
   2831                     char beforeType = ((PatternItem) before).type;
   2832                     char afterType = ((PatternItem) after).type;
   2833                     if (DATE_PATTERN_TYPE.contains(beforeType) != DATE_PATTERN_TYPE.contains(afterType)) {
   2834                         int newPos = originalPos;
   2835                         while (true) {
   2836                             char ich = text.charAt(newPos);
   2837                             if (!PatternProps.isWhiteSpace(ich)) {
   2838                                 break;
   2839                             }
   2840                             ++newPos;
   2841                         }
   2842                         complete[0] = newPos > originalPos;
   2843                         pos = newPos;
   2844                     }
   2845                 }
   2846             }
   2847         }
   2848         return pos;
   2849     }
   2850 
   2851     static final UnicodeSet DATE_PATTERN_TYPE = new UnicodeSet("[GyYuUQqMLlwWd]").freeze();
   2852 
   2853     /**
   2854      * Attempt to match the text at a given position against an array of
   2855      * strings.  Since multiple strings in the array may match (for
   2856      * example, if the array contains "a", "ab", and "abc", all will match
   2857      * the input string "abcd") the longest match is returned.  As a side
   2858      * effect, the given field of <code>cal</code> is set to the index
   2859      * of the best match, if there is one.
   2860      * @param text the time text being parsed.
   2861      * @param start where to start parsing.
   2862      * @param field the date field being parsed.
   2863      * @param data the string array to parsed.
   2864      * @param cal
   2865      * @return the new start position if matching succeeded; a negative
   2866      * number indicating matching failure, otherwise.  As a side effect,
   2867      * sets the <code>cal</code> field <code>field</code> to the index
   2868      * of the best match, if matching succeeded.
   2869      */
   2870     protected int matchString(String text, int start, int field, String[] data, Calendar cal)
   2871     {
   2872         return matchString(text, start, field, data, null, cal);
   2873     }
   2874 
   2875     /**
   2876      * Attempt to match the text at a given position against an array of
   2877      * strings.  Since multiple strings in the array may match (for
   2878      * example, if the array contains "a", "ab", and "abc", all will match
   2879      * the input string "abcd") the longest match is returned.  As a side
   2880      * effect, the given field of <code>cal</code> is set to the index
   2881      * of the best match, if there is one.
   2882      * @param text the time text being parsed.
   2883      * @param start where to start parsing.
   2884      * @param field the date field being parsed.
   2885      * @param data the string array to parsed.
   2886      * @param monthPattern leap month pattern, or null if none.
   2887      * @param cal
   2888      * @return the new start position if matching succeeded; a negative
   2889      * number indicating matching failure, otherwise.  As a side effect,
   2890      * sets the <code>cal</code> field <code>field</code> to the index
   2891      * of the best match, if matching succeeded.
   2892      * @deprecated This API is ICU internal only.
   2893      * @hide draft / provisional / internal are hidden on Android
   2894      */
   2895     @Deprecated
   2896     private int matchString(String text, int start, int field, String[] data, String monthPattern, Calendar cal)
   2897     {
   2898         int i = 0;
   2899         int count = data.length;
   2900 
   2901         if (field == Calendar.DAY_OF_WEEK) i = 1;
   2902 
   2903         // There may be multiple strings in the data[] array which begin with
   2904         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
   2905         // We keep track of the longest match, and return that.  Note that this
   2906         // unfortunately requires us to test all array elements.
   2907         int bestMatchLength = 0, bestMatch = -1;
   2908         int isLeapMonth = 0;
   2909         int matchLength = 0;
   2910 
   2911         for (; i<count; ++i)
   2912             {
   2913                 int length = data[i].length();
   2914                 // Always compare if we have no match yet; otherwise only compare
   2915                 // against potentially better matches (longer strings).
   2916                 if (length > bestMatchLength &&
   2917                     (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0)
   2918                     {
   2919                         bestMatch = i;
   2920                         bestMatchLength = matchLength;
   2921                         isLeapMonth = 0;
   2922                     }
   2923                 if (monthPattern != null) {
   2924                     String leapMonthName = SimpleFormatterImpl.formatRawPattern(
   2925                             monthPattern, 1, 1, data[i]);
   2926                     length = leapMonthName.length();
   2927                     if (length > bestMatchLength &&
   2928                         (matchLength = regionMatchesWithOptionalDot(text, start, leapMonthName, length)) >= 0)
   2929                         {
   2930                             bestMatch = i;
   2931                             bestMatchLength = matchLength;
   2932                             isLeapMonth = 1;
   2933                         }
   2934                  }
   2935             }
   2936         if (bestMatch >= 0)
   2937             {
   2938                 if (field >= 0) {
   2939                     if (field == Calendar.YEAR) {
   2940                         bestMatch++; // only get here for cyclic year names, which match 1-based years 1-60
   2941                     }
   2942                     cal.set(field, bestMatch);
   2943                     if (monthPattern != null) {
   2944                         cal.set(Calendar.IS_LEAP_MONTH, isLeapMonth);
   2945                     }
   2946                 }
   2947                 return start + bestMatchLength;
   2948             }
   2949         return ~start;
   2950     }
   2951 
   2952     private int regionMatchesWithOptionalDot(String text, int start, String data, int length) {
   2953         boolean matches = text.regionMatches(true, start, data, 0, length);
   2954         if (matches) {
   2955             return length;
   2956         }
   2957         if (data.length() > 0 && data.charAt(data.length()-1) == '.') {
   2958             if (text.regionMatches(true, start, data, 0, length-1)) {
   2959                 return length - 1;
   2960             }
   2961         }
   2962         return -1;
   2963     }
   2964 
   2965     /**
   2966      * Attempt to match the text at a given position against an array of quarter
   2967      * strings.  Since multiple strings in the array may match (for
   2968      * example, if the array contains "a", "ab", and "abc", all will match
   2969      * the input string "abcd") the longest match is returned.  As a side
   2970      * effect, the given field of <code>cal</code> is set to the index
   2971      * of the best match, if there is one.
   2972      * @param text the time text being parsed.
   2973      * @param start where to start parsing.
   2974      * @param field the date field being parsed.
   2975      * @param data the string array to parsed.
   2976      * @return the new start position if matching succeeded; a negative
   2977      * number indicating matching failure, otherwise.  As a side effect,
   2978      * sets the <code>cal</code> field <code>field</code> to the index
   2979      * of the best match, if matching succeeded.
   2980      */
   2981     protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
   2982     {
   2983         int i = 0;
   2984         int count = data.length;
   2985 
   2986         // There may be multiple strings in the data[] array which begin with
   2987         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
   2988         // We keep track of the longest match, and return that.  Note that this
   2989         // unfortunately requires us to test all array elements.
   2990         int bestMatchLength = 0, bestMatch = -1;
   2991         int matchLength = 0;
   2992         for (; i<count; ++i) {
   2993             int length = data[i].length();
   2994             // Always compare if we have no match yet; otherwise only compare
   2995             // against potentially better matches (longer strings).
   2996             if (length > bestMatchLength &&
   2997                 (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) {
   2998 
   2999                 bestMatch = i;
   3000                 bestMatchLength = matchLength;
   3001             }
   3002         }
   3003 
   3004         if (bestMatch >= 0) {
   3005             cal.set(field, bestMatch * 3);
   3006             return start + bestMatchLength;
   3007         }
   3008 
   3009         return -start;
   3010     }
   3011 
   3012     /**
   3013      * Similar to matchQuarterString but customized for day periods.
   3014      */
   3015     private int matchDayPeriodString(String text, int start, String[] data, int dataLength,
   3016             Output<DayPeriodRules.DayPeriod> dayPeriod)
   3017     {
   3018         int bestMatchLength = 0, bestMatch = -1;
   3019         int matchLength = 0;
   3020         for (int i = 0; i < dataLength; ++i) {
   3021             // Only try matching if the string exists.
   3022             if (data[i] != null) {
   3023                 int length = data[i].length();
   3024                 if (length > bestMatchLength &&
   3025                         (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) {
   3026                     bestMatch = i;
   3027                     bestMatchLength = matchLength;
   3028                 }
   3029             }
   3030         }
   3031 
   3032         if (bestMatch >= 0) {
   3033             dayPeriod.value = DayPeriodRules.DayPeriod.VALUES[bestMatch];
   3034             return start + bestMatchLength;
   3035         }
   3036 
   3037         return -start;
   3038     }
   3039 
   3040     /**
   3041      * Protected method that converts one field of the input string into a
   3042      * numeric field value in <code>cal</code>.  Returns -start (for
   3043      * ParsePosition) if failed.  Subclasses may override this method to
   3044      * modify or add parsing capabilities.
   3045      * @param text the time text to be parsed.
   3046      * @param start where to start parsing.
   3047      * @param ch the pattern character for the date field text to be parsed.
   3048      * @param count the count of a pattern character.
   3049      * @param obeyCount if true, then the next field directly abuts this one,
   3050      * and we should use the count to know when to stop parsing.
   3051      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
   3052      * is true, then a two-digit year was parsed and may need to be readjusted.
   3053      * @param cal
   3054      * @return the new start position if matching succeeded; a negative
   3055      * number indicating matching failure, otherwise.  As a side effect,
   3056      * set the appropriate field of <code>cal</code> with the parsed
   3057      * value.
   3058      */
   3059     protected int subParse(String text, int start, char ch, int count,
   3060                            boolean obeyCount, boolean allowNegative,
   3061                            boolean[] ambiguousYear, Calendar cal)
   3062     {
   3063         return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null);
   3064     }
   3065 
   3066     /**
   3067      * Overloading to provide default argument (null) for day period.
   3068      */
   3069     private int subParse(String text, int start, char ch, int count,
   3070             boolean obeyCount, boolean allowNegative,
   3071             boolean[] ambiguousYear, Calendar cal,
   3072             MessageFormat numericLeapMonthFormatter, Output<TimeType> tzTimeType) {
   3073         return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null, null);
   3074     }
   3075 
   3076     /**
   3077      * Protected method that converts one field of the input string into a
   3078      * numeric field value in <code>cal</code>.  Returns -start (for
   3079      * ParsePosition) if failed.  Subclasses may override this method to
   3080      * modify or add parsing capabilities.
   3081      * @param text the time text to be parsed.
   3082      * @param start where to start parsing.
   3083      * @param ch the pattern character for the date field text to be parsed.
   3084      * @param count the count of a pattern character.
   3085      * @param obeyCount if true, then the next field directly abuts this one,
   3086      * and we should use the count to know when to stop parsing.
   3087      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
   3088      * is true, then a two-digit year was parsed and may need to be readjusted.
   3089      * @param cal
   3090      * @param numericLeapMonthFormatter if non-null, used to parse numeric leap months.
   3091      * @param tzTimeType the type of parsed time zone - standard, daylight or unknown (output).
   3092      *      This parameter can be null if caller does not need the information.
   3093      * @return the new start position if matching succeeded; a negative
   3094      * number indicating matching failure, otherwise.  As a side effect,
   3095      * set the appropriate field of <code>cal</code> with the parsed
   3096      * value.
   3097      * @deprecated This API is ICU internal only.
   3098      * @hide draft / provisional / internal are hidden on Android
   3099      */
   3100     @Deprecated
   3101     @SuppressWarnings("fallthrough")
   3102     private int subParse(String text, int start, char ch, int count,
   3103                            boolean obeyCount, boolean allowNegative,
   3104                            boolean[] ambiguousYear, Calendar cal,
   3105                            MessageFormat numericLeapMonthFormatter, Output<TimeType> tzTimeType,
   3106                            Output<DayPeriodRules.DayPeriod> dayPeriod)
   3107     {
   3108         Number number = null;
   3109         NumberFormat currentNumberFormat = null;
   3110         int value = 0;
   3111         int i;
   3112         ParsePosition pos = new ParsePosition(0);
   3113 
   3114         int patternCharIndex = getIndexFromChar(ch);
   3115         if (patternCharIndex == -1) {
   3116             return ~start;
   3117         }
   3118 
   3119         currentNumberFormat = getNumberFormat(ch);
   3120 
   3121         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; // -1 if irrelevant
   3122 
   3123         if (numericLeapMonthFormatter != null) {
   3124             numericLeapMonthFormatter.setFormatByArgumentIndex(0, currentNumberFormat);
   3125         }
   3126         boolean isChineseCalendar = ( cal.getType().equals("chinese") || cal.getType().equals("dangi") );
   3127 
   3128         // If there are any spaces here, skip over them.  If we hit the end
   3129         // of the string, then fail.
   3130         for (;;) {
   3131             if (start >= text.length()) {
   3132                 return ~start;
   3133             }
   3134             int c = UTF16.charAt(text, start);
   3135             if (!UCharacter.isUWhiteSpace(c) || !PatternProps.isWhiteSpace(c)) {
   3136                 break;
   3137             }
   3138             start += UTF16.getCharCount(c);
   3139         }
   3140         pos.setIndex(start);
   3141 
   3142         // We handle a few special cases here where we need to parse
   3143         // a number value.  We handle further, more generic cases below.  We need
   3144         // to handle some of them here because some fields require extra processing on
   3145         // the parsed value.
   3146         if (patternCharIndex == 4 /*'k' HOUR_OF_DAY1_FIELD*/ ||
   3147             patternCharIndex == 15 /*'h' HOUR1_FIELD*/ ||
   3148             (patternCharIndex == 2 /*'M' MONTH_FIELD*/ && count <= 2) ||
   3149             patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ ||
   3150             patternCharIndex == 19 /*'e' DOW_LOCAL*/ ||
   3151             patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ ||
   3152             patternCharIndex == 1 /*'y' YEAR */ || patternCharIndex == 18 /*'Y' YEAR_WOY */ ||
   3153             patternCharIndex == 30 /*'U' YEAR_NAME_FIELD, falls back to numeric */ ||
   3154             (patternCharIndex == 0 /*'G' ERA */ && isChineseCalendar) ||
   3155             patternCharIndex == 27 /* 'Q' - QUARTER*/ ||
   3156             patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/ ||
   3157             patternCharIndex == 8 /*'S' FRACTIONAL_SECOND */ )
   3158             {
   3159                 // It would be good to unify this with the obeyCount logic below,
   3160                 // but that's going to be difficult.
   3161 
   3162                 boolean parsedNumericLeapMonth = false;
   3163                 if (numericLeapMonthFormatter != null && (patternCharIndex == 2 || patternCharIndex == 26)) {
   3164                     // First see if we can parse month number with leap month pattern
   3165                     Object[] args = numericLeapMonthFormatter.parse(text, pos);
   3166                     if (args != null && pos.getIndex() > start && (args[0] instanceof Number)) {
   3167                         parsedNumericLeapMonth = true;
   3168                         number = (Number)args[0];
   3169                         cal.set(Calendar.IS_LEAP_MONTH, 1);
   3170                     } else {
   3171                         pos.setIndex(start);
   3172                         cal.set(Calendar.IS_LEAP_MONTH, 0);
   3173                    }
   3174                 }
   3175 
   3176                 if (!parsedNumericLeapMonth) {
   3177                     if (obeyCount) {
   3178                         if ((start+count) > text.length()) {
   3179                             return ~start;
   3180                         }
   3181                         number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
   3182                     } else {
   3183                         number = parseInt(text, pos, allowNegative,currentNumberFormat);
   3184                     }
   3185                     if (number == null && !allowNumericFallback(patternCharIndex)) {
   3186                         // only return if pattern is NOT one that allows numeric fallback
   3187                         return ~start;
   3188                     }
   3189                 }
   3190 
   3191                 if (number != null) {
   3192                     value = number.intValue();
   3193                 }
   3194             }
   3195 
   3196         switch (patternCharIndex)
   3197             {
   3198             case 0: // 'G' - ERA
   3199                 if ( isChineseCalendar ) {
   3200                     // Numeric era handling moved from ChineseDateFormat,
   3201                     // If we didn't have a number, already returned -start above
   3202                     cal.set(Calendar.ERA, value);
   3203                     return pos.getIndex();
   3204                 }
   3205                 int ps = 0;
   3206                 if (count == 5) {
   3207                     ps = matchString(text, start, Calendar.ERA, formatData.narrowEras, null, cal);
   3208                 } else if (count == 4) {
   3209                     ps = matchString(text, start, Calendar.ERA, formatData.eraNames, null, cal);
   3210                 } else {
   3211                     ps = matchString(text, start, Calendar.ERA, formatData.eras, null, cal);
   3212                 }
   3213 
   3214                 // check return position, if it equals -start, then matchString error
   3215                 // special case the return code so we don't necessarily fail out until we
   3216                 // verify no year information also
   3217                 if (ps == ~start)
   3218                     ps = ISOSpecialEra;
   3219 
   3220                 return ps;
   3221 
   3222             case 1: // 'y' - YEAR
   3223             case 18: // 'Y' - YEAR_WOY
   3224                 // If there are 3 or more YEAR pattern characters, this indicates
   3225                 // that the year value is to be treated literally, without any
   3226                 // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
   3227                 // we made adjustments to place the 2-digit year in the proper
   3228                 // century, for parsed strings from "00" to "99".  Any other string
   3229                 // is treated literally:  "2250", "-1", "1", "002".
   3230                 /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
   3231                 /* Skip this for Chinese calendar, moved from ChineseDateFormat */
   3232                 if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) && value < 1000 ) {
   3233                     value += HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
   3234                 } else if (count == 2 && countDigits(text, start, pos.getIndex()) == 2 && cal.haveDefaultCentury()) {
   3235                         // Assume for example that the defaultCenturyStart is 6/18/1903.
   3236                         // This means that two-digit years will be forced into the range
   3237                         // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
   3238                         // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
   3239                         // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
   3240                         // other fields specify a date before 6/18, or 1903 if they specify a
   3241                         // date afterwards.  As a result, 03 is an ambiguous year.  All other
   3242                         // two-digit years are unambiguous.
   3243                         int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
   3244                         ambiguousYear[0] = value == ambiguousTwoDigitYear;
   3245                         value += (getDefaultCenturyStartYear()/100)*100 +
   3246                             (value < ambiguousTwoDigitYear ? 100 : 0);
   3247                 }
   3248                 cal.set(field, value);
   3249 
   3250                 // Delayed checking for adjustment of Hebrew month numbers in non-leap years.
   3251                 if (DelayedHebrewMonthCheck) {
   3252                     if (!HebrewCalendar.isLeapYear(value)) {
   3253                         cal.add(Calendar.MONTH,1);
   3254                     }
   3255                     DelayedHebrewMonthCheck = false;
   3256                 }
   3257                 return pos.getIndex();
   3258             case 30: // 'U' - YEAR_NAME_FIELD
   3259                 if (formatData.shortYearNames != null) {
   3260                     int newStart = matchString(text, start, Calendar.YEAR, formatData.shortYearNames, null, cal);
   3261                     if (newStart > 0) {
   3262                         return newStart;
   3263                     }
   3264                 }
   3265                 if ( number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC) || formatData.shortYearNames == null || value > formatData.shortYearNames.length) ) {
   3266                     cal.set(Calendar.YEAR, value);
   3267                     return pos.getIndex();
   3268                 }
   3269                 return ~start;
   3270             case 2: // 'M' - MONTH
   3271             case 26: // 'L' - STAND_ALONE_MONTH
   3272                 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
   3273                     // i.e., M/MM, L/LL or lenient & have a number
   3274                     // Don't want to parse the month if it is a string
   3275                     // while pattern uses numeric style: M/MM, L/LL.
   3276                     // [We computed 'value' above.]
   3277                     cal.set(Calendar.MONTH, value - 1);
   3278                     // When parsing month numbers from the Hebrew Calendar, we might need
   3279                     // to adjust the month depending on whether or not it was a leap year.
   3280                     // We may or may not yet know what year it is, so might have to delay
   3281                     // checking until the year is parsed.
   3282                     if (cal.getType().equals("hebrew") && value >= 6) {
   3283                         if (cal.isSet(Calendar.YEAR)) {
   3284                             if (!HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR))) {
   3285                                 cal.set(Calendar.MONTH, value);
   3286                             }
   3287                         } else {
   3288                             DelayedHebrewMonthCheck = true;
   3289                         }
   3290                     }
   3291                     return pos.getIndex();
   3292                 } else {
   3293                     // count >= 3 // i.e., MMM/MMMM or LLL/LLLL
   3294                     // Want to be able to parse both short and long forms.
   3295                     boolean haveMonthPat = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT);
   3296                     // Try count == 4 first:, unless we're strict
   3297                     int newStart = 0;
   3298                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3299                         newStart = (patternCharIndex == 2)?
   3300                             matchString(text, start, Calendar.MONTH, formatData.months,
   3301                                     (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null, cal):
   3302                             matchString(text, start, Calendar.MONTH, formatData.standaloneMonths,
   3303                                     (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null, cal);
   3304                     if (newStart > 0) {
   3305                         return newStart;
   3306                         }
   3307                     }
   3308                     // count == 4 failed, now try count == 3
   3309                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3310                         return (patternCharIndex == 2)?
   3311                                 matchString(text, start, Calendar.MONTH, formatData.shortMonths,
   3312                                         (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null, cal):
   3313                                 matchString(text, start, Calendar.MONTH, formatData.standaloneShortMonths,
   3314                                         (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null, cal);
   3315                     }
   3316                     return newStart;
   3317                 }
   3318             case 4: // 'k' - HOUR_OF_DAY (1..24)
   3319                 // [We computed 'value' above.]
   3320                 if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) {
   3321                     value = 0;
   3322                 }
   3323                 cal.set(Calendar.HOUR_OF_DAY, value);
   3324                 return pos.getIndex();
   3325             case 8: // 'S' - FRACTIONAL_SECOND
   3326                 // Fractional seconds left-justify
   3327                 i = countDigits(text, start, pos.getIndex());
   3328                 if (i < 3) {
   3329                     while (i < 3) {
   3330                         value *= 10;
   3331                         i++;
   3332                     }
   3333                 } else {
   3334                     int a = 1;
   3335                     while (i > 3) {
   3336                         a *= 10;
   3337                         i--;
   3338                     }
   3339                     value /= a;
   3340                 }
   3341                 cal.set(Calendar.MILLISECOND, value);
   3342                 return pos.getIndex();
   3343             case 19: // 'e' - DOW_LOCAL
   3344                 if(count <= 2 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) {
   3345                     // i.e. e/ee or lenient and have a number
   3346                     cal.set(field, value);
   3347                     return pos.getIndex();
   3348                 }
   3349                 // else for eee-eeeeee, fall through to EEE-EEEEEE handling
   3350                 //$FALL-THROUGH$
   3351             case 9: { // 'E' - DAY_OF_WEEK
   3352                 // Want to be able to parse at least wide, abbrev, short, and narrow forms.
   3353                 int newStart = 0;
   3354                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3355                     if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.weekdays, null, cal)) > 0) { // try EEEE wide
   3356                         return newStart;
   3357                     }
   3358                 }
   3359                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3360                     if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shortWeekdays, null, cal)) > 0) { // try EEE abbrev
   3361                         return newStart;
   3362                     }
   3363                 }
   3364                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) {
   3365                     if (formatData.shorterWeekdays != null) {
   3366                         if((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shorterWeekdays, null, cal)) > 0) { // try EEEEEE short
   3367                             return newStart;
   3368                         }
   3369                     }
   3370                 }
   3371                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 5) {
   3372                     if (formatData.narrowWeekdays != null) {
   3373                         if((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.narrowWeekdays, null, cal)) > 0) { // try EEEEE narrow
   3374                             return newStart;
   3375                         }
   3376                     }
   3377                 }
   3378                 return newStart;
   3379             }
   3380             case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
   3381                 if(count == 1 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) {
   3382                     // i.e. c or lenient and have a number
   3383                     cal.set(field, value);
   3384                     return pos.getIndex();
   3385                 }
   3386                 // Want to be able to parse at least wide, abbrev, short forms.
   3387                 int newStart = 0;
   3388                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3389                     if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneWeekdays, null, cal)) > 0) { // try cccc wide
   3390                         return newStart;
   3391                     }
   3392                 }
   3393                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3394                     if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShortWeekdays, null, cal)) > 0) { // try ccc abbrev
   3395                         return newStart;
   3396                     }
   3397                 }
   3398                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) {
   3399                     if (formatData.standaloneShorterWeekdays != null) {
   3400                         return matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShorterWeekdays, null, cal); // try cccccc short
   3401                     }
   3402                 }
   3403                 return newStart;
   3404             }
   3405             case 14: { // 'a' - AM_PM
   3406                 // Optionally try both wide/abbrev and narrow forms.
   3407                 // formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version,
   3408                 // in which case our only option is wide form
   3409                 int newStart = 0;
   3410                 // try wide/abbrev a-aaaa
   3411                 if(formatData.ampmsNarrow == null || count < 5 || getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH)) {
   3412                     if ((newStart = matchString(text, start, Calendar.AM_PM, formatData.ampms, null, cal)) > 0) {
   3413                         return newStart;
   3414                     }
   3415                 }
   3416                 // try narrow aaaaa
   3417                 if(formatData.ampmsNarrow != null && (count >= 5 || getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH))) {
   3418                     if ((newStart = matchString(text, start, Calendar.AM_PM, formatData.ampmsNarrow, null, cal)) > 0) {
   3419                         return newStart;
   3420                     }
   3421                 }
   3422                 // no matches for given options
   3423                 return ~start;
   3424             }
   3425             case 15: // 'h' - HOUR (1..12)
   3426                 // [We computed 'value' above.]
   3427                 if (value == cal.getLeastMaximum(Calendar.HOUR)+1) {
   3428                     value = 0;
   3429                 }
   3430                 cal.set(Calendar.HOUR, value);
   3431                 return pos.getIndex();
   3432             case 17: // 'z' - ZONE_OFFSET
   3433             {
   3434                 Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.SPECIFIC_LONG;
   3435                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3436                 if (tz != null) {
   3437                     cal.setTimeZone(tz);
   3438                     return pos.getIndex();
   3439                 }
   3440                 return ~start;
   3441             }
   3442             case 23: // 'Z' - TIMEZONE_RFC
   3443             {
   3444                 Style style = (count < 4) ? Style.ISO_BASIC_LOCAL_FULL : ((count == 5) ? Style.ISO_EXTENDED_FULL : Style.LOCALIZED_GMT);
   3445                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3446                 if (tz != null) {
   3447                     cal.setTimeZone(tz);
   3448                     return pos.getIndex();
   3449                     }
   3450                 return ~start;
   3451                 }
   3452             case 24: // 'v' - TIMEZONE_GENERIC
   3453             {
   3454                 // Note: 'v' only supports count 1 and 4
   3455                 Style style = (count < 4) ? Style.GENERIC_SHORT : Style.GENERIC_LONG;
   3456                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3457                 if (tz != null) {
   3458                     cal.setTimeZone(tz);
   3459                     return pos.getIndex();
   3460                 }
   3461                 return ~start;
   3462             }
   3463             case 29: // 'V' - TIMEZONE_SPECIAL
   3464             {
   3465                 Style style = null;
   3466                 switch (count) {
   3467                 case 1:
   3468                     style = Style.ZONE_ID_SHORT;
   3469                     break;
   3470                 case 2:
   3471                     style = Style.ZONE_ID;
   3472                     break;
   3473                 case 3:
   3474                     style = Style.EXEMPLAR_LOCATION;
   3475                     break;
   3476                 default:
   3477                     style = Style.GENERIC_LOCATION;
   3478                     break;
   3479                 }
   3480                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3481                 if (tz != null) {
   3482                     cal.setTimeZone(tz);
   3483                     return pos.getIndex();
   3484                 }
   3485                 return ~start;
   3486             }
   3487             case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET
   3488             {
   3489                 Style style = (count < 4) ? Style.LOCALIZED_GMT_SHORT : Style.LOCALIZED_GMT;
   3490                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3491                 if (tz != null) {
   3492                     cal.setTimeZone(tz);
   3493                     return pos.getIndex();
   3494                 }
   3495                 return ~start;
   3496             }
   3497             case 32: // 'X' - TIMEZONE_ISO
   3498             {
   3499                 Style style;
   3500                 switch (count) {
   3501                 case 1:
   3502                     style = Style.ISO_BASIC_SHORT;
   3503                     break;
   3504                 case 2:
   3505                     style = Style.ISO_BASIC_FIXED;
   3506                     break;
   3507                 case 3:
   3508                     style = Style.ISO_EXTENDED_FIXED;
   3509                     break;
   3510                 case 4:
   3511                     style = Style.ISO_BASIC_FULL;
   3512                     break;
   3513                 default: // count >= 5
   3514                     style = Style.ISO_EXTENDED_FULL;
   3515                     break;
   3516                 }
   3517                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3518                 if (tz != null) {
   3519                     cal.setTimeZone(tz);
   3520                     return pos.getIndex();
   3521                 }
   3522                 return ~start;
   3523             }
   3524             case 33: // 'x' - TIMEZONE_ISO_LOCAL
   3525             {
   3526                 Style style;
   3527                 switch (count) {
   3528                 case 1:
   3529                     style = Style.ISO_BASIC_LOCAL_SHORT;
   3530                     break;
   3531                 case 2:
   3532                     style = Style.ISO_BASIC_LOCAL_FIXED;
   3533                     break;
   3534                 case 3:
   3535                     style = Style.ISO_EXTENDED_LOCAL_FIXED;
   3536                     break;
   3537                 case 4:
   3538                     style = Style.ISO_BASIC_LOCAL_FULL;
   3539                     break;
   3540                 default: // count >= 5
   3541                     style = Style.ISO_EXTENDED_LOCAL_FULL;
   3542                     break;
   3543                 }
   3544                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3545                 if (tz != null) {
   3546                     cal.setTimeZone(tz);
   3547                     return pos.getIndex();
   3548                 }
   3549                 return ~start;
   3550             }
   3551             case 27: // 'Q' - QUARTER
   3552                 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
   3553                     // i.e., Q or QQ. or lenient & have number
   3554                     // Don't want to parse the quarter if it is a string
   3555                     // while pattern uses numeric style: Q or QQ.
   3556                     // [We computed 'value' above.]
   3557                     cal.set(Calendar.MONTH, (value - 1) * 3);
   3558                     return pos.getIndex();
   3559                 } else {
   3560                     // count >= 3 // i.e., QQQ or QQQQ
   3561                     // Want to be able to parse both short and long forms.
   3562                     // Try count == 4 first:
   3563                     int newStart = 0;
   3564                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3565                         if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.quarters, cal)) > 0) {
   3566                             return newStart;
   3567                         }
   3568                     }
   3569                     // count == 4 failed, now try count == 3
   3570                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3571                         return matchQuarterString(text, start, Calendar.MONTH,
   3572                                            formatData.shortQuarters, cal);
   3573                     }
   3574                     return newStart;
   3575                 }
   3576 
   3577             case 28: // 'q' - STANDALONE QUARTER
   3578                 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
   3579                     // i.e., q or qq. or lenient & have number
   3580                     // Don't want to parse the quarter if it is a string
   3581                     // while pattern uses numeric style: q or qq.
   3582                     // [We computed 'value' above.]
   3583                     cal.set(Calendar.MONTH, (value - 1) * 3);
   3584                     return pos.getIndex();
   3585                 } else {
   3586                     // count >= 3 // i.e., qqq or qqqq
   3587                     // Want to be able to parse both short and long forms.
   3588                     // Try count == 4 first:
   3589                     int newStart = 0;
   3590                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3591                         if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.standaloneQuarters, cal)) > 0) {
   3592                             return newStart;
   3593                         }
   3594                     }
   3595                     // count == 4 failed, now try count == 3
   3596                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3597                         return matchQuarterString(text, start, Calendar.MONTH,
   3598                                            formatData.standaloneShortQuarters, cal);
   3599                     }
   3600                     return newStart;
   3601                 }
   3602 
   3603             case 37: // TIME SEPARATOR (no pattern character currently defined, we should
   3604                      // not get here but leave support in for future definition.
   3605             {
   3606                 // Try matching a time separator.
   3607                 ArrayList<String> data = new ArrayList<String>(3);
   3608                 data.add(formatData.getTimeSeparatorString());
   3609 
   3610                 // Add the default, if different from the locale.
   3611                 if (!formatData.getTimeSeparatorString().equals(DateFormatSymbols.DEFAULT_TIME_SEPARATOR)) {
   3612                     data.add(DateFormatSymbols.DEFAULT_TIME_SEPARATOR);
   3613                 }
   3614 
   3615                 // If lenient, add also the alternate, if different from the locale.
   3616                 if (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH) &&
   3617                         !formatData.getTimeSeparatorString().equals(DateFormatSymbols.ALTERNATE_TIME_SEPARATOR)) {
   3618                     data.add(DateFormatSymbols.ALTERNATE_TIME_SEPARATOR);
   3619                 }
   3620 
   3621                 return matchString(text, start, -1 /* => nothing to set */, data.toArray(new String[0]), cal);
   3622             }
   3623 
   3624             case 35: // 'b' -- fixed day period (am/pm/midnight/noon)
   3625             {
   3626                 int ampmStart = subParse(text, start, 'a', count, obeyCount, allowNegative, ambiguousYear, cal,
   3627                         numericLeapMonthFormatter, tzTimeType, dayPeriod);
   3628 
   3629                 if (ampmStart > 0) {
   3630                     return ampmStart;
   3631                 } else {
   3632                     int newStart = 0;
   3633                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3634                         if ((newStart = matchDayPeriodString(
   3635                                 text, start, formatData.abbreviatedDayPeriods, 2, dayPeriod)) > 0) {
   3636                             return newStart;
   3637                         }
   3638                     }
   3639                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3640                         if ((newStart = matchDayPeriodString(
   3641                                 text, start, formatData.wideDayPeriods, 2, dayPeriod)) > 0) {
   3642                             return newStart;
   3643                         }
   3644                     }
   3645                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3646                         if ((newStart = matchDayPeriodString(
   3647                                 text, start, formatData.narrowDayPeriods, 2, dayPeriod)) > 0) {
   3648                             return newStart;
   3649                         }
   3650                     }
   3651 
   3652                     return newStart;
   3653                 }
   3654             }
   3655 
   3656             case 36: // 'B' -- flexible day period
   3657             {
   3658                 int newStart = 0;
   3659                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3660                     if ((newStart = matchDayPeriodString(
   3661                             text, start, formatData.abbreviatedDayPeriods,
   3662                             formatData.abbreviatedDayPeriods.length, dayPeriod)) > 0) {
   3663                         return newStart;
   3664                     }
   3665                 }
   3666                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3667                     if ((newStart = matchDayPeriodString(
   3668                             text, start, formatData.wideDayPeriods,
   3669                             formatData.wideDayPeriods.length, dayPeriod)) > 0) {
   3670                         return newStart;
   3671                     }
   3672                 }
   3673                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3674                     if ((newStart = matchDayPeriodString(
   3675                             text, start, formatData.narrowDayPeriods,
   3676                             formatData.narrowDayPeriods.length, dayPeriod)) > 0) {
   3677                         return newStart;
   3678                     }
   3679                 }
   3680 
   3681                 return newStart;
   3682             }
   3683 
   3684             default:
   3685                 // case 3: // 'd' - DATE
   3686                 // case 5: // 'H' - HOUR_OF_DAY (0..23)
   3687                 // case 6: // 'm' - MINUTE
   3688                 // case 7: // 's' - SECOND
   3689                 // case 10: // 'D' - DAY_OF_YEAR
   3690                 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
   3691                 // case 12: // 'w' - WEEK_OF_YEAR
   3692                 // case 13: // 'W' - WEEK_OF_MONTH
   3693                 // case 16: // 'K' - HOUR (0..11)
   3694                 // case 20: // 'u' - EXTENDED_YEAR
   3695                 // case 21: // 'g' - JULIAN_DAY
   3696                 // case 22: // 'A' - MILLISECONDS_IN_DAY
   3697                 // case 34: //
   3698 
   3699                 // Handle "generic" fields
   3700                 if (obeyCount) {
   3701                     if ((start+count) > text.length()) return -start;
   3702                     number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
   3703                 } else {
   3704                     number = parseInt(text, pos, allowNegative,currentNumberFormat);
   3705                 }
   3706                 if (number != null) {
   3707                     if (patternCharIndex != DateFormat.RELATED_YEAR) {
   3708                         cal.set(field, number.intValue());
   3709                     } else {
   3710                         cal.setRelatedYear(number.intValue());
   3711                     }
   3712                     return pos.getIndex();
   3713                 }
   3714                 return ~start;
   3715             }
   3716     }
   3717 
   3718     /**
   3719      * return true if the pattern specified by patternCharIndex is one that allows
   3720      * numeric fallback regardless of actual pattern size.
   3721      */
   3722     private boolean allowNumericFallback(int patternCharIndex) {
   3723         if (patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ ||
   3724             patternCharIndex == 19 /*'e' DOW_LOCAL*/ ||
   3725             patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ ||
   3726             patternCharIndex == 30 /*'U' YEAR_NAME_FIELD*/ ||
   3727             patternCharIndex == 27 /* 'Q' - QUARTER*/ ||
   3728             patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/) {
   3729             return true;
   3730         }
   3731         return false;
   3732     }
   3733 
   3734     /**
   3735      * Parse an integer using numberFormat.  This method is semantically
   3736      * const, but actually may modify fNumberFormat.
   3737      */
   3738     private Number parseInt(String text,
   3739                             ParsePosition pos,
   3740                             boolean allowNegative,
   3741                             NumberFormat fmt) {
   3742         return parseInt(text, -1, pos, allowNegative, fmt);
   3743     }
   3744 
   3745     /**
   3746      * Parse an integer using numberFormat up to maxDigits.
   3747      */
   3748     private Number parseInt(String text,
   3749                             int maxDigits,
   3750                             ParsePosition pos,
   3751                             boolean allowNegative,
   3752                             NumberFormat fmt) {
   3753         Number number;
   3754         int oldPos = pos.getIndex();
   3755         if (allowNegative) {
   3756             number = fmt.parse(text, pos);
   3757         } else {
   3758             // Invalidate negative numbers
   3759             if (fmt instanceof DecimalFormat) {
   3760                 String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();
   3761                 ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
   3762                 number = fmt.parse(text, pos);
   3763                 ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);
   3764             } else {
   3765                 boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
   3766                 if (dateNumberFormat) {
   3767                     ((DateNumberFormat)fmt).setParsePositiveOnly(true);
   3768                 }
   3769                 number = fmt.parse(text, pos);
   3770                 if (dateNumberFormat) {
   3771                     ((DateNumberFormat)fmt).setParsePositiveOnly(false);
   3772                 }
   3773             }
   3774         }
   3775         if (maxDigits > 0) {
   3776             // adjust the result to fit into
   3777             // the maxDigits and move the position back
   3778             int nDigits = pos.getIndex() - oldPos;
   3779             if (nDigits > maxDigits) {
   3780                 double val = number.doubleValue();
   3781                 nDigits -= maxDigits;
   3782                 while (nDigits > 0) {
   3783                     val /= 10;
   3784                     nDigits--;
   3785                 }
   3786                 pos.setIndex(oldPos + maxDigits);
   3787                 number = Integer.valueOf((int)val);
   3788             }
   3789         }
   3790         return number;
   3791     }
   3792 
   3793     /**
   3794      * Counts number of digit code points in the specified text.
   3795      *
   3796      * @param text  input text
   3797      * @param start start index, inclusive
   3798      * @param end   end index, exclusive
   3799      * @return  number of digits found in the text in the specified range.
   3800      */
   3801     private static int countDigits(String text, int start, int end) {
   3802         int numDigits = 0;
   3803         int idx = start;
   3804         while (idx < end) {
   3805             int cp = text.codePointAt(idx);
   3806             if (UCharacter.isDigit(cp)) {
   3807                 numDigits++;
   3808             }
   3809             idx += UCharacter.charCount(cp);
   3810         }
   3811         return numDigits;
   3812     }
   3813 
   3814     /**
   3815      * Translate a pattern, mapping each character in the from string to the
   3816      * corresponding character in the to string.
   3817      */
   3818     private String translatePattern(String pat, String from, String to) {
   3819         StringBuilder result = new StringBuilder();
   3820         boolean inQuote = false;
   3821         for (int i = 0; i < pat.length(); ++i) {
   3822             char c = pat.charAt(i);
   3823             if (inQuote) {
   3824                 if (c == '\'')
   3825                     inQuote = false;
   3826             } else {
   3827                 if (c == '\'') {
   3828                     inQuote = true;
   3829                 } else if (isSyntaxChar(c)) {
   3830                     int ci = from.indexOf(c);
   3831                     if (ci != -1) {
   3832                         c = to.charAt(ci);
   3833                     }
   3834                     // do not worry on translatepattern if the character is not listed
   3835                     // we do the validity check elsewhere
   3836                 }
   3837             }
   3838             result.append(c);
   3839         }
   3840         if (inQuote) {
   3841             throw new IllegalArgumentException("Unfinished quote in pattern");
   3842         }
   3843         return result.toString();
   3844     }
   3845 
   3846     /**
   3847      * Return a pattern string describing this date format.
   3848      */
   3849     public String toPattern() {
   3850         return pattern;
   3851     }
   3852 
   3853     /**
   3854      * Return a localized pattern string describing this date format.
   3855      * <p>
   3856      * <b>Note:</b> This implementation depends on {@link DateFormatSymbols#getLocalPatternChars()}
   3857      * to get localized format pattern characters. ICU does not include
   3858      * localized pattern character data, therefore, unless user sets localized
   3859      * pattern characters manually, this method returns the same result as
   3860      * {@link #toPattern()}.
   3861      */
   3862     public String toLocalizedPattern() {
   3863         return translatePattern(pattern,
   3864                                 DateFormatSymbols.patternChars,
   3865                                 formatData.localPatternChars);
   3866     }
   3867 
   3868     /**
   3869      * Apply the given unlocalized pattern string to this date format.
   3870      */
   3871     public void applyPattern(String pat)
   3872     {
   3873         this.pattern = pat;
   3874         parsePattern();
   3875 
   3876         setLocale(null, null);
   3877         // reset parsed pattern items
   3878         patternItems = null;
   3879     }
   3880 
   3881     /**
   3882      * Apply the given localized pattern string to this date format.
   3883      */
   3884     public void applyLocalizedPattern(String pat) {
   3885         this.pattern = translatePattern(pat,
   3886                                         formatData.localPatternChars,
   3887                                         DateFormatSymbols.patternChars);
   3888         setLocale(null, null);
   3889     }
   3890 
   3891     /**
   3892      * Gets the date/time formatting data.
   3893      * @return a copy of the date-time formatting data associated
   3894      * with this date-time formatter.
   3895      */
   3896     public DateFormatSymbols getDateFormatSymbols()
   3897     {
   3898         return (DateFormatSymbols)formatData.clone();
   3899     }
   3900 
   3901     /**
   3902      * Allows you to set the date/time formatting data.
   3903      * @param newFormatSymbols the new symbols
   3904      */
   3905     public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
   3906     {
   3907         this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
   3908     }
   3909 
   3910     /**
   3911      * Method for subclasses to access the DateFormatSymbols.
   3912      */
   3913     protected DateFormatSymbols getSymbols() {
   3914         return formatData;
   3915     }
   3916 
   3917     /**
   3918      * <strong>[icu]</strong> Gets the time zone formatter which this date/time
   3919      * formatter uses to format and parse a time zone.
   3920      *
   3921      * @return the time zone formatter which this date/time
   3922      * formatter uses.
   3923      */
   3924     public TimeZoneFormat getTimeZoneFormat() {
   3925         return tzFormat().freeze();
   3926     }
   3927 
   3928     /**
   3929      * <strong>[icu]</strong> Allows you to set the time zone formatter.
   3930      *
   3931      * @param tzfmt the new time zone formatter
   3932      */
   3933     public void setTimeZoneFormat(TimeZoneFormat tzfmt) {
   3934         if (tzfmt.isFrozen()) {
   3935             // If frozen, use it as is.
   3936             tzFormat = tzfmt;
   3937         } else {
   3938             // If not frozen, clone and freeze.
   3939             tzFormat = tzfmt.cloneAsThawed().freeze();
   3940         }
   3941     }
   3942 
   3943     /**
   3944      * Overrides Cloneable
   3945      */
   3946     @Override
   3947     public Object clone() {
   3948         SimpleDateFormat other = (SimpleDateFormat) super.clone();
   3949         other.formatData = (DateFormatSymbols) formatData.clone();
   3950         // We must create a new copy of work buffer used by
   3951         // the fast numeric field format code.
   3952         if (this.decimalBuf != null) {
   3953             other.decimalBuf = new char[DECIMAL_BUF_SIZE];
   3954         }
   3955         return other;
   3956     }
   3957 
   3958     /**
   3959      * Override hashCode.
   3960      * Generates the hash code for the SimpleDateFormat object
   3961      */
   3962     @Override
   3963     public int hashCode()
   3964     {
   3965         return pattern.hashCode();
   3966         // just enough fields for a reasonable distribution
   3967     }
   3968 
   3969     /**
   3970      * Override equals.
   3971      */
   3972     @Override
   3973     public boolean equals(Object obj)
   3974     {
   3975         if (!super.equals(obj)) return false; // super does class check
   3976         SimpleDateFormat that = (SimpleDateFormat) obj;
   3977         return (pattern.equals(that.pattern)
   3978                 && formatData.equals(that.formatData));
   3979     }
   3980 
   3981     /**
   3982      * Override writeObject.
   3983      * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectOutputStream.html
   3984      */
   3985     private void writeObject(ObjectOutputStream stream) throws IOException{
   3986         if (defaultCenturyStart == null) {
   3987             // if defaultCenturyStart is not yet initialized,
   3988             // calculate and set value before serialization.
   3989             initializeDefaultCenturyStart(defaultCenturyBase);
   3990         }
   3991         initializeTimeZoneFormat(false);
   3992         stream.defaultWriteObject();
   3993         stream.writeInt(getContext(DisplayContext.Type.CAPITALIZATION).value());
   3994     }
   3995 
   3996     /**
   3997      * Override readObject.
   3998      * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectInputStream.html
   3999      */
   4000     private void readObject(ObjectInputStream stream)
   4001         throws IOException, ClassNotFoundException {
   4002         stream.defaultReadObject();
   4003         int capitalizationSettingValue = (serialVersionOnStream > 1)? stream.readInt(): -1;
   4004         ///CLOVER:OFF
   4005         // don't have old serial data to test with
   4006         if (serialVersionOnStream < 1) {
   4007             // didn't have defaultCenturyStart field
   4008             defaultCenturyBase = System.currentTimeMillis();
   4009         }
   4010         ///CLOVER:ON
   4011         else {
   4012             // fill in dependent transient field
   4013             parseAmbiguousDatesAsAfter(defaultCenturyStart);
   4014         }
   4015         serialVersionOnStream = currentSerialVersion;
   4016         locale = getLocale(ULocale.VALID_LOCALE);
   4017         if (locale == null) {
   4018             // ICU4J 3.6 or older versions did not have UFormat locales
   4019             // in the serialized data. This is just for preventing the
   4020             // worst case scenario...
   4021             locale = ULocale.getDefault(Category.FORMAT);
   4022         }
   4023 
   4024         initLocalZeroPaddingNumberFormat();
   4025 
   4026         setContext(DisplayContext.CAPITALIZATION_NONE);
   4027         if (capitalizationSettingValue >= 0) {
   4028             for (DisplayContext context: DisplayContext.values()) {
   4029                 if (context.value() == capitalizationSettingValue) {
   4030                     setContext(context);
   4031                     break;
   4032                 }
   4033             }
   4034         }
   4035 
   4036         // if serialized pre-56 update & turned off partial match switch to new enum value
   4037         if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH) == false) {
   4038             setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH, false);
   4039         }
   4040 
   4041         parsePattern();
   4042     }
   4043 
   4044     /**
   4045      * Format the object to an attributed string, and return the corresponding iterator
   4046      * Overrides superclass method.
   4047      *
   4048      * @param obj The object to format
   4049      * @return <code>AttributedCharacterIterator</code> describing the formatted value.
   4050      */
   4051     @Override
   4052     public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
   4053         Calendar cal = calendar;
   4054         if (obj instanceof Calendar) {
   4055             cal = (Calendar)obj;
   4056         } else if (obj instanceof Date) {
   4057             calendar.setTime((Date)obj);
   4058         } else if (obj instanceof Number) {
   4059             calendar.setTimeInMillis(((Number)obj).longValue());
   4060         } else {
   4061             throw new IllegalArgumentException("Cannot format given Object as a Date");
   4062         }
   4063         StringBuffer toAppendTo = new StringBuffer();
   4064         FieldPosition pos = new FieldPosition(0);
   4065         List<FieldPosition> attributes = new ArrayList<FieldPosition>();
   4066         format(cal, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, attributes);
   4067 
   4068         AttributedString as = new AttributedString(toAppendTo.toString());
   4069 
   4070         // add DateFormat field attributes to the AttributedString
   4071         for (int i = 0; i < attributes.size(); i++) {
   4072             FieldPosition fp = attributes.get(i);
   4073             Format.Field attribute = fp.getFieldAttribute();
   4074             as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());
   4075         }
   4076         // return the CharacterIterator from AttributedString
   4077         return as.getIterator();
   4078     }
   4079 
   4080     /**
   4081      * Get the locale of this simple date formatter.
   4082      * It is package accessible. also used in DateIntervalFormat.
   4083      *
   4084      * @return   locale in this simple date formatter
   4085      */
   4086     ULocale getLocale()
   4087     {
   4088         return locale;
   4089     }
   4090 
   4091 
   4092 
   4093     /**
   4094      * Check whether the 'field' is smaller than all the fields covered in
   4095      * pattern, return true if it is.
   4096      * The sequence of calendar field,
   4097      * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
   4098      * @param field    the calendar field need to check against
   4099      * @return         true if the 'field' is smaller than all the fields
   4100      *                 covered in pattern. false otherwise.
   4101      */
   4102 
   4103     boolean isFieldUnitIgnored(int field) {
   4104         return isFieldUnitIgnored(pattern, field);
   4105     }
   4106 
   4107 
   4108     /*
   4109      * Check whether the 'field' is smaller than all the fields covered in
   4110      * pattern, return true if it is.
   4111      * The sequence of calendar field,
   4112      * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
   4113      * @param pattern  the pattern to check against
   4114      * @param field    the calendar field need to check against
   4115      * @return         true if the 'field' is smaller than all the fields
   4116      *                 covered in pattern. false otherwise.
   4117      */
   4118     static boolean isFieldUnitIgnored(String pattern, int field) {
   4119         int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
   4120         int level;
   4121         char ch;
   4122         boolean inQuote = false;
   4123         char prevCh = 0;
   4124         int count = 0;
   4125 
   4126         for (int i = 0; i < pattern.length(); ++i) {
   4127             ch = pattern.charAt(i);
   4128             if (ch != prevCh && count > 0) {
   4129                 level = getLevelFromChar(prevCh);
   4130                 if (fieldLevel <= level) {
   4131                     return false;
   4132                 }
   4133                 count = 0;
   4134             }
   4135             if (ch == '\'') {
   4136                 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
   4137                     ++i;
   4138                 } else {
   4139                     inQuote = ! inQuote;
   4140                 }
   4141             } else if (!inQuote && isSyntaxChar(ch)) {
   4142                 prevCh = ch;
   4143                 ++count;
   4144             }
   4145         }
   4146         if (count > 0) {
   4147             // last item
   4148             level = getLevelFromChar(prevCh);
   4149             if (fieldLevel <= level) {
   4150                 return false;
   4151             }
   4152         }
   4153         return true;
   4154     }
   4155 
   4156 
   4157     /**
   4158      * Format date interval by algorithm.
   4159      * It is supposed to be used only by CLDR survey tool.
   4160      *
   4161      * @param fromCalendar      calendar set to the from date in date interval
   4162      *                          to be formatted into date interval stirng
   4163      * @param toCalendar        calendar set to the to date in date interval
   4164      *                          to be formatted into date interval stirng
   4165      * @param appendTo          Output parameter to receive result.
   4166      *                          Result is appended to existing contents.
   4167      * @param pos               On input: an alignment field, if desired.
   4168      *                          On output: the offsets of the alignment field.
   4169      * @exception IllegalArgumentException when there is non-recognized
   4170      *                                     pattern letter
   4171      * @return                  Reference to 'appendTo' parameter.
   4172      * @deprecated This API is ICU internal only.
   4173      * @hide original deprecated declaration
   4174      * @hide draft / provisional / internal are hidden on Android
   4175      */
   4176     @Deprecated
   4177     public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
   4178                                                         Calendar toCalendar,
   4179                                                         StringBuffer appendTo,
   4180                                                         FieldPosition pos)
   4181                               throws IllegalArgumentException
   4182     {
   4183         // not support different calendar types and time zones
   4184         if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
   4185             throw new IllegalArgumentException("can not format on two different calendars");
   4186         }
   4187 
   4188         Object[] items = getPatternItems();
   4189         int diffBegin = -1;
   4190         int diffEnd = -1;
   4191 
   4192         /* look for different formatting string range */
   4193         // look for start of difference
   4194         try {
   4195             for (int i = 0; i < items.length; i++) {
   4196                 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
   4197                     diffBegin = i;
   4198                     break;
   4199                 }
   4200             }
   4201 
   4202             if ( diffBegin == -1 ) {
   4203                 // no difference, single date format
   4204                 return format(fromCalendar, appendTo, pos);
   4205             }
   4206 
   4207             // look for end of difference
   4208             for (int i = items.length-1; i >= diffBegin; i--) {
   4209                 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
   4210                     diffEnd = i;
   4211                     break;
   4212                 }
   4213             }
   4214         } catch ( IllegalArgumentException e ) {
   4215             throw new IllegalArgumentException(e.toString());
   4216         }
   4217 
   4218         // full range is different
   4219         if ( diffBegin == 0 && diffEnd == items.length-1 ) {
   4220             format(fromCalendar, appendTo, pos);
   4221             appendTo.append(" \u2013 "); // default separator
   4222             format(toCalendar, appendTo, pos);
   4223             return appendTo;
   4224         }
   4225 
   4226 
   4227         /* search for largest calendar field within the different range */
   4228         int highestLevel = 1000;
   4229         for (int i = diffBegin; i <= diffEnd; i++) {
   4230             if ( items[i] instanceof String) {
   4231                 continue;
   4232             }
   4233             PatternItem item = (PatternItem)items[i];
   4234             char ch = item.type;
   4235             int patternCharIndex = getIndexFromChar(ch);
   4236             if (patternCharIndex == -1) {
   4237                 throw new IllegalArgumentException("Illegal pattern character " +
   4238                                                    "'" + ch + "' in \"" +
   4239                                                    pattern + '"');
   4240             }
   4241 
   4242             if ( patternCharIndex < highestLevel ) {
   4243                 highestLevel = patternCharIndex;
   4244             }
   4245         }
   4246 
   4247         /* re-calculate diff range, including those calendar field which
   4248            is in lower level than the largest calendar field covered
   4249            in diff range calculated. */
   4250         try {
   4251             for (int i = 0; i < diffBegin; i++) {
   4252                 if ( lowerLevel(items, i, highestLevel) ) {
   4253                     diffBegin = i;
   4254                     break;
   4255                 }
   4256             }
   4257 
   4258 
   4259             for (int i = items.length-1; i > diffEnd; i--) {
   4260                 if ( lowerLevel(items, i, highestLevel) ) {
   4261                     diffEnd = i;
   4262                     break;
   4263                 }
   4264             }
   4265         } catch ( IllegalArgumentException e ) {
   4266             throw new IllegalArgumentException(e.toString());
   4267         }
   4268 
   4269 
   4270         // full range is different
   4271         if ( diffBegin == 0 && diffEnd == items.length-1 ) {
   4272             format(fromCalendar, appendTo, pos);
   4273             appendTo.append(" \u2013 "); // default separator
   4274             format(toCalendar, appendTo, pos);
   4275             return appendTo;
   4276         }
   4277 
   4278 
   4279         // formatting
   4280         // Initialize
   4281         pos.setBeginIndex(0);
   4282         pos.setEndIndex(0);
   4283         DisplayContext capSetting = getContext(DisplayContext.Type.CAPITALIZATION);
   4284 
   4285         // formatting date 1
   4286         for (int i = 0; i <= diffEnd; i++) {
   4287             if (items[i] instanceof String) {
   4288                 appendTo.append((String)items[i]);
   4289             } else {
   4290                 PatternItem item = (PatternItem)items[i];
   4291                 if (useFastFormat) {
   4292                     subFormat(appendTo, item.type, item.length, appendTo.length(),
   4293                               i, capSetting, pos, fromCalendar);
   4294                 } else {
   4295                     appendTo.append(subFormat(item.type, item.length, appendTo.length(),
   4296                                               i, capSetting, pos, fromCalendar));
   4297                 }
   4298             }
   4299         }
   4300 
   4301         appendTo.append(" \u2013 "); // default separator
   4302 
   4303         // formatting date 2
   4304         for (int i = diffBegin; i < items.length; i++) {
   4305             if (items[i] instanceof String) {
   4306                 appendTo.append((String)items[i]);
   4307             } else {
   4308                 PatternItem item = (PatternItem)items[i];
   4309                 if (useFastFormat) {
   4310                     subFormat(appendTo, item.type, item.length, appendTo.length(),
   4311                               i, capSetting, pos, toCalendar);
   4312                 } else {
   4313                     appendTo.append(subFormat(item.type, item.length, appendTo.length(),
   4314                                               i, capSetting, pos, toCalendar));
   4315                 }
   4316             }
   4317         }
   4318         return appendTo;
   4319     }
   4320 
   4321 
   4322     /**
   4323      * check whether the i-th item in 2 calendar is in different value.
   4324      *
   4325      * It is supposed to be used only by CLDR survey tool.
   4326      * It is used by intervalFormatByAlgorithm().
   4327      *
   4328      * @param fromCalendar   one calendar
   4329      * @param toCalendar     the other calendar
   4330      * @param items          pattern items
   4331      * @param i              the i-th item in pattern items
   4332      * @exception IllegalArgumentException when there is non-recognized
   4333      *                                     pattern letter
   4334      * @return               true is i-th item in 2 calendar is in different
   4335      *                       value, false otherwise.
   4336      */
   4337     private boolean diffCalFieldValue(Calendar fromCalendar,
   4338                                       Calendar toCalendar,
   4339                                       Object[] items,
   4340                                       int i) throws IllegalArgumentException {
   4341         if ( items[i] instanceof String) {
   4342             return false;
   4343         }
   4344         PatternItem item = (PatternItem)items[i];
   4345         char ch = item.type;
   4346         int patternCharIndex = getIndexFromChar(ch);
   4347         if (patternCharIndex == -1) {
   4348             throw new IllegalArgumentException("Illegal pattern character " +
   4349                                                "'" + ch + "' in \"" +
   4350                                                pattern + '"');
   4351         }
   4352 
   4353         final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
   4354         if (field >= 0) {
   4355             int value = fromCalendar.get(field);
   4356             int value_2 = toCalendar.get(field);
   4357             if ( value != value_2 ) {
   4358                 return true;
   4359             }
   4360         }
   4361         return false;
   4362     }
   4363 
   4364 
   4365     /**
   4366      * check whether the i-th item's level is lower than the input 'level'
   4367      *
   4368      * It is supposed to be used only by CLDR survey tool.
   4369      * It is used by intervalFormatByAlgorithm().
   4370      *
   4371      * @param items  the pattern items
   4372      * @param i      the i-th item in pattern items
   4373      * @param level  the level with which the i-th pattern item compared to
   4374      * @exception IllegalArgumentException when there is non-recognized
   4375      *                                     pattern letter
   4376      * @return       true if i-th pattern item is lower than 'level',
   4377      *               false otherwise
   4378      */
   4379     private boolean lowerLevel(Object[] items, int i, int level)
   4380                     throws IllegalArgumentException {
   4381         if (items[i] instanceof String) {
   4382             return false;
   4383         }
   4384         PatternItem item = (PatternItem)items[i];
   4385         char ch = item.type;
   4386         int patternCharIndex = getLevelFromChar(ch);
   4387         if (patternCharIndex == -1) {
   4388             throw new IllegalArgumentException("Illegal pattern character " +
   4389                                                "'" + ch + "' in \"" +
   4390                                                pattern + '"');
   4391         }
   4392 
   4393         if (patternCharIndex >= level) {
   4394             return true;
   4395         }
   4396         return false;
   4397     }
   4398 
   4399     /**
   4400      * allow the user to set the NumberFormat for several fields
   4401      * It can be a single field like: "y"(year) or "M"(month)
   4402      * It can be several field combined together: "yMd"(year, month and date)
   4403      * Note:
   4404      * 1 symbol field is enough for multiple symbol fields (so "y" will override "yy", "yyy")
   4405      * If the field is not numeric, then override has no effect (like "MMM" will use abbreviation, not numerical field)
   4406      *
   4407      * @param fields the fields to override
   4408      * @param overrideNF the NumbeferFormat used
   4409      * @exception IllegalArgumentException when the fields contain invalid field
   4410      */
   4411     public void setNumberFormat(String fields, NumberFormat overrideNF) {
   4412         overrideNF.setGroupingUsed(false);
   4413         String nsName = "$" + UUID.randomUUID().toString();
   4414 
   4415         // initialize mapping if not there
   4416         if (numberFormatters == null) {
   4417             numberFormatters = new HashMap<String, NumberFormat>();
   4418         }
   4419         if (overrideMap == null) {
   4420             overrideMap = new HashMap<Character, String>();
   4421         }
   4422 
   4423         // separate string into char and add to maps
   4424         for (int i = 0; i < fields.length(); i++) {
   4425             char field = fields.charAt(i);
   4426             if (DateFormatSymbols.patternChars.indexOf(field) == -1) {
   4427                 throw new IllegalArgumentException("Illegal field character " + "'" + field + "' in setNumberFormat.");
   4428             }
   4429             overrideMap.put(field, nsName);
   4430             numberFormatters.put(nsName, overrideNF);
   4431         }
   4432 
   4433         // Since one or more of the override number formatters might be complex,
   4434         // we can't rely on the fast numfmt where we have a partial field override.
   4435         useLocalZeroPaddingNumberFormat = false;
   4436     }
   4437 
   4438     /**
   4439      * give the NumberFormat used for the field like 'y'(year) and 'M'(year)
   4440      *
   4441      * @param field the field the user wants
   4442      * @return override NumberFormat used for the field
   4443      */
   4444     public NumberFormat getNumberFormat(char field) {
   4445         Character ovrField;
   4446         ovrField = Character.valueOf(field);
   4447         if (overrideMap != null && overrideMap.containsKey(ovrField)) {
   4448             String nsName = overrideMap.get(ovrField).toString();
   4449             NumberFormat nf = numberFormatters.get(nsName);
   4450             return nf;
   4451         } else {
   4452             return numberFormat;
   4453         }
   4454     }
   4455 
   4456     private void initNumberFormatters(ULocale loc) {
   4457 
   4458        numberFormatters = new HashMap<String, NumberFormat>();
   4459        overrideMap = new HashMap<Character, String>();
   4460        processOverrideString(loc,override);
   4461 
   4462     }
   4463 
   4464     private void processOverrideString(ULocale loc, String str) {
   4465 
   4466         if ( str == null || str.length() == 0 )
   4467             return;
   4468 
   4469         int start = 0;
   4470         int end;
   4471         String nsName;
   4472         Character ovrField;
   4473         boolean moreToProcess = true;
   4474         boolean fullOverride;
   4475 
   4476         while (moreToProcess) {
   4477             int delimiterPosition = str.indexOf(";",start);
   4478             if (delimiterPosition == -1) {
   4479                 moreToProcess = false;
   4480                 end = str.length();
   4481             } else {
   4482                 end = delimiterPosition;
   4483             }
   4484 
   4485             String currentString = str.substring(start,end);
   4486             int equalSignPosition = currentString.indexOf("=");
   4487             if (equalSignPosition == -1) { // Simple override string such as "hebrew"
   4488                nsName = currentString;
   4489                fullOverride = true;
   4490             } else { // Field specific override string such as "y=hebrew"
   4491                nsName = currentString.substring(equalSignPosition+1);
   4492                ovrField = Character.valueOf(currentString.charAt(0));
   4493                overrideMap.put(ovrField,nsName);
   4494                fullOverride = false;
   4495             }
   4496 
   4497             ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
   4498             NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
   4499             nf.setGroupingUsed(false);
   4500 
   4501             if (fullOverride) {
   4502                 setNumberFormat(nf);
   4503             } else {
   4504                 // Since one or more of the override number formatters might be complex,
   4505                 // we can't rely on the fast numfmt where we have a partial field override.
   4506                 useLocalZeroPaddingNumberFormat = false;
   4507             }
   4508 
   4509             if (!fullOverride && !numberFormatters.containsKey(nsName)) {
   4510                   numberFormatters.put(nsName,nf);
   4511             }
   4512 
   4513             start = delimiterPosition + 1;
   4514         }
   4515     }
   4516 
   4517     private void parsePattern() {
   4518         hasMinute = false;
   4519         hasSecond = false;
   4520 
   4521         boolean inQuote = false;
   4522         for (int i = 0; i < pattern.length(); ++i) {
   4523             char ch = pattern.charAt(i);
   4524             if (ch == '\'') {
   4525                 inQuote = !inQuote;
   4526             }
   4527             if (!inQuote) {
   4528                 if (ch == 'm') {
   4529                     hasMinute = true;
   4530                 }
   4531                 if (ch == 's') {
   4532                     hasSecond = true;
   4533                 }
   4534             }
   4535         }
   4536     }
   4537 }
   4538