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