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