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             String digitString = ns.getDescription();
   1119             // DateNumberFormat does not support non-BMP digits at this moment.
   1120             if (ns.isAlgorithmic() || digitString.length() != 10) {
   1121                 numberFormat = NumberFormat.getInstance(locale);
   1122             } else {
   1123                 String nsName = ns.getName();
   1124                 // Use a NumberFormat optimized for date formatting
   1125                 numberFormat = new DateNumberFormat(locale, digitString, nsName);
   1126             }
   1127         }
   1128         if (numberFormat instanceof DecimalFormat) {
   1129             fixNumberFormatForDates(numberFormat);
   1130         }
   1131 
   1132         // Note: deferring calendar calculation until when we really need it.
   1133         // Instead, we just record time of construction for backward compatibility.
   1134         defaultCenturyBase = System.currentTimeMillis();
   1135 
   1136         setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
   1137         initLocalZeroPaddingNumberFormat();
   1138 
   1139         if (override != null) {
   1140            initNumberFormatters(locale);
   1141         }
   1142 
   1143         parsePattern();
   1144     }
   1145 
   1146     /**
   1147      * Private method lazily instantiate the TimeZoneFormat field
   1148      * @param bForceUpdate when true, check if tzFormat is synchronized with
   1149      * the current numberFormat and update its digits if necessary. When false,
   1150      * this check is skipped.
   1151      */
   1152     private synchronized void initializeTimeZoneFormat(boolean bForceUpdate) {
   1153         if (bForceUpdate || tzFormat == null) {
   1154             tzFormat = TimeZoneFormat.getInstance(locale);
   1155 
   1156             String digits = null;
   1157             if (numberFormat instanceof DecimalFormat) {
   1158                 DecimalFormatSymbols decsym = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
   1159                 String[] strDigits = decsym.getDigitStringsLocal();
   1160                 // Note: TimeZoneFormat#setGMTOffsetDigits() does not support string array,
   1161                 // so we need to concatenate digits to make a single string.
   1162                 StringBuilder digitsBuf = new StringBuilder();
   1163                 for (String digit : strDigits) {
   1164                     digitsBuf.append(digit);
   1165                 }
   1166                 digits = digitsBuf.toString();
   1167             } else if (numberFormat instanceof DateNumberFormat) {
   1168                 digits = new String(((DateNumberFormat)numberFormat).getDigits());
   1169             }
   1170 
   1171             if (digits != null) {
   1172                 if (!tzFormat.getGMTOffsetDigits().equals(digits)) {
   1173                     if (tzFormat.isFrozen()) {
   1174                         tzFormat = tzFormat.cloneAsThawed();
   1175                     }
   1176                     tzFormat.setGMTOffsetDigits(digits);
   1177                 }
   1178             }
   1179         }
   1180     }
   1181 
   1182     /**
   1183      * Private method, returns non-null TimeZoneFormat.
   1184      * @return the TimeZoneFormat used by this formatter.
   1185      */
   1186     private TimeZoneFormat tzFormat() {
   1187         if (tzFormat == null) {
   1188             initializeTimeZoneFormat(false);
   1189         }
   1190         return tzFormat;
   1191     }
   1192 
   1193     // privates for the default pattern
   1194     private static ULocale cachedDefaultLocale = null;
   1195     private static String cachedDefaultPattern = null;
   1196     private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
   1197 
   1198     /*
   1199      * Returns the default date and time pattern (SHORT) for the default locale.
   1200      * This method is only used by the default SimpleDateFormat constructor.
   1201      */
   1202     private static synchronized String getDefaultPattern() {
   1203         ULocale defaultLocale = ULocale.getDefault(Category.FORMAT);
   1204         if (!defaultLocale.equals(cachedDefaultLocale)) {
   1205             cachedDefaultLocale = defaultLocale;
   1206             Calendar cal = Calendar.getInstance(cachedDefaultLocale);
   1207 
   1208             try {
   1209                 // Load the calendar data directly.
   1210                 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(
   1211                         ICUData.ICU_BASE_NAME, cachedDefaultLocale);
   1212                 String resourcePath = "calendar/" + cal.getType() + "/DateTimePatterns";
   1213                 ICUResourceBundle patternsRb= rb.findWithFallback(resourcePath);
   1214 
   1215                 if (patternsRb == null) {
   1216                     patternsRb = rb.findWithFallback("calendar/gregorian/DateTimePatterns");
   1217                 }
   1218                 if (patternsRb == null || patternsRb.getSize() < 9) {
   1219                     cachedDefaultPattern = FALLBACKPATTERN;
   1220                 } else {
   1221                     int defaultIndex = 8;
   1222                     if (patternsRb.getSize() >= 13) {
   1223                         defaultIndex += (SHORT + 1);
   1224                     }
   1225                     String basePattern = patternsRb.getString(defaultIndex);
   1226 
   1227                     cachedDefaultPattern = SimpleFormatterImpl.formatRawPattern(
   1228                             basePattern, 2, 2,
   1229                             patternsRb.getString(SHORT), patternsRb.getString(SHORT + 4));
   1230                 }
   1231             } catch (MissingResourceException e) {
   1232                 cachedDefaultPattern = FALLBACKPATTERN;
   1233             }
   1234         }
   1235         return cachedDefaultPattern;
   1236     }
   1237 
   1238     /* Define one-century window into which to disambiguate dates using
   1239      * two-digit years.
   1240      */
   1241     private void parseAmbiguousDatesAsAfter(Date startDate) {
   1242         defaultCenturyStart = startDate;
   1243         calendar.setTime(startDate);
   1244         defaultCenturyStartYear = calendar.get(Calendar.YEAR);
   1245     }
   1246 
   1247     /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
   1248      * The default start time is 80 years before the creation time of this object.
   1249      */
   1250     private void initializeDefaultCenturyStart(long baseTime) {
   1251         defaultCenturyBase = baseTime;
   1252         // clone to avoid messing up date stored in calendar object
   1253         // when this method is called while parsing
   1254         Calendar tmpCal = (Calendar)calendar.clone();
   1255         tmpCal.setTimeInMillis(baseTime);
   1256         tmpCal.add(Calendar.YEAR, -80);
   1257         defaultCenturyStart = tmpCal.getTime();
   1258         defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
   1259     }
   1260 
   1261     /* Gets the default century start date for this object */
   1262     private Date getDefaultCenturyStart() {
   1263         if (defaultCenturyStart == null) {
   1264             // not yet initialized
   1265             initializeDefaultCenturyStart(defaultCenturyBase);
   1266         }
   1267         return defaultCenturyStart;
   1268     }
   1269 
   1270     /* Gets the default century start year for this object */
   1271     private int getDefaultCenturyStartYear() {
   1272         if (defaultCenturyStart == null) {
   1273             // not yet initialized
   1274             initializeDefaultCenturyStart(defaultCenturyBase);
   1275         }
   1276         return defaultCenturyStartYear;
   1277     }
   1278 
   1279     /**
   1280      * Sets the 100-year period 2-digit years will be interpreted as being in
   1281      * to begin on the date the user specifies.
   1282      * @param startDate During parsing, two digit years will be placed in the range
   1283      * <code>startDate</code> to <code>startDate + 100 years</code>.
   1284      * @stable ICU 2.0
   1285      */
   1286     public void set2DigitYearStart(Date startDate) {
   1287         parseAmbiguousDatesAsAfter(startDate);
   1288     }
   1289 
   1290     /**
   1291      * Returns the beginning date of the 100-year period 2-digit years are interpreted
   1292      * as being within.
   1293      * @return the start of the 100-year period into which two digit years are
   1294      * parsed
   1295      * @stable ICU 2.0
   1296      */
   1297     public Date get2DigitYearStart() {
   1298         return getDefaultCenturyStart();
   1299     }
   1300 
   1301     /**
   1302      * {@icu} Set a particular DisplayContext value in the formatter,
   1303      * such as CAPITALIZATION_FOR_STANDALONE. Note: For getContext, see
   1304      * DateFormat.
   1305      *
   1306      * @param context The DisplayContext value to set.
   1307      * @stable ICU 53
   1308      */
   1309     // Here we override the DateFormat implementation in order to lazily initialize relevant items
   1310     @Override
   1311     public void setContext(DisplayContext context) {
   1312         super.setContext(context);
   1313         if (capitalizationBrkIter == null && (context==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
   1314               context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU ||
   1315               context==DisplayContext.CAPITALIZATION_FOR_STANDALONE)) {
   1316             capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
   1317         }
   1318     }
   1319 
   1320     /**
   1321      * Formats a date or time, which is the standard millis
   1322      * since January 1, 1970, 00:00:00 GMT.
   1323      * <p>Example: using the US locale:
   1324      * "yyyy.MM.dd G 'at' HH:mm:ss zzz" -&gt;&gt; 1996.07.10 AD at 15:08:56 PDT
   1325      * @param cal the calendar whose date-time value is to be formatted into a date-time string
   1326      * @param toAppendTo where the new date-time text is to be appended
   1327      * @param pos the formatting position. On input: an alignment field,
   1328      * if desired. On output: the offsets of the alignment field.
   1329      * @return the formatted date-time string.
   1330      * @see DateFormat
   1331      * @stable ICU 2.0
   1332      */
   1333     @Override
   1334     public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
   1335                                FieldPosition pos) {
   1336         TimeZone backupTZ = null;
   1337         if (cal != calendar && !cal.getType().equals(calendar.getType())) {
   1338             // Different calendar type
   1339             // We use the time and time zone from the input calendar, but
   1340             // do not use the input calendar for field calculation.
   1341             calendar.setTimeInMillis(cal.getTimeInMillis());
   1342             backupTZ = calendar.getTimeZone();
   1343             calendar.setTimeZone(cal.getTimeZone());
   1344             cal = calendar;
   1345         }
   1346         StringBuffer result = format(cal, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, null);
   1347         if (backupTZ != null) {
   1348             // Restore the original time zone
   1349             calendar.setTimeZone(backupTZ);
   1350         }
   1351         return result;
   1352     }
   1353 
   1354     // The actual method to format date. If List attributes is not null,
   1355     // then attribute information will be recorded.
   1356     private StringBuffer format(Calendar cal, DisplayContext capitalizationContext,
   1357             StringBuffer toAppendTo, FieldPosition pos, List<FieldPosition> attributes) {
   1358         // Initialize
   1359         pos.setBeginIndex(0);
   1360         pos.setEndIndex(0);
   1361 
   1362         // Careful: For best performance, minimize the number of calls
   1363         // to StringBuffer.append() by consolidating appends when
   1364         // possible.
   1365 
   1366         Object[] items = getPatternItems();
   1367         for (int i = 0; i < items.length; i++) {
   1368             if (items[i] instanceof String) {
   1369                 toAppendTo.append((String)items[i]);
   1370             } else {
   1371                 PatternItem item = (PatternItem)items[i];
   1372                 int start = 0;
   1373                 if (attributes != null) {
   1374                     // Save the current length
   1375                     start = toAppendTo.length();
   1376                 }
   1377                 if (useFastFormat) {
   1378                     subFormat(toAppendTo, item.type, item.length, toAppendTo.length(),
   1379                               i, capitalizationContext, pos, cal);
   1380                 } else {
   1381                     toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(),
   1382                                                 i, capitalizationContext, pos, cal));
   1383                 }
   1384                 if (attributes != null) {
   1385                     // Check the sub format length
   1386                     int end = toAppendTo.length();
   1387                     if (end - start > 0) {
   1388                         // Append the attribute to the list
   1389                         DateFormat.Field attr = patternCharToDateFormatField(item.type);
   1390                         FieldPosition fp = new FieldPosition(attr);
   1391                         fp.setBeginIndex(start);
   1392                         fp.setEndIndex(end);
   1393                         attributes.add(fp);
   1394                     }
   1395                 }
   1396             }
   1397         }
   1398         return toAppendTo;
   1399 
   1400     }
   1401 
   1402     // Map pattern character to index
   1403     private static final int[] PATTERN_CHAR_TO_INDEX =
   1404     {
   1405         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   1406     //
   1407         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   1408     //       !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /
   1409         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   1410     //   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
   1411         -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   1412     //   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
   1413         -1, 22, 36, -1, 10,  9, 11,  0,  5, -1, -1, 16, 26,  2, -1, 31,
   1414     //   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
   1415         -1, 27, -1,  8, -1, 30, 29, 13, 32, 18, 23, -1, -1, -1, -1, -1,
   1416     //   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
   1417         -1, 14, 35, 25,  3, 19, -1, 21, 15, -1, -1,  4, -1,  6, -1, -1,
   1418     //   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~
   1419         -1, 28, 34,  7, -1, 20, 24, 12, 33,  1, 17, -1, -1, -1, -1, -1,
   1420     };
   1421 
   1422     private static int getIndexFromChar(char ch) {
   1423         return ch < PATTERN_CHAR_TO_INDEX.length ? PATTERN_CHAR_TO_INDEX[ch & 0xff] : -1;
   1424     }
   1425 
   1426     // Map pattern character index to Calendar field number
   1427     private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
   1428     {
   1429         /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
   1430         /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
   1431         /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
   1432         /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
   1433         /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
   1434         /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
   1435         /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
   1436         /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1437         /*v*/   Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1438         /*c*/   Calendar.DOW_LOCAL,
   1439         /*L*/   Calendar.MONTH,
   1440         /*Qq*/  Calendar.MONTH, Calendar.MONTH,
   1441         /*V*/   Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1442         /*U*/   Calendar.YEAR,
   1443         /*O*/   Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1444         /*Xx*/  Calendar.ZONE_OFFSET /* also DST_OFFSET */, Calendar.ZONE_OFFSET /* also DST_OFFSET */,
   1445         /*r*/   Calendar.EXTENDED_YEAR /* not an exact match */,
   1446         /*bB*/  -1, -1 /* am/pm/midnight/noon and flexible day period fields; no mapping to calendar fields */
   1447         /*:*/   -1, /* => no useful mapping to any calendar field, can't use protected Calendar.BASE_FIELD_COUNT */
   1448     };
   1449 
   1450     // Map pattern character index to DateFormat field number
   1451     private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
   1452         /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
   1453         /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
   1454         /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
   1455         /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
   1456         /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
   1457         /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
   1458         /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
   1459         /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
   1460         /*v*/   DateFormat.TIMEZONE_GENERIC_FIELD,
   1461         /*c*/   DateFormat.STANDALONE_DAY_FIELD,
   1462         /*L*/   DateFormat.STANDALONE_MONTH_FIELD,
   1463         /*Qq*/  DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
   1464         /*V*/   DateFormat.TIMEZONE_SPECIAL_FIELD,
   1465         /*U*/   DateFormat.YEAR_NAME_FIELD,
   1466         /*O*/   DateFormat.TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD,
   1467         /*Xx*/  DateFormat.TIMEZONE_ISO_FIELD, DateFormat.TIMEZONE_ISO_LOCAL_FIELD,
   1468         /*r*/   DateFormat.RELATED_YEAR,
   1469         /*bB*/  DateFormat.AM_PM_MIDNIGHT_NOON_FIELD, DateFormat.FLEXIBLE_DAY_PERIOD_FIELD,
   1470         /*(no pattern character defined for this)*/   DateFormat.TIME_SEPARATOR,
   1471     };
   1472 
   1473     // Map pattern character index to DateFormat.Field
   1474     private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
   1475         /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
   1476         /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
   1477         /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
   1478         /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
   1479         /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
   1480         /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
   1481         /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
   1482         /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
   1483         /*v*/   DateFormat.Field.TIME_ZONE,
   1484         /*c*/   DateFormat.Field.DAY_OF_WEEK,
   1485         /*L*/   DateFormat.Field.MONTH,
   1486         /*Qq*/  DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
   1487         /*V*/   DateFormat.Field.TIME_ZONE,
   1488         /*U*/   DateFormat.Field.YEAR,
   1489         /*O*/   DateFormat.Field.TIME_ZONE,
   1490         /*Xx*/  DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE,
   1491         /*r*/   DateFormat.Field.RELATED_YEAR,
   1492         /*bB*/  DateFormat.Field.AM_PM_MIDNIGHT_NOON, DateFormat.Field.FLEXIBLE_DAY_PERIOD,
   1493         /*(no pattern character defined for this)*/   DateFormat.Field.TIME_SEPARATOR,
   1494     };
   1495 
   1496     /**
   1497      * Returns a DateFormat.Field constant associated with the specified format pattern
   1498      * character.
   1499      *
   1500      * @param ch The pattern character
   1501      * @return DateFormat.Field associated with the pattern character
   1502      *
   1503      * @stable ICU 3.8
   1504      */
   1505     protected DateFormat.Field patternCharToDateFormatField(char ch) {
   1506         int patternCharIndex = getIndexFromChar(ch);
   1507         if (patternCharIndex != -1) {
   1508             return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
   1509         }
   1510         return null;
   1511     }
   1512 
   1513     /**
   1514      * Formats a single field, given its pattern character.  Subclasses may
   1515      * override this method in order to modify or add formatting
   1516      * capabilities.
   1517      * @param ch the pattern character
   1518      * @param count the number of times ch is repeated in the pattern
   1519      * @param beginOffset the offset of the output string at the start of
   1520      * this field; used to set pos when appropriate
   1521      * @param pos receives the position of a field, when appropriate
   1522      * @param fmtData the symbols for this formatter
   1523      * @stable ICU 2.0
   1524      */
   1525     protected String subFormat(char ch, int count, int beginOffset,
   1526                                FieldPosition pos, DateFormatSymbols fmtData,
   1527                                Calendar cal)
   1528         throws IllegalArgumentException
   1529     {
   1530         // Note: formatData is ignored
   1531         return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal);
   1532     }
   1533 
   1534      /**
   1535      * Formats a single field. This is the version called internally; it
   1536      * adds fieldNum and capitalizationContext parameters.
   1537      *
   1538      * @internal
   1539      * @deprecated This API is ICU internal only.
   1540      */
   1541     @Deprecated
   1542     protected String subFormat(char ch, int count, int beginOffset,
   1543                                int fieldNum, DisplayContext capitalizationContext,
   1544                                FieldPosition pos,
   1545                                Calendar cal)
   1546     {
   1547         StringBuffer buf = new StringBuffer();
   1548         subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
   1549         return buf.toString();
   1550     }
   1551 
   1552    /**
   1553      * Formats a single field; useFastFormat variant.  Reuses a
   1554      * StringBuffer for results instead of creating a String on the
   1555      * heap for each call.
   1556      *
   1557      * NOTE We don't really need the beginOffset parameter, EXCEPT for
   1558      * the need to support the slow subFormat variant (above) which
   1559      * has to pass it in to us.
   1560      *
   1561      * @internal
   1562      * @deprecated This API is ICU internal only.
   1563      */
   1564     @Deprecated
   1565     @SuppressWarnings("fallthrough")
   1566     protected void subFormat(StringBuffer buf,
   1567                              char ch, int count, int beginOffset,
   1568                              int fieldNum, DisplayContext capitalizationContext,
   1569                              FieldPosition pos,
   1570                              Calendar cal) {
   1571 
   1572         final int maxIntCount = Integer.MAX_VALUE;
   1573         final int bufstart = buf.length();
   1574         TimeZone tz = cal.getTimeZone();
   1575         long date = cal.getTimeInMillis();
   1576         String result = null;
   1577 
   1578         int patternCharIndex = getIndexFromChar(ch);
   1579         if (patternCharIndex == -1) {
   1580             if (ch == 'l') { // (SMALL LETTER L) deprecated placeholder for leap month marker, ignore
   1581                 return;
   1582             } else {
   1583                 throw new IllegalArgumentException("Illegal pattern character " +
   1584                                                    "'" + ch + "' in \"" +
   1585                                                    pattern + '"');
   1586             }
   1587         }
   1588 
   1589         final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
   1590         int value = 0;
   1591         // Don't get value unless it is useful
   1592         if (field >= 0) {
   1593             value = (patternCharIndex != DateFormat.RELATED_YEAR)? cal.get(field): cal.getRelatedYear();
   1594         }
   1595 
   1596         NumberFormat currentNumberFormat = getNumberFormat(ch);
   1597         DateFormatSymbols.CapitalizationContextUsage capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.OTHER;
   1598 
   1599         switch (patternCharIndex) {
   1600         case 0: // 'G' - ERA
   1601             if ( cal.getType().equals("chinese") || cal.getType().equals("dangi") ) {
   1602                 // moved from ChineseDateFormat
   1603                 zeroPaddingNumber(currentNumberFormat, buf, value, 1, 9);
   1604             } else {
   1605                 if (count == 5) {
   1606                     safeAppend(formatData.narrowEras, value, buf);
   1607                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_NARROW;
   1608                 } else if (count == 4) {
   1609                     safeAppend(formatData.eraNames, value, buf);
   1610                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_WIDE;
   1611                 } else {
   1612                     safeAppend(formatData.eras, value, buf);
   1613                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_ABBREV;
   1614                 }
   1615             }
   1616             break;
   1617         case 30: // 'U' - YEAR_NAME_FIELD
   1618             if (formatData.shortYearNames != null && value <= formatData.shortYearNames.length) {
   1619                 safeAppend(formatData.shortYearNames, value-1, buf);
   1620                 break;
   1621             }
   1622             // else fall through to numeric year handling, do not break here
   1623         case 1: // 'y' - YEAR
   1624         case 18: // 'Y' - YEAR_WOY
   1625             if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) &&
   1626                     value > HEBREW_CAL_CUR_MILLENIUM_START_YEAR && value < HEBREW_CAL_CUR_MILLENIUM_END_YEAR ) {
   1627                 value -= HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
   1628             }
   1629             /* According to the specification, if the number of pattern letters ('y') is 2,
   1630              * the year is truncated to 2 digits; otherwise it is interpreted as a number.
   1631              * But the original code process 'y', 'yy', 'yyy' in the same way. and process
   1632              * patterns with 4 or more than 4 'y' characters in the same way.
   1633              * So I change the codes to meet the specification. [Richard/GCl]
   1634              */
   1635             if (count == 2) {
   1636                 zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
   1637             } else { //count = 1 or count > 2
   1638                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   1639             }
   1640             break;
   1641         case 2: // 'M' - MONTH
   1642         case 26: // 'L' - STANDALONE MONTH
   1643             if ( cal.getType().equals("hebrew")) {
   1644                 boolean isLeap = HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR));
   1645                 if (isLeap && value == 6 && count >= 3 ) {
   1646                     value = 13; // Show alternate form for Adar II in leap years in Hebrew calendar.
   1647                 }
   1648                 if (!isLeap && value >= 6 && count < 3 ) {
   1649                     value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7.
   1650                 }
   1651             }
   1652             int isLeapMonth = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT)?
   1653                      cal.get(Calendar.IS_LEAP_MONTH): 0;
   1654             // should consolidate the next section by using arrays of pointers & counts for the right symbols...
   1655             if (count == 5) {
   1656                 if (patternCharIndex == 2) {
   1657                     safeAppendWithMonthPattern(formatData.narrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_NARROW]: null);
   1658                 } else {
   1659                     safeAppendWithMonthPattern(formatData.standaloneNarrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW]: null);
   1660                 }
   1661                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_NARROW;
   1662             } else if (count == 4) {
   1663                 if (patternCharIndex == 2) {
   1664                     safeAppendWithMonthPattern(formatData.months, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null);
   1665                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
   1666                 } else {
   1667                     safeAppendWithMonthPattern(formatData.standaloneMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null);
   1668                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
   1669                 }
   1670             } else if (count == 3) {
   1671                 if (patternCharIndex == 2) {
   1672                     safeAppendWithMonthPattern(formatData.shortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null);
   1673                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
   1674                 } else {
   1675                     safeAppendWithMonthPattern(formatData.standaloneShortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null);
   1676                     capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
   1677                 }
   1678             } else {
   1679                 StringBuffer monthNumber = new StringBuffer();
   1680                 zeroPaddingNumber(currentNumberFormat, monthNumber, value+1, count, maxIntCount);
   1681                 String[] monthNumberStrings = new String[1];
   1682                 monthNumberStrings[0] = monthNumber.toString();
   1683                 safeAppendWithMonthPattern(monthNumberStrings, 0, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC]: null);
   1684             }
   1685             break;
   1686         case 4: // 'k' - HOUR_OF_DAY (1..24)
   1687             if (value == 0) {
   1688                 zeroPaddingNumber(currentNumberFormat,buf,
   1689                                   cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
   1690                                   count, maxIntCount);
   1691             } else {
   1692                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   1693             }
   1694             break;
   1695         case 8: // 'S' - FRACTIONAL_SECOND
   1696             // Fractional seconds left-justify
   1697             {
   1698                 numberFormat.setMinimumIntegerDigits(Math.min(3, count));
   1699                 numberFormat.setMaximumIntegerDigits(maxIntCount);
   1700                 if (count == 1) {
   1701                     value /= 100;
   1702                 } else if (count == 2) {
   1703                     value /= 10;
   1704                 }
   1705                 FieldPosition p = new FieldPosition(-1);
   1706                 numberFormat.format(value, buf, p);
   1707                 if (count > 3) {
   1708                     numberFormat.setMinimumIntegerDigits(count - 3);
   1709                     numberFormat.format(0L, buf, p);
   1710                 }
   1711             }
   1712             break;
   1713         case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
   1714             if (count < 3) {
   1715                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   1716                 break;
   1717             }
   1718             // For alpha day-of-week, we don't want DOW_LOCAL,
   1719             // we need the standard DAY_OF_WEEK.
   1720             value = cal.get(Calendar.DAY_OF_WEEK);
   1721             // fall through, do not break here
   1722         case 9: // 'E' - DAY_OF_WEEK
   1723             if (count == 5) {
   1724                 safeAppend(formatData.narrowWeekdays, value, buf);
   1725                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
   1726             } else if (count == 4) {
   1727                 safeAppend(formatData.weekdays, value, buf);
   1728                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
   1729             } else if (count == 6 && formatData.shorterWeekdays != null) {
   1730                 safeAppend(formatData.shorterWeekdays, value, buf);
   1731                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
   1732             } else {// count <= 3, use abbreviated form if exists
   1733                 safeAppend(formatData.shortWeekdays, value, buf);
   1734                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
   1735             }
   1736             break;
   1737         case 14: // 'a' - AM_PM
   1738             // formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version
   1739             if (count < 5 || formatData.ampmsNarrow == null) {
   1740                 safeAppend(formatData.ampms, value, buf);
   1741             } else {
   1742                 safeAppend(formatData.ampmsNarrow, value, buf);
   1743             }
   1744             break;
   1745         case 15: // 'h' - HOUR (1..12)
   1746             if (value == 0) {
   1747                 zeroPaddingNumber(currentNumberFormat,buf,
   1748                                   cal.getLeastMaximum(Calendar.HOUR)+1,
   1749                                   count, maxIntCount);
   1750             } else {
   1751                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   1752             }
   1753             break;
   1754 
   1755         case 17: // 'z' - TIMEZONE_FIELD
   1756             if (count < 4) {
   1757                 // "z", "zz", "zzz"
   1758                 result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date);
   1759                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
   1760             } else {
   1761                 result = tzFormat().format(Style.SPECIFIC_LONG, tz, date);
   1762                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
   1763             }
   1764             buf.append(result);
   1765             break;
   1766         case 23: // 'Z' - TIMEZONE_RFC_FIELD
   1767             if (count < 4) {
   1768                 // RFC822 format - equivalent to ISO 8601 local offset fixed width format
   1769                 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
   1770             } else if (count == 5) {
   1771                 // ISO 8601 extended format
   1772                 result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
   1773             } else {
   1774                 // long form, localized GMT pattern
   1775                 result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
   1776             }
   1777                 buf.append(result);
   1778             break;
   1779         case 24: // 'v' - TIMEZONE_GENERIC_FIELD
   1780             if (count == 1) {
   1781                 // "v"
   1782                 result = tzFormat().format(Style.GENERIC_SHORT, tz, date);
   1783                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
   1784             } else if (count == 4) {
   1785                 // "vvvv"
   1786                 result = tzFormat().format(Style.GENERIC_LONG, tz, date);
   1787                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
   1788             }
   1789             buf.append(result);
   1790             break;
   1791         case 29: // 'V' - TIMEZONE_SPECIAL_FIELD
   1792             if (count == 1) {
   1793                 // "V"
   1794                 result = tzFormat().format(Style.ZONE_ID_SHORT, tz, date);
   1795             } else if (count == 2) {
   1796                 // "VV"
   1797                 result = tzFormat().format(Style.ZONE_ID, tz, date);
   1798             } else if (count == 3) {
   1799                 // "VVV"
   1800                 result = tzFormat().format(Style.EXEMPLAR_LOCATION, tz, date);
   1801             } else if (count == 4) {
   1802                 // "VVVV"
   1803                 result = tzFormat().format(Style.GENERIC_LOCATION, tz, date);
   1804                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ZONE_LONG;
   1805             }
   1806             buf.append(result);
   1807             break;
   1808         case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
   1809             if (count == 1) {
   1810                 // "O" - Short Localized GMT format
   1811                 result = tzFormat().format(Style.LOCALIZED_GMT_SHORT, tz, date);
   1812             } else if (count == 4) {
   1813                 // "OOOO" - Localized GMT format
   1814                 result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
   1815             }
   1816             buf.append(result);
   1817             break;
   1818         case 32: // 'X' - TIMEZONE_ISO_FIELD
   1819             if (count == 1) {
   1820                 // "X" - ISO Basic/Short
   1821                 result = tzFormat().format(Style.ISO_BASIC_SHORT, tz, date);
   1822             } else if (count == 2) {
   1823                 // "XX" - ISO Basic/Fixed
   1824                 result = tzFormat().format(Style.ISO_BASIC_FIXED, tz, date);
   1825             } else if (count == 3) {
   1826                 // "XXX" - ISO Extended/Fixed
   1827                 result = tzFormat().format(Style.ISO_EXTENDED_FIXED, tz, date);
   1828             } else if (count == 4) {
   1829                 // "XXXX" - ISO Basic/Optional second field
   1830                 result = tzFormat().format(Style.ISO_BASIC_FULL, tz, date);
   1831             } else if (count == 5) {
   1832                 // "XXXXX" - ISO Extended/Optional second field
   1833                 result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
   1834             }
   1835             buf.append(result);
   1836             break;
   1837         case 33: // 'x' - TIMEZONE_ISO_LOCAL_FIELD
   1838             if (count == 1) {
   1839                 // "x" - ISO Local Basic/Short
   1840                 result = tzFormat().format(Style.ISO_BASIC_LOCAL_SHORT, tz, date);
   1841             } else if (count == 2) {
   1842                 // "x" - ISO Local Basic/Fixed
   1843                 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FIXED, tz, date);
   1844             } else if (count == 3) {
   1845                 // "xxx" - ISO Local Extended/Fixed
   1846                 result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FIXED, tz, date);
   1847             } else if (count == 4) {
   1848                 // "xxxx" - ISO Local Basic/Optional second field
   1849                 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
   1850             } else if (count == 5) {
   1851                 // "xxxxx" - ISO Local Extended/Optional second field
   1852                 result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FULL, tz, date);
   1853             }
   1854             buf.append(result);
   1855             break;
   1856 
   1857         case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone)
   1858             if (count < 3) {
   1859                 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
   1860                 break;
   1861             }
   1862             // For alpha day-of-week, we don't want DOW_LOCAL,
   1863             // we need the standard DAY_OF_WEEK.
   1864             value = cal.get(Calendar.DAY_OF_WEEK);
   1865             if (count == 5) {
   1866                 safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
   1867                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
   1868             } else if (count == 4) {
   1869                 safeAppend(formatData.standaloneWeekdays, value, buf);
   1870                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
   1871             } else if (count == 6 && formatData.standaloneShorterWeekdays != null) {
   1872                 safeAppend(formatData.standaloneShorterWeekdays, value, buf);
   1873                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
   1874             } else { // count == 3
   1875                 safeAppend(formatData.standaloneShortWeekdays, value, buf);
   1876                 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
   1877             }
   1878             break;
   1879         case 27: // 'Q' - QUARTER
   1880             if (count >= 4) {
   1881                 safeAppend(formatData.quarters, value/3, buf);
   1882             } else if (count == 3) {
   1883                 safeAppend(formatData.shortQuarters, value/3, buf);
   1884             } else {
   1885                 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
   1886             }
   1887             break;
   1888         case 28: // 'q' - STANDALONE QUARTER
   1889             if (count >= 4) {
   1890                 safeAppend(formatData.standaloneQuarters, value/3, buf);
   1891             } else if (count == 3) {
   1892                 safeAppend(formatData.standaloneShortQuarters, value/3, buf);
   1893             } else {
   1894                 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
   1895             }
   1896             break;
   1897         case 35: // 'b' - am/pm/noon/midnight
   1898         {
   1899             // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day.
   1900             // For ICU 57 output of "midnight" is temporarily suppressed.
   1901 
   1902             int hour = cal.get(Calendar.HOUR_OF_DAY);
   1903             String toAppend = null;
   1904 
   1905             // For "midnight" and "noon":
   1906             // Time, as displayed, must be exactly noon or midnight.
   1907             // This means minutes and seconds, if present, must be zero.
   1908             if ((/*hour == 0 ||*/ hour == 12) &&
   1909                     (!hasMinute || cal.get(Calendar.MINUTE) == 0) &&
   1910                     (!hasSecond || cal.get(Calendar.SECOND) == 0)) {
   1911                 // Stealing am/pm value to use as our array index.
   1912                 // It works out: am/midnight are both 0, pm/noon are both 1,
   1913                 // 12 am is 12 midnight, and 12 pm is 12 noon.
   1914                 value = cal.get(Calendar.AM_PM);
   1915 
   1916                 if (count <= 3) {
   1917                     toAppend = formatData.abbreviatedDayPeriods[value];
   1918                 } else if (count == 4 || count > 5) {
   1919                     toAppend = formatData.wideDayPeriods[value];
   1920                 } else { // count == 5
   1921                     toAppend = formatData.narrowDayPeriods[value];
   1922                 }
   1923             }
   1924 
   1925             if (toAppend == null) {
   1926                 // Time isn't exactly midnight or noon (as displayed) or localized string doesn't
   1927                 // exist for requested period. Fall back to am/pm instead.
   1928                 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
   1929             } else {
   1930                 buf.append(toAppend);
   1931             }
   1932 
   1933             break;
   1934         }
   1935         case 36: // 'B' - flexible day period
   1936         {
   1937             // TODO: Maybe fetch the DayperiodRules during initialization (instead of at the first
   1938             // loading of an instance) if a relevant pattern character (b or B) is used.
   1939             DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale());
   1940             if (ruleSet == null) {
   1941                 // Data doesn't exist for the locale we're looking for.
   1942                 // Fall back to am/pm.
   1943                 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
   1944                 break;
   1945             }
   1946 
   1947             // Get current display time.
   1948             int hour = cal.get(Calendar.HOUR_OF_DAY);
   1949             int minute = 0;
   1950             int second = 0;
   1951             if (hasMinute) { minute = cal.get(Calendar.MINUTE); }
   1952             if (hasSecond) { second = cal.get(Calendar.SECOND); }
   1953 
   1954             // Determine day period.
   1955             DayPeriodRules.DayPeriod periodType;
   1956             if (hour == 0 && minute == 0 && second == 0 && ruleSet.hasMidnight()) {
   1957                 periodType = DayPeriodRules.DayPeriod.MIDNIGHT;
   1958             } else if (hour == 12 && minute == 0 && second == 0 && ruleSet.hasNoon()) {
   1959                 periodType = DayPeriodRules.DayPeriod.NOON;
   1960             } else {
   1961                 periodType = ruleSet.getDayPeriodForHour(hour);
   1962             }
   1963 
   1964             // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day.
   1965             // For ICU 57 output of "midnight" is temporarily suppressed.
   1966 
   1967             // Rule set exists, therefore periodType can't be null.
   1968             // Get localized string.
   1969             assert(periodType != null);
   1970             String toAppend = null;
   1971             int index;
   1972 
   1973             if (periodType != DayPeriodRules.DayPeriod.AM && periodType != DayPeriodRules.DayPeriod.PM &&
   1974                     periodType != DayPeriodRules.DayPeriod.MIDNIGHT) {
   1975                 index = periodType.ordinal();
   1976                 if (count <= 3) {
   1977                     toAppend = formatData.abbreviatedDayPeriods[index];  // i.e. short
   1978                 } else if (count == 4 || count > 5) {
   1979                     toAppend = formatData.wideDayPeriods[index];
   1980                 } else {  // count == 5
   1981                     toAppend = formatData.narrowDayPeriods[index];
   1982                 }
   1983             }
   1984 
   1985 
   1986             // Fallback schedule:
   1987             // Midnight/Noon -> General Periods -> AM/PM.
   1988 
   1989             // Midnight/Noon -> General Periods.
   1990             if (toAppend == null &&
   1991                     (periodType == DayPeriodRules.DayPeriod.MIDNIGHT ||
   1992                      periodType == DayPeriodRules.DayPeriod.NOON)) {
   1993                 periodType = ruleSet.getDayPeriodForHour(hour);
   1994                 index = periodType.ordinal();
   1995 
   1996                 if (count <= 3) {
   1997                     toAppend = formatData.abbreviatedDayPeriods[index];  // i.e. short
   1998                 } else if (count == 4 || count > 5) {
   1999                     toAppend = formatData.wideDayPeriods[index];
   2000                 } else {  // count == 5
   2001                     toAppend = formatData.narrowDayPeriods[index];
   2002                 }
   2003             }
   2004 
   2005             // General Periods -> AM/PM.
   2006             if (periodType == DayPeriodRules.DayPeriod.AM ||
   2007                     periodType == DayPeriodRules.DayPeriod.PM ||
   2008                     toAppend == null) {
   2009                 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal);
   2010             }
   2011             else {
   2012                 buf.append(toAppend);
   2013             }
   2014 
   2015             break;
   2016         }
   2017         case 37: // TIME SEPARATOR (no pattern character currently defined, we should
   2018                  // not get here but leave support in for future definition.
   2019             buf.append(formatData.getTimeSeparatorString());
   2020             break;
   2021         default:
   2022             // case 3: // 'd' - DATE
   2023             // case 5: // 'H' - HOUR_OF_DAY (0..23)
   2024             // case 6: // 'm' - MINUTE
   2025             // case 7: // 's' - SECOND
   2026             // case 10: // 'D' - DAY_OF_YEAR
   2027             // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
   2028             // case 12: // 'w' - WEEK_OF_YEAR
   2029             // case 13: // 'W' - WEEK_OF_MONTH
   2030             // case 16: // 'K' - HOUR (0..11)
   2031             // case 20: // 'u' - EXTENDED_YEAR
   2032             // case 21: // 'g' - JULIAN_DAY
   2033             // case 22: // 'A' - MILLISECONDS_IN_DAY
   2034 
   2035             zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
   2036             break;
   2037         } // switch (patternCharIndex)
   2038 
   2039         if (fieldNum == 0 && capitalizationContext != null && UCharacter.isLowerCase(buf.codePointAt(bufstart))) {
   2040             boolean titlecase = false;
   2041             switch (capitalizationContext) {
   2042                 case CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE:
   2043                     titlecase = true;
   2044                     break;
   2045                 case CAPITALIZATION_FOR_UI_LIST_OR_MENU:
   2046                 case CAPITALIZATION_FOR_STANDALONE:
   2047                     if (formatData.capitalization != null) {
   2048                         boolean[] transforms = formatData.capitalization.get(capContextUsageType);
   2049                         titlecase = (capitalizationContext==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)?
   2050                                     transforms[0]: transforms[1];
   2051                     }
   2052                     break;
   2053                 default:
   2054                    break;
   2055             }
   2056             if (titlecase) {
   2057                 if (capitalizationBrkIter == null) {
   2058                     // should only happen when deserializing, etc.
   2059                     capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
   2060                 }
   2061                 String firstField = buf.substring(bufstart); // bufstart or beginOffset, should be the same
   2062                 String firstFieldTitleCase = UCharacter.toTitleCase(locale, firstField, capitalizationBrkIter,
   2063                                                      UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
   2064                 buf.replace(bufstart, buf.length(), firstFieldTitleCase);
   2065             }
   2066         }
   2067 
   2068         // Set the FieldPosition (for the first occurrence only)
   2069         if (pos.getBeginIndex() == pos.getEndIndex()) {
   2070             if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
   2071                 pos.setBeginIndex(beginOffset);
   2072                 pos.setEndIndex(beginOffset + buf.length() - bufstart);
   2073             } else if (pos.getFieldAttribute() ==
   2074                        PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
   2075                 pos.setBeginIndex(beginOffset);
   2076                 pos.setEndIndex(beginOffset + buf.length() - bufstart);
   2077             }
   2078         }
   2079     }
   2080 
   2081     private static void safeAppend(String[] array, int value, StringBuffer appendTo) {
   2082         if (array != null && value >= 0 && value < array.length) {
   2083             appendTo.append(array[value]);
   2084         }
   2085     }
   2086 
   2087     private static void safeAppendWithMonthPattern(String[] array, int value, StringBuffer appendTo, String monthPattern) {
   2088         if (array != null && value >= 0 && value < array.length) {
   2089             if (monthPattern == null) {
   2090                 appendTo.append(array[value]);
   2091             } else {
   2092                 String s = SimpleFormatterImpl.formatRawPattern(monthPattern, 1, 1, array[value]);
   2093                 appendTo.append(s);
   2094             }
   2095         }
   2096     }
   2097 
   2098     /*
   2099      * PatternItem store parsed date/time field pattern information.
   2100      */
   2101     private static class PatternItem {
   2102         final char type;
   2103         final int length;
   2104         final boolean isNumeric;
   2105 
   2106         PatternItem(char type, int length) {
   2107             this.type = type;
   2108             this.length = length;
   2109             isNumeric = isNumeric(type, length);
   2110         }
   2111     }
   2112 
   2113     private static ICUCache<String, Object[]> PARSED_PATTERN_CACHE =
   2114         new SimpleCache<String, Object[]>();
   2115     private transient Object[] patternItems;
   2116 
   2117     /*
   2118      * Returns parsed pattern items.  Each item is either String or
   2119      * PatternItem.
   2120      */
   2121     private Object[] getPatternItems() {
   2122         if (patternItems != null) {
   2123             return patternItems;
   2124         }
   2125 
   2126         patternItems = PARSED_PATTERN_CACHE.get(pattern);
   2127         if (patternItems != null) {
   2128             return patternItems;
   2129         }
   2130 
   2131         boolean isPrevQuote = false;
   2132         boolean inQuote = false;
   2133         StringBuilder text = new StringBuilder();
   2134         char itemType = 0;  // 0 for string literal, otherwise date/time pattern character
   2135         int itemLength = 1;
   2136 
   2137         List<Object> items = new ArrayList<Object>();
   2138 
   2139         for (int i = 0; i < pattern.length(); i++) {
   2140             char ch = pattern.charAt(i);
   2141             if (ch == '\'') {
   2142                 if (isPrevQuote) {
   2143                     text.append('\'');
   2144                     isPrevQuote = false;
   2145                 } else {
   2146                     isPrevQuote = true;
   2147                     if (itemType != 0) {
   2148                         items.add(new PatternItem(itemType, itemLength));
   2149                         itemType = 0;
   2150                     }
   2151                 }
   2152                 inQuote = !inQuote;
   2153             } else {
   2154                 isPrevQuote = false;
   2155                 if (inQuote) {
   2156                     text.append(ch);
   2157                 } else {
   2158                     if (isSyntaxChar(ch)) {
   2159                         // a date/time pattern character
   2160                         if (ch == itemType) {
   2161                             itemLength++;
   2162                         } else {
   2163                             if (itemType == 0) {
   2164                                 if (text.length() > 0) {
   2165                                     items.add(text.toString());
   2166                                     text.setLength(0);
   2167                                 }
   2168                             } else {
   2169                                 items.add(new PatternItem(itemType, itemLength));
   2170                             }
   2171                             itemType = ch;
   2172                             itemLength = 1;
   2173                         }
   2174                     } else {
   2175                         // a string literal
   2176                         if (itemType != 0) {
   2177                             items.add(new PatternItem(itemType, itemLength));
   2178                             itemType = 0;
   2179                         }
   2180                         text.append(ch);
   2181                     }
   2182                 }
   2183             }
   2184         }
   2185         // handle last item
   2186         if (itemType == 0) {
   2187             if (text.length() > 0) {
   2188                 items.add(text.toString());
   2189                 text.setLength(0);
   2190             }
   2191         } else {
   2192             items.add(new PatternItem(itemType, itemLength));
   2193         }
   2194 
   2195         patternItems = items.toArray(new Object[items.size()]);
   2196 
   2197         PARSED_PATTERN_CACHE.put(pattern, patternItems);
   2198 
   2199         return patternItems;
   2200     }
   2201 
   2202     /**
   2203      * Internal high-speed method.  Reuses a StringBuffer for results
   2204      * instead of creating a String on the heap for each call.
   2205      * @internal
   2206      * @deprecated This API is ICU internal only.
   2207      */
   2208     @Deprecated
   2209     protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
   2210                                      int minDigits, int maxDigits) {
   2211         // Note: Indian calendar uses negative value for a calendar
   2212         // field. fastZeroPaddingNumber cannot handle negative numbers.
   2213         // BTW, it looks like a design bug in the Indian calendar...
   2214         if (useLocalZeroPaddingNumberFormat && value >= 0) {
   2215             fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
   2216         } else {
   2217             nf.setMinimumIntegerDigits(minDigits);
   2218             nf.setMaximumIntegerDigits(maxDigits);
   2219             nf.format(value, buf, new FieldPosition(-1));
   2220         }
   2221     }
   2222 
   2223     /**
   2224      * Overrides superclass method and
   2225      * This method also clears per field NumberFormat instances
   2226      * previously set by {@link #setNumberFormat(String, NumberFormat)}
   2227      *
   2228      * @stable ICU 2.0
   2229      */
   2230     @Override
   2231     public void setNumberFormat(NumberFormat newNumberFormat) {
   2232         // Override this method to update local zero padding number formatter
   2233         super.setNumberFormat(newNumberFormat);
   2234         initLocalZeroPaddingNumberFormat();
   2235         initializeTimeZoneFormat(true);
   2236 
   2237         if (numberFormatters != null) {
   2238             numberFormatters = null;
   2239         }
   2240         if (overrideMap != null) {
   2241             overrideMap = null;
   2242         }
   2243     }
   2244 
   2245     /*
   2246      * Initializes transient fields for fast simple numeric formatting
   2247      * code. This method should be called whenever number format is updated.
   2248      */
   2249     private void initLocalZeroPaddingNumberFormat() {
   2250         if (numberFormat instanceof DecimalFormat) {
   2251             DecimalFormatSymbols tmpDecfs = ((DecimalFormat)numberFormat).getDecimalFormatSymbols();
   2252             String[] tmpDigits = tmpDecfs.getDigitStringsLocal();
   2253             useLocalZeroPaddingNumberFormat = true;
   2254             decDigits = new char[10];
   2255             for (int i = 0; i < 10; i++) {
   2256                 if (tmpDigits[i].length() > 1) {
   2257                     useLocalZeroPaddingNumberFormat = false;
   2258                     break;
   2259                 }
   2260                 decDigits[i] = tmpDigits[i].charAt(0);
   2261             }
   2262         } else if (numberFormat instanceof DateNumberFormat) {
   2263             decDigits = ((DateNumberFormat)numberFormat).getDigits();
   2264             useLocalZeroPaddingNumberFormat = true;
   2265         } else {
   2266             useLocalZeroPaddingNumberFormat = false;
   2267         }
   2268 
   2269         if (useLocalZeroPaddingNumberFormat) {
   2270             decimalBuf = new char[DECIMAL_BUF_SIZE];
   2271         }
   2272     }
   2273 
   2274     // If true, use local version of zero padding number format
   2275     private transient boolean useLocalZeroPaddingNumberFormat;
   2276     private transient char[] decDigits;     // read-only - can be shared by multiple instances
   2277     private transient char[] decimalBuf;    // mutable - one per instance
   2278     private static final int DECIMAL_BUF_SIZE = 10; // sufficient for int numbers
   2279 
   2280     /*
   2281      * Lightweight zero padding integer number format function.
   2282      *
   2283      * Note: This implementation is almost equivalent to format method in DateNumberFormat.
   2284      * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
   2285      * but, it does not help IBM J9's JIT to optimize the performance much.  In simple repeative
   2286      * date format test case, having local implementation is ~10% faster than using one in
   2287      * DateNumberFormat on IBM J9 VM.  On Sun Hotspot VM, I do not see such difference.
   2288      *
   2289      * -Yoshito
   2290      */
   2291     private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {
   2292         int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
   2293         int index = limit - 1;
   2294         while (true) {
   2295             decimalBuf[index] = decDigits[(value % 10)];
   2296             value /= 10;
   2297             if (index == 0 || value == 0) {
   2298                 break;
   2299             }
   2300             index--;
   2301         }
   2302         int padding = minDigits - (limit - index);
   2303         while (padding > 0 && index > 0) {
   2304             decimalBuf[--index] = decDigits[0];
   2305             padding--;
   2306         }
   2307         while (padding > 0) {
   2308             // when pattern width is longer than decimalBuf, need extra
   2309             // leading zeros - ticke#7595
   2310             buf.append(decDigits[0]);
   2311             padding--;
   2312         }
   2313         buf.append(decimalBuf, index, limit - index);
   2314     }
   2315 
   2316     /**
   2317      * Formats a number with the specified minimum and maximum number of digits.
   2318      * @stable ICU 2.0
   2319      */
   2320     protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
   2321     {
   2322         numberFormat.setMinimumIntegerDigits(minDigits);
   2323         numberFormat.setMaximumIntegerDigits(maxDigits);
   2324         return numberFormat.format(value);
   2325     }
   2326 
   2327     /**
   2328      * Format characters that indicate numeric fields always.
   2329      */
   2330     private static final String NUMERIC_FORMAT_CHARS = "ADdFgHhKkmrSsuWwYy";
   2331 
   2332     /**
   2333      * Format characters that indicate numeric fields when pattern lengh
   2334      * is up to 2.
   2335      */
   2336     private static final String NUMERIC_FORMAT_CHARS2 = "ceLMQq";
   2337 
   2338     /**
   2339      * Return true if the given format character, occuring count
   2340      * times, represents a numeric field.
   2341      */
   2342     private static final boolean isNumeric(char formatChar, int count) {
   2343         return NUMERIC_FORMAT_CHARS.indexOf(formatChar) >= 0
   2344                 || (count <= 2 && NUMERIC_FORMAT_CHARS2.indexOf(formatChar) >= 0);
   2345     }
   2346 
   2347     /**
   2348      * Overrides DateFormat
   2349      * @see DateFormat
   2350      * @stable ICU 2.0
   2351      */
   2352     @Override
   2353     public void parse(String text, Calendar cal, ParsePosition parsePos)
   2354     {
   2355         TimeZone backupTZ = null;
   2356         Calendar resultCal = null;
   2357         if (cal != calendar && !cal.getType().equals(calendar.getType())) {
   2358             // Different calendar type
   2359             // We use the time/zone from the input calendar, but
   2360             // do not use the input calendar for field calculation.
   2361             calendar.setTimeInMillis(cal.getTimeInMillis());
   2362             backupTZ = calendar.getTimeZone();
   2363             calendar.setTimeZone(cal.getTimeZone());
   2364             resultCal = cal;
   2365             cal = calendar;
   2366         }
   2367 
   2368         int pos = parsePos.getIndex();
   2369         if(pos < 0) {
   2370             parsePos.setErrorIndex(0);
   2371             return;
   2372         }
   2373         int start = pos;
   2374 
   2375         // Hold the day period until everything else is parsed, because we need
   2376         // the hour to interpret time correctly.
   2377         // Using an one-element array for output parameter.
   2378         Output<DayPeriodRules.DayPeriod> dayPeriod = new Output<DayPeriodRules.DayPeriod>(null);
   2379 
   2380         Output<TimeType> tzTimeType = new Output<TimeType>(TimeType.UNKNOWN);
   2381         boolean[] ambiguousYear = { false };
   2382 
   2383         // item index for the first numeric field within a contiguous numeric run
   2384         int numericFieldStart = -1;
   2385         // item length for the first numeric field within a contiguous numeric run
   2386         int numericFieldLength = 0;
   2387         // start index of numeric text run in the input text
   2388         int numericStartPos = 0;
   2389 
   2390         MessageFormat numericLeapMonthFormatter = null;
   2391         if (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT) {
   2392             numericLeapMonthFormatter = new MessageFormat(formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC], locale);
   2393         }
   2394 
   2395         Object[] items = getPatternItems();
   2396         int i = 0;
   2397         while (i < items.length) {
   2398             if (items[i] instanceof PatternItem) {
   2399                 // Handle pattern field
   2400                 PatternItem field = (PatternItem)items[i];
   2401                 if (field.isNumeric) {
   2402                     // Handle fields within a run of abutting numeric fields.  Take
   2403                     // the pattern "HHmmss" as an example. We will try to parse
   2404                     // 2/2/2 characters of the input text, then if that fails,
   2405                     // 1/2/2.  We only adjust the width of the leftmost field; the
   2406                     // others remain fixed.  This allows "123456" => 12:34:56, but
   2407                     // "12345" => 1:23:45.  Likewise, for the pattern "yyyyMMdd" we
   2408                     // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
   2409                     if (numericFieldStart == -1) {
   2410                         // check if this field is followed by abutting another numeric field
   2411                         if ((i + 1) < items.length
   2412                                 && (items[i + 1] instanceof PatternItem)
   2413                                 && ((PatternItem)items[i + 1]).isNumeric) {
   2414                             // record the first numeric field within a numeric text run
   2415                             numericFieldStart = i;
   2416                             numericFieldLength = field.length;
   2417                             numericStartPos = pos;
   2418                         }
   2419                     }
   2420                 }
   2421                 if (numericFieldStart != -1) {
   2422                     // Handle a numeric field within abutting numeric fields
   2423                     int len = field.length;
   2424                     if (numericFieldStart == i) {
   2425                         len = numericFieldLength;
   2426                     }
   2427 
   2428                     // Parse a numeric field
   2429                     pos = subParse(text, pos, field.type, len,
   2430                             true, false, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType);
   2431 
   2432                     if (pos < 0) {
   2433                         // If the parse fails anywhere in the numeric run, back up to the
   2434                         // start of the run and use shorter pattern length for the first
   2435                         // numeric field.
   2436                         --numericFieldLength;
   2437                         if (numericFieldLength == 0) {
   2438                             // can not make shorter any more
   2439                             parsePos.setIndex(start);
   2440                             parsePos.setErrorIndex(pos);
   2441                             if (backupTZ != null) {
   2442                                 calendar.setTimeZone(backupTZ);
   2443                             }
   2444                             return;
   2445                         }
   2446                         i = numericFieldStart;
   2447                         pos = numericStartPos;
   2448                         continue;
   2449                     }
   2450 
   2451                 } else if (field.type != 'l') { // (SMALL LETTER L) obsolete pattern char just gets ignored
   2452                     // Handle a non-numeric field or a non-abutting numeric field
   2453                     numericFieldStart = -1;
   2454 
   2455                     int s = pos;
   2456                     pos = subParse(text, pos, field.type, field.length,
   2457                             false, true, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType, dayPeriod);
   2458 
   2459                     if (pos < 0) {
   2460                         if (pos == ISOSpecialEra) {
   2461                             // era not present, in special cases allow this to continue
   2462                             pos = s;
   2463 
   2464                             if (i+1 < items.length) {
   2465 
   2466                                 String patl = null;
   2467                                 // if it will cause a class cast exception to String, we can't use it
   2468                                 try {
   2469                                     patl = (String)items[i+1];
   2470                                 } catch(ClassCastException cce) {
   2471                                     parsePos.setIndex(start);
   2472                                     parsePos.setErrorIndex(s);
   2473                                     if (backupTZ != null) {
   2474                                         calendar.setTimeZone(backupTZ);
   2475                                     }
   2476                                     return;
   2477                                 }
   2478 
   2479                                 // get next item in pattern
   2480                                 if(patl == null)
   2481                                     patl = (String)items[i+1];
   2482                                 int plen = patl.length();
   2483                                 int idx=0;
   2484 
   2485                                 // White space characters found in pattern.
   2486                                 // Skip contiguous white spaces.
   2487                                 while (idx < plen) {
   2488 
   2489                                     char pch = patl.charAt(idx);
   2490                                     if (PatternProps.isWhiteSpace(pch))
   2491                                         idx++;
   2492                                     else
   2493                                         break;
   2494                                 }
   2495 
   2496                                 // if next item in pattern is all whitespace, skip it
   2497                                 if (idx == plen) {
   2498                                     i++;
   2499                                 }
   2500 
   2501                             }
   2502                         } else {
   2503                             parsePos.setIndex(start);
   2504                             parsePos.setErrorIndex(s);
   2505                             if (backupTZ != null) {
   2506                                 calendar.setTimeZone(backupTZ);
   2507                             }
   2508                             return;
   2509                         }
   2510                     }
   2511 
   2512                 }
   2513             } else {
   2514                 // Handle literal pattern text literal
   2515                 numericFieldStart = -1;
   2516                 boolean[] complete = new boolean[1];
   2517                 pos = matchLiteral(text, pos, items, i, complete);
   2518                 if (!complete[0]) {
   2519                     // Set the position of mismatch
   2520                     parsePos.setIndex(start);
   2521                     parsePos.setErrorIndex(pos);
   2522                     if (backupTZ != null) {
   2523                         calendar.setTimeZone(backupTZ);
   2524                     }
   2525                     return;
   2526                 }
   2527             }
   2528             ++i;
   2529         }
   2530 
   2531         // Special hack for trailing "." after non-numeric field.
   2532         if (pos < text.length()) {
   2533             char extra = text.charAt(pos);
   2534             if (extra == '.' && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && items.length != 0) {
   2535                 // only do if the last field is not numeric
   2536                 Object lastItem = items[items.length - 1];
   2537                 if (lastItem instanceof PatternItem && !((PatternItem)lastItem).isNumeric) {
   2538                     pos++; // skip the extra "."
   2539                 }
   2540             }
   2541         }
   2542 
   2543         // If dayPeriod is set, use it in conjunction with hour-of-day to determine am/pm.
   2544         if (dayPeriod.value != null) {
   2545             DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale());
   2546 
   2547             if (!cal.isSet(Calendar.HOUR) && !cal.isSet(Calendar.HOUR_OF_DAY)) {
   2548                 // If hour is not set, set time to the midpoint of current day period, overwriting
   2549                 // minutes if it's set.
   2550                 double midPoint = ruleSet.getMidPointForDayPeriod(dayPeriod.value);
   2551 
   2552                 // Truncate midPoint toward zero to get the hour.
   2553                 // Any leftover means it was a half-hour.
   2554                 int midPointHour = (int) midPoint;
   2555                 int midPointMinute = (midPoint - midPointHour) > 0 ? 30 : 0;
   2556 
   2557                 // No need to set am/pm because hour-of-day is set last therefore takes precedence.
   2558                 cal.set(Calendar.HOUR_OF_DAY, midPointHour);
   2559                 cal.set(Calendar.MINUTE, midPointMinute);
   2560             } else {
   2561                 int hourOfDay;
   2562 
   2563                 if (cal.isSet(Calendar.HOUR_OF_DAY)) {  // Hour is parsed in 24-hour format.
   2564                     hourOfDay = cal.get(Calendar.HOUR_OF_DAY);
   2565                 } else {  // Hour is parsed in 12-hour format.
   2566                     hourOfDay = cal.get(Calendar.HOUR);
   2567                     // cal.get() turns 12 to 0 for 12-hour time; change 0 to 12
   2568                     // so 0 unambiguously means a 24-hour time from above.
   2569                     if (hourOfDay == 0) { hourOfDay = 12; }
   2570                 }
   2571                 assert(0 <= hourOfDay && hourOfDay <= 23);
   2572 
   2573 
   2574                 // If hour-of-day is 0 or 13 thru 23 then input time in unambiguously in 24-hour format.
   2575                 if (hourOfDay == 0 || (13 <= hourOfDay && hourOfDay <= 23)) {
   2576                     // Make hour-of-day take precedence over (hour + am/pm) by setting it again.
   2577                     cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
   2578                 } else {
   2579                     // We have a 12-hour time and need to choose between am and pm.
   2580                     // Behave as if dayPeriod spanned 6 hours each way from its center point.
   2581                     // This will parse correctly for consistent time + period (e.g. 10 at night) as
   2582                     // well as provide a reasonable recovery for inconsistent time + period (e.g.
   2583                     // 9 in the afternoon).
   2584 
   2585                     // Assume current time is in the AM.
   2586                     // - Change 12 back to 0 for easier handling of 12am.
   2587                     // - Append minutes as fractional hours because e.g. 8:15 and 8:45 could be parsed
   2588                     // into different half-days if center of dayPeriod is at 14:30.
   2589                     // - cal.get(MINUTE) will return 0 if MINUTE is unset, which works.
   2590                     if (hourOfDay == 12) { hourOfDay = 0; }
   2591                     double currentHour = hourOfDay + cal.get(Calendar.MINUTE) / 60.0;
   2592                     double midPointHour = ruleSet.getMidPointForDayPeriod(dayPeriod.value);
   2593 
   2594                     double hoursAheadMidPoint = currentHour - midPointHour;
   2595 
   2596                     // Assume current time is in the AM.
   2597                     if (-6 <= hoursAheadMidPoint && hoursAheadMidPoint < 6) {
   2598                         // Assumption holds; set time as such.
   2599                         cal.set(Calendar.AM_PM, 0);
   2600                     } else {
   2601                         cal.set(Calendar.AM_PM, 1);
   2602                     }
   2603                 }
   2604             }
   2605         }
   2606 
   2607         // At this point the fields of Calendar have been set.  Calendar
   2608         // will fill in default values for missing fields when the time
   2609         // is computed.
   2610 
   2611         parsePos.setIndex(pos);
   2612 
   2613         // This part is a problem:  When we call parsedDate.after, we compute the time.
   2614         // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year
   2615         // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.
   2616         // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am
   2617         // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
   2618         // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we
   2619         // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is
   2620         // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
   2621         /*
   2622           Date parsedDate = cal.getTime();
   2623           if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
   2624           cal.add(Calendar.YEAR, 100);
   2625           parsedDate = cal.getTime();
   2626           }
   2627         */
   2628         // Because of the above condition, save off the fields in case we need to readjust.
   2629         // The procedure we use here is not particularly efficient, but there is no other
   2630         // way to do this given the API restrictions present in Calendar.  We minimize
   2631         // inefficiency by only performing this computation when it might apply, that is,
   2632         // when the two-digit year is equal to the start year, and thus might fall at the
   2633         // front or the back of the default century.  This only works because we adjust
   2634         // the year correctly to start with in other cases -- see subParse().
   2635         try {
   2636             TimeType tztype = tzTimeType.value;
   2637             if (ambiguousYear[0] || tztype != TimeType.UNKNOWN) {
   2638                 // We need a copy of the fields, and we need to avoid triggering a call to
   2639                 // complete(), which will recalculate the fields.  Since we can't access
   2640                 // the fields[] array in Calendar, we clone the entire object.  This will
   2641                 // stop working if Calendar.clone() is ever rewritten to call complete().
   2642                 Calendar copy;
   2643                 if (ambiguousYear[0]) { // the two-digit year == the default start year
   2644                     copy = (Calendar)cal.clone();
   2645                     Date parsedDate = copy.getTime();
   2646                     if (parsedDate.before(getDefaultCenturyStart())) {
   2647                         // We can't use add here because that does a complete() first.
   2648                         cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
   2649                     }
   2650                 }
   2651                 if (tztype != TimeType.UNKNOWN) {
   2652                     copy = (Calendar)cal.clone();
   2653                     TimeZone tz = copy.getTimeZone();
   2654                     BasicTimeZone btz = null;
   2655                     if (tz instanceof BasicTimeZone) {
   2656                         btz = (BasicTimeZone)tz;
   2657                     }
   2658 
   2659                     // Get local millis
   2660                     copy.set(Calendar.ZONE_OFFSET, 0);
   2661                     copy.set(Calendar.DST_OFFSET, 0);
   2662                     long localMillis = copy.getTimeInMillis();
   2663 
   2664                     // Make sure parsed time zone type (Standard or Daylight)
   2665                     // matches the rule used by the parsed time zone.
   2666                     int[] offsets = new int[2];
   2667                     if (btz != null) {
   2668                         if (tztype == TimeType.STANDARD) {
   2669                             btz.getOffsetFromLocal(localMillis,
   2670                                     BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
   2671                         } else {
   2672                             btz.getOffsetFromLocal(localMillis,
   2673                                     BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
   2674                         }
   2675                     } else {
   2676                         // No good way to resolve ambiguous time at transition,
   2677                         // but following code work in most case.
   2678                         tz.getOffset(localMillis, true, offsets);
   2679 
   2680                         if (tztype == TimeType.STANDARD && offsets[1] != 0
   2681                             || tztype == TimeType.DAYLIGHT && offsets[1] == 0) {
   2682                             // Roll back one day and try it again.
   2683                             // Note: This code assumes 1. timezone transition only happens
   2684                             // once within 24 hours at max
   2685                             // 2. the difference of local offsets at the transition is
   2686                             // less than 24 hours.
   2687                             tz.getOffset(localMillis - (24*60*60*1000), true, offsets);
   2688                         }
   2689                     }
   2690 
   2691                     // Now, compare the results with parsed type, either standard or
   2692                     // daylight saving time
   2693                     int resolvedSavings = offsets[1];
   2694                     if (tztype == TimeType.STANDARD) {
   2695                         if (offsets[1] != 0) {
   2696                             // Override DST_OFFSET = 0 in the result calendar
   2697                             resolvedSavings = 0;
   2698                         }
   2699                     } else { // tztype == TZTYPE_DST
   2700                         if (offsets[1] == 0) {
   2701                             if (btz != null) {
   2702                                 long time = localMillis + offsets[0];
   2703                                 // We use the nearest daylight saving time rule.
   2704                                 TimeZoneTransition beforeTrs, afterTrs;
   2705                                 long beforeT = time, afterT = time;
   2706                                 int beforeSav = 0, afterSav = 0;
   2707 
   2708                                 // Search for DST rule before or on the time
   2709                                 while (true) {
   2710                                     beforeTrs = btz.getPreviousTransition(beforeT, true);
   2711                                     if (beforeTrs == null) {
   2712                                         break;
   2713                                     }
   2714                                     beforeT = beforeTrs.getTime() - 1;
   2715                                     beforeSav = beforeTrs.getFrom().getDSTSavings();
   2716                                     if (beforeSav != 0) {
   2717                                         break;
   2718                                     }
   2719                                 }
   2720 
   2721                                 // Search for DST rule after the time
   2722                                 while (true) {
   2723                                     afterTrs = btz.getNextTransition(afterT, false);
   2724                                     if (afterTrs == null) {
   2725                                         break;
   2726                                     }
   2727                                     afterT = afterTrs.getTime();
   2728                                     afterSav = afterTrs.getTo().getDSTSavings();
   2729                                     if (afterSav != 0) {
   2730                                         break;
   2731                                     }
   2732                                 }
   2733 
   2734                                 if (beforeTrs != null && afterTrs != null) {
   2735                                     if (time - beforeT > afterT - time) {
   2736                                         resolvedSavings = afterSav;
   2737                                     } else {
   2738                                         resolvedSavings = beforeSav;
   2739                                     }
   2740                                 } else if (beforeTrs != null && beforeSav != 0) {
   2741                                     resolvedSavings = beforeSav;
   2742                                 } else if (afterTrs != null && afterSav != 0) {
   2743                                     resolvedSavings = afterSav;
   2744                                 } else {
   2745                                     resolvedSavings = btz.getDSTSavings();
   2746                                 }
   2747                             } else {
   2748                                 resolvedSavings = tz.getDSTSavings();
   2749                             }
   2750                             if (resolvedSavings == 0) {
   2751                                 // Final fallback
   2752                                 resolvedSavings = millisPerHour;
   2753                             }
   2754                         }
   2755                     }
   2756                     cal.set(Calendar.ZONE_OFFSET, offsets[0]);
   2757                     cal.set(Calendar.DST_OFFSET, resolvedSavings);
   2758                 }
   2759             }
   2760         }
   2761         // An IllegalArgumentException will be thrown by Calendar.getTime()
   2762         // if any fields are out of range, e.g., MONTH == 17.
   2763         catch (IllegalArgumentException e) {
   2764             parsePos.setErrorIndex(pos);
   2765             parsePos.setIndex(start);
   2766             if (backupTZ != null) {
   2767                 calendar.setTimeZone(backupTZ);
   2768             }
   2769             return;
   2770         }
   2771         // Set the parsed result if local calendar is used
   2772         // instead of the input calendar
   2773         if (resultCal != null) {
   2774             resultCal.setTimeZone(cal.getTimeZone());
   2775             resultCal.setTimeInMillis(cal.getTimeInMillis());
   2776         }
   2777         // Restore the original time zone if required
   2778         if (backupTZ != null) {
   2779             calendar.setTimeZone(backupTZ);
   2780         }
   2781     }
   2782 
   2783     /**
   2784      * Matches text (starting at pos) with patl. Returns the new pos, and sets complete[0]
   2785      * if it matched the entire text. Whitespace sequences are treated as singletons.
   2786      * <p>If isLenient and if we fail to match the first time, some special hacks are put into place.
   2787      * <ul><li>we are between date and time fields, then one or more whitespace characters
   2788      * in the text are accepted instead.</li>
   2789      * <ul><li>we are after a non-numeric field, and the text starts with a ".", we skip it.</li>
   2790      * </ul>
   2791      */
   2792     private int matchLiteral(String text, int pos, Object[] items, int itemIndex, boolean[] complete) {
   2793         int originalPos = pos;
   2794         String patternLiteral = (String)items[itemIndex];
   2795         int plen = patternLiteral.length();
   2796         int tlen = text.length();
   2797         int idx = 0;
   2798         while (idx < plen && pos < tlen) {
   2799             char pch = patternLiteral.charAt(idx);
   2800             char ich = text.charAt(pos);
   2801             if (PatternProps.isWhiteSpace(pch)
   2802                 && PatternProps.isWhiteSpace(ich)) {
   2803                 // White space characters found in both patten and input.
   2804                 // Skip contiguous white spaces.
   2805                 while ((idx + 1) < plen &&
   2806                         PatternProps.isWhiteSpace(patternLiteral.charAt(idx + 1))) {
   2807                      ++idx;
   2808                 }
   2809                 while ((pos + 1) < tlen &&
   2810                         PatternProps.isWhiteSpace(text.charAt(pos + 1))) {
   2811                      ++pos;
   2812                 }
   2813             } else if (pch != ich) {
   2814                 if (ich == '.' && pos == originalPos && 0 < itemIndex && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
   2815                     Object before = items[itemIndex-1];
   2816                     if (before instanceof PatternItem) {
   2817                         boolean isNumeric = ((PatternItem) before).isNumeric;
   2818                         if (!isNumeric) {
   2819                             ++pos; // just update pos
   2820                             continue;
   2821                         }
   2822                     }
   2823                 } else if ((pch == ' ' || pch == '.') && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
   2824                     ++idx;
   2825                     continue;
   2826                 } else if (pos != originalPos && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH)) {
   2827                     ++idx;
   2828                     continue;
   2829                 }
   2830                 break;
   2831             }
   2832             ++idx;
   2833             ++pos;
   2834         }
   2835         complete[0] = idx == plen;
   2836         if (complete[0] == false && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && 0 < itemIndex && itemIndex < items.length - 1) {
   2837             // If fully lenient, accept " "* for any text between a date and a time field
   2838             // We don't go more lenient, because we don't want to accept "12/31" for "12:31".
   2839             // People may be trying to parse for a date, then for a time.
   2840             if (originalPos < tlen) {
   2841                 Object before = items[itemIndex-1];
   2842                 Object after = items[itemIndex+1];
   2843                 if (before instanceof PatternItem && after instanceof PatternItem) {
   2844                     char beforeType = ((PatternItem) before).type;
   2845                     char afterType = ((PatternItem) after).type;
   2846                     if (DATE_PATTERN_TYPE.contains(beforeType) != DATE_PATTERN_TYPE.contains(afterType)) {
   2847                         int newPos = originalPos;
   2848                         while (true) {
   2849                             char ich = text.charAt(newPos);
   2850                             if (!PatternProps.isWhiteSpace(ich)) {
   2851                                 break;
   2852                             }
   2853                             ++newPos;
   2854                         }
   2855                         complete[0] = newPos > originalPos;
   2856                         pos = newPos;
   2857                     }
   2858                 }
   2859             }
   2860         }
   2861         return pos;
   2862     }
   2863 
   2864     static final UnicodeSet DATE_PATTERN_TYPE = new UnicodeSet("[GyYuUQqMLlwWd]").freeze();
   2865 
   2866     /**
   2867      * Attempt to match the text at a given position against an array of
   2868      * strings.  Since multiple strings in the array may match (for
   2869      * example, if the array contains "a", "ab", and "abc", all will match
   2870      * the input string "abcd") the longest match is returned.  As a side
   2871      * effect, the given field of <code>cal</code> is set to the index
   2872      * of the best match, if there is one.
   2873      * @param text the time text being parsed.
   2874      * @param start where to start parsing.
   2875      * @param field the date field being parsed.
   2876      * @param data the string array to parsed.
   2877      * @param cal
   2878      * @return the new start position if matching succeeded; a negative
   2879      * number indicating matching failure, otherwise.  As a side effect,
   2880      * sets the <code>cal</code> field <code>field</code> to the index
   2881      * of the best match, if matching succeeded.
   2882      * @stable ICU 2.0
   2883      */
   2884     protected int matchString(String text, int start, int field, String[] data, Calendar cal)
   2885     {
   2886         return matchString(text, start, field, data, null, cal);
   2887     }
   2888 
   2889     /**
   2890      * Attempt to match the text at a given position against an array of
   2891      * strings.  Since multiple strings in the array may match (for
   2892      * example, if the array contains "a", "ab", and "abc", all will match
   2893      * the input string "abcd") the longest match is returned.  As a side
   2894      * effect, the given field of <code>cal</code> is set to the index
   2895      * of the best match, if there is one.
   2896      * @param text the time text being parsed.
   2897      * @param start where to start parsing.
   2898      * @param field the date field being parsed.
   2899      * @param data the string array to parsed.
   2900      * @param monthPattern leap month pattern, or null if none.
   2901      * @param cal
   2902      * @return the new start position if matching succeeded; a negative
   2903      * number indicating matching failure, otherwise.  As a side effect,
   2904      * sets the <code>cal</code> field <code>field</code> to the index
   2905      * of the best match, if matching succeeded.
   2906      * @internal
   2907      * @deprecated This API is ICU internal only.
   2908      */
   2909     @Deprecated
   2910     private int matchString(String text, int start, int field, String[] data, String monthPattern, Calendar cal)
   2911     {
   2912         int i = 0;
   2913         int count = data.length;
   2914 
   2915         if (field == Calendar.DAY_OF_WEEK) i = 1;
   2916 
   2917         // There may be multiple strings in the data[] array which begin with
   2918         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
   2919         // We keep track of the longest match, and return that.  Note that this
   2920         // unfortunately requires us to test all array elements.
   2921         int bestMatchLength = 0, bestMatch = -1;
   2922         int isLeapMonth = 0;
   2923         int matchLength = 0;
   2924 
   2925         for (; i<count; ++i)
   2926             {
   2927                 int length = data[i].length();
   2928                 // Always compare if we have no match yet; otherwise only compare
   2929                 // against potentially better matches (longer strings).
   2930                 if (length > bestMatchLength &&
   2931                     (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0)
   2932                     {
   2933                         bestMatch = i;
   2934                         bestMatchLength = matchLength;
   2935                         isLeapMonth = 0;
   2936                     }
   2937                 if (monthPattern != null) {
   2938                     String leapMonthName = SimpleFormatterImpl.formatRawPattern(
   2939                             monthPattern, 1, 1, data[i]);
   2940                     length = leapMonthName.length();
   2941                     if (length > bestMatchLength &&
   2942                         (matchLength = regionMatchesWithOptionalDot(text, start, leapMonthName, length)) >= 0)
   2943                         {
   2944                             bestMatch = i;
   2945                             bestMatchLength = matchLength;
   2946                             isLeapMonth = 1;
   2947                         }
   2948                  }
   2949             }
   2950         if (bestMatch >= 0)
   2951             {
   2952                 if (field >= 0) {
   2953                     if (field == Calendar.YEAR) {
   2954                         bestMatch++; // only get here for cyclic year names, which match 1-based years 1-60
   2955                     }
   2956                     cal.set(field, bestMatch);
   2957                     if (monthPattern != null) {
   2958                         cal.set(Calendar.IS_LEAP_MONTH, isLeapMonth);
   2959                     }
   2960                 }
   2961                 return start + bestMatchLength;
   2962             }
   2963         return ~start;
   2964     }
   2965 
   2966     private int regionMatchesWithOptionalDot(String text, int start, String data, int length) {
   2967         boolean matches = text.regionMatches(true, start, data, 0, length);
   2968         if (matches) {
   2969             return length;
   2970         }
   2971         if (data.length() > 0 && data.charAt(data.length()-1) == '.') {
   2972             if (text.regionMatches(true, start, data, 0, length-1)) {
   2973                 return length - 1;
   2974             }
   2975         }
   2976         return -1;
   2977     }
   2978 
   2979     /**
   2980      * Attempt to match the text at a given position against an array of quarter
   2981      * strings.  Since multiple strings in the array may match (for
   2982      * example, if the array contains "a", "ab", and "abc", all will match
   2983      * the input string "abcd") the longest match is returned.  As a side
   2984      * effect, the given field of <code>cal</code> is set to the index
   2985      * of the best match, if there is one.
   2986      * @param text the time text being parsed.
   2987      * @param start where to start parsing.
   2988      * @param field the date field being parsed.
   2989      * @param data the string array to parsed.
   2990      * @return the new start position if matching succeeded; a negative
   2991      * number indicating matching failure, otherwise.  As a side effect,
   2992      * sets the <code>cal</code> field <code>field</code> to the index
   2993      * of the best match, if matching succeeded.
   2994      * @stable ICU 2.0
   2995      */
   2996     protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
   2997     {
   2998         int i = 0;
   2999         int count = data.length;
   3000 
   3001         // There may be multiple strings in the data[] array which begin with
   3002         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
   3003         // We keep track of the longest match, and return that.  Note that this
   3004         // unfortunately requires us to test all array elements.
   3005         int bestMatchLength = 0, bestMatch = -1;
   3006         int matchLength = 0;
   3007         for (; i<count; ++i) {
   3008             int length = data[i].length();
   3009             // Always compare if we have no match yet; otherwise only compare
   3010             // against potentially better matches (longer strings).
   3011             if (length > bestMatchLength &&
   3012                 (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) {
   3013 
   3014                 bestMatch = i;
   3015                 bestMatchLength = matchLength;
   3016             }
   3017         }
   3018 
   3019         if (bestMatch >= 0) {
   3020             cal.set(field, bestMatch * 3);
   3021             return start + bestMatchLength;
   3022         }
   3023 
   3024         return -start;
   3025     }
   3026 
   3027     /**
   3028      * Similar to matchQuarterString but customized for day periods.
   3029      */
   3030     private int matchDayPeriodString(String text, int start, String[] data, int dataLength,
   3031             Output<DayPeriodRules.DayPeriod> dayPeriod)
   3032     {
   3033         int bestMatchLength = 0, bestMatch = -1;
   3034         int matchLength = 0;
   3035         for (int i = 0; i < dataLength; ++i) {
   3036             // Only try matching if the string exists.
   3037             if (data[i] != null) {
   3038                 int length = data[i].length();
   3039                 if (length > bestMatchLength &&
   3040                         (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) {
   3041                     bestMatch = i;
   3042                     bestMatchLength = matchLength;
   3043                 }
   3044             }
   3045         }
   3046 
   3047         if (bestMatch >= 0) {
   3048             dayPeriod.value = DayPeriodRules.DayPeriod.VALUES[bestMatch];
   3049             return start + bestMatchLength;
   3050         }
   3051 
   3052         return -start;
   3053     }
   3054 
   3055     /**
   3056      * Protected method that converts one field of the input string into a
   3057      * numeric field value in <code>cal</code>.  Returns -start (for
   3058      * ParsePosition) if failed.  Subclasses may override this method to
   3059      * modify or add parsing capabilities.
   3060      * @param text the time text to be parsed.
   3061      * @param start where to start parsing.
   3062      * @param ch the pattern character for the date field text to be parsed.
   3063      * @param count the count of a pattern character.
   3064      * @param obeyCount if true, then the next field directly abuts this one,
   3065      * and we should use the count to know when to stop parsing.
   3066      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
   3067      * is true, then a two-digit year was parsed and may need to be readjusted.
   3068      * @param cal
   3069      * @return the new start position if matching succeeded; a negative
   3070      * number indicating matching failure, otherwise.  As a side effect,
   3071      * set the appropriate field of <code>cal</code> with the parsed
   3072      * value.
   3073      * @stable ICU 2.0
   3074      */
   3075     protected int subParse(String text, int start, char ch, int count,
   3076                            boolean obeyCount, boolean allowNegative,
   3077                            boolean[] ambiguousYear, Calendar cal)
   3078     {
   3079         return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null);
   3080     }
   3081 
   3082     /**
   3083      * Overloading to provide default argument (null) for day period.
   3084      */
   3085     private int subParse(String text, int start, char ch, int count,
   3086             boolean obeyCount, boolean allowNegative,
   3087             boolean[] ambiguousYear, Calendar cal,
   3088             MessageFormat numericLeapMonthFormatter, Output<TimeType> tzTimeType) {
   3089         return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null, null);
   3090     }
   3091 
   3092     /**
   3093      * Protected method that converts one field of the input string into a
   3094      * numeric field value in <code>cal</code>.  Returns -start (for
   3095      * ParsePosition) if failed.  Subclasses may override this method to
   3096      * modify or add parsing capabilities.
   3097      * @param text the time text to be parsed.
   3098      * @param start where to start parsing.
   3099      * @param ch the pattern character for the date field text to be parsed.
   3100      * @param count the count of a pattern character.
   3101      * @param obeyCount if true, then the next field directly abuts this one,
   3102      * and we should use the count to know when to stop parsing.
   3103      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
   3104      * is true, then a two-digit year was parsed and may need to be readjusted.
   3105      * @param cal
   3106      * @param numericLeapMonthFormatter if non-null, used to parse numeric leap months.
   3107      * @param tzTimeType the type of parsed time zone - standard, daylight or unknown (output).
   3108      *      This parameter can be null if caller does not need the information.
   3109      * @return the new start position if matching succeeded; a negative
   3110      * number indicating matching failure, otherwise.  As a side effect,
   3111      * set the appropriate field of <code>cal</code> with the parsed
   3112      * value.
   3113      * @internal
   3114      * @deprecated This API is ICU internal only.
   3115      */
   3116     @Deprecated
   3117     @SuppressWarnings("fallthrough")
   3118     private int subParse(String text, int start, char ch, int count,
   3119                            boolean obeyCount, boolean allowNegative,
   3120                            boolean[] ambiguousYear, Calendar cal,
   3121                            MessageFormat numericLeapMonthFormatter, Output<TimeType> tzTimeType,
   3122                            Output<DayPeriodRules.DayPeriod> dayPeriod)
   3123     {
   3124         Number number = null;
   3125         NumberFormat currentNumberFormat = null;
   3126         int value = 0;
   3127         int i;
   3128         ParsePosition pos = new ParsePosition(0);
   3129 
   3130         int patternCharIndex = getIndexFromChar(ch);
   3131         if (patternCharIndex == -1) {
   3132             return ~start;
   3133         }
   3134 
   3135         currentNumberFormat = getNumberFormat(ch);
   3136 
   3137         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; // -1 if irrelevant
   3138 
   3139         if (numericLeapMonthFormatter != null) {
   3140             numericLeapMonthFormatter.setFormatByArgumentIndex(0, currentNumberFormat);
   3141         }
   3142         boolean isChineseCalendar = ( cal.getType().equals("chinese") || cal.getType().equals("dangi") );
   3143 
   3144         // If there are any spaces here, skip over them.  If we hit the end
   3145         // of the string, then fail.
   3146         for (;;) {
   3147             if (start >= text.length()) {
   3148                 return ~start;
   3149             }
   3150             int c = UTF16.charAt(text, start);
   3151             if (!UCharacter.isUWhiteSpace(c) || !PatternProps.isWhiteSpace(c)) {
   3152                 break;
   3153             }
   3154             start += UTF16.getCharCount(c);
   3155         }
   3156         pos.setIndex(start);
   3157 
   3158         // We handle a few special cases here where we need to parse
   3159         // a number value.  We handle further, more generic cases below.  We need
   3160         // to handle some of them here because some fields require extra processing on
   3161         // the parsed value.
   3162         if (patternCharIndex == 4 /*'k' HOUR_OF_DAY1_FIELD*/ ||
   3163             patternCharIndex == 15 /*'h' HOUR1_FIELD*/ ||
   3164             (patternCharIndex == 2 /*'M' MONTH_FIELD*/ && count <= 2) ||
   3165             patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ ||
   3166             patternCharIndex == 19 /*'e' DOW_LOCAL*/ ||
   3167             patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ ||
   3168             patternCharIndex == 1 /*'y' YEAR */ || patternCharIndex == 18 /*'Y' YEAR_WOY */ ||
   3169             patternCharIndex == 30 /*'U' YEAR_NAME_FIELD, falls back to numeric */ ||
   3170             (patternCharIndex == 0 /*'G' ERA */ && isChineseCalendar) ||
   3171             patternCharIndex == 27 /* 'Q' - QUARTER*/ ||
   3172             patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/ ||
   3173             patternCharIndex == 8 /*'S' FRACTIONAL_SECOND */ )
   3174             {
   3175                 // It would be good to unify this with the obeyCount logic below,
   3176                 // but that's going to be difficult.
   3177 
   3178                 boolean parsedNumericLeapMonth = false;
   3179                 if (numericLeapMonthFormatter != null && (patternCharIndex == 2 || patternCharIndex == 26)) {
   3180                     // First see if we can parse month number with leap month pattern
   3181                     Object[] args = numericLeapMonthFormatter.parse(text, pos);
   3182                     if (args != null && pos.getIndex() > start && (args[0] instanceof Number)) {
   3183                         parsedNumericLeapMonth = true;
   3184                         number = (Number)args[0];
   3185                         cal.set(Calendar.IS_LEAP_MONTH, 1);
   3186                     } else {
   3187                         pos.setIndex(start);
   3188                         cal.set(Calendar.IS_LEAP_MONTH, 0);
   3189                    }
   3190                 }
   3191 
   3192                 if (!parsedNumericLeapMonth) {
   3193                     if (obeyCount) {
   3194                         if ((start+count) > text.length()) {
   3195                             return ~start;
   3196                         }
   3197                         number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
   3198                     } else {
   3199                         number = parseInt(text, pos, allowNegative,currentNumberFormat);
   3200                     }
   3201                     if (number == null && !allowNumericFallback(patternCharIndex)) {
   3202                         // only return if pattern is NOT one that allows numeric fallback
   3203                         return ~start;
   3204                     }
   3205                 }
   3206 
   3207                 if (number != null) {
   3208                     value = number.intValue();
   3209                 }
   3210             }
   3211 
   3212         switch (patternCharIndex)
   3213             {
   3214             case 0: // 'G' - ERA
   3215                 if ( isChineseCalendar ) {
   3216                     // Numeric era handling moved from ChineseDateFormat,
   3217                     // If we didn't have a number, already returned -start above
   3218                     cal.set(Calendar.ERA, value);
   3219                     return pos.getIndex();
   3220                 }
   3221                 int ps = 0;
   3222                 if (count == 5) {
   3223                     ps = matchString(text, start, Calendar.ERA, formatData.narrowEras, null, cal);
   3224                 } else if (count == 4) {
   3225                     ps = matchString(text, start, Calendar.ERA, formatData.eraNames, null, cal);
   3226                 } else {
   3227                     ps = matchString(text, start, Calendar.ERA, formatData.eras, null, cal);
   3228                 }
   3229 
   3230                 // check return position, if it equals -start, then matchString error
   3231                 // special case the return code so we don't necessarily fail out until we
   3232                 // verify no year information also
   3233                 if (ps == ~start)
   3234                     ps = ISOSpecialEra;
   3235 
   3236                 return ps;
   3237 
   3238             case 1: // 'y' - YEAR
   3239             case 18: // 'Y' - YEAR_WOY
   3240                 // If there are 3 or more YEAR pattern characters, this indicates
   3241                 // that the year value is to be treated literally, without any
   3242                 // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
   3243                 // we made adjustments to place the 2-digit year in the proper
   3244                 // century, for parsed strings from "00" to "99".  Any other string
   3245                 // is treated literally:  "2250", "-1", "1", "002".
   3246                 /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
   3247                 /* Skip this for Chinese calendar, moved from ChineseDateFormat */
   3248                 if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) && value < 1000 ) {
   3249                     value += HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
   3250                 } else if (count == 2 && countDigits(text, start, pos.getIndex()) == 2 && cal.haveDefaultCentury()) {
   3251                         // Assume for example that the defaultCenturyStart is 6/18/1903.
   3252                         // This means that two-digit years will be forced into the range
   3253                         // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
   3254                         // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
   3255                         // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
   3256                         // other fields specify a date before 6/18, or 1903 if they specify a
   3257                         // date afterwards.  As a result, 03 is an ambiguous year.  All other
   3258                         // two-digit years are unambiguous.
   3259                         int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
   3260                         ambiguousYear[0] = value == ambiguousTwoDigitYear;
   3261                         value += (getDefaultCenturyStartYear()/100)*100 +
   3262                             (value < ambiguousTwoDigitYear ? 100 : 0);
   3263                 }
   3264                 cal.set(field, value);
   3265 
   3266                 // Delayed checking for adjustment of Hebrew month numbers in non-leap years.
   3267                 if (DelayedHebrewMonthCheck) {
   3268                     if (!HebrewCalendar.isLeapYear(value)) {
   3269                         cal.add(Calendar.MONTH,1);
   3270                     }
   3271                     DelayedHebrewMonthCheck = false;
   3272                 }
   3273                 return pos.getIndex();
   3274             case 30: // 'U' - YEAR_NAME_FIELD
   3275                 if (formatData.shortYearNames != null) {
   3276                     int newStart = matchString(text, start, Calendar.YEAR, formatData.shortYearNames, null, cal);
   3277                     if (newStart > 0) {
   3278                         return newStart;
   3279                     }
   3280                 }
   3281                 if ( number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC) || formatData.shortYearNames == null || value > formatData.shortYearNames.length) ) {
   3282                     cal.set(Calendar.YEAR, value);
   3283                     return pos.getIndex();
   3284                 }
   3285                 return ~start;
   3286             case 2: // 'M' - MONTH
   3287             case 26: // 'L' - STAND_ALONE_MONTH
   3288                 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
   3289                     // i.e., M/MM, L/LL or lenient & have a number
   3290                     // Don't want to parse the month if it is a string
   3291                     // while pattern uses numeric style: M/MM, L/LL.
   3292                     // [We computed 'value' above.]
   3293                     cal.set(Calendar.MONTH, value - 1);
   3294                     // When parsing month numbers from the Hebrew Calendar, we might need
   3295                     // to adjust the month depending on whether or not it was a leap year.
   3296                     // We may or may not yet know what year it is, so might have to delay
   3297                     // checking until the year is parsed.
   3298                     if (cal.getType().equals("hebrew") && value >= 6) {
   3299                         if (cal.isSet(Calendar.YEAR)) {
   3300                             if (!HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR))) {
   3301                                 cal.set(Calendar.MONTH, value);
   3302                             }
   3303                         } else {
   3304                             DelayedHebrewMonthCheck = true;
   3305                         }
   3306                     }
   3307                     return pos.getIndex();
   3308                 } else {
   3309                     // count >= 3 // i.e., MMM/MMMM or LLL/LLLL
   3310                     // Want to be able to parse both short and long forms.
   3311                     boolean haveMonthPat = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT);
   3312                     // Try count == 4 first:, unless we're strict
   3313                     int newStart = 0;
   3314                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3315                         newStart = (patternCharIndex == 2)?
   3316                             matchString(text, start, Calendar.MONTH, formatData.months,
   3317                                     (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null, cal):
   3318                             matchString(text, start, Calendar.MONTH, formatData.standaloneMonths,
   3319                                     (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null, cal);
   3320                     if (newStart > 0) {
   3321                         return newStart;
   3322                         }
   3323                     }
   3324                     // count == 4 failed, now try count == 3
   3325                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3326                         return (patternCharIndex == 2)?
   3327                                 matchString(text, start, Calendar.MONTH, formatData.shortMonths,
   3328                                         (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null, cal):
   3329                                 matchString(text, start, Calendar.MONTH, formatData.standaloneShortMonths,
   3330                                         (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null, cal);
   3331                     }
   3332                     return newStart;
   3333                 }
   3334             case 4: // 'k' - HOUR_OF_DAY (1..24)
   3335                 // [We computed 'value' above.]
   3336                 if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) {
   3337                     value = 0;
   3338                 }
   3339                 cal.set(Calendar.HOUR_OF_DAY, value);
   3340                 return pos.getIndex();
   3341             case 8: // 'S' - FRACTIONAL_SECOND
   3342                 // Fractional seconds left-justify
   3343                 i = countDigits(text, start, pos.getIndex());
   3344                 if (i < 3) {
   3345                     while (i < 3) {
   3346                         value *= 10;
   3347                         i++;
   3348                     }
   3349                 } else {
   3350                     int a = 1;
   3351                     while (i > 3) {
   3352                         a *= 10;
   3353                         i--;
   3354                     }
   3355                     value /= a;
   3356                 }
   3357                 cal.set(Calendar.MILLISECOND, value);
   3358                 return pos.getIndex();
   3359             case 19: // 'e' - DOW_LOCAL
   3360                 if(count <= 2 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) {
   3361                     // i.e. e/ee or lenient and have a number
   3362                     cal.set(field, value);
   3363                     return pos.getIndex();
   3364                 }
   3365                 // else for eee-eeeeee, fall through to EEE-EEEEEE handling
   3366                 //$FALL-THROUGH$
   3367             case 9: { // 'E' - DAY_OF_WEEK
   3368                 // Want to be able to parse at least wide, abbrev, short, and narrow forms.
   3369                 int newStart = 0;
   3370                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3371                     if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.weekdays, null, cal)) > 0) { // try EEEE wide
   3372                         return newStart;
   3373                     }
   3374                 }
   3375                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3376                     if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shortWeekdays, null, cal)) > 0) { // try EEE abbrev
   3377                         return newStart;
   3378                     }
   3379                 }
   3380                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) {
   3381                     if (formatData.shorterWeekdays != null) {
   3382                         if((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shorterWeekdays, null, cal)) > 0) { // try EEEEEE short
   3383                             return newStart;
   3384                         }
   3385                     }
   3386                 }
   3387                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 5) {
   3388                     if (formatData.narrowWeekdays != null) {
   3389                         if((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.narrowWeekdays, null, cal)) > 0) { // try EEEEE narrow
   3390                             return newStart;
   3391                         }
   3392                     }
   3393                 }
   3394                 return newStart;
   3395             }
   3396             case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
   3397                 if(count == 1 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) {
   3398                     // i.e. c or lenient and have a number
   3399                     cal.set(field, value);
   3400                     return pos.getIndex();
   3401                 }
   3402                 // Want to be able to parse at least wide, abbrev, short forms.
   3403                 int newStart = 0;
   3404                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3405                     if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneWeekdays, null, cal)) > 0) { // try cccc wide
   3406                         return newStart;
   3407                     }
   3408                 }
   3409                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3410                     if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShortWeekdays, null, cal)) > 0) { // try ccc abbrev
   3411                         return newStart;
   3412                     }
   3413                 }
   3414                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) {
   3415                     if (formatData.standaloneShorterWeekdays != null) {
   3416                         return matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShorterWeekdays, null, cal); // try cccccc short
   3417                     }
   3418                 }
   3419                 return newStart;
   3420             }
   3421             case 14: { // 'a' - AM_PM
   3422                 // Optionally try both wide/abbrev and narrow forms.
   3423                 // formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version,
   3424                 // in which case our only option is wide form
   3425                 int newStart = 0;
   3426                 // try wide/abbrev a-aaaa
   3427                 if(formatData.ampmsNarrow == null || count < 5 || getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH)) {
   3428                     if ((newStart = matchString(text, start, Calendar.AM_PM, formatData.ampms, null, cal)) > 0) {
   3429                         return newStart;
   3430                     }
   3431                 }
   3432                 // try narrow aaaaa
   3433                 if(formatData.ampmsNarrow != null && (count >= 5 || getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH))) {
   3434                     if ((newStart = matchString(text, start, Calendar.AM_PM, formatData.ampmsNarrow, null, cal)) > 0) {
   3435                         return newStart;
   3436                     }
   3437                 }
   3438                 // no matches for given options
   3439                 return ~start;
   3440             }
   3441             case 15: // 'h' - HOUR (1..12)
   3442                 // [We computed 'value' above.]
   3443                 if (value == cal.getLeastMaximum(Calendar.HOUR)+1) {
   3444                     value = 0;
   3445                 }
   3446                 cal.set(Calendar.HOUR, value);
   3447                 return pos.getIndex();
   3448             case 17: // 'z' - ZONE_OFFSET
   3449             {
   3450                 Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.SPECIFIC_LONG;
   3451                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3452                 if (tz != null) {
   3453                     cal.setTimeZone(tz);
   3454                     return pos.getIndex();
   3455                 }
   3456                 return ~start;
   3457             }
   3458             case 23: // 'Z' - TIMEZONE_RFC
   3459             {
   3460                 Style style = (count < 4) ? Style.ISO_BASIC_LOCAL_FULL : ((count == 5) ? Style.ISO_EXTENDED_FULL : Style.LOCALIZED_GMT);
   3461                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3462                 if (tz != null) {
   3463                     cal.setTimeZone(tz);
   3464                     return pos.getIndex();
   3465                     }
   3466                 return ~start;
   3467                 }
   3468             case 24: // 'v' - TIMEZONE_GENERIC
   3469             {
   3470                 // Note: 'v' only supports count 1 and 4
   3471                 Style style = (count < 4) ? Style.GENERIC_SHORT : Style.GENERIC_LONG;
   3472                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3473                 if (tz != null) {
   3474                     cal.setTimeZone(tz);
   3475                     return pos.getIndex();
   3476                 }
   3477                 return ~start;
   3478             }
   3479             case 29: // 'V' - TIMEZONE_SPECIAL
   3480             {
   3481                 Style style = null;
   3482                 switch (count) {
   3483                 case 1:
   3484                     style = Style.ZONE_ID_SHORT;
   3485                     break;
   3486                 case 2:
   3487                     style = Style.ZONE_ID;
   3488                     break;
   3489                 case 3:
   3490                     style = Style.EXEMPLAR_LOCATION;
   3491                     break;
   3492                 default:
   3493                     style = Style.GENERIC_LOCATION;
   3494                     break;
   3495                 }
   3496                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3497                 if (tz != null) {
   3498                     cal.setTimeZone(tz);
   3499                     return pos.getIndex();
   3500                 }
   3501                 return ~start;
   3502             }
   3503             case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET
   3504             {
   3505                 Style style = (count < 4) ? Style.LOCALIZED_GMT_SHORT : Style.LOCALIZED_GMT;
   3506                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3507                 if (tz != null) {
   3508                     cal.setTimeZone(tz);
   3509                     return pos.getIndex();
   3510                 }
   3511                 return ~start;
   3512             }
   3513             case 32: // 'X' - TIMEZONE_ISO
   3514             {
   3515                 Style style;
   3516                 switch (count) {
   3517                 case 1:
   3518                     style = Style.ISO_BASIC_SHORT;
   3519                     break;
   3520                 case 2:
   3521                     style = Style.ISO_BASIC_FIXED;
   3522                     break;
   3523                 case 3:
   3524                     style = Style.ISO_EXTENDED_FIXED;
   3525                     break;
   3526                 case 4:
   3527                     style = Style.ISO_BASIC_FULL;
   3528                     break;
   3529                 default: // count >= 5
   3530                     style = Style.ISO_EXTENDED_FULL;
   3531                     break;
   3532                 }
   3533                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3534                 if (tz != null) {
   3535                     cal.setTimeZone(tz);
   3536                     return pos.getIndex();
   3537                 }
   3538                 return ~start;
   3539             }
   3540             case 33: // 'x' - TIMEZONE_ISO_LOCAL
   3541             {
   3542                 Style style;
   3543                 switch (count) {
   3544                 case 1:
   3545                     style = Style.ISO_BASIC_LOCAL_SHORT;
   3546                     break;
   3547                 case 2:
   3548                     style = Style.ISO_BASIC_LOCAL_FIXED;
   3549                     break;
   3550                 case 3:
   3551                     style = Style.ISO_EXTENDED_LOCAL_FIXED;
   3552                     break;
   3553                 case 4:
   3554                     style = Style.ISO_BASIC_LOCAL_FULL;
   3555                     break;
   3556                 default: // count >= 5
   3557                     style = Style.ISO_EXTENDED_LOCAL_FULL;
   3558                     break;
   3559                 }
   3560                 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
   3561                 if (tz != null) {
   3562                     cal.setTimeZone(tz);
   3563                     return pos.getIndex();
   3564                 }
   3565                 return ~start;
   3566             }
   3567             case 27: // 'Q' - QUARTER
   3568                 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
   3569                     // i.e., Q or QQ. or lenient & have number
   3570                     // Don't want to parse the quarter if it is a string
   3571                     // while pattern uses numeric style: Q or QQ.
   3572                     // [We computed 'value' above.]
   3573                     cal.set(Calendar.MONTH, (value - 1) * 3);
   3574                     return pos.getIndex();
   3575                 } else {
   3576                     // count >= 3 // i.e., QQQ or QQQQ
   3577                     // Want to be able to parse both short and long forms.
   3578                     // Try count == 4 first:
   3579                     int newStart = 0;
   3580                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3581                         if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.quarters, cal)) > 0) {
   3582                             return newStart;
   3583                         }
   3584                     }
   3585                     // count == 4 failed, now try count == 3
   3586                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3587                         return matchQuarterString(text, start, Calendar.MONTH,
   3588                                            formatData.shortQuarters, cal);
   3589                     }
   3590                     return newStart;
   3591                 }
   3592 
   3593             case 28: // 'q' - STANDALONE QUARTER
   3594                 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
   3595                     // i.e., q or qq. or lenient & have number
   3596                     // Don't want to parse the quarter if it is a string
   3597                     // while pattern uses numeric style: q or qq.
   3598                     // [We computed 'value' above.]
   3599                     cal.set(Calendar.MONTH, (value - 1) * 3);
   3600                     return pos.getIndex();
   3601                 } else {
   3602                     // count >= 3 // i.e., qqq or qqqq
   3603                     // Want to be able to parse both short and long forms.
   3604                     // Try count == 4 first:
   3605                     int newStart = 0;
   3606                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3607                         if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.standaloneQuarters, cal)) > 0) {
   3608                             return newStart;
   3609                         }
   3610                     }
   3611                     // count == 4 failed, now try count == 3
   3612                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3613                         return matchQuarterString(text, start, Calendar.MONTH,
   3614                                            formatData.standaloneShortQuarters, cal);
   3615                     }
   3616                     return newStart;
   3617                 }
   3618 
   3619             case 37: // TIME SEPARATOR (no pattern character currently defined, we should
   3620                      // not get here but leave support in for future definition.
   3621             {
   3622                 // Try matching a time separator.
   3623                 ArrayList<String> data = new ArrayList<String>(3);
   3624                 data.add(formatData.getTimeSeparatorString());
   3625 
   3626                 // Add the default, if different from the locale.
   3627                 if (!formatData.getTimeSeparatorString().equals(DateFormatSymbols.DEFAULT_TIME_SEPARATOR)) {
   3628                     data.add(DateFormatSymbols.DEFAULT_TIME_SEPARATOR);
   3629                 }
   3630 
   3631                 // If lenient, add also the alternate, if different from the locale.
   3632                 if (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH) &&
   3633                         !formatData.getTimeSeparatorString().equals(DateFormatSymbols.ALTERNATE_TIME_SEPARATOR)) {
   3634                     data.add(DateFormatSymbols.ALTERNATE_TIME_SEPARATOR);
   3635                 }
   3636 
   3637                 return matchString(text, start, -1 /* => nothing to set */, data.toArray(new String[0]), cal);
   3638             }
   3639 
   3640             case 35: // 'b' -- fixed day period (am/pm/midnight/noon)
   3641             {
   3642                 int ampmStart = subParse(text, start, 'a', count, obeyCount, allowNegative, ambiguousYear, cal,
   3643                         numericLeapMonthFormatter, tzTimeType, dayPeriod);
   3644 
   3645                 if (ampmStart > 0) {
   3646                     return ampmStart;
   3647                 } else {
   3648                     int newStart = 0;
   3649                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3650                         if ((newStart = matchDayPeriodString(
   3651                                 text, start, formatData.abbreviatedDayPeriods, 2, dayPeriod)) > 0) {
   3652                             return newStart;
   3653                         }
   3654                     }
   3655                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3656                         if ((newStart = matchDayPeriodString(
   3657                                 text, start, formatData.wideDayPeriods, 2, dayPeriod)) > 0) {
   3658                             return newStart;
   3659                         }
   3660                     }
   3661                     if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3662                         if ((newStart = matchDayPeriodString(
   3663                                 text, start, formatData.narrowDayPeriods, 2, dayPeriod)) > 0) {
   3664                             return newStart;
   3665                         }
   3666                     }
   3667 
   3668                     return newStart;
   3669                 }
   3670             }
   3671 
   3672             case 36: // 'B' -- flexible day period
   3673             {
   3674                 int newStart = 0;
   3675                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) {
   3676                     if ((newStart = matchDayPeriodString(
   3677                             text, start, formatData.abbreviatedDayPeriods,
   3678                             formatData.abbreviatedDayPeriods.length, dayPeriod)) > 0) {
   3679                         return newStart;
   3680                     }
   3681                 }
   3682                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3683                     if ((newStart = matchDayPeriodString(
   3684                             text, start, formatData.wideDayPeriods,
   3685                             formatData.wideDayPeriods.length, dayPeriod)) > 0) {
   3686                         return newStart;
   3687                     }
   3688                 }
   3689                 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) {
   3690                     if ((newStart = matchDayPeriodString(
   3691                             text, start, formatData.narrowDayPeriods,
   3692                             formatData.narrowDayPeriods.length, dayPeriod)) > 0) {
   3693                         return newStart;
   3694                     }
   3695                 }
   3696 
   3697                 return newStart;
   3698             }
   3699 
   3700             default:
   3701                 // case 3: // 'd' - DATE
   3702                 // case 5: // 'H' - HOUR_OF_DAY (0..23)
   3703                 // case 6: // 'm' - MINUTE
   3704                 // case 7: // 's' - SECOND
   3705                 // case 10: // 'D' - DAY_OF_YEAR
   3706                 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
   3707                 // case 12: // 'w' - WEEK_OF_YEAR
   3708                 // case 13: // 'W' - WEEK_OF_MONTH
   3709                 // case 16: // 'K' - HOUR (0..11)
   3710                 // case 20: // 'u' - EXTENDED_YEAR
   3711                 // case 21: // 'g' - JULIAN_DAY
   3712                 // case 22: // 'A' - MILLISECONDS_IN_DAY
   3713                 // case 34: //
   3714 
   3715                 // Handle "generic" fields
   3716                 if (obeyCount) {
   3717                     if ((start+count) > text.length()) return -start;
   3718                     number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
   3719                 } else {
   3720                     number = parseInt(text, pos, allowNegative,currentNumberFormat);
   3721                 }
   3722                 if (number != null) {
   3723                     if (patternCharIndex != DateFormat.RELATED_YEAR) {
   3724                         cal.set(field, number.intValue());
   3725                     } else {
   3726                         cal.setRelatedYear(number.intValue());
   3727                     }
   3728                     return pos.getIndex();
   3729                 }
   3730                 return ~start;
   3731             }
   3732     }
   3733 
   3734     /**
   3735      * return true if the pattern specified by patternCharIndex is one that allows
   3736      * numeric fallback regardless of actual pattern size.
   3737      */
   3738     private boolean allowNumericFallback(int patternCharIndex) {
   3739         if (patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ ||
   3740             patternCharIndex == 19 /*'e' DOW_LOCAL*/ ||
   3741             patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ ||
   3742             patternCharIndex == 30 /*'U' YEAR_NAME_FIELD*/ ||
   3743             patternCharIndex == 27 /* 'Q' - QUARTER*/ ||
   3744             patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/) {
   3745             return true;
   3746         }
   3747         return false;
   3748     }
   3749 
   3750     /**
   3751      * Parse an integer using numberFormat.  This method is semantically
   3752      * const, but actually may modify fNumberFormat.
   3753      */
   3754     private Number parseInt(String text,
   3755                             ParsePosition pos,
   3756                             boolean allowNegative,
   3757                             NumberFormat fmt) {
   3758         return parseInt(text, -1, pos, allowNegative, fmt);
   3759     }
   3760 
   3761     /**
   3762      * Parse an integer using numberFormat up to maxDigits.
   3763      */
   3764     private Number parseInt(String text,
   3765                             int maxDigits,
   3766                             ParsePosition pos,
   3767                             boolean allowNegative,
   3768                             NumberFormat fmt) {
   3769         Number number;
   3770         int oldPos = pos.getIndex();
   3771         if (allowNegative) {
   3772             number = fmt.parse(text, pos);
   3773         } else {
   3774             // Invalidate negative numbers
   3775             if (fmt instanceof DecimalFormat) {
   3776                 String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();
   3777                 ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
   3778                 number = fmt.parse(text, pos);
   3779                 ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);
   3780             } else {
   3781                 boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
   3782                 if (dateNumberFormat) {
   3783                     ((DateNumberFormat)fmt).setParsePositiveOnly(true);
   3784                 }
   3785                 number = fmt.parse(text, pos);
   3786                 if (dateNumberFormat) {
   3787                     ((DateNumberFormat)fmt).setParsePositiveOnly(false);
   3788                 }
   3789             }
   3790         }
   3791         if (maxDigits > 0) {
   3792             // adjust the result to fit into
   3793             // the maxDigits and move the position back
   3794             int nDigits = pos.getIndex() - oldPos;
   3795             if (nDigits > maxDigits) {
   3796                 double val = number.doubleValue();
   3797                 nDigits -= maxDigits;
   3798                 while (nDigits > 0) {
   3799                     val /= 10;
   3800                     nDigits--;
   3801                 }
   3802                 pos.setIndex(oldPos + maxDigits);
   3803                 number = Integer.valueOf((int)val);
   3804             }
   3805         }
   3806         return number;
   3807     }
   3808 
   3809     /**
   3810      * Counts number of digit code points in the specified text.
   3811      *
   3812      * @param text  input text
   3813      * @param start start index, inclusive
   3814      * @param end   end index, exclusive
   3815      * @return  number of digits found in the text in the specified range.
   3816      */
   3817     private static int countDigits(String text, int start, int end) {
   3818         int numDigits = 0;
   3819         int idx = start;
   3820         while (idx < end) {
   3821             int cp = text.codePointAt(idx);
   3822             if (UCharacter.isDigit(cp)) {
   3823                 numDigits++;
   3824             }
   3825             idx += UCharacter.charCount(cp);
   3826         }
   3827         return numDigits;
   3828     }
   3829 
   3830     /**
   3831      * Translate a pattern, mapping each character in the from string to the
   3832      * corresponding character in the to string.
   3833      */
   3834     private String translatePattern(String pat, String from, String to) {
   3835         StringBuilder result = new StringBuilder();
   3836         boolean inQuote = false;
   3837         for (int i = 0; i < pat.length(); ++i) {
   3838             char c = pat.charAt(i);
   3839             if (inQuote) {
   3840                 if (c == '\'')
   3841                     inQuote = false;
   3842             } else {
   3843                 if (c == '\'') {
   3844                     inQuote = true;
   3845                 } else if (isSyntaxChar(c)) {
   3846                     int ci = from.indexOf(c);
   3847                     if (ci != -1) {
   3848                         c = to.charAt(ci);
   3849                     }
   3850                     // do not worry on translatepattern if the character is not listed
   3851                     // we do the validity check elsewhere
   3852                 }
   3853             }
   3854             result.append(c);
   3855         }
   3856         if (inQuote) {
   3857             throw new IllegalArgumentException("Unfinished quote in pattern");
   3858         }
   3859         return result.toString();
   3860     }
   3861 
   3862     /**
   3863      * Return a pattern string describing this date format.
   3864      * @stable ICU 2.0
   3865      */
   3866     public String toPattern() {
   3867         return pattern;
   3868     }
   3869 
   3870     /**
   3871      * Return a localized pattern string describing this date format.
   3872      * <p>
   3873      * <b>Note:</b> This implementation depends on {@link DateFormatSymbols#getLocalPatternChars()}
   3874      * to get localized format pattern characters. ICU does not include
   3875      * localized pattern character data, therefore, unless user sets localized
   3876      * pattern characters manually, this method returns the same result as
   3877      * {@link #toPattern()}.
   3878      *
   3879      * @stable ICU 2.0
   3880      */
   3881     public String toLocalizedPattern() {
   3882         return translatePattern(pattern,
   3883                                 DateFormatSymbols.patternChars,
   3884                                 formatData.localPatternChars);
   3885     }
   3886 
   3887     /**
   3888      * Apply the given unlocalized pattern string to this date format.
   3889      * @stable ICU 2.0
   3890      */
   3891     public void applyPattern(String pat)
   3892     {
   3893         this.pattern = pat;
   3894         parsePattern();
   3895 
   3896         setLocale(null, null);
   3897         // reset parsed pattern items
   3898         patternItems = null;
   3899     }
   3900 
   3901     /**
   3902      * Apply the given localized pattern string to this date format.
   3903      * @stable ICU 2.0
   3904      */
   3905     public void applyLocalizedPattern(String pat) {
   3906         this.pattern = translatePattern(pat,
   3907                                         formatData.localPatternChars,
   3908                                         DateFormatSymbols.patternChars);
   3909         setLocale(null, null);
   3910     }
   3911 
   3912     /**
   3913      * Gets the date/time formatting data.
   3914      * @return a copy of the date-time formatting data associated
   3915      * with this date-time formatter.
   3916      * @stable ICU 2.0
   3917      */
   3918     public DateFormatSymbols getDateFormatSymbols()
   3919     {
   3920         return (DateFormatSymbols)formatData.clone();
   3921     }
   3922 
   3923     /**
   3924      * Allows you to set the date/time formatting data.
   3925      * @param newFormatSymbols the new symbols
   3926      * @stable ICU 2.0
   3927      */
   3928     public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
   3929     {
   3930         this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
   3931     }
   3932 
   3933     /**
   3934      * Method for subclasses to access the DateFormatSymbols.
   3935      * @stable ICU 2.0
   3936      */
   3937     protected DateFormatSymbols getSymbols() {
   3938         return formatData;
   3939     }
   3940 
   3941     /**
   3942      * {@icu} Gets the time zone formatter which this date/time
   3943      * formatter uses to format and parse a time zone.
   3944      *
   3945      * @return the time zone formatter which this date/time
   3946      * formatter uses.
   3947      * @stable ICU 49
   3948      */
   3949     public TimeZoneFormat getTimeZoneFormat() {
   3950         return tzFormat().freeze();
   3951     }
   3952 
   3953     /**
   3954      * {@icu} Allows you to set the time zone formatter.
   3955      *
   3956      * @param tzfmt the new time zone formatter
   3957      * @stable ICU 49
   3958      */
   3959     public void setTimeZoneFormat(TimeZoneFormat tzfmt) {
   3960         if (tzfmt.isFrozen()) {
   3961             // If frozen, use it as is.
   3962             tzFormat = tzfmt;
   3963         } else {
   3964             // If not frozen, clone and freeze.
   3965             tzFormat = tzfmt.cloneAsThawed().freeze();
   3966         }
   3967     }
   3968 
   3969     /**
   3970      * Overrides Cloneable
   3971      * @stable ICU 2.0
   3972      */
   3973     @Override
   3974     public Object clone() {
   3975         SimpleDateFormat other = (SimpleDateFormat) super.clone();
   3976         other.formatData = (DateFormatSymbols) formatData.clone();
   3977         // We must create a new copy of work buffer used by
   3978         // the fast numeric field format code.
   3979         if (this.decimalBuf != null) {
   3980             other.decimalBuf = new char[DECIMAL_BUF_SIZE];
   3981         }
   3982         return other;
   3983     }
   3984 
   3985     /**
   3986      * Override hashCode.
   3987      * Generates the hash code for the SimpleDateFormat object
   3988      * @stable ICU 2.0
   3989      */
   3990     @Override
   3991     public int hashCode()
   3992     {
   3993         return pattern.hashCode();
   3994         // just enough fields for a reasonable distribution
   3995     }
   3996 
   3997     /**
   3998      * Override equals.
   3999      * @stable ICU 2.0
   4000      */
   4001     @Override
   4002     public boolean equals(Object obj)
   4003     {
   4004         if (!super.equals(obj)) return false; // super does class check
   4005         SimpleDateFormat that = (SimpleDateFormat) obj;
   4006         return (pattern.equals(that.pattern)
   4007                 && formatData.equals(that.formatData));
   4008     }
   4009 
   4010     /**
   4011      * Override writeObject.
   4012      * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectOutputStream.html
   4013      */
   4014     private void writeObject(ObjectOutputStream stream) throws IOException{
   4015         if (defaultCenturyStart == null) {
   4016             // if defaultCenturyStart is not yet initialized,
   4017             // calculate and set value before serialization.
   4018             initializeDefaultCenturyStart(defaultCenturyBase);
   4019         }
   4020         initializeTimeZoneFormat(false);
   4021         stream.defaultWriteObject();
   4022         stream.writeInt(getContext(DisplayContext.Type.CAPITALIZATION).value());
   4023     }
   4024 
   4025     /**
   4026      * Override readObject.
   4027      * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectInputStream.html
   4028      */
   4029     private void readObject(ObjectInputStream stream)
   4030         throws IOException, ClassNotFoundException {
   4031         stream.defaultReadObject();
   4032         int capitalizationSettingValue = (serialVersionOnStream > 1)? stream.readInt(): -1;
   4033         ///CLOVER:OFF
   4034         // don't have old serial data to test with
   4035         if (serialVersionOnStream < 1) {
   4036             // didn't have defaultCenturyStart field
   4037             defaultCenturyBase = System.currentTimeMillis();
   4038         }
   4039         ///CLOVER:ON
   4040         else {
   4041             // fill in dependent transient field
   4042             parseAmbiguousDatesAsAfter(defaultCenturyStart);
   4043         }
   4044         serialVersionOnStream = currentSerialVersion;
   4045         locale = getLocale(ULocale.VALID_LOCALE);
   4046         if (locale == null) {
   4047             // ICU4J 3.6 or older versions did not have UFormat locales
   4048             // in the serialized data. This is just for preventing the
   4049             // worst case scenario...
   4050             locale = ULocale.getDefault(Category.FORMAT);
   4051         }
   4052 
   4053         initLocalZeroPaddingNumberFormat();
   4054 
   4055         setContext(DisplayContext.CAPITALIZATION_NONE);
   4056         if (capitalizationSettingValue >= 0) {
   4057             for (DisplayContext context: DisplayContext.values()) {
   4058                 if (context.value() == capitalizationSettingValue) {
   4059                     setContext(context);
   4060                     break;
   4061                 }
   4062             }
   4063         }
   4064 
   4065         // if serialized pre-56 update & turned off partial match switch to new enum value
   4066         if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH) == false) {
   4067             setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH, false);
   4068         }
   4069 
   4070         parsePattern();
   4071     }
   4072 
   4073     /**
   4074      * Format the object to an attributed string, and return the corresponding iterator
   4075      * Overrides superclass method.
   4076      *
   4077      * @param obj The object to format
   4078      * @return <code>AttributedCharacterIterator</code> describing the formatted value.
   4079      *
   4080      * @stable ICU 3.8
   4081      */
   4082     @Override
   4083     public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
   4084         Calendar cal = calendar;
   4085         if (obj instanceof Calendar) {
   4086             cal = (Calendar)obj;
   4087         } else if (obj instanceof Date) {
   4088             calendar.setTime((Date)obj);
   4089         } else if (obj instanceof Number) {
   4090             calendar.setTimeInMillis(((Number)obj).longValue());
   4091         } else {
   4092             throw new IllegalArgumentException("Cannot format given Object as a Date");
   4093         }
   4094         StringBuffer toAppendTo = new StringBuffer();
   4095         FieldPosition pos = new FieldPosition(0);
   4096         List<FieldPosition> attributes = new ArrayList<FieldPosition>();
   4097         format(cal, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, attributes);
   4098 
   4099         AttributedString as = new AttributedString(toAppendTo.toString());
   4100 
   4101         // add DateFormat field attributes to the AttributedString
   4102         for (int i = 0; i < attributes.size(); i++) {
   4103             FieldPosition fp = attributes.get(i);
   4104             Format.Field attribute = fp.getFieldAttribute();
   4105             as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());
   4106         }
   4107         // return the CharacterIterator from AttributedString
   4108         return as.getIterator();
   4109     }
   4110 
   4111     /**
   4112      * Get the locale of this simple date formatter.
   4113      * It is package accessible. also used in DateIntervalFormat.
   4114      *
   4115      * @return   locale in this simple date formatter
   4116      */
   4117     ULocale getLocale()
   4118     {
   4119         return locale;
   4120     }
   4121 
   4122 
   4123 
   4124     /**
   4125      * Check whether the 'field' is smaller than all the fields covered in
   4126      * pattern, return true if it is.
   4127      * The sequence of calendar field,
   4128      * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
   4129      * @param field    the calendar field need to check against
   4130      * @return         true if the 'field' is smaller than all the fields
   4131      *                 covered in pattern. false otherwise.
   4132      */
   4133 
   4134     boolean isFieldUnitIgnored(int field) {
   4135         return isFieldUnitIgnored(pattern, field);
   4136     }
   4137 
   4138 
   4139     /*
   4140      * Check whether the 'field' is smaller than all the fields covered in
   4141      * pattern, return true if it is.
   4142      * The sequence of calendar field,
   4143      * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
   4144      * @param pattern  the pattern to check against
   4145      * @param field    the calendar field need to check against
   4146      * @return         true if the 'field' is smaller than all the fields
   4147      *                 covered in pattern. false otherwise.
   4148      */
   4149     static boolean isFieldUnitIgnored(String pattern, int field) {
   4150         int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
   4151         int level;
   4152         char ch;
   4153         boolean inQuote = false;
   4154         char prevCh = 0;
   4155         int count = 0;
   4156 
   4157         for (int i = 0; i < pattern.length(); ++i) {
   4158             ch = pattern.charAt(i);
   4159             if (ch != prevCh && count > 0) {
   4160                 level = getLevelFromChar(prevCh);
   4161                 if (fieldLevel <= level) {
   4162                     return false;
   4163                 }
   4164                 count = 0;
   4165             }
   4166             if (ch == '\'') {
   4167                 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
   4168                     ++i;
   4169                 } else {
   4170                     inQuote = ! inQuote;
   4171                 }
   4172             } else if (!inQuote && isSyntaxChar(ch)) {
   4173                 prevCh = ch;
   4174                 ++count;
   4175             }
   4176         }
   4177         if (count > 0) {
   4178             // last item
   4179             level = getLevelFromChar(prevCh);
   4180             if (fieldLevel <= level) {
   4181                 return false;
   4182             }
   4183         }
   4184         return true;
   4185     }
   4186 
   4187 
   4188     /**
   4189      * Format date interval by algorithm.
   4190      * It is supposed to be used only by CLDR survey tool.
   4191      *
   4192      * @param fromCalendar      calendar set to the from date in date interval
   4193      *                          to be formatted into date interval stirng
   4194      * @param toCalendar        calendar set to the to date in date interval
   4195      *                          to be formatted into date interval stirng
   4196      * @param appendTo          Output parameter to receive result.
   4197      *                          Result is appended to existing contents.
   4198      * @param pos               On input: an alignment field, if desired.
   4199      *                          On output: the offsets of the alignment field.
   4200      * @exception IllegalArgumentException when there is non-recognized
   4201      *                                     pattern letter
   4202      * @return                  Reference to 'appendTo' parameter.
   4203      * @internal
   4204      * @deprecated This API is ICU internal only.
   4205      */
   4206     @Deprecated
   4207     public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
   4208                                                         Calendar toCalendar,
   4209                                                         StringBuffer appendTo,
   4210                                                         FieldPosition pos)
   4211                               throws IllegalArgumentException
   4212     {
   4213         // not support different calendar types and time zones
   4214         if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
   4215             throw new IllegalArgumentException("can not format on two different calendars");
   4216         }
   4217 
   4218         Object[] items = getPatternItems();
   4219         int diffBegin = -1;
   4220         int diffEnd = -1;
   4221 
   4222         /* look for different formatting string range */
   4223         // look for start of difference
   4224         try {
   4225             for (int i = 0; i < items.length; i++) {
   4226                 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
   4227                     diffBegin = i;
   4228                     break;
   4229                 }
   4230             }
   4231 
   4232             if ( diffBegin == -1 ) {
   4233                 // no difference, single date format
   4234                 return format(fromCalendar, appendTo, pos);
   4235             }
   4236 
   4237             // look for end of difference
   4238             for (int i = items.length-1; i >= diffBegin; i--) {
   4239                 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
   4240                     diffEnd = i;
   4241                     break;
   4242                 }
   4243             }
   4244         } catch ( IllegalArgumentException e ) {
   4245             throw new IllegalArgumentException(e.toString());
   4246         }
   4247 
   4248         // full range is different
   4249         if ( diffBegin == 0 && diffEnd == items.length-1 ) {
   4250             format(fromCalendar, appendTo, pos);
   4251             appendTo.append(" \u2013 "); // default separator
   4252             format(toCalendar, appendTo, pos);
   4253             return appendTo;
   4254         }
   4255 
   4256 
   4257         /* search for largest calendar field within the different range */
   4258         int highestLevel = 1000;
   4259         for (int i = diffBegin; i <= diffEnd; i++) {
   4260             if ( items[i] instanceof String) {
   4261                 continue;
   4262             }
   4263             PatternItem item = (PatternItem)items[i];
   4264             char ch = item.type;
   4265             int patternCharIndex = getIndexFromChar(ch);
   4266             if (patternCharIndex == -1) {
   4267                 throw new IllegalArgumentException("Illegal pattern character " +
   4268                                                    "'" + ch + "' in \"" +
   4269                                                    pattern + '"');
   4270             }
   4271 
   4272             if ( patternCharIndex < highestLevel ) {
   4273                 highestLevel = patternCharIndex;
   4274             }
   4275         }
   4276 
   4277         /* re-calculate diff range, including those calendar field which
   4278            is in lower level than the largest calendar field covered
   4279            in diff range calculated. */
   4280         try {
   4281             for (int i = 0; i < diffBegin; i++) {
   4282                 if ( lowerLevel(items, i, highestLevel) ) {
   4283                     diffBegin = i;
   4284                     break;
   4285                 }
   4286             }
   4287 
   4288 
   4289             for (int i = items.length-1; i > diffEnd; i--) {
   4290                 if ( lowerLevel(items, i, highestLevel) ) {
   4291                     diffEnd = i;
   4292                     break;
   4293                 }
   4294             }
   4295         } catch ( IllegalArgumentException e ) {
   4296             throw new IllegalArgumentException(e.toString());
   4297         }
   4298 
   4299 
   4300         // full range is different
   4301         if ( diffBegin == 0 && diffEnd == items.length-1 ) {
   4302             format(fromCalendar, appendTo, pos);
   4303             appendTo.append(" \u2013 "); // default separator
   4304             format(toCalendar, appendTo, pos);
   4305             return appendTo;
   4306         }
   4307 
   4308 
   4309         // formatting
   4310         // Initialize
   4311         pos.setBeginIndex(0);
   4312         pos.setEndIndex(0);
   4313         DisplayContext capSetting = getContext(DisplayContext.Type.CAPITALIZATION);
   4314 
   4315         // formatting date 1
   4316         for (int i = 0; i <= diffEnd; i++) {
   4317             if (items[i] instanceof String) {
   4318                 appendTo.append((String)items[i]);
   4319             } else {
   4320                 PatternItem item = (PatternItem)items[i];
   4321                 if (useFastFormat) {
   4322                     subFormat(appendTo, item.type, item.length, appendTo.length(),
   4323                               i, capSetting, pos, fromCalendar);
   4324                 } else {
   4325                     appendTo.append(subFormat(item.type, item.length, appendTo.length(),
   4326                                               i, capSetting, pos, fromCalendar));
   4327                 }
   4328             }
   4329         }
   4330 
   4331         appendTo.append(" \u2013 "); // default separator
   4332 
   4333         // formatting date 2
   4334         for (int i = diffBegin; i < items.length; i++) {
   4335             if (items[i] instanceof String) {
   4336                 appendTo.append((String)items[i]);
   4337             } else {
   4338                 PatternItem item = (PatternItem)items[i];
   4339                 if (useFastFormat) {
   4340                     subFormat(appendTo, item.type, item.length, appendTo.length(),
   4341                               i, capSetting, pos, toCalendar);
   4342                 } else {
   4343                     appendTo.append(subFormat(item.type, item.length, appendTo.length(),
   4344                                               i, capSetting, pos, toCalendar));
   4345                 }
   4346             }
   4347         }
   4348         return appendTo;
   4349     }
   4350 
   4351 
   4352     /**
   4353      * check whether the i-th item in 2 calendar is in different value.
   4354      *
   4355      * It is supposed to be used only by CLDR survey tool.
   4356      * It is used by intervalFormatByAlgorithm().
   4357      *
   4358      * @param fromCalendar   one calendar
   4359      * @param toCalendar     the other calendar
   4360      * @param items          pattern items
   4361      * @param i              the i-th item in pattern items
   4362      * @exception IllegalArgumentException when there is non-recognized
   4363      *                                     pattern letter
   4364      * @return               true is i-th item in 2 calendar is in different
   4365      *                       value, false otherwise.
   4366      */
   4367     private boolean diffCalFieldValue(Calendar fromCalendar,
   4368                                       Calendar toCalendar,
   4369                                       Object[] items,
   4370                                       int i) throws IllegalArgumentException {
   4371         if ( items[i] instanceof String) {
   4372             return false;
   4373         }
   4374         PatternItem item = (PatternItem)items[i];
   4375         char ch = item.type;
   4376         int patternCharIndex = getIndexFromChar(ch);
   4377         if (patternCharIndex == -1) {
   4378             throw new IllegalArgumentException("Illegal pattern character " +
   4379                                                "'" + ch + "' in \"" +
   4380                                                pattern + '"');
   4381         }
   4382 
   4383         final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
   4384         if (field >= 0) {
   4385             int value = fromCalendar.get(field);
   4386             int value_2 = toCalendar.get(field);
   4387             if ( value != value_2 ) {
   4388                 return true;
   4389             }
   4390         }
   4391         return false;
   4392     }
   4393 
   4394 
   4395     /**
   4396      * check whether the i-th item's level is lower than the input 'level'
   4397      *
   4398      * It is supposed to be used only by CLDR survey tool.
   4399      * It is used by intervalFormatByAlgorithm().
   4400      *
   4401      * @param items  the pattern items
   4402      * @param i      the i-th item in pattern items
   4403      * @param level  the level with which the i-th pattern item compared to
   4404      * @exception IllegalArgumentException when there is non-recognized
   4405      *                                     pattern letter
   4406      * @return       true if i-th pattern item is lower than 'level',
   4407      *               false otherwise
   4408      */
   4409     private boolean lowerLevel(Object[] items, int i, int level)
   4410                     throws IllegalArgumentException {
   4411         if (items[i] instanceof String) {
   4412             return false;
   4413         }
   4414         PatternItem item = (PatternItem)items[i];
   4415         char ch = item.type;
   4416         int patternCharIndex = getLevelFromChar(ch);
   4417         if (patternCharIndex == -1) {
   4418             throw new IllegalArgumentException("Illegal pattern character " +
   4419                                                "'" + ch + "' in \"" +
   4420                                                pattern + '"');
   4421         }
   4422 
   4423         if (patternCharIndex >= level) {
   4424             return true;
   4425         }
   4426         return false;
   4427     }
   4428 
   4429     /**
   4430      * allow the user to set the NumberFormat for several fields
   4431      * It can be a single field like: "y"(year) or "M"(month)
   4432      * It can be several field combined together: "yMd"(year, month and date)
   4433      * Note:
   4434      * 1 symbol field is enough for multiple symbol fields (so "y" will override "yy", "yyy")
   4435      * If the field is not numeric, then override has no effect (like "MMM" will use abbreviation, not numerical field)
   4436      *
   4437      * @param fields the fields to override
   4438      * @param overrideNF the NumbeferFormat used
   4439      * @exception IllegalArgumentException when the fields contain invalid field
   4440      * @stable ICU 54
   4441      */
   4442     public void setNumberFormat(String fields, NumberFormat overrideNF) {
   4443         overrideNF.setGroupingUsed(false);
   4444         String nsName = "$" + UUID.randomUUID().toString();
   4445 
   4446         // initialize mapping if not there
   4447         if (numberFormatters == null) {
   4448             numberFormatters = new HashMap<String, NumberFormat>();
   4449         }
   4450         if (overrideMap == null) {
   4451             overrideMap = new HashMap<Character, String>();
   4452         }
   4453 
   4454         // separate string into char and add to maps
   4455         for (int i = 0; i < fields.length(); i++) {
   4456             char field = fields.charAt(i);
   4457             if (DateFormatSymbols.patternChars.indexOf(field) == -1) {
   4458                 throw new IllegalArgumentException("Illegal field character " + "'" + field + "' in setNumberFormat.");
   4459             }
   4460             overrideMap.put(field, nsName);
   4461             numberFormatters.put(nsName, overrideNF);
   4462         }
   4463 
   4464         // Since one or more of the override number formatters might be complex,
   4465         // we can't rely on the fast numfmt where we have a partial field override.
   4466         useLocalZeroPaddingNumberFormat = false;
   4467     }
   4468 
   4469     /**
   4470      * give the NumberFormat used for the field like 'y'(year) and 'M'(year)
   4471      *
   4472      * @param field the field the user wants
   4473      * @return override NumberFormat used for the field
   4474      * @stable ICU 54
   4475      */
   4476     public NumberFormat getNumberFormat(char field) {
   4477         Character ovrField;
   4478         ovrField = Character.valueOf(field);
   4479         if (overrideMap != null && overrideMap.containsKey(ovrField)) {
   4480             String nsName = overrideMap.get(ovrField).toString();
   4481             NumberFormat nf = numberFormatters.get(nsName);
   4482             return nf;
   4483         } else {
   4484             return numberFormat;
   4485         }
   4486     }
   4487 
   4488     private void initNumberFormatters(ULocale loc) {
   4489 
   4490        numberFormatters = new HashMap<String, NumberFormat>();
   4491        overrideMap = new HashMap<Character, String>();
   4492        processOverrideString(loc,override);
   4493 
   4494     }
   4495 
   4496     private void processOverrideString(ULocale loc, String str) {
   4497 
   4498         if ( str == null || str.length() == 0 )
   4499             return;
   4500 
   4501         int start = 0;
   4502         int end;
   4503         String nsName;
   4504         Character ovrField;
   4505         boolean moreToProcess = true;
   4506         boolean fullOverride;
   4507 
   4508         while (moreToProcess) {
   4509             int delimiterPosition = str.indexOf(";",start);
   4510             if (delimiterPosition == -1) {
   4511                 moreToProcess = false;
   4512                 end = str.length();
   4513             } else {
   4514                 end = delimiterPosition;
   4515             }
   4516 
   4517             String currentString = str.substring(start,end);
   4518             int equalSignPosition = currentString.indexOf("=");
   4519             if (equalSignPosition == -1) { // Simple override string such as "hebrew"
   4520                nsName = currentString;
   4521                fullOverride = true;
   4522             } else { // Field specific override string such as "y=hebrew"
   4523                nsName = currentString.substring(equalSignPosition+1);
   4524                ovrField = Character.valueOf(currentString.charAt(0));
   4525                overrideMap.put(ovrField,nsName);
   4526                fullOverride = false;
   4527             }
   4528 
   4529             ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
   4530             NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
   4531             nf.setGroupingUsed(false);
   4532 
   4533             if (fullOverride) {
   4534                 setNumberFormat(nf);
   4535             } else {
   4536                 // Since one or more of the override number formatters might be complex,
   4537                 // we can't rely on the fast numfmt where we have a partial field override.
   4538                 useLocalZeroPaddingNumberFormat = false;
   4539             }
   4540 
   4541             if (!fullOverride && !numberFormatters.containsKey(nsName)) {
   4542                   numberFormatters.put(nsName,nf);
   4543             }
   4544 
   4545             start = delimiterPosition + 1;
   4546         }
   4547     }
   4548 
   4549     private void parsePattern() {
   4550         hasMinute = false;
   4551         hasSecond = false;
   4552 
   4553         boolean inQuote = false;
   4554         for (int i = 0; i < pattern.length(); ++i) {
   4555             char ch = pattern.charAt(i);
   4556             if (ch == '\'') {
   4557                 inQuote = !inQuote;
   4558             }
   4559             if (!inQuote) {
   4560                 if (ch == 'm') {
   4561                     hasMinute = true;
   4562                 }
   4563                 if (ch == 's') {
   4564                     hasSecond = true;
   4565                 }
   4566             }
   4567         }
   4568     }
   4569 }
   4570