2 *******************************************************************************
3 * Copyright (C) 1996-2013, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
8 package com.ibm.icu.text;
10 import java.io.IOException;
11 import java.io.ObjectInputStream;
12 import java.io.ObjectOutputStream;
13 import java.text.AttributedCharacterIterator;
14 import java.text.AttributedString;
15 import java.text.FieldPosition;
16 import java.text.Format;
17 import java.text.ParsePosition;
18 import java.util.ArrayList;
19 import java.util.Date;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.MissingResourceException;
25 import com.ibm.icu.impl.CalendarData;
26 import com.ibm.icu.impl.DateNumberFormat;
27 import com.ibm.icu.impl.ICUCache;
28 import com.ibm.icu.impl.PatternProps;
29 import com.ibm.icu.impl.SimpleCache;
30 import com.ibm.icu.lang.UCharacter;
31 import com.ibm.icu.text.TimeZoneFormat.Style;
32 import com.ibm.icu.text.TimeZoneFormat.TimeType;
33 import com.ibm.icu.util.BasicTimeZone;
34 import com.ibm.icu.util.Calendar;
35 import com.ibm.icu.util.HebrewCalendar;
36 import com.ibm.icu.util.Output;
37 import com.ibm.icu.util.TimeZone;
38 import com.ibm.icu.util.TimeZoneTransition;
39 import com.ibm.icu.util.ULocale;
40 import com.ibm.icu.util.ULocale.Category;
44 * {@icuenhanced java.text.SimpleDateFormat}.{@icu _usage_}
46 * <p><code>SimpleDateFormat</code> is a concrete class for formatting and
47 * parsing dates in a locale-sensitive manner. It allows for formatting
48 * (date -> text), parsing (text -> date), and normalization.
51 * <code>SimpleDateFormat</code> allows you to start by choosing
52 * any user-defined patterns for date-time formatting. However, you
53 * are encouraged to create a date-time formatter with either
54 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
55 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
56 * of these class methods can return a date/time formatter initialized
57 * with a default format pattern. You may modify the format pattern
58 * using the <code>applyPattern</code> methods as desired.
59 * For more information on using these methods, see
62 * <p><strong>Date and Time Patterns:</strong></p>
64 * <p>Date and time formats are specified by <em>date and time pattern</em> strings.
65 * Within date and time pattern strings, all unquoted ASCII letters [A-Za-z] are reserved
66 * as pattern letters representing calendar fields. <code>SimpleDateFormat</code> supports
67 * the date and time formatting algorithm and pattern letters defined by <a href="http://www.unicode.org/reports/tr35/">UTS#35
68 * Unicode Locale Data Markup Language (LDML)</a>. The following pattern letters are
69 * currently available:</p>
74 * <th style="text-align: center">Sym.</th>
75 * <th style="text-align: center">No.</th>
77 * <th>Description</th>
80 * <th rowspan="3">era</th>
81 * <td style="text-align: center" rowspan="3">G</td>
82 * <td style="text-align: center">1..3</td>
84 * <td rowspan="3">Era - Replaced with the Era string for the current date. One to three letters for the
85 * abbreviated form, four letters for the long form, five for the narrow form.</td>
88 * <td style="text-align: center">4</td>
89 * <td>Anno Domini</td>
92 * <td style="text-align: center">5</td>
96 * <th rowspan="6">year</th>
97 * <td style="text-align: center">y</td>
98 * <td style="text-align: center">1..n</td>
100 * <td>Year. Normally the length specifies the padding, but for two letters it also specifies the maximum
101 * length. Example:<div align="center">
103 * <table border="1" cellpadding="2" cellspacing="0">
106 * <th style="text-align: right">y</th>
107 * <th style="text-align: right">yy</th>
108 * <th style="text-align: right">yyy</th>
109 * <th style="text-align: right">yyyy</th>
110 * <th style="text-align: right">yyyyy</th>
114 * <td style="text-align: right">1</td>
115 * <td style="text-align: right">01</td>
116 * <td style="text-align: right">001</td>
117 * <td style="text-align: right">0001</td>
118 * <td style="text-align: right">00001</td>
122 * <td style="text-align: right">12</td>
123 * <td style="text-align: right">12</td>
124 * <td style="text-align: right">012</td>
125 * <td style="text-align: right">0012</td>
126 * <td style="text-align: right">00012</td>
130 * <td style="text-align: right">123</td>
131 * <td style="text-align: right">23</td>
132 * <td style="text-align: right">123</td>
133 * <td style="text-align: right">0123</td>
134 * <td style="text-align: right">00123</td>
138 * <td style="text-align: right">1234</td>
139 * <td style="text-align: right">34</td>
140 * <td style="text-align: right">1234</td>
141 * <td style="text-align: right">1234</td>
142 * <td style="text-align: right">01234</td>
146 * <td style="text-align: right">12345</td>
147 * <td style="text-align: right">45</td>
148 * <td style="text-align: right">12345</td>
149 * <td style="text-align: right">12345</td>
150 * <td style="text-align: right">12345</td>
157 * <td style="text-align: center">Y</td>
158 * <td style="text-align: center">1..n</td>
160 * <td>Year (in "Week of Year" based calendars). Normally the length specifies the padding,
161 * but for two letters it also specifies the maximum length. This year designation is used in ISO
162 * year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems
163 * where week date processing is desired. May not always be the same value as calendar year.</td>
166 * <td style="text-align: center">u</td>
167 * <td style="text-align: center">1..n</td>
169 * <td>Extended year. This is a single number designating the year of this calendar system, encompassing
170 * all supra-year fields. For example, for the Julian calendar system, year numbers are positive, with an
171 * era of BCE or CE. An extended year value for the Julian calendar system assigns positive values to CE
172 * years and negative values to BCE years, with 1 BCE being year 0.</td>
175 * <td style="text-align: center" rowspan="3">U</td>
176 * <td style="text-align: center">1..3</td>
178 * <td rowspan="3">Cyclic year name. Calendars such as the Chinese lunar calendar (and related calendars)
179 * and the Hindu calendars use 60-year cycles of year names. Use one through three letters for the abbreviated
180 * name, four for the full name, or five for the narrow name (currently the data only provides abbreviated names,
181 * which will be used for all requested name widths). If the calendar does not provide cyclic year name data,
182 * or if the year value to be formatted is out of the range of years for which cyclic name data is provided,
183 * then numeric formatting is used (behaves like 'y').</td>
186 * <td style="text-align: center">4</td>
187 * <td>(currently also 甲子)</td>
190 * <td style="text-align: center">5</td>
191 * <td>(currently also 甲子)</td>
194 * <th rowspan="6">quarter</th>
195 * <td rowspan="3" style="text-align: center">Q</td>
196 * <td style="text-align: center">1..2</td>
198 * <td rowspan="3">Quarter - Use one or two for the numerical quarter, three for the abbreviation, or four
199 * for the full name.</td>
202 * <td style="text-align: center">3</td>
206 * <td style="text-align: center">4</td>
207 * <td>2nd quarter</td>
210 * <td rowspan="3" style="text-align: center">q</td>
211 * <td style="text-align: center">1..2</td>
213 * <td rowspan="3"><b>Stand-Alone</b> Quarter - Use one or two for the numerical quarter, three for the abbreviation,
214 * or four for the full name.</td>
217 * <td style="text-align: center">3</td>
221 * <td style="text-align: center">4</td>
222 * <td>2nd quarter</td>
225 * <th rowspan="8">month</th>
226 * <td rowspan="4" style="text-align: center">M</td>
227 * <td style="text-align: center">1..2</td>
229 * <td rowspan="4">Month - Use one or two for the numerical month, three for the abbreviation, four for
230 * the full name, or five for the narrow name.</td>
233 * <td style="text-align: center">3</td>
237 * <td style="text-align: center">4</td>
241 * <td style="text-align: center">5</td>
245 * <td rowspan="4" style="text-align: center">L</td>
246 * <td style="text-align: center">1..2</td>
248 * <td rowspan="4"><b>Stand-Alone</b> Month - Use one or two for the numerical month, three for the abbreviation,
249 * or four for the full name, or 5 for the narrow name.</td>
252 * <td style="text-align: center">3</td>
256 * <td style="text-align: center">4</td>
260 * <td style="text-align: center">5</td>
264 * <th rowspan="2">week</th>
265 * <td style="text-align: center">w</td>
266 * <td style="text-align: center">1..2</td>
268 * <td>Week of Year.</td>
271 * <td style="text-align: center">W</td>
272 * <td style="text-align: center">1</td>
274 * <td>Week of Month</td>
277 * <th rowspan="4">day</th>
278 * <td style="text-align: center">d</td>
279 * <td style="text-align: center">1..2</td>
281 * <td>Date - Day of the month</td>
284 * <td style="text-align: center">D</td>
285 * <td style="text-align: center">1..3</td>
287 * <td>Day of year</td>
290 * <td style="text-align: center">F</td>
291 * <td style="text-align: center">1</td>
293 * <td>Day of Week in Month. The example is for the 2nd Wed in July</td>
296 * <td style="text-align: center">g</td>
297 * <td style="text-align: center">1..n</td>
299 * <td>Modified Julian day. This is different from the conventional Julian day number in two regards.
300 * First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number;
301 * that is, it depends on the local time zone. It can be thought of as a single number that encompasses
302 * all the date-related fields.</td>
305 * <th rowspan="14">week<br>
307 * <td rowspan="4" style="text-align: center">E</td>
308 * <td style="text-align: center">1..3</td>
310 * <td rowspan="4">Day of week - Use one through three letters for the short day, or four for the full name,
311 * five for the narrow name, or six for the short name.</td>
314 * <td style="text-align: center">4</td>
318 * <td style="text-align: center">5</td>
322 * <td style="text-align: center">6</td>
326 * <td rowspan="5" style="text-align: center">e</td>
327 * <td style="text-align: center">1..2</td>
329 * <td rowspan="5">Local day of week. Same as E except adds a numeric value that will depend on the local
330 * starting day of the week, using one or two letters. For this example, Monday is the first day of the week.</td>
333 * <td style="text-align: center">3</td>
337 * <td style="text-align: center">4</td>
341 * <td style="text-align: center">5</td>
345 * <td style="text-align: center">6</td>
349 * <td rowspan="5" style="text-align: center">c</td>
350 * <td style="text-align: center">1</td>
352 * <td rowspan="5"><b>Stand-Alone</b> local day of week - Use one letter for the local numeric value (same
353 * as 'e'), three for the short day, four for the full name, five for the narrow name, or six for
354 * the short name.</td>
357 * <td style="text-align: center">3</td>
361 * <td style="text-align: center">4</td>
365 * <td style="text-align: center">5</td>
369 * <td style="text-align: center">6</td>
374 * <td style="text-align: center">a</td>
375 * <td style="text-align: center">1</td>
380 * <th rowspan="4">hour</th>
381 * <td style="text-align: center">h</td>
382 * <td style="text-align: center">1..2</td>
384 * <td>Hour [1-12]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern
385 * generation, it should match the 12-hour-cycle format preferred by the locale (h or K); it should not match
386 * a 24-hour-cycle format (H or k). Use hh for zero padding.</td>
389 * <td style="text-align: center">H</td>
390 * <td style="text-align: center">1..2</td>
392 * <td>Hour [0-23]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern
393 * generation, it should match the 24-hour-cycle format preferred by the locale (H or k); it should not match a
394 * 12-hour-cycle format (h or K). Use HH for zero padding.</td>
397 * <td style="text-align: center">K</td>
398 * <td style="text-align: center">1..2</td>
400 * <td>Hour [0-11]. When used in a skeleton, only matches K or h, see above. Use KK for zero padding.</td>
403 * <td style="text-align: center">k</td>
404 * <td style="text-align: center">1..2</td>
406 * <td>Hour [1-24]. When used in a skeleton, only matches k or H, see above. Use kk for zero padding.</td>
410 * <td style="text-align: center">m</td>
411 * <td style="text-align: center">1..2</td>
413 * <td>Minute. Use one or two for zero padding.</td>
416 * <th rowspan="3">second</th>
417 * <td style="text-align: center">s</td>
418 * <td style="text-align: center">1..2</td>
420 * <td>Second. Use one or two for zero padding.</td>
423 * <td style="text-align: center">S</td>
424 * <td style="text-align: center">1..n</td>
426 * <td>Fractional Second - truncates (like other time fields) to the count of letters.
427 * (example shows display using pattern SSSS for seconds value 12.34567)</td>
430 * <td style="text-align: center">A</td>
431 * <td style="text-align: center">1..n</td>
433 * <td>Milliseconds in day. This field behaves <i>exactly</i> like a composite of all time-related fields,
434 * not including the zone fields. As such, it also reflects discontinuities of those fields on DST transition
435 * days. On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. This
436 * reflects the fact that is must be combined with the offset field to obtain a unique local time value.</td>
439 * <th rowspan="23">zone</th>
440 * <td rowspan="2" style="text-align: center">z</td>
441 * <td style="text-align: center">1..3</td>
443 * <td>The <i>short specific non-location format</i>.
444 * Where that is unavailable, falls back to the <i>short localized GMT format</i> ("O").</td>
447 * <td style="text-align: center">4</td>
448 * <td>Pacific Daylight Time</td>
449 * <td>The <i>long specific non-location format</i>.
450 * Where that is unavailable, falls back to the <i>long localized GMT format</i> ("OOOO").</td>
453 * <td rowspan="3" style="text-align: center">Z</td>
454 * <td style="text-align: center">1..3</td>
456 * <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
457 * The format is equivalent to RFC 822 zone format (when optional seconds field is absent).
458 * This is equivalent to the "xxxx" specifier.</td>
461 * <td style="text-align: center">4</td>
463 * <td>The <i>long localized GMT format</i>.
464 * This is equivalent to the "OOOO" specifier.</td>
467 * <td style="text-align: center">5</td>
470 * <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
471 * The ISO8601 UTC indicator "Z" is used when local time offset is 0.
472 * This is equivalent to the "XXXXX" specifier.</td>
475 * <td rowspan="2" style="text-align: center">O</td>
476 * <td style="text-align: center">1</td>
478 * <td>The <i>short localized GMT format</i>.</td>
481 * <td style="text-align: center">4</td>
483 * <td>The <i>long localized GMT format</i>.</td>
486 * <td rowspan="2" style="text-align: center">v</td>
487 * <td style="text-align: center">1</td>
489 * <td>The <i>short generic non-location format</i>.
490 * Where that is unavailable, falls back to the <i>generic location format</i> ("VVVV"),
491 * then the <i>short localized GMT format</i> as the final fallback.</td>
494 * <td style="text-align: center">4</td>
495 * <td>Pacific Time</td>
496 * <td>The <i>long generic non-location format</i>.
497 * Where that is unavailable, falls back to <i>generic location format</i> ("VVVV").
500 * <td rowspan="4" style="text-align: center">V</td>
501 * <td style="text-align: center">1</td>
503 * <td>The short time zone ID.
504 * Where that is unavailable, the special short time zone ID <i>unk</i> (Unknown Zone) is used.<br>
505 * <i><b>Note</b>: This specifier was originally used for a variant of the short specific non-location format,
506 * but it was deprecated in the later version of the LDML specification. In CLDR 23/ICU 51, the definition of
507 * the specifier was changed to designate a short time zone ID.</i></td>
510 * <td style="text-align: center">2</td>
511 * <td>America/Los_Angeles</td>
512 * <td>The long time zone ID.</td>
515 * <td style="text-align: center">3</td>
516 * <td>Los Angeles</td>
517 * <td>The exemplar city (location) for the time zone.
518 * Where that is unavailable, the localized exemplar city name for the special zone <i>Etc/Unknown</i> is used
519 * as the fallback (for example, "Unknown City"). </td>
522 * <td style="text-align: center">4</td>
523 * <td>Los Angeles Time</td>
524 * <td>The <i>generic location format</i>.
525 * Where that is unavailable, falls back to the <i>long localized GMT format</i> ("OOOO";
526 * Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.)<br>
527 * This is especially useful when presenting possible timezone choices for user selection,
528 * since the naming is more uniform than the "v" format.</td>
531 * <td rowspan="5" style="text-align: center">X</td>
532 * <td style="text-align: center">1</td>
536 * <td>The <i>ISO8601 basic format</i> with hours field and optional minutes field.
537 * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
540 * <td style="text-align: center">2</td>
543 * <td>The <i>ISO8601 basic format</i> with hours and minutes fields.
544 * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
547 * <td style="text-align: center">3</td>
550 * <td>The <i>ISO8601 extended format</i> with hours and minutes fields.
551 * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
554 * <td style="text-align: center">4</td>
558 * <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
559 * (Note: The seconds field is not supported by the ISO8601 specification.)
560 * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
563 * <td style="text-align: center">5</td>
567 * <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
568 * (Note: The seconds field is not supported by the ISO8601 specification.)
569 * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
572 * <td rowspan="5" style="text-align: center">x</td>
573 * <td style="text-align: center">1</td>
576 * <td>The <i>ISO8601 basic format</i> with hours field and optional minutes field.</td>
579 * <td style="text-align: center">2</td>
581 * <td>The <i>ISO8601 basic format</i> with hours and minutes fields.</td>
584 * <td style="text-align: center">3</td>
586 * <td>The <i>ISO8601 extended format</i> with hours and minutes fields.</td>
589 * <td style="text-align: center">4</td>
592 * <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
593 * (Note: The seconds field is not supported by the ISO8601 specification.)</td>
596 * <td style="text-align: center">5</td>
599 * <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
600 * (Note: The seconds field is not supported by the ISO8601 specification.)</td>
606 * Any characters in the pattern that are not in the ranges of ['a'..'z']
607 * and ['A'..'Z'] will be treated as quoted text. For instance, characters
608 * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
609 * even they are not embraced within single quotes.
611 * A pattern containing any invalid pattern letter will result in a thrown
612 * exception during formatting or parsing.
615 * <strong>Examples Using the US Locale:</strong>
618 * Format Pattern Result
619 * -------------- -------
620 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time
621 * "EEE, MMM d, ''yy" ->> Wed, July 10, '96
622 * "h:mm a" ->> 12:08 PM
623 * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
624 * "K:mm a, vvv" ->> 0:00 PM, PT
625 * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM
628 * <strong>Code Sample:</strong>
631 * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
632 * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
633 * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
635 * // Format the current time.
636 * SimpleDateFormat formatter
637 * = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
638 * Date currentTime_1 = new Date();
639 * String dateString = formatter.format(currentTime_1);
641 * // Parse the previous string back into a Date.
642 * ParsePosition pos = new ParsePosition(0);
643 * Date currentTime_2 = formatter.parse(dateString, pos);
646 * In the example, the time value <code>currentTime_2</code> obtained from
647 * parsing will be equal to <code>currentTime_1</code>. However, they may not be
648 * equal if the am/pm marker 'a' is left out from the format pattern while
649 * the "hour in am/pm" pattern symbol is used. This information loss can
650 * happen when formatting the time in PM.
652 * <p>When parsing a date string using the abbreviated year pattern ("yy"),
653 * SimpleDateFormat must interpret the abbreviated year
654 * relative to some century. It does this by adjusting dates to be
655 * within 80 years before and 20 years after the time the SimpleDateFormat
656 * instance is created. For example, using a pattern of "MM/dd/yy" and a
657 * SimpleDateFormat instance created on Jan 1, 1997, the string
658 * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
659 * would be interpreted as May 4, 1964.
660 * During parsing, only strings consisting of exactly two digits, as defined by
661 * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default
663 * Any other numeric string, such as a one digit string, a three or more digit
664 * string, or a two digit string that isn't all digits (for example, "-1"), is
665 * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
666 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
668 * <p>If the year pattern does not have exactly two 'y' characters, the year is
669 * interpreted literally, regardless of the number of digits. So using the
670 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
672 * <p>When numeric fields abut one another directly, with no intervening delimiter
673 * characters, they constitute a run of abutting numeric fields. Such runs are
674 * parsed specially. For example, the format "HHmmss" parses the input text
675 * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
676 * parse "1234". In other words, the leftmost field of the run is flexible,
677 * while the others keep a fixed width. If the parse fails anywhere in the run,
678 * then the leftmost field is shortened by one character, and the entire run is
679 * parsed again. This is repeated until either the parse succeeds or the
680 * leftmost field is one character in length. If the parse still fails at that
681 * point, the parse of the run fails.
683 * <p>For time zones that have no names, use strings GMT+hours:minutes or
686 * <p>The calendar defines what is the first day of the week, the first week
687 * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
688 * time zone. There is one common decimal format to handle all the numbers;
689 * the digit count is handled programmatically according to the pattern.
691 * <h4>Synchronization</h4>
693 * Date formats are not synchronized. It is recommended to create separate
694 * format instances for each thread. If multiple threads access a format
695 * concurrently, it must be synchronized externally.
697 * @see com.ibm.icu.util.Calendar
698 * @see com.ibm.icu.util.GregorianCalendar
699 * @see com.ibm.icu.util.TimeZone
701 * @see DateFormatSymbols
703 * @see TimeZoneFormat
704 * @author Mark Davis, Chen-Lieh Huang, Alan Liu
707 public class SimpleDateFormat extends DateFormat {
709 // the official serial version ID which says cryptically
710 // which version we're compatible with
711 private static final long serialVersionUID = 4774881970558875024L;
713 // the internal serial version which says which version was written
714 // - 0 (default) for version up to JDK 1.1.3
715 // - 1 for version from JDK 1.1.4, which includes a new field
716 // - 2 we write additional int for capitalizationContext
717 static final int currentSerialVersion = 2;
719 static boolean DelayedHebrewMonthCheck = false;
722 * From calendar field to its level.
723 * Used to order calendar field.
724 * For example, calendar fields can be defined in the following order:
725 * year > month > date > am-pm > hour > minute
726 * YEAR --> 10, MONTH -->20, DATE --> 30;
727 * AM_PM -->40, HOUR --> 50, MINUTE -->60
729 private static final int[] CALENDAR_FIELD_TO_LEVEL =
733 /*dDEF*/ 30, 20, 30, 30,
734 /*ahHm*/ 40, 50, 50, 60,
744 * From calendar field letter to its level.
745 * Used to order calendar field.
746 * For example, calendar fields can be defined in the following order:
747 * year > month > date > am-pm > hour > minute
748 * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60
750 private static final int[] PATTERN_CHAR_TO_LEVEL =
752 // A B C D E F G H I J K L M N O
753 -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, 0,
754 // P Q R S T U V W X Y Z
755 -1, 20, -1, 80, -1, 10, 0, 30, 0, 10, 0, -1, -1, -1, -1, -1,
756 // a b c d e f g h i j k l m n o
757 -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,
758 // p q r s t u v w x y z
759 -1, 20, -1, 70, -1, 10, 0, 20, 0, 10, 0, -1, -1, -1, -1, -1
762 // When calendar uses hebr numbering (i.e. he@calendar=hebrew),
763 // offset the years within the current millenium down to 1-999
764 private static final int HEBREW_CAL_CUR_MILLENIUM_START_YEAR = 5000;
765 private static final int HEBREW_CAL_CUR_MILLENIUM_END_YEAR = 6000;
768 * The version of the serialized data on the stream. Possible values:
770 * <li><b>0</b> or not present on stream: JDK 1.1.3. This version
771 * has no <code>defaultCenturyStart</code> on stream.
772 * <li><b>1</b> JDK 1.1.4 or later. This version adds
773 * <code>defaultCenturyStart</code>.
774 * <li><b>2</b> This version writes an additional int for
775 * <code>capitalizationContext</code>.
777 * When streaming out this class, the most recent format
778 * and the highest allowable <code>serialVersionOnStream</code>
782 private int serialVersionOnStream = currentSerialVersion;
785 * The pattern string of this formatter. This is always a non-localized
786 * pattern. May not be null. See class documentation for details.
789 private String pattern;
792 * The override string of this formatter. Used to override the
793 * numbering system for one or more fields.
796 private String override;
799 * The hash map used for number format overrides.
802 private HashMap<String, NumberFormat> numberFormatters;
805 * The hash map used for number format overrides.
808 private HashMap<Character, String> overrideMap;
811 * The symbols used by this formatter for week names, month names,
812 * etc. May not be null.
814 * @see DateFormatSymbols
816 private DateFormatSymbols formatData;
818 private transient ULocale locale;
821 * We map dates with two-digit years into the century starting at
822 * <code>defaultCenturyStart</code>, which may be any date. May
827 private Date defaultCenturyStart;
829 private transient int defaultCenturyStartYear;
831 // defaultCenturyBase is set when an instance is created
832 // and may be used for calculating defaultCenturyStart when needed.
833 private transient long defaultCenturyBase;
835 // We need to preserve time zone type when parsing specific
836 // time zone text (xxx Standard Time vs xxx Daylight Time)
837 private transient TimeType tztype = TimeType.UNKNOWN;
839 private static final int millisPerHour = 60 * 60 * 1000;
841 // When possessing ISO format, the ERA may be ommitted is the
842 // year specifier is a negative number.
843 private static final int ISOSpecialEra = -32000;
845 // This prefix is designed to NEVER MATCH real text, in order to
846 // suppress the parsing of negative numbers. Adjust as needed (if
847 // this becomes valid Unicode).
848 private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
851 * If true, this object supports fast formatting using the
852 * subFormat variant that takes a StringBuffer.
854 private transient boolean useFastFormat;
857 * The time zone sub-formatter, introduced in ICU 4.8
859 private volatile TimeZoneFormat tzFormat;
862 * Capitalization setting, introduced in ICU 50
863 * Special serialization, see writeObject & readObject below
865 private transient DisplayContext capitalizationSetting;
868 * Old defaultCapitalizationContext field
871 //private ContextValue defaultCapitalizationContext;
873 * Old ContextValue enum, preserved only to avoid
874 * deserialization errs from ICU 49.1.
876 @SuppressWarnings("unused")
877 private enum ContextValue {
879 CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,
880 CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE,
881 CAPITALIZATION_FOR_UI_LIST_OR_MENU,
882 CAPITALIZATION_FOR_STANDALONE
886 * Constructs a SimpleDateFormat using the default pattern for the default <code>FORMAT</code>
887 * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
888 * generality, use the factory methods in the DateFormat class.
891 * @see Category#FORMAT
894 public SimpleDateFormat() {
895 this(getDefaultPattern(), null, null, null, null, true, null);
899 * Constructs a SimpleDateFormat using the given pattern in the default <code>FORMAT</code>
900 * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
901 * generality, use the factory methods in the DateFormat class.
902 * @see Category#FORMAT
905 public SimpleDateFormat(String pattern)
907 this(pattern, null, null, null, null, true, null);
911 * Constructs a SimpleDateFormat using the given pattern and locale.
912 * <b>Note:</b> Not all locales support SimpleDateFormat; for full
913 * generality, use the factory methods in the DateFormat class.
916 public SimpleDateFormat(String pattern, Locale loc)
918 this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
922 * Constructs a SimpleDateFormat using the given pattern and locale.
923 * <b>Note:</b> Not all locales support SimpleDateFormat; for full
924 * generality, use the factory methods in the DateFormat class.
927 public SimpleDateFormat(String pattern, ULocale loc)
929 this(pattern, null, null, null, loc, true, null);
933 * Constructs a SimpleDateFormat using the given pattern , override and locale.
934 * @param pattern The pattern to be used
935 * @param override The override string. A numbering system override string can take one of the following forms:
936 * 1). If just a numbering system name is specified, it applies to all numeric fields in the date format pattern.
937 * 2). To specify an alternate numbering system on a field by field basis, use the field letters from the pattern
938 * followed by an = sign, followed by the numbering system name. For example, to specify that just the year
939 * be formatted using Hebrew digits, use the override "y=hebr". Multiple overrides can be specified in a single
940 * string by separating them with a semi-colon. For example, the override string "m=thai;y=deva" would format using
941 * Thai digits for the month and Devanagari digits for the year.
942 * @param loc The locale to be used
945 public SimpleDateFormat(String pattern, String override, ULocale loc)
947 this(pattern, null, null, null, loc, false,override);
951 * Constructs a SimpleDateFormat using the given pattern and
952 * locale-specific symbol data.
953 * Warning: uses default <code>FORMAT</code> locale for digits!
956 public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
958 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
963 * @deprecated This API is ICU internal only.
965 public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
967 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
971 * Package-private constructor that allows a subclass to specify
972 * whether it supports fast formatting.
974 * TODO make this API public.
976 SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
977 boolean useFastFormat, String override) {
978 this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);
982 * The constructor called from all other SimpleDateFormat constructors
984 private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,
985 NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {
986 this.pattern = pattern;
987 this.formatData = formatData;
988 this.calendar = calendar;
989 this.numberFormat = numberFormat;
990 this.locale = locale; // time zone formatting
991 this.useFastFormat = useFastFormat;
992 this.override = override;
997 * Creates an instance of SimpleDateFormat for the given format configuration
998 * @param formatConfig the format configuration
999 * @return A SimpleDateFormat instance
1001 * @deprecated This API is ICU internal only.
1003 public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
1005 String ostr = formatConfig.getOverrideString();
1006 boolean useFast = ( ostr != null && ostr.length() > 0 );
1008 return new SimpleDateFormat(formatConfig.getPatternString(),
1009 formatConfig.getDateFormatSymbols(),
1010 formatConfig.getCalendar(),
1012 formatConfig.getLocale(),
1014 formatConfig.getOverrideString());
1018 * Initialized fields
1020 private void initialize() {
1021 if (locale == null) {
1022 locale = ULocale.getDefault(Category.FORMAT);
1024 if (formatData == null) {
1025 formatData = new DateFormatSymbols(locale);
1027 if (calendar == null) {
1028 calendar = Calendar.getInstance(locale);
1030 if (numberFormat == null) {
1031 NumberingSystem ns = NumberingSystem.getInstance(locale);
1032 if (ns.isAlgorithmic()) {
1033 numberFormat = NumberFormat.getInstance(locale);
1035 String digitString = ns.getDescription();
1036 String nsName = ns.getName();
1037 // Use a NumberFormat optimized for date formatting
1038 numberFormat = new DateNumberFormat(locale, digitString, nsName);
1041 // Note: deferring calendar calculation until when we really need it.
1042 // Instead, we just record time of construction for backward compatibility.
1043 defaultCenturyBase = System.currentTimeMillis();
1045 setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
1046 initLocalZeroPaddingNumberFormat();
1048 if (override != null) {
1049 initNumberFormatters(locale);
1052 capitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
1057 * Private method lazily instantiate the TimeZoneFormat field
1058 * @param bForceUpdate when true, check if tzFormat is synchronized with
1059 * the current numberFormat and update its digits if necessary. When false,
1060 * this check is skipped.
1062 private synchronized void initializeTimeZoneFormat(boolean bForceUpdate) {
1063 if (bForceUpdate || tzFormat == null) {
1064 tzFormat = TimeZoneFormat.getInstance(locale);
1066 String digits = null;
1067 if (numberFormat instanceof DecimalFormat) {
1068 DecimalFormatSymbols decsym = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
1069 digits = new String(decsym.getDigits());
1070 } else if (numberFormat instanceof DateNumberFormat) {
1071 digits = new String(((DateNumberFormat)numberFormat).getDigits());
1074 if (digits != null) {
1075 if (!tzFormat.getGMTOffsetDigits().equals(digits)) {
1076 if (tzFormat.isFrozen()) {
1077 tzFormat = tzFormat.cloneAsThawed();
1079 tzFormat.setGMTOffsetDigits(digits);
1086 * Private method, returns non-null TimeZoneFormat.
1087 * @return the TimeZoneFormat used by this formatter.
1089 private TimeZoneFormat tzFormat() {
1090 if (tzFormat == null) {
1091 initializeTimeZoneFormat(false);
1096 // privates for the default pattern
1097 private static ULocale cachedDefaultLocale = null;
1098 private static String cachedDefaultPattern = null;
1099 private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
1102 * Returns the default date and time pattern (SHORT) for the default locale.
1103 * This method is only used by the default SimpleDateFormat constructor.
1105 private static synchronized String getDefaultPattern() {
1106 ULocale defaultLocale = ULocale.getDefault(Category.FORMAT);
1107 if (!defaultLocale.equals(cachedDefaultLocale)) {
1108 cachedDefaultLocale = defaultLocale;
1109 Calendar cal = Calendar.getInstance(cachedDefaultLocale);
1111 CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
1112 String[] dateTimePatterns = calData.getDateTimePatterns();
1114 if (dateTimePatterns.length >= 13)
1116 glueIndex += (SHORT + 1);
1118 cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],
1119 new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
1120 } catch (MissingResourceException e) {
1121 cachedDefaultPattern = FALLBACKPATTERN;
1124 return cachedDefaultPattern;
1127 /* Define one-century window into which to disambiguate dates using
1130 private void parseAmbiguousDatesAsAfter(Date startDate) {
1131 defaultCenturyStart = startDate;
1132 calendar.setTime(startDate);
1133 defaultCenturyStartYear = calendar.get(Calendar.YEAR);
1136 /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
1137 * The default start time is 80 years before the creation time of this object.
1139 private void initializeDefaultCenturyStart(long baseTime) {
1140 defaultCenturyBase = baseTime;
1141 // clone to avoid messing up date stored in calendar object
1142 // when this method is called while parsing
1143 Calendar tmpCal = (Calendar)calendar.clone();
1144 tmpCal.setTimeInMillis(baseTime);
1145 tmpCal.add(Calendar.YEAR, -80);
1146 defaultCenturyStart = tmpCal.getTime();
1147 defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
1150 /* Gets the default century start date for this object */
1151 private Date getDefaultCenturyStart() {
1152 if (defaultCenturyStart == null) {
1153 // not yet initialized
1154 initializeDefaultCenturyStart(defaultCenturyBase);
1156 return defaultCenturyStart;
1159 /* Gets the default century start year for this object */
1160 private int getDefaultCenturyStartYear() {
1161 if (defaultCenturyStart == null) {
1162 // not yet initialized
1163 initializeDefaultCenturyStart(defaultCenturyBase);
1165 return defaultCenturyStartYear;
1169 * Sets the 100-year period 2-digit years will be interpreted as being in
1170 * to begin on the date the user specifies.
1171 * @param startDate During parsing, two digit years will be placed in the range
1172 * <code>startDate</code> to <code>startDate + 100 years</code>.
1175 public void set2DigitYearStart(Date startDate) {
1176 parseAmbiguousDatesAsAfter(startDate);
1180 * Returns the beginning date of the 100-year period 2-digit years are interpreted
1182 * @return the start of the 100-year period into which two digit years are
1186 public Date get2DigitYearStart() {
1187 return getDefaultCenturyStart();
1191 * Formats a date or time, which is the standard millis
1192 * since January 1, 1970, 00:00:00 GMT.
1193 * <p>Example: using the US locale:
1194 * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
1195 * @param cal the calendar whose date-time value is to be formatted into a date-time string
1196 * @param toAppendTo where the new date-time text is to be appended
1197 * @param pos the formatting position. On input: an alignment field,
1198 * if desired. On output: the offsets of the alignment field.
1199 * @return the formatted date-time string.
1203 public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
1204 FieldPosition pos) {
1205 TimeZone backupTZ = null;
1206 if (cal != calendar && !cal.getType().equals(calendar.getType())) {
1207 // Different calendar type
1208 // We use the time and time zone from the input calendar, but
1209 // do not use the input calendar for field calculation.
1210 calendar.setTimeInMillis(cal.getTimeInMillis());
1211 backupTZ = calendar.getTimeZone();
1212 calendar.setTimeZone(cal.getTimeZone());
1215 StringBuffer result = format(cal, capitalizationSetting, toAppendTo, pos, null);
1216 if (backupTZ != null) {
1217 // Restore the original time zone
1218 calendar.setTimeZone(backupTZ);
1223 // The actual method to format date. If List attributes is not null,
1224 // then attribute information will be recorded.
1225 private StringBuffer format(Calendar cal, DisplayContext capitalizationContext,
1226 StringBuffer toAppendTo, FieldPosition pos, List<FieldPosition> attributes) {
1228 pos.setBeginIndex(0);
1231 // Careful: For best performance, minimize the number of calls
1232 // to StringBuffer.append() by consolidating appends when
1235 Object[] items = getPatternItems();
1236 for (int i = 0; i < items.length; i++) {
1237 if (items[i] instanceof String) {
1238 toAppendTo.append((String)items[i]);
1240 PatternItem item = (PatternItem)items[i];
1242 if (attributes != null) {
1243 // Save the current length
1244 start = toAppendTo.length();
1246 if (useFastFormat) {
1247 subFormat(toAppendTo, item.type, item.length, toAppendTo.length(),
1248 i, capitalizationContext, pos, cal);
1250 toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(),
1251 i, capitalizationContext, pos, cal));
1253 if (attributes != null) {
1254 // Check the sub format length
1255 int end = toAppendTo.length();
1256 if (end - start > 0) {
1257 // Append the attribute to the list
1258 DateFormat.Field attr = patternCharToDateFormatField(item.type);
1259 FieldPosition fp = new FieldPosition(attr);
1260 fp.setBeginIndex(start);
1261 fp.setEndIndex(end);
1271 // Map pattern character to index
1272 private static final int PATTERN_CHAR_BASE = 0x40;
1273 private static final int[] PATTERN_CHAR_TO_INDEX =
1275 // A B C D E F G H I J K L M N O
1276 -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, 31,
1277 // P Q R S T U V W X Y Z
1278 -1, 27, -1, 8, -1, 30, 29, 13, 32, 18, 23, -1, -1, -1, -1, -1,
1279 // a b c d e f g h i j k l m n o
1280 -1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,
1281 // p q r s t u v w x y z
1282 -1, 28, -1, 7, -1, 20, 24, 12, 33, 1, 17, -1, -1, -1, -1, -1
1285 // Map pattern character index to Calendar field number
1286 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
1288 /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
1289 /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
1290 /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
1291 /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
1292 /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
1293 /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
1294 /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
1295 /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,
1296 /*v*/ Calendar.ZONE_OFFSET,
1297 /*c*/ Calendar.DOW_LOCAL,
1298 /*L*/ Calendar.MONTH,
1299 /*Qq*/ Calendar.MONTH, Calendar.MONTH,
1300 /*V*/ Calendar.ZONE_OFFSET,
1301 /*U*/ Calendar.YEAR,
1302 /*O*/ Calendar.ZONE_OFFSET,
1303 /*Xx*/ Calendar.ZONE_OFFSET, Calendar.ZONE_OFFSET,
1306 // Map pattern character index to DateFormat field number
1307 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
1308 /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
1309 /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
1310 /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
1311 /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
1312 /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
1313 /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
1314 /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
1315 /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
1316 /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD,
1317 /*c*/ DateFormat.STANDALONE_DAY_FIELD,
1318 /*L*/ DateFormat.STANDALONE_MONTH_FIELD,
1319 /*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
1320 /*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD,
1321 /*U*/ DateFormat.YEAR_NAME_FIELD,
1322 /*O*/ DateFormat.TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD,
1323 /*Xx*/ DateFormat.TIMEZONE_ISO_FIELD, DateFormat.TIMEZONE_ISO_LOCAL_FIELD,
1326 // Map pattern character index to DateFormat.Field
1327 private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
1328 /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
1329 /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
1330 /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
1331 /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
1332 /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
1333 /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
1334 /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
1335 /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
1336 /*v*/ DateFormat.Field.TIME_ZONE,
1337 /*c*/ DateFormat.Field.DAY_OF_WEEK,
1338 /*L*/ DateFormat.Field.MONTH,
1339 /*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
1340 /*V*/ DateFormat.Field.TIME_ZONE,
1341 /*U*/ DateFormat.Field.YEAR,
1342 /*O*/ DateFormat.Field.TIME_ZONE,
1343 /*Xx*/ DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE,
1347 * Returns a DateFormat.Field constant associated with the specified format pattern
1350 * @param ch The pattern character
1351 * @return DateFormat.Field associated with the pattern character
1355 protected DateFormat.Field patternCharToDateFormatField(char ch) {
1356 int patternCharIndex = -1;
1357 if ('A' <= ch && ch <= 'z') {
1358 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
1360 if (patternCharIndex != -1) {
1361 return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
1367 * Formats a single field, given its pattern character. Subclasses may
1368 * override this method in order to modify or add formatting
1370 * @param ch the pattern character
1371 * @param count the number of times ch is repeated in the pattern
1372 * @param beginOffset the offset of the output string at the start of
1373 * this field; used to set pos when appropriate
1374 * @param pos receives the position of a field, when appropriate
1375 * @param fmtData the symbols for this formatter
1378 protected String subFormat(char ch, int count, int beginOffset,
1379 FieldPosition pos, DateFormatSymbols fmtData,
1381 throws IllegalArgumentException
1383 // Note: formatData is ignored
1384 return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal);
1388 * Formats a single field. This is the version called internally; it
1389 * adds fieldNum and capitalizationContext parameters.
1392 * @deprecated This API is ICU internal only.
1394 protected String subFormat(char ch, int count, int beginOffset,
1395 int fieldNum, DisplayContext capitalizationContext,
1399 StringBuffer buf = new StringBuffer();
1400 subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
1401 return buf.toString();
1405 * Formats a single field; useFastFormat variant. Reuses a
1406 * StringBuffer for results instead of creating a String on the
1407 * heap for each call.
1409 * NOTE We don't really need the beginOffset parameter, EXCEPT for
1410 * the need to support the slow subFormat variant (above) which
1411 * has to pass it in to us.
1414 * @deprecated This API is ICU internal only.
1416 @SuppressWarnings("fallthrough")
1417 protected void subFormat(StringBuffer buf,
1418 char ch, int count, int beginOffset,
1419 int fieldNum, DisplayContext capitalizationContext,
1423 final int maxIntCount = Integer.MAX_VALUE;
1424 final int bufstart = buf.length();
1425 TimeZone tz = cal.getTimeZone();
1426 long date = cal.getTimeInMillis();
1427 String result = null;
1429 // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);
1430 int patternCharIndex = -1;
1431 if ('A' <= ch && ch <= 'z') {
1432 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
1435 if (patternCharIndex == -1) {
1436 if (ch == 'l') { // (SMALL LETTER L) deprecated placeholder for leap month marker, ignore
1439 throw new IllegalArgumentException("Illegal pattern character " +
1440 "'" + ch + "' in \"" +
1445 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1446 int value = cal.get(field);
1448 NumberFormat currentNumberFormat = getNumberFormat(ch);
1449 DateFormatSymbols.CapitalizationContextUsage capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.OTHER;
1451 switch (patternCharIndex) {
1452 case 0: // 'G' - ERA
1453 if ( cal.getType().equals("chinese") || cal.getType().equals("dangi") ) {
1454 // moved from ChineseDateFormat
1455 zeroPaddingNumber(currentNumberFormat, buf, value, 1, 9);
1458 safeAppend(formatData.narrowEras, value, buf);
1459 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_NARROW;
1460 } else if (count == 4) {
1461 safeAppend(formatData.eraNames, value, buf);
1462 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_WIDE;
1464 safeAppend(formatData.eras, value, buf);
1465 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_ABBREV;
1469 case 30: // 'U' - YEAR_NAME_FIELD
1470 if (formatData.shortYearNames != null && value <= formatData.shortYearNames.length) {
1471 safeAppend(formatData.shortYearNames, value-1, buf);
1474 // else fall through to numeric year handling, do not break here
1475 case 1: // 'y' - YEAR
1476 case 18: // 'Y' - YEAR_WOY
1477 if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) &&
1478 value > HEBREW_CAL_CUR_MILLENIUM_START_YEAR && value < HEBREW_CAL_CUR_MILLENIUM_END_YEAR ) {
1479 value -= HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
1481 /* According to the specification, if the number of pattern letters ('y') is 2,
1482 * the year is truncated to 2 digits; otherwise it is interpreted as a number.
1483 * But the original code process 'y', 'yy', 'yyy' in the same way. and process
1484 * patterns with 4 or more than 4 'y' characters in the same way.
1485 * So I change the codes to meet the specification. [Richard/GCl]
1488 zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
1489 } else { //count = 1 or count > 2
1490 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
1493 case 2: // 'M' - MONTH
1494 case 26: // 'L' - STANDALONE MONTH
1495 if ( cal.getType().equals("hebrew")) {
1496 boolean isLeap = HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR));
1497 if (isLeap && value == 6 && count >= 3 ) {
1498 value = 13; // Show alternate form for Adar II in leap years in Hebrew calendar.
1500 if (!isLeap && value >= 6 && count < 3 ) {
1501 value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7.
1504 int isLeapMonth = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT)?
1505 cal.get(Calendar.IS_LEAP_MONTH): 0;
1506 // should consolidate the next section by using arrays of pointers & counts for the right symbols...
1508 if (patternCharIndex == 2) {
1509 safeAppendWithMonthPattern(formatData.narrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_NARROW]: null);
1511 safeAppendWithMonthPattern(formatData.standaloneNarrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW]: null);
1513 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_NARROW;
1514 } else if (count == 4) {
1515 if (patternCharIndex == 2) {
1516 safeAppendWithMonthPattern(formatData.months, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null);
1517 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
1519 safeAppendWithMonthPattern(formatData.standaloneMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null);
1520 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
1522 } else if (count == 3) {
1523 if (patternCharIndex == 2) {
1524 safeAppendWithMonthPattern(formatData.shortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null);
1525 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
1527 safeAppendWithMonthPattern(formatData.standaloneShortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null);
1528 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
1531 StringBuffer monthNumber = new StringBuffer();
1532 zeroPaddingNumber(currentNumberFormat, monthNumber, value+1, count, maxIntCount);
1533 String[] monthNumberStrings = new String[1];
1534 monthNumberStrings[0] = monthNumber.toString();
1535 safeAppendWithMonthPattern(monthNumberStrings, 0, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC]: null);
1538 case 4: // 'k' - HOUR_OF_DAY (1..24)
1540 zeroPaddingNumber(currentNumberFormat,buf,
1541 cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
1542 count, maxIntCount);
1544 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
1547 case 8: // 'S' - FRACTIONAL_SECOND
1548 // Fractional seconds left-justify
1550 numberFormat.setMinimumIntegerDigits(Math.min(3, count));
1551 numberFormat.setMaximumIntegerDigits(maxIntCount);
1554 } else if (count == 2) {
1557 FieldPosition p = new FieldPosition(-1);
1558 numberFormat.format((long) value, buf, p);
1560 numberFormat.setMinimumIntegerDigits(count - 3);
1561 numberFormat.format(0L, buf, p);
1565 case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
1567 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
1570 // For alpha day-of-week, we don't want DOW_LOCAL,
1571 // we need the standard DAY_OF_WEEK.
1572 value = cal.get(Calendar.DAY_OF_WEEK);
1573 // fall through, do not break here
1574 case 9: // 'E' - DAY_OF_WEEK
1576 safeAppend(formatData.narrowWeekdays, value, buf);
1577 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
1578 } else if (count == 4) {
1579 safeAppend(formatData.weekdays, value, buf);
1580 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
1581 } else if (count == 6 && formatData.shorterWeekdays != null) {
1582 safeAppend(formatData.shorterWeekdays, value, buf);
1583 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
1584 } else {// count <= 3, use abbreviated form if exists
1585 safeAppend(formatData.shortWeekdays, value, buf);
1586 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
1589 case 14: // 'a' - AM_PM
1590 safeAppend(formatData.ampms, value, buf);
1592 case 15: // 'h' - HOUR (1..12)
1594 zeroPaddingNumber(currentNumberFormat,buf,
1595 cal.getLeastMaximum(Calendar.HOUR)+1,
1596 count, maxIntCount);
1598 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
1602 case 17: // 'z' - TIMEZONE_FIELD
1605 result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date);
1606 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
1608 result = tzFormat().format(Style.SPECIFIC_LONG, tz, date);
1609 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
1613 case 23: // 'Z' - TIMEZONE_RFC_FIELD
1615 // RFC822 format - equivalent to ISO 8601 local offset fixed width format
1616 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
1617 } else if (count == 5) {
1618 // ISO 8601 extended format
1619 result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
1621 // long form, localized GMT pattern
1622 result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
1626 case 24: // 'v' - TIMEZONE_GENERIC_FIELD
1629 result = tzFormat().format(Style.GENERIC_SHORT, tz, date);
1630 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
1631 } else if (count == 4) {
1633 result = tzFormat().format(Style.GENERIC_LONG, tz, date);
1634 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
1638 case 29: // 'V' - TIMEZONE_SPECIAL_FIELD
1641 result = tzFormat().format(Style.ZONE_ID_SHORT, tz, date);
1642 } else if (count == 2) {
1644 result = tzFormat().format(Style.ZONE_ID, tz, date);
1645 } else if (count == 3) {
1647 result = tzFormat().format(Style.EXEMPLAR_LOCATION, tz, date);
1648 } else if (count == 4) {
1650 result = tzFormat().format(Style.GENERIC_LOCATION, tz, date);
1651 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ZONE_LONG;
1655 case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
1657 // "O" - Short Localized GMT format
1658 result = tzFormat().format(Style.LOCALIZED_GMT_SHORT, tz, date);
1659 } else if (count == 4) {
1660 // "OOOO" - Localized GMT format
1661 result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
1665 case 32: // 'X' - TIMEZONE_ISO_FIELD
1667 // "X" - ISO Basic/Short
1668 result = tzFormat().format(Style.ISO_BASIC_SHORT, tz, date);
1669 } else if (count == 2) {
1670 // "XX" - ISO Basic/Fixed
1671 result = tzFormat().format(Style.ISO_BASIC_FIXED, tz, date);
1672 } else if (count == 3) {
1673 // "XXX" - ISO Extended/Fixed
1674 result = tzFormat().format(Style.ISO_EXTENDED_FIXED, tz, date);
1675 } else if (count == 4) {
1676 // "XXXX" - ISO Basic/Optional second field
1677 result = tzFormat().format(Style.ISO_BASIC_FULL, tz, date);
1678 } else if (count == 5) {
1679 // "XXXXX" - ISO Extended/Optional second field
1680 result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
1684 case 33: // 'x' - TIMEZONE_ISO_LOCAL_FIELD
1686 // "x" - ISO Local Basic/Short
1687 result = tzFormat().format(Style.ISO_BASIC_LOCAL_SHORT, tz, date);
1688 } else if (count == 2) {
1689 // "x" - ISO Local Basic/Fixed
1690 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FIXED, tz, date);
1691 } else if (count == 3) {
1692 // "xxx" - ISO Local Extended/Fixed
1693 result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FIXED, tz, date);
1694 } else if (count == 4) {
1695 // "xxxx" - ISO Local Basic/Optional second field
1696 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
1697 } else if (count == 5) {
1698 // "xxxxx" - ISO Local Extended/Optional second field
1699 result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FULL, tz, date);
1704 case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone)
1706 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
1709 // For alpha day-of-week, we don't want DOW_LOCAL,
1710 // we need the standard DAY_OF_WEEK.
1711 value = cal.get(Calendar.DAY_OF_WEEK);
1713 safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
1714 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
1715 } else if (count == 4) {
1716 safeAppend(formatData.standaloneWeekdays, value, buf);
1717 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
1718 } else if (count == 6 && formatData.standaloneShorterWeekdays != null) {
1719 safeAppend(formatData.standaloneShorterWeekdays, value, buf);
1720 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
1721 } else { // count == 3
1722 safeAppend(formatData.standaloneShortWeekdays, value, buf);
1723 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
1726 case 27: // 'Q' - QUARTER
1728 safeAppend(formatData.quarters, value/3, buf);
1729 } else if (count == 3) {
1730 safeAppend(formatData.shortQuarters, value/3, buf);
1732 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
1735 case 28: // 'q' - STANDALONE QUARTER
1737 safeAppend(formatData.standaloneQuarters, value/3, buf);
1738 } else if (count == 3) {
1739 safeAppend(formatData.standaloneShortQuarters, value/3, buf);
1741 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
1745 // case 3: // 'd' - DATE
1746 // case 5: // 'H' - HOUR_OF_DAY (0..23)
1747 // case 6: // 'm' - MINUTE
1748 // case 7: // 's' - SECOND
1749 // case 10: // 'D' - DAY_OF_YEAR
1750 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
1751 // case 12: // 'w' - WEEK_OF_YEAR
1752 // case 13: // 'W' - WEEK_OF_MONTH
1753 // case 16: // 'K' - HOUR (0..11)
1754 // case 20: // 'u' - EXTENDED_YEAR
1755 // case 21: // 'g' - JULIAN_DAY
1756 // case 22: // 'A' - MILLISECONDS_IN_DAY
1758 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
1760 } // switch (patternCharIndex)
1762 if (fieldNum == 0) {
1763 boolean titlecase = false;
1764 if (capitalizationContext != null) {
1765 switch (capitalizationContext) {
1766 case CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE:
1769 case CAPITALIZATION_FOR_UI_LIST_OR_MENU:
1770 case CAPITALIZATION_FOR_STANDALONE:
1771 if (formatData.capitalization != null) {
1772 boolean[] transforms = formatData.capitalization.get(capContextUsageType);
1773 titlecase = (capitalizationContext==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)?
1774 transforms[0]: transforms[1];
1782 String firstField = buf.substring(bufstart); // bufstart or beginOffset, should be the same
1783 String firstFieldTitleCase = UCharacter.toTitleCase(locale, firstField, null,
1784 UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
1785 buf.replace(bufstart, buf.length(), firstFieldTitleCase);
1789 // Set the FieldPosition (for the first occurrence only)
1790 if (pos.getBeginIndex() == pos.getEndIndex()) {
1791 if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
1792 pos.setBeginIndex(beginOffset);
1793 pos.setEndIndex(beginOffset + buf.length() - bufstart);
1794 } else if (pos.getFieldAttribute() ==
1795 PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
1796 pos.setBeginIndex(beginOffset);
1797 pos.setEndIndex(beginOffset + buf.length() - bufstart);
1802 private static void safeAppend(String[] array, int value, StringBuffer appendTo) {
1803 if (array != null && value >= 0 && value < array.length) {
1804 appendTo.append(array[value]);
1808 private static void safeAppendWithMonthPattern(String[] array, int value, StringBuffer appendTo, String monthPattern) {
1809 if (array != null && value >= 0 && value < array.length) {
1810 if (monthPattern == null) {
1811 appendTo.append(array[value]);
1813 appendTo.append(MessageFormat.format(monthPattern, array[value]));
1819 * PatternItem store parsed date/time field pattern information.
1821 private static class PatternItem {
1824 final boolean isNumeric;
1826 PatternItem(char type, int length) {
1828 this.length = length;
1829 isNumeric = isNumeric(type, length);
1833 private static ICUCache<String, Object[]> PARSED_PATTERN_CACHE =
1834 new SimpleCache<String, Object[]>();
1835 private transient Object[] patternItems;
1838 * Returns parsed pattern items. Each item is either String or
1841 private Object[] getPatternItems() {
1842 if (patternItems != null) {
1843 return patternItems;
1846 patternItems = PARSED_PATTERN_CACHE.get(pattern);
1847 if (patternItems != null) {
1848 return patternItems;
1851 boolean isPrevQuote = false;
1852 boolean inQuote = false;
1853 StringBuilder text = new StringBuilder();
1854 char itemType = 0; // 0 for string literal, otherwise date/time pattern character
1857 List<Object> items = new ArrayList<Object>();
1859 for (int i = 0; i < pattern.length(); i++) {
1860 char ch = pattern.charAt(i);
1864 isPrevQuote = false;
1867 if (itemType != 0) {
1868 items.add(new PatternItem(itemType, itemLength));
1874 isPrevQuote = false;
1878 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
1879 // a date/time pattern character
1880 if (ch == itemType) {
1883 if (itemType == 0) {
1884 if (text.length() > 0) {
1885 items.add(text.toString());
1889 items.add(new PatternItem(itemType, itemLength));
1896 if (itemType != 0) {
1897 items.add(new PatternItem(itemType, itemLength));
1906 if (itemType == 0) {
1907 if (text.length() > 0) {
1908 items.add(text.toString());
1912 items.add(new PatternItem(itemType, itemLength));
1915 patternItems = items.toArray(new Object[items.size()]);
1917 PARSED_PATTERN_CACHE.put(pattern, patternItems);
1919 return patternItems;
1923 * Internal high-speed method. Reuses a StringBuffer for results
1924 * instead of creating a String on the heap for each call.
1926 * @deprecated This API is ICU internal only.
1928 protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
1929 int minDigits, int maxDigits) {
1930 // Note: Indian calendar uses negative value for a calendar
1931 // field. fastZeroPaddingNumber cannot handle negative numbers.
1932 // BTW, it looks like a design bug in the Indian calendar...
1933 if (useLocalZeroPaddingNumberFormat && value >= 0) {
1934 fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
1936 nf.setMinimumIntegerDigits(minDigits);
1937 nf.setMaximumIntegerDigits(maxDigits);
1938 nf.format(value, buf, new FieldPosition(-1));
1943 * Overrides superclass method
1946 public void setNumberFormat(NumberFormat newNumberFormat) {
1947 // Override this method to update local zero padding number formatter
1948 super.setNumberFormat(newNumberFormat);
1949 initLocalZeroPaddingNumberFormat();
1950 initializeTimeZoneFormat(true);
1953 private void initLocalZeroPaddingNumberFormat() {
1954 if (numberFormat instanceof DecimalFormat) {
1955 decDigits = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getDigits();
1956 useLocalZeroPaddingNumberFormat = true;
1957 } else if (numberFormat instanceof DateNumberFormat) {
1958 decDigits = ((DateNumberFormat)numberFormat).getDigits();
1959 useLocalZeroPaddingNumberFormat = true;
1961 useLocalZeroPaddingNumberFormat = false;
1964 if (useLocalZeroPaddingNumberFormat) {
1965 decimalBuf = new char[10]; // sufficient for int numbers
1969 // If true, use local version of zero padding number format
1970 private transient boolean useLocalZeroPaddingNumberFormat;
1971 private transient char[] decDigits;
1972 private transient char[] decimalBuf;
1975 * Lightweight zero padding integer number format function.
1977 * Note: This implementation is almost equivalent to format method in DateNumberFormat.
1978 * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
1979 * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative
1980 * date format test case, having local implementation is ~10% faster than using one in
1981 * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference.
1985 private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {
1986 int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
1987 int index = limit - 1;
1989 decimalBuf[index] = decDigits[(value % 10)];
1991 if (index == 0 || value == 0) {
1996 int padding = minDigits - (limit - index);
1997 while (padding > 0 && index > 0) {
1998 decimalBuf[--index] = decDigits[0];
2001 while (padding > 0) {
2002 // when pattern width is longer than decimalBuf, need extra
2003 // leading zeros - ticke#7595
2004 buf.append(decDigits[0]);
2007 buf.append(decimalBuf, index, limit - index);
2011 * Formats a number with the specified minimum and maximum number of digits.
2014 protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
2016 numberFormat.setMinimumIntegerDigits(minDigits);
2017 numberFormat.setMaximumIntegerDigits(maxDigits);
2018 return numberFormat.format(value);
2022 * Format characters that indicate numeric fields. The character
2023 * at index 0 is treated specially.
2025 private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";
2028 * Return true if the given format character, occuring count
2029 * times, represents a numeric field.
2031 private static final boolean isNumeric(char formatChar, int count) {
2032 int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
2033 return (i > 0 || (i == 0 && count < 3));
2037 * Overrides DateFormat
2041 public void parse(String text, Calendar cal, ParsePosition parsePos)
2043 TimeZone backupTZ = null;
2044 Calendar resultCal = null;
2045 if (cal != calendar && !cal.getType().equals(calendar.getType())) {
2046 // Different calendar type
2047 // We use the time/zone from the input calendar, but
2048 // do not use the input calendar for field calculation.
2049 calendar.setTimeInMillis(cal.getTimeInMillis());
2050 backupTZ = calendar.getTimeZone();
2051 calendar.setTimeZone(cal.getTimeZone());
2056 int pos = parsePos.getIndex();
2060 tztype = TimeType.UNKNOWN;
2061 boolean[] ambiguousYear = { false };
2063 // item index for the first numeric field within a contiguous numeric run
2064 int numericFieldStart = -1;
2065 // item length for the first numeric field within a contiguous numeric run
2066 int numericFieldLength = 0;
2067 // start index of numeric text run in the input text
2068 int numericStartPos = 0;
2070 MessageFormat numericLeapMonthFormatter = null;
2071 if (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT) {
2072 numericLeapMonthFormatter = new MessageFormat(formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC], locale);
2075 Object[] items = getPatternItems();
2077 while (i < items.length) {
2078 if (items[i] instanceof PatternItem) {
2079 // Handle pattern field
2080 PatternItem field = (PatternItem)items[i];
2081 if (field.isNumeric) {
2082 // Handle fields within a run of abutting numeric fields. Take
2083 // the pattern "HHmmss" as an example. We will try to parse
2084 // 2/2/2 characters of the input text, then if that fails,
2085 // 1/2/2. We only adjust the width of the leftmost field; the
2086 // others remain fixed. This allows "123456" => 12:34:56, but
2087 // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
2088 // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
2089 if (numericFieldStart == -1) {
2090 // check if this field is followed by abutting another numeric field
2091 if ((i + 1) < items.length
2092 && (items[i + 1] instanceof PatternItem)
2093 && ((PatternItem)items[i + 1]).isNumeric) {
2094 // record the first numeric field within a numeric text run
2095 numericFieldStart = i;
2096 numericFieldLength = field.length;
2097 numericStartPos = pos;
2101 if (numericFieldStart != -1) {
2102 // Handle a numeric field within abutting numeric fields
2103 int len = field.length;
2104 if (numericFieldStart == i) {
2105 len = numericFieldLength;
2108 // Parse a numeric field
2109 pos = subParse(text, pos, field.type, len,
2110 true, false, ambiguousYear, cal, numericLeapMonthFormatter);
2113 // If the parse fails anywhere in the numeric run, back up to the
2114 // start of the run and use shorter pattern length for the first
2116 --numericFieldLength;
2117 if (numericFieldLength == 0) {
2118 // can not make shorter any more
2119 parsePos.setIndex(start);
2120 parsePos.setErrorIndex(pos);
2121 if (backupTZ != null) {
2122 calendar.setTimeZone(backupTZ);
2126 i = numericFieldStart;
2127 pos = numericStartPos;
2131 } else if (field.type != 'l') { // (SMALL LETTER L) obsolete pattern char just gets ignored
2132 // Handle a non-numeric field or a non-abutting numeric field
2133 numericFieldStart = -1;
2136 pos = subParse(text, pos, field.type, field.length,
2137 false, true, ambiguousYear, cal, numericLeapMonthFormatter);
2140 if (pos == ISOSpecialEra) {
2141 // era not present, in special cases allow this to continue
2144 if (i+1 < items.length) {
2147 // if it will cause a class cast exception to String, we can't use it
2149 patl = (String)items[i+1];
2150 } catch(ClassCastException cce) {
2151 parsePos.setIndex(start);
2152 parsePos.setErrorIndex(s);
2153 if (backupTZ != null) {
2154 calendar.setTimeZone(backupTZ);
2159 // get next item in pattern
2161 patl = (String)items[i+1];
2162 int plen = patl.length();
2165 // White space characters found in patten.
2166 // Skip contiguous white spaces.
2167 while (idx < plen) {
2169 char pch = patl.charAt(idx);
2170 if (PatternProps.isWhiteSpace(pch))
2176 // if next item in pattern is all whitespace, skip it
2183 parsePos.setIndex(start);
2184 parsePos.setErrorIndex(s);
2185 if (backupTZ != null) {
2186 calendar.setTimeZone(backupTZ);
2194 // Handle literal pattern text literal
2195 numericFieldStart = -1;
2196 boolean[] complete = new boolean[1];
2197 pos = matchLiteral(text, pos, items, i, complete);
2199 // Set the position of mismatch
2200 parsePos.setIndex(start);
2201 parsePos.setErrorIndex(pos);
2202 if (backupTZ != null) {
2203 calendar.setTimeZone(backupTZ);
2211 // Special hack for trailing "." after non-numeric field.
2212 if (pos < text.length()) {
2213 char extra = text.charAt(pos);
2214 if (extra == '.' && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && items.length != 0) {
2215 // only do if the last field is not numeric
2216 Object lastItem = items[items.length - 1];
2217 if (lastItem instanceof PatternItem && !((PatternItem)lastItem).isNumeric) {
2218 pos++; // skip the extra "."
2223 // At this point the fields of Calendar have been set. Calendar
2224 // will fill in default values for missing fields when the time
2227 parsePos.setIndex(pos);
2229 // This part is a problem: When we call parsedDate.after, we compute the time.
2230 // Take the date April 3 2004 at 2:30 am. When this is first set up, the year
2231 // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
2232 // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
2233 // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
2234 // on that day. It is therefore parsed out to fields as 3:30 am. Then we
2235 // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
2236 // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
2238 Date parsedDate = cal.getTime();
2239 if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
2240 cal.add(Calendar.YEAR, 100);
2241 parsedDate = cal.getTime();
2244 // Because of the above condition, save off the fields in case we need to readjust.
2245 // The procedure we use here is not particularly efficient, but there is no other
2246 // way to do this given the API restrictions present in Calendar. We minimize
2247 // inefficiency by only performing this computation when it might apply, that is,
2248 // when the two-digit year is equal to the start year, and thus might fall at the
2249 // front or the back of the default century. This only works because we adjust
2250 // the year correctly to start with in other cases -- see subParse().
2252 if (ambiguousYear[0] || tztype != TimeType.UNKNOWN) {
2253 // We need a copy of the fields, and we need to avoid triggering a call to
2254 // complete(), which will recalculate the fields. Since we can't access
2255 // the fields[] array in Calendar, we clone the entire object. This will
2256 // stop working if Calendar.clone() is ever rewritten to call complete().
2258 if (ambiguousYear[0]) { // the two-digit year == the default start year
2259 copy = (Calendar)cal.clone();
2260 Date parsedDate = copy.getTime();
2261 if (parsedDate.before(getDefaultCenturyStart())) {
2262 // We can't use add here because that does a complete() first.
2263 cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
2266 if (tztype != TimeType.UNKNOWN) {
2267 copy = (Calendar)cal.clone();
2268 TimeZone tz = copy.getTimeZone();
2269 BasicTimeZone btz = null;
2270 if (tz instanceof BasicTimeZone) {
2271 btz = (BasicTimeZone)tz;
2275 copy.set(Calendar.ZONE_OFFSET, 0);
2276 copy.set(Calendar.DST_OFFSET, 0);
2277 long localMillis = copy.getTimeInMillis();
2279 // Make sure parsed time zone type (Standard or Daylight)
2280 // matches the rule used by the parsed time zone.
2281 int[] offsets = new int[2];
2283 if (tztype == TimeType.STANDARD) {
2284 btz.getOffsetFromLocal(localMillis,
2285 BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
2287 btz.getOffsetFromLocal(localMillis,
2288 BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
2291 // No good way to resolve ambiguous time at transition,
2292 // but following code work in most case.
2293 tz.getOffset(localMillis, true, offsets);
2295 if (tztype == TimeType.STANDARD && offsets[1] != 0
2296 || tztype == TimeType.DAYLIGHT && offsets[1] == 0) {
2297 // Roll back one day and try it again.
2298 // Note: This code assumes 1. timezone transition only happens
2299 // once within 24 hours at max
2300 // 2. the difference of local offsets at the transition is
2301 // less than 24 hours.
2302 tz.getOffset(localMillis - (24*60*60*1000), true, offsets);
2306 // Now, compare the results with parsed type, either standard or
2307 // daylight saving time
2308 int resolvedSavings = offsets[1];
2309 if (tztype == TimeType.STANDARD) {
2310 if (offsets[1] != 0) {
2311 // Override DST_OFFSET = 0 in the result calendar
2312 resolvedSavings = 0;
2314 } else { // tztype == TZTYPE_DST
2315 if (offsets[1] == 0) {
2317 long time = localMillis + offsets[0];
2318 // We use the nearest daylight saving time rule.
2319 TimeZoneTransition beforeTrs, afterTrs;
2320 long beforeT = time, afterT = time;
2321 int beforeSav = 0, afterSav = 0;
2323 // Search for DST rule before or on the time
2325 beforeTrs = btz.getPreviousTransition(beforeT, true);
2326 if (beforeTrs == null) {
2329 beforeT = beforeTrs.getTime() - 1;
2330 beforeSav = beforeTrs.getFrom().getDSTSavings();
2331 if (beforeSav != 0) {
2336 // Search for DST rule after the time
2338 afterTrs = btz.getNextTransition(afterT, false);
2339 if (afterTrs == null) {
2342 afterT = afterTrs.getTime();
2343 afterSav = afterTrs.getTo().getDSTSavings();
2344 if (afterSav != 0) {
2349 if (beforeTrs != null && afterTrs != null) {
2350 if (time - beforeT > afterT - time) {
2351 resolvedSavings = afterSav;
2353 resolvedSavings = beforeSav;
2355 } else if (beforeTrs != null && beforeSav != 0) {
2356 resolvedSavings = beforeSav;
2357 } else if (afterTrs != null && afterSav != 0) {
2358 resolvedSavings = afterSav;
2360 resolvedSavings = btz.getDSTSavings();
2363 resolvedSavings = tz.getDSTSavings();
2365 if (resolvedSavings == 0) {
2367 resolvedSavings = millisPerHour;
2371 cal.set(Calendar.ZONE_OFFSET, offsets[0]);
2372 cal.set(Calendar.DST_OFFSET, resolvedSavings);
2376 // An IllegalArgumentException will be thrown by Calendar.getTime()
2377 // if any fields are out of range, e.g., MONTH == 17.
2378 catch (IllegalArgumentException e) {
2379 parsePos.setErrorIndex(pos);
2380 parsePos.setIndex(start);
2381 if (backupTZ != null) {
2382 calendar.setTimeZone(backupTZ);
2386 // Set the parsed result if local calendar is used
2387 // instead of the input calendar
2388 if (resultCal != null) {
2389 resultCal.setTimeZone(cal.getTimeZone());
2390 resultCal.setTimeInMillis(cal.getTimeInMillis());
2392 // Restore the original time zone if required
2393 if (backupTZ != null) {
2394 calendar.setTimeZone(backupTZ);
2399 * Matches text (starting at pos) with patl. Returns the new pos, and sets complete[0]
2400 * if it matched the entire text. Whitespace sequences are treated as singletons.
2401 * <p>If isLenient and if we fail to match the first time, some special hacks are put into place.
2402 * <ul><li>we are between date and time fields, then one or more whitespace characters
2403 * in the text are accepted instead.</li>
2404 * <ul><li>we are after a non-numeric field, and the text starts with a ".", we skip it.</li>
2407 private int matchLiteral(String text, int pos, Object[] items, int itemIndex, boolean[] complete) {
2408 int originalPos = pos;
2409 String patternLiteral = (String)items[itemIndex];
2410 int plen = patternLiteral.length();
2411 int tlen = text.length();
2413 while (idx < plen && pos < tlen) {
2414 char pch = patternLiteral.charAt(idx);
2415 char ich = text.charAt(pos);
2416 if (PatternProps.isWhiteSpace(pch)
2417 && PatternProps.isWhiteSpace(ich)) {
2418 // White space characters found in both patten and input.
2419 // Skip contiguous white spaces.
2420 while ((idx + 1) < plen &&
2421 PatternProps.isWhiteSpace(patternLiteral.charAt(idx + 1))) {
2424 while ((pos + 1) < tlen &&
2425 PatternProps.isWhiteSpace(text.charAt(pos + 1))) {
2428 } else if (pch != ich) {
2429 if (ich == '.' && pos == originalPos && 0 < itemIndex && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
2430 Object before = items[itemIndex-1];
2431 if (before instanceof PatternItem) {
2432 boolean isNumeric = ((PatternItem) before).isNumeric;
2434 ++pos; // just update pos
2438 } else if ((pch == ' ' || pch == '.') && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
2447 complete[0] = idx == plen;
2448 if (complete[0] == false && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && 0 < itemIndex && itemIndex < items.length - 1) {
2449 // If fully lenient, accept " "* for any text between a date and a time field
2450 // We don't go more lenient, because we don't want to accept "12/31" for "12:31".
2451 // People may be trying to parse for a date, then for a time.
2452 if (originalPos < tlen) {
2453 Object before = items[itemIndex-1];
2454 Object after = items[itemIndex+1];
2455 if (before instanceof PatternItem && after instanceof PatternItem) {
2456 char beforeType = ((PatternItem) before).type;
2457 char afterType = ((PatternItem) after).type;
2458 if (DATE_PATTERN_TYPE.contains(beforeType) != DATE_PATTERN_TYPE.contains(afterType)) {
2459 int newPos = originalPos;
2461 char ich = text.charAt(newPos);
2462 if (!PatternProps.isWhiteSpace(ich)) {
2467 complete[0] = newPos > originalPos;
2476 static final UnicodeSet DATE_PATTERN_TYPE = new UnicodeSet("[GyYuUQqMLlwWd]").freeze();
2479 * Attempt to match the text at a given position against an array of
2480 * strings. Since multiple strings in the array may match (for
2481 * example, if the array contains "a", "ab", and "abc", all will match
2482 * the input string "abcd") the longest match is returned. As a side
2483 * effect, the given field of <code>cal</code> is set to the index
2484 * of the best match, if there is one.
2485 * @param text the time text being parsed.
2486 * @param start where to start parsing.
2487 * @param field the date field being parsed.
2488 * @param data the string array to parsed.
2490 * @return the new start position if matching succeeded; a negative
2491 * number indicating matching failure, otherwise. As a side effect,
2492 * sets the <code>cal</code> field <code>field</code> to the index
2493 * of the best match, if matching succeeded.
2496 protected int matchString(String text, int start, int field, String[] data, Calendar cal)
2498 return matchString(text, start, field, data, null, cal);
2502 * Attempt to match the text at a given position against an array of
2503 * strings. Since multiple strings in the array may match (for
2504 * example, if the array contains "a", "ab", and "abc", all will match
2505 * the input string "abcd") the longest match is returned. As a side
2506 * effect, the given field of <code>cal</code> is set to the index
2507 * of the best match, if there is one.
2508 * @param text the time text being parsed.
2509 * @param start where to start parsing.
2510 * @param field the date field being parsed.
2511 * @param data the string array to parsed.
2512 * @param monthPattern leap month pattern, or null if none.
2514 * @return the new start position if matching succeeded; a negative
2515 * number indicating matching failure, otherwise. As a side effect,
2516 * sets the <code>cal</code> field <code>field</code> to the index
2517 * of the best match, if matching succeeded.
2519 * @deprecated This API is ICU internal only.
2521 private int matchString(String text, int start, int field, String[] data, String monthPattern, Calendar cal)
2524 int count = data.length;
2526 if (field == Calendar.DAY_OF_WEEK) i = 1;
2528 // There may be multiple strings in the data[] array which begin with
2529 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
2530 // We keep track of the longest match, and return that. Note that this
2531 // unfortunately requires us to test all array elements.
2532 int bestMatchLength = 0, bestMatch = -1;
2533 int isLeapMonth = 0;
2534 int matchLength = 0;
2536 for (; i<count; ++i)
2538 int length = data[i].length();
2539 // Always compare if we have no match yet; otherwise only compare
2540 // against potentially better matches (longer strings).
2541 if (length > bestMatchLength &&
2542 (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0)
2545 bestMatchLength = matchLength;
2548 if (monthPattern != null) {
2549 String leapMonthName = MessageFormat.format(monthPattern, data[i]);
2550 length = leapMonthName.length();
2551 if (length > bestMatchLength &&
2552 (matchLength = regionMatchesWithOptionalDot(text, start, leapMonthName, length)) >= 0)
2555 bestMatchLength = matchLength;
2562 if (field == Calendar.YEAR) {
2563 bestMatch++; // only get here for cyclic year names, which match 1-based years 1-60
2565 cal.set(field, bestMatch);
2566 if (monthPattern != null) {
2567 cal.set(Calendar.IS_LEAP_MONTH, isLeapMonth);
2569 return start + bestMatchLength;
2574 private int regionMatchesWithOptionalDot(String text, int start, String data, int length) {
2575 boolean matches = text.regionMatches(true, start, data, 0, length);
2579 if (data.length() > 0 && data.charAt(data.length()-1) == '.') {
2580 if (text.regionMatches(true, start, data, 0, length-1)) {
2588 * Attempt to match the text at a given position against an array of quarter
2589 * strings. Since multiple strings in the array may match (for
2590 * example, if the array contains "a", "ab", and "abc", all will match
2591 * the input string "abcd") the longest match is returned. As a side
2592 * effect, the given field of <code>cal</code> is set to the index
2593 * of the best match, if there is one.
2594 * @param text the time text being parsed.
2595 * @param start where to start parsing.
2596 * @param field the date field being parsed.
2597 * @param data the string array to parsed.
2598 * @return the new start position if matching succeeded; a negative
2599 * number indicating matching failure, otherwise. As a side effect,
2600 * sets the <code>cal</code> field <code>field</code> to the index
2601 * of the best match, if matching succeeded.
2604 protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
2607 int count = data.length;
2609 // There may be multiple strings in the data[] array which begin with
2610 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
2611 // We keep track of the longest match, and return that. Note that this
2612 // unfortunately requires us to test all array elements.
2613 int bestMatchLength = 0, bestMatch = -1;
2614 int matchLength = 0;
2615 for (; i<count; ++i) {
2616 int length = data[i].length();
2617 // Always compare if we have no match yet; otherwise only compare
2618 // against potentially better matches (longer strings).
2619 if (length > bestMatchLength &&
2620 (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) {
2623 bestMatchLength = matchLength;
2627 if (bestMatch >= 0) {
2628 cal.set(field, bestMatch * 3);
2629 return start + bestMatchLength;
2636 * Protected method that converts one field of the input string into a
2637 * numeric field value in <code>cal</code>. Returns -start (for
2638 * ParsePosition) if failed. Subclasses may override this method to
2639 * modify or add parsing capabilities.
2640 * @param text the time text to be parsed.
2641 * @param start where to start parsing.
2642 * @param ch the pattern character for the date field text to be parsed.
2643 * @param count the count of a pattern character.
2644 * @param obeyCount if true, then the next field directly abuts this one,
2645 * and we should use the count to know when to stop parsing.
2646 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
2647 * is true, then a two-digit year was parsed and may need to be readjusted.
2649 * @return the new start position if matching succeeded; a negative
2650 * number indicating matching failure, otherwise. As a side effect,
2651 * set the appropriate field of <code>cal</code> with the parsed
2655 protected int subParse(String text, int start, char ch, int count,
2656 boolean obeyCount, boolean allowNegative,
2657 boolean[] ambiguousYear, Calendar cal)
2659 return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null);
2663 * Protected method that converts one field of the input string into a
2664 * numeric field value in <code>cal</code>. Returns -start (for
2665 * ParsePosition) if failed. Subclasses may override this method to
2666 * modify or add parsing capabilities.
2667 * @param text the time text to be parsed.
2668 * @param start where to start parsing.
2669 * @param ch the pattern character for the date field text to be parsed.
2670 * @param count the count of a pattern character.
2671 * @param obeyCount if true, then the next field directly abuts this one,
2672 * and we should use the count to know when to stop parsing.
2673 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
2674 * is true, then a two-digit year was parsed and may need to be readjusted.
2676 * @param numericLeapMonthFormatter if non-null, used to parse numeric leap months.
2677 * @return the new start position if matching succeeded; a negative
2678 * number indicating matching failure, otherwise. As a side effect,
2679 * set the appropriate field of <code>cal</code> with the parsed
2682 * @deprecated This API is ICU internal only.
2684 private int subParse(String text, int start, char ch, int count,
2685 boolean obeyCount, boolean allowNegative,
2686 boolean[] ambiguousYear, Calendar cal, MessageFormat numericLeapMonthFormatter)
2688 Number number = null;
2689 NumberFormat currentNumberFormat = null;
2692 ParsePosition pos = new ParsePosition(0);
2694 //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c
2695 int patternCharIndex = -1;
2696 if ('A' <= ch && ch <= 'z') {
2697 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
2700 if (patternCharIndex == -1) {
2704 currentNumberFormat = getNumberFormat(ch);
2706 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
2708 if (numericLeapMonthFormatter != null) {
2709 numericLeapMonthFormatter.setFormatByArgumentIndex(0, currentNumberFormat);
2711 boolean isChineseCalendar = ( cal.getType().equals("chinese") || cal.getType().equals("dangi") );
2713 // If there are any spaces here, skip over them. If we hit the end
2714 // of the string, then fail.
2716 if (start >= text.length()) {
2719 int c = UTF16.charAt(text, start);
2720 if (!UCharacter.isUWhiteSpace(c) || !PatternProps.isWhiteSpace(c)) {
2723 start += UTF16.getCharCount(c);
2725 pos.setIndex(start);
2727 // We handle a few special cases here where we need to parse
2728 // a number value. We handle further, more generic cases below. We need
2729 // to handle some of them here because some fields require extra processing on
2730 // the parsed value.
2731 if (patternCharIndex == 4 /*'k' HOUR_OF_DAY1_FIELD*/ ||
2732 patternCharIndex == 15 /*'h' HOUR1_FIELD*/ ||
2733 (patternCharIndex == 2 /*'M' MONTH_FIELD*/ && count <= 2) ||
2734 (patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ && count <= 2) ||
2735 patternCharIndex == 1 /*'y' YEAR */ || patternCharIndex == 18 /*'Y' YEAR_WOY */ ||
2736 patternCharIndex == 30 /*'U' YEAR_NAME_FIELD, falls back to numeric */ ||
2737 (patternCharIndex == 0 /*'G' ERA */ && isChineseCalendar) ||
2738 patternCharIndex == 8 /*'S' FRACTIONAL_SECOND */ )
2740 // It would be good to unify this with the obeyCount logic below,
2741 // but that's going to be difficult.
2743 boolean parsedNumericLeapMonth = false;
2744 if (numericLeapMonthFormatter != null && (patternCharIndex == 2 || patternCharIndex == 26)) {
2745 // First see if we can parse month number with leap month pattern
2746 Object[] args = numericLeapMonthFormatter.parse(text, pos);
2747 if (args != null && pos.getIndex() > start && (args[0] instanceof Number)) {
2748 parsedNumericLeapMonth = true;
2749 number = (Number)args[0];
2750 cal.set(Calendar.IS_LEAP_MONTH, 1);
2752 pos.setIndex(start);
2753 cal.set(Calendar.IS_LEAP_MONTH, 0);
2757 if (!parsedNumericLeapMonth) {
2759 if ((start+count) > text.length()) {
2762 number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
2764 number = parseInt(text, pos, allowNegative,currentNumberFormat);
2766 if (number == null && patternCharIndex != 30) {
2771 if (number != null) {
2772 value = number.intValue();
2776 switch (patternCharIndex)
2778 case 0: // 'G' - ERA
2779 if ( isChineseCalendar ) {
2780 // Numeric era handling moved from ChineseDateFormat,
2781 // If we didn't have a number, already returned -start above
2782 cal.set(Calendar.ERA, value);
2783 return pos.getIndex();
2787 ps = matchString(text, start, Calendar.ERA, formatData.narrowEras, null, cal);
2788 } else if (count == 4) {
2789 ps = matchString(text, start, Calendar.ERA, formatData.eraNames, null, cal);
2791 ps = matchString(text, start, Calendar.ERA, formatData.eras, null, cal);
2794 // check return position, if it equals -start, then matchString error
2795 // special case the return code so we don't necessarily fail out until we
2796 // verify no year information also
2802 case 1: // 'y' - YEAR
2803 case 18: // 'Y' - YEAR_WOY
2804 // If there are 3 or more YEAR pattern characters, this indicates
2805 // that the year value is to be treated literally, without any
2806 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
2807 // we made adjustments to place the 2-digit year in the proper
2808 // century, for parsed strings from "00" to "99". Any other string
2809 // is treated literally: "2250", "-1", "1", "002".
2810 /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
2811 /* Skip this for Chinese calendar, moved from ChineseDateFormat */
2812 if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) && value < 1000 ) {
2813 value += HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
2814 } else if (count == 2 && (pos.getIndex() - start) == 2 && !isChineseCalendar
2815 && UCharacter.isDigit(text.charAt(start))
2816 && UCharacter.isDigit(text.charAt(start+1)))
2818 // Assume for example that the defaultCenturyStart is 6/18/1903.
2819 // This means that two-digit years will be forced into the range
2820 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
2821 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
2822 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
2823 // other fields specify a date before 6/18, or 1903 if they specify a
2824 // date afterwards. As a result, 03 is an ambiguous year. All other
2825 // two-digit years are unambiguous.
2826 int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
2827 ambiguousYear[0] = value == ambiguousTwoDigitYear;
2828 value += (getDefaultCenturyStartYear()/100)*100 +
2829 (value < ambiguousTwoDigitYear ? 100 : 0);
2831 cal.set(field, value);
2833 // Delayed checking for adjustment of Hebrew month numbers in non-leap years.
2834 if (DelayedHebrewMonthCheck) {
2835 if (!HebrewCalendar.isLeapYear(value)) {
2836 cal.add(Calendar.MONTH,1);
2838 DelayedHebrewMonthCheck = false;
2840 return pos.getIndex();
2841 case 30: // 'U' - YEAR_NAME_FIELD
2842 if (formatData.shortYearNames != null) {
2843 int newStart = matchString(text, start, Calendar.YEAR, formatData.shortYearNames, null, cal);
2848 if ( number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC) || formatData.shortYearNames == null || value > formatData.shortYearNames.length) ) {
2849 cal.set(Calendar.YEAR, value);
2850 return pos.getIndex();
2853 case 2: // 'M' - MONTH
2854 case 26: // 'L' - STAND_ALONE_MONTH
2855 if (count <= 2) { // i.e., M/MM, L/LL
2856 // Don't want to parse the month if it is a string
2857 // while pattern uses numeric style: M/MM, L/LL.
2858 // [We computed 'value' above.]
2859 cal.set(Calendar.MONTH, value - 1);
2860 // When parsing month numbers from the Hebrew Calendar, we might need
2861 // to adjust the month depending on whether or not it was a leap year.
2862 // We may or may not yet know what year it is, so might have to delay
2863 // checking until the year is parsed.
2864 if (cal.getType().equals("hebrew") && value >= 6) {
2865 if (cal.isSet(Calendar.YEAR)) {
2866 if (!HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR))) {
2867 cal.set(Calendar.MONTH, value);
2870 DelayedHebrewMonthCheck = true;
2873 return pos.getIndex();
2875 // count >= 3 // i.e., MMM/MMMM or LLL/LLLL
2876 // Want to be able to parse both short and long forms.
2877 boolean haveMonthPat = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT);
2878 // Try count == 4 first:
2879 int newStart = (patternCharIndex == 2)?
2880 matchString(text, start, Calendar.MONTH, formatData.months,
2881 (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null, cal):
2882 matchString(text, start, Calendar.MONTH, formatData.standaloneMonths,
2883 (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null, cal);
2886 } else { // count == 4 failed, now try count == 3
2887 return (patternCharIndex == 2)?
2888 matchString(text, start, Calendar.MONTH, formatData.shortMonths,
2889 (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null, cal):
2890 matchString(text, start, Calendar.MONTH, formatData.standaloneShortMonths,
2891 (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null, cal);
2894 case 4: // 'k' - HOUR_OF_DAY (1..24)
2895 // [We computed 'value' above.]
2896 if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) {
2899 cal.set(Calendar.HOUR_OF_DAY, value);
2900 return pos.getIndex();
2901 case 8: // 'S' - FRACTIONAL_SECOND
2902 // Fractional seconds left-justify
2903 i = pos.getIndex() - start;
2917 cal.set(Calendar.MILLISECOND, value);
2918 return pos.getIndex();
2919 case 9: { // 'E' - DAY_OF_WEEK
2920 // Want to be able to parse at least wide, abbrev, short forms.
2921 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.weekdays, null, cal); // try EEEE wide
2924 } else if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shortWeekdays, null, cal)) > 0) { // try EEE abbrev
2926 } else if (formatData.shorterWeekdays != null) {
2927 return matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shorterWeekdays, null, cal); // try EEEEEE short
2931 case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
2932 // Want to be able to parse at least wide, abbrev, short forms.
2933 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneWeekdays, null, cal); // try cccc wide
2936 } else if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShortWeekdays, null, cal)) > 0) { // try ccc abbrev
2938 } else if (formatData.standaloneShorterWeekdays != null) {
2939 return matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShorterWeekdays, null, cal); // try cccccc short
2943 case 14: // 'a' - AM_PM
2944 return matchString(text, start, Calendar.AM_PM, formatData.ampms, null, cal);
2945 case 15: // 'h' - HOUR (1..12)
2946 // [We computed 'value' above.]
2947 if (value == cal.getLeastMaximum(Calendar.HOUR)+1) {
2950 cal.set(Calendar.HOUR, value);
2951 return pos.getIndex();
2952 case 17: // 'z' - ZONE_OFFSET
2954 Output<TimeType> tzTimeType = new Output<TimeType>();
2955 Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.SPECIFIC_LONG;
2956 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
2958 tztype = tzTimeType.value;
2959 cal.setTimeZone(tz);
2960 return pos.getIndex();
2964 case 23: // 'Z' - TIMEZONE_RFC
2966 Output<TimeType> tzTimeType = new Output<TimeType>();
2967 Style style = (count < 4) ? Style.ISO_BASIC_LOCAL_FULL : ((count == 5) ? Style.ISO_EXTENDED_FULL : Style.LOCALIZED_GMT);
2968 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
2970 tztype = tzTimeType.value;
2971 cal.setTimeZone(tz);
2972 return pos.getIndex();
2976 case 24: // 'v' - TIMEZONE_GENERIC
2978 Output<TimeType> tzTimeType = new Output<TimeType>();
2979 // Note: 'v' only supports count 1 and 4
2980 Style style = (count < 4) ? Style.GENERIC_SHORT : Style.GENERIC_LONG;
2981 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
2983 tztype = tzTimeType.value;
2984 cal.setTimeZone(tz);
2985 return pos.getIndex();
2989 case 29: // 'V' - TIMEZONE_SPECIAL
2991 Output<TimeType> tzTimeType = new Output<TimeType>();
2995 style = Style.ZONE_ID_SHORT;
2998 style = Style.ZONE_ID;
3001 style = Style.EXEMPLAR_LOCATION;
3004 style = Style.GENERIC_LOCATION;
3007 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
3009 tztype = tzTimeType.value;
3010 cal.setTimeZone(tz);
3011 return pos.getIndex();
3015 case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET
3017 Output<TimeType> tzTimeType = new Output<TimeType>();
3018 Style style = (count < 4) ? Style.LOCALIZED_GMT_SHORT : Style.LOCALIZED_GMT;
3019 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
3021 tztype = tzTimeType.value;
3022 cal.setTimeZone(tz);
3023 return pos.getIndex();
3027 case 32: // 'X' - TIMEZONE_ISO
3029 Output<TimeType> tzTimeType = new Output<TimeType>();
3033 style = Style.ISO_BASIC_SHORT;
3036 style = Style.ISO_BASIC_FIXED;
3039 style = Style.ISO_EXTENDED_FIXED;
3042 style = Style.ISO_BASIC_FULL;
3044 default: // count >= 5
3045 style = Style.ISO_EXTENDED_FULL;
3048 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
3050 tztype = tzTimeType.value;
3051 cal.setTimeZone(tz);
3052 return pos.getIndex();
3056 case 33: // 'x' - TIMEZONE_ISO_LOCAL
3058 Output<TimeType> tzTimeType = new Output<TimeType>();
3062 style = Style.ISO_BASIC_LOCAL_SHORT;
3065 style = Style.ISO_BASIC_LOCAL_FIXED;
3068 style = Style.ISO_EXTENDED_LOCAL_FIXED;
3071 style = Style.ISO_BASIC_LOCAL_FULL;
3073 default: // count >= 5
3074 style = Style.ISO_EXTENDED_LOCAL_FULL;
3077 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
3079 tztype = tzTimeType.value;
3080 cal.setTimeZone(tz);
3081 return pos.getIndex();
3085 case 27: // 'Q' - QUARTER
3086 if (count <= 2) { // i.e., Q or QQ.
3087 // Don't want to parse the quarter if it is a string
3088 // while pattern uses numeric style: Q or QQ.
3089 // [We computed 'value' above.]
3090 cal.set(Calendar.MONTH, (value - 1) * 3);
3091 return pos.getIndex();
3093 // count >= 3 // i.e., QQQ or QQQQ
3094 // Want to be able to parse both short and long forms.
3095 // Try count == 4 first:
3096 int newStart = matchQuarterString(text, start, Calendar.MONTH,
3097 formatData.quarters, cal);
3100 } else { // count == 4 failed, now try count == 3
3101 return matchQuarterString(text, start, Calendar.MONTH,
3102 formatData.shortQuarters, cal);
3106 case 28: // 'q' - STANDALONE QUARTER
3107 if (count <= 2) { // i.e., q or qq.
3108 // Don't want to parse the quarter if it is a string
3109 // while pattern uses numeric style: q or qq.
3110 // [We computed 'value' above.]
3111 cal.set(Calendar.MONTH, (value - 1) * 3);
3112 return pos.getIndex();
3114 // count >= 3 // i.e., qqq or qqqq
3115 // Want to be able to parse both short and long forms.
3116 // Try count == 4 first:
3117 int newStart = matchQuarterString(text, start, Calendar.MONTH,
3118 formatData.standaloneQuarters, cal);
3121 } else { // count == 4 failed, now try count == 3
3122 return matchQuarterString(text, start, Calendar.MONTH,
3123 formatData.standaloneShortQuarters, cal);
3128 // case 3: // 'd' - DATE
3129 // case 5: // 'H' - HOUR_OF_DAY (0..23)
3130 // case 6: // 'm' - MINUTE
3131 // case 7: // 's' - SECOND
3132 // case 10: // 'D' - DAY_OF_YEAR
3133 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
3134 // case 12: // 'w' - WEEK_OF_YEAR
3135 // case 13: // 'W' - WEEK_OF_MONTH
3136 // case 16: // 'K' - HOUR (0..11)
3137 // case 19: // 'e' - DOW_LOCAL
3138 // case 20: // 'u' - EXTENDED_YEAR
3139 // case 21: // 'g' - JULIAN_DAY
3140 // case 22: // 'A' - MILLISECONDS_IN_DAY
3142 // Handle "generic" fields
3144 if ((start+count) > text.length()) return -start;
3145 number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
3147 number = parseInt(text, pos, allowNegative,currentNumberFormat);
3149 if (number != null) {
3150 cal.set(field, number.intValue());
3151 return pos.getIndex();
3158 * Parse an integer using numberFormat. This method is semantically
3159 * const, but actually may modify fNumberFormat.
3161 private Number parseInt(String text,
3163 boolean allowNegative,
3165 return parseInt(text, -1, pos, allowNegative, fmt);
3169 * Parse an integer using numberFormat up to maxDigits.
3171 private Number parseInt(String text,
3174 boolean allowNegative,
3177 int oldPos = pos.getIndex();
3178 if (allowNegative) {
3179 number = fmt.parse(text, pos);
3181 // Invalidate negative numbers
3182 if (fmt instanceof DecimalFormat) {
3183 String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();
3184 ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
3185 number = fmt.parse(text, pos);
3186 ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);
3188 boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
3189 if (dateNumberFormat) {
3190 ((DateNumberFormat)fmt).setParsePositiveOnly(true);
3192 number = fmt.parse(text, pos);
3193 if (dateNumberFormat) {
3194 ((DateNumberFormat)fmt).setParsePositiveOnly(false);
3198 if (maxDigits > 0) {
3199 // adjust the result to fit into
3200 // the maxDigits and move the position back
3201 int nDigits = pos.getIndex() - oldPos;
3202 if (nDigits > maxDigits) {
3203 double val = number.doubleValue();
3204 nDigits -= maxDigits;
3205 while (nDigits > 0) {
3209 pos.setIndex(oldPos + maxDigits);
3210 number = Integer.valueOf((int)val);
3218 * Translate a pattern, mapping each character in the from string to the
3219 * corresponding character in the to string.
3221 private String translatePattern(String pat, String from, String to) {
3222 StringBuilder result = new StringBuilder();
3223 boolean inQuote = false;
3224 for (int i = 0; i < pat.length(); ++i) {
3225 char c = pat.charAt(i);
3232 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
3233 int ci = from.indexOf(c);
3237 // do not worry on translatepattern if the character is not listed
3238 // we do the validity check elsewhere
3244 throw new IllegalArgumentException("Unfinished quote in pattern");
3246 return result.toString();
3250 * Return a pattern string describing this date format.
3253 public String toPattern() {
3258 * Return a localized pattern string describing this date format.
3261 public String toLocalizedPattern() {
3262 return translatePattern(pattern,
3263 DateFormatSymbols.patternChars,
3264 formatData.localPatternChars);
3268 * Apply the given unlocalized pattern string to this date format.
3271 public void applyPattern(String pat)
3274 setLocale(null, null);
3275 // reset parsed pattern items
3276 patternItems = null;
3280 * Apply the given localized pattern string to this date format.
3283 public void applyLocalizedPattern(String pat) {
3284 this.pattern = translatePattern(pat,
3285 formatData.localPatternChars,
3286 DateFormatSymbols.patternChars);
3287 setLocale(null, null);
3291 * Gets the date/time formatting data.
3292 * @return a copy of the date-time formatting data associated
3293 * with this date-time formatter.
3296 public DateFormatSymbols getDateFormatSymbols()
3298 return (DateFormatSymbols)formatData.clone();
3302 * Allows you to set the date/time formatting data.
3303 * @param newFormatSymbols the new symbols
3306 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
3308 this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
3312 * Method for subclasses to access the DateFormatSymbols.
3315 protected DateFormatSymbols getSymbols() {
3320 * {@icu} Gets the time zone formatter which this date/time
3321 * formatter uses to format and parse a time zone.
3323 * @return the time zone formatter which this date/time
3327 public TimeZoneFormat getTimeZoneFormat() {
3328 return tzFormat().freeze();
3332 * {@icu} Allows you to set the time zone formatter.
3334 * @param tzfmt the new time zone formatter
3337 public void setTimeZoneFormat(TimeZoneFormat tzfmt) {
3338 if (tzfmt.isFrozen()) {
3339 // If frozen, use it as is.
3342 // If not frozen, clone and freeze.
3343 tzFormat = tzfmt.cloneAsThawed().freeze();
3348 * {@icu} Set a particular DisplayContext value in the formatter,
3349 * such as CAPITALIZATION_FOR_STANDALONE.
3351 * @param context The DisplayContext value to set.
3353 * @provisional This API might change or be removed in a future release.
3355 public void setContext(DisplayContext context) {
3356 if (context.type() == DisplayContext.Type.CAPITALIZATION) {
3357 capitalizationSetting = context;
3362 * {@icu} Get the formatter's DisplayContext value for the specified DisplayContext.Type,
3363 * such as CAPITALIZATION.
3365 * @param type the DisplayContext.Type whose value to return
3366 * @return the current DisplayContext setting for the specified type
3368 * @provisional This API might change or be removed in a future release.
3370 public DisplayContext getContext(DisplayContext.Type type) {
3371 return (type == DisplayContext.Type.CAPITALIZATION && capitalizationSetting != null)?
3372 capitalizationSetting: DisplayContext.CAPITALIZATION_NONE;
3376 * Overrides Cloneable
3379 public Object clone() {
3380 SimpleDateFormat other = (SimpleDateFormat) super.clone();
3381 other.formatData = (DateFormatSymbols) formatData.clone();
3386 * Override hashCode.
3387 * Generates the hash code for the SimpleDateFormat object
3390 public int hashCode()
3392 return pattern.hashCode();
3393 // just enough fields for a reasonable distribution
3400 public boolean equals(Object obj)
3402 if (!super.equals(obj)) return false; // super does class check
3403 SimpleDateFormat that = (SimpleDateFormat) obj;
3404 return (pattern.equals(that.pattern)
3405 && formatData.equals(that.formatData));
3409 * Override writeObject.
3410 * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectOutputStream.html
3412 private void writeObject(ObjectOutputStream stream) throws IOException{
3413 if (defaultCenturyStart == null) {
3414 // if defaultCenturyStart is not yet initialized,
3415 // calculate and set value before serialization.
3416 initializeDefaultCenturyStart(defaultCenturyBase);
3418 initializeTimeZoneFormat(false);
3419 stream.defaultWriteObject();
3420 stream.writeInt(capitalizationSetting.value());
3424 * Override readObject.
3425 * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectInputStream.html
3427 private void readObject(ObjectInputStream stream)
3428 throws IOException, ClassNotFoundException {
3429 stream.defaultReadObject();
3430 int capitalizationSettingValue = (serialVersionOnStream > 1)? stream.readInt(): -1;
3432 // don't have old serial data to test with
3433 if (serialVersionOnStream < 1) {
3434 // didn't have defaultCenturyStart field
3435 defaultCenturyBase = System.currentTimeMillis();
3439 // fill in dependent transient field
3440 parseAmbiguousDatesAsAfter(defaultCenturyStart);
3442 serialVersionOnStream = currentSerialVersion;
3443 locale = getLocale(ULocale.VALID_LOCALE);
3444 if (locale == null) {
3445 // ICU4J 3.6 or older versions did not have UFormat locales
3446 // in the serialized data. This is just for preventing the
3447 // worst case scenario...
3448 locale = ULocale.getDefault(Category.FORMAT);
3451 initLocalZeroPaddingNumberFormat();
3453 capitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
3454 if (capitalizationSettingValue >= 0) {
3455 for (DisplayContext context: DisplayContext.values()) {
3456 if (context.value() == capitalizationSettingValue) {
3457 capitalizationSetting = context;
3465 * Format the object to an attributed string, and return the corresponding iterator
3466 * Overrides superclass method.
3468 * @param obj The object to format
3469 * @return <code>AttributedCharacterIterator</code> describing the formatted value.
3473 public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
3474 Calendar cal = calendar;
3475 if (obj instanceof Calendar) {
3476 cal = (Calendar)obj;
3477 } else if (obj instanceof Date) {
3478 calendar.setTime((Date)obj);
3479 } else if (obj instanceof Number) {
3480 calendar.setTimeInMillis(((Number)obj).longValue());
3482 throw new IllegalArgumentException("Cannot format given Object as a Date");
3484 StringBuffer toAppendTo = new StringBuffer();
3485 FieldPosition pos = new FieldPosition(0);
3486 List<FieldPosition> attributes = new ArrayList<FieldPosition>();
3487 format(cal, capitalizationSetting, toAppendTo, pos, attributes);
3489 AttributedString as = new AttributedString(toAppendTo.toString());
3491 // add DateFormat field attributes to the AttributedString
3492 for (int i = 0; i < attributes.size(); i++) {
3493 FieldPosition fp = attributes.get(i);
3494 Format.Field attribute = fp.getFieldAttribute();
3495 as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());
3497 // return the CharacterIterator from AttributedString
3498 return as.getIterator();
3502 * Get the locale of this simple date formatter.
3503 * It is package accessible. also used in DateIntervalFormat.
3505 * @return locale in this simple date formatter
3515 * Check whether the 'field' is smaller than all the fields covered in
3516 * pattern, return true if it is.
3517 * The sequence of calendar field,
3518 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
3519 * @param field the calendar field need to check against
3520 * @return true if the 'field' is smaller than all the fields
3521 * covered in pattern. false otherwise.
3524 boolean isFieldUnitIgnored(int field) {
3525 return isFieldUnitIgnored(pattern, field);
3530 * Check whether the 'field' is smaller than all the fields covered in
3531 * pattern, return true if it is.
3532 * The sequence of calendar field,
3533 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
3534 * @param pattern the pattern to check against
3535 * @param field the calendar field need to check against
3536 * @return true if the 'field' is smaller than all the fields
3537 * covered in pattern. false otherwise.
3539 static boolean isFieldUnitIgnored(String pattern, int field) {
3540 int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
3543 boolean inQuote = false;
3547 for (int i = 0; i < pattern.length(); ++i) {
3548 ch = pattern.charAt(i);
3549 if (ch != prevCh && count > 0) {
3550 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
3551 if ( fieldLevel <= level ) {
3557 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
3560 inQuote = ! inQuote;
3562 } else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
3563 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
3570 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
3571 if ( fieldLevel <= level ) {
3580 * Format date interval by algorithm.
3581 * It is supposed to be used only by CLDR survey tool.
3583 * @param fromCalendar calendar set to the from date in date interval
3584 * to be formatted into date interval stirng
3585 * @param toCalendar calendar set to the to date in date interval
3586 * to be formatted into date interval stirng
3587 * @param appendTo Output parameter to receive result.
3588 * Result is appended to existing contents.
3589 * @param pos On input: an alignment field, if desired.
3590 * On output: the offsets of the alignment field.
3591 * @exception IllegalArgumentException when there is non-recognized
3593 * @return Reference to 'appendTo' parameter.
3595 * @deprecated This API is ICU internal only.
3597 public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
3598 Calendar toCalendar,
3599 StringBuffer appendTo,
3601 throws IllegalArgumentException
3603 // not support different calendar types and time zones
3604 if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
3605 throw new IllegalArgumentException("can not format on two different calendars");
3608 Object[] items = getPatternItems();
3612 /* look for different formatting string range */
3613 // look for start of difference
3615 for (int i = 0; i < items.length; i++) {
3616 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
3622 if ( diffBegin == -1 ) {
3623 // no difference, single date format
3624 return format(fromCalendar, appendTo, pos);
3627 // look for end of difference
3628 for (int i = items.length-1; i >= diffBegin; i--) {
3629 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
3634 } catch ( IllegalArgumentException e ) {
3635 throw new IllegalArgumentException(e.toString());
3638 // full range is different
3639 if ( diffBegin == 0 && diffEnd == items.length-1 ) {
3640 format(fromCalendar, appendTo, pos);
3641 appendTo.append(" \u2013 "); // default separator
3642 format(toCalendar, appendTo, pos);
3647 /* search for largest calendar field within the different range */
3648 int highestLevel = 1000;
3649 for (int i = diffBegin; i <= diffEnd; i++) {
3650 if ( items[i] instanceof String) {
3653 PatternItem item = (PatternItem)items[i];
3654 char ch = item.type;
3655 int patternCharIndex = -1;
3656 if ('A' <= ch && ch <= 'z') {
3657 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
3660 if (patternCharIndex == -1) {
3661 throw new IllegalArgumentException("Illegal pattern character " +
3662 "'" + ch + "' in \"" +
3666 if ( patternCharIndex < highestLevel ) {
3667 highestLevel = patternCharIndex;
3671 /* re-calculate diff range, including those calendar field which
3672 is in lower level than the largest calendar field covered
3673 in diff range calculated. */
3675 for (int i = 0; i < diffBegin; i++) {
3676 if ( lowerLevel(items, i, highestLevel) ) {
3683 for (int i = items.length-1; i > diffEnd; i--) {
3684 if ( lowerLevel(items, i, highestLevel) ) {
3689 } catch ( IllegalArgumentException e ) {
3690 throw new IllegalArgumentException(e.toString());
3694 // full range is different
3695 if ( diffBegin == 0 && diffEnd == items.length-1 ) {
3696 format(fromCalendar, appendTo, pos);
3697 appendTo.append(" \u2013 "); // default separator
3698 format(toCalendar, appendTo, pos);
3705 pos.setBeginIndex(0);
3708 // formatting date 1
3709 for (int i = 0; i <= diffEnd; i++) {
3710 if (items[i] instanceof String) {
3711 appendTo.append((String)items[i]);
3713 PatternItem item = (PatternItem)items[i];
3714 if (useFastFormat) {
3715 subFormat(appendTo, item.type, item.length, appendTo.length(),
3716 i, capitalizationSetting, pos, fromCalendar);
3718 appendTo.append(subFormat(item.type, item.length, appendTo.length(),
3719 i, capitalizationSetting, pos, fromCalendar));
3724 appendTo.append(" \u2013 "); // default separator
3726 // formatting date 2
3727 for (int i = diffBegin; i < items.length; i++) {
3728 if (items[i] instanceof String) {
3729 appendTo.append((String)items[i]);
3731 PatternItem item = (PatternItem)items[i];
3732 if (useFastFormat) {
3733 subFormat(appendTo, item.type, item.length, appendTo.length(),
3734 i, capitalizationSetting, pos, toCalendar);
3736 appendTo.append(subFormat(item.type, item.length, appendTo.length(),
3737 i, capitalizationSetting, pos, toCalendar));
3746 * check whether the i-th item in 2 calendar is in different value.
3748 * It is supposed to be used only by CLDR survey tool.
3749 * It is used by intervalFormatByAlgorithm().
3751 * @param fromCalendar one calendar
3752 * @param toCalendar the other calendar
3753 * @param items pattern items
3754 * @param i the i-th item in pattern items
3755 * @exception IllegalArgumentException when there is non-recognized
3757 * @return true is i-th item in 2 calendar is in different
3758 * value, false otherwise.
3760 private boolean diffCalFieldValue(Calendar fromCalendar,
3761 Calendar toCalendar,
3763 int i) throws IllegalArgumentException {
3764 if ( items[i] instanceof String) {
3767 PatternItem item = (PatternItem)items[i];
3768 char ch = item.type;
3769 int patternCharIndex = -1;
3770 if ('A' <= ch && ch <= 'z') {
3771 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
3774 if (patternCharIndex == -1) {
3775 throw new IllegalArgumentException("Illegal pattern character " +
3776 "'" + ch + "' in \"" +
3780 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
3781 int value = fromCalendar.get(field);
3782 int value_2 = toCalendar.get(field);
3783 if ( value != value_2 ) {
3791 * check whether the i-th item's level is lower than the input 'level'
3793 * It is supposed to be used only by CLDR survey tool.
3794 * It is used by intervalFormatByAlgorithm().
3796 * @param items the pattern items
3797 * @param i the i-th item in pattern items
3798 * @param level the level with which the i-th pattern item compared to
3799 * @exception IllegalArgumentException when there is non-recognized
3801 * @return true if i-th pattern item is lower than 'level',
3804 private boolean lowerLevel(Object[] items, int i, int level)
3805 throws IllegalArgumentException {
3806 if ( items[i] instanceof String) {
3809 PatternItem item = (PatternItem)items[i];
3810 char ch = item.type;
3811 int patternCharIndex = -1;
3812 if ('A' <= ch && ch <= 'z') {
3813 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
3816 if (patternCharIndex == -1) {
3817 throw new IllegalArgumentException("Illegal pattern character " +
3818 "'" + ch + "' in \"" +
3822 if ( patternCharIndex >= level ) {
3830 * @deprecated This API is ICU internal only.
3832 protected NumberFormat getNumberFormat(char ch) {
3835 ovrField = Character.valueOf(ch);
3836 if (overrideMap != null && overrideMap.containsKey(ovrField)) {
3837 String nsName = overrideMap.get(ovrField).toString();
3838 NumberFormat nf = numberFormatters.get(nsName);
3841 return numberFormat;
3845 private void initNumberFormatters(ULocale loc) {
3847 numberFormatters = new HashMap<String, NumberFormat>();
3848 overrideMap = new HashMap<Character, String>();
3849 processOverrideString(loc,override);
3853 private void processOverrideString(ULocale loc, String str) {
3855 if ( str == null || str.length() == 0 )
3862 boolean moreToProcess = true;
3863 boolean fullOverride;
3865 while (moreToProcess) {
3866 int delimiterPosition = str.indexOf(";",start);
3867 if (delimiterPosition == -1) {
3868 moreToProcess = false;
3871 end = delimiterPosition;
3874 String currentString = str.substring(start,end);
3875 int equalSignPosition = currentString.indexOf("=");
3876 if (equalSignPosition == -1) { // Simple override string such as "hebrew"
3877 nsName = currentString;
3878 fullOverride = true;
3879 } else { // Field specific override string such as "y=hebrew"
3880 nsName = currentString.substring(equalSignPosition+1);
3881 ovrField = Character.valueOf(currentString.charAt(0));
3882 overrideMap.put(ovrField,nsName);
3883 fullOverride = false;
3886 ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
3887 NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
3888 nf.setGroupingUsed(false);
3891 setNumberFormat(nf);
3893 // Since one or more of the override number formatters might be complex,
3894 // we can't rely on the fast numfmt where we have a partial field override.
3895 useLocalZeroPaddingNumberFormat = false;
3898 if (!numberFormatters.containsKey(nsName)) {
3899 numberFormatters.put(nsName,nf);
3902 start = delimiterPosition + 1;