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