/*
*******************************************************************************
- * Copyright (C) 1996-2011, International Business Machines Corporation and *
+ * Copyright (C) 1996-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
* For more information on using these methods, see
* {@link DateFormat}.
*
- * <p>
- * <strong>Time Format Syntax:</strong>
- * <p>
- * To specify the time format use a <em>time pattern</em> string.
- * In this pattern, all ASCII letters are reserved as pattern letters,
- * which are defined as the following:
+ * <p><strong>Date and Time Patterns:</strong></p>
+ *
+ * <p>Date and time formats are specified by <em>date and time pattern</em> strings.
+ * Within date and time pattern strings, all unquoted ASCII letters [A-Za-z] are reserved
+ * as pattern letters representing calendar fields. <code>SimpleDateFormat</code> supports
+ * the date and time formatting algorithm and pattern letters defined by <a href="http://www.unicode.org/reports/tr35/">UTS#35
+ * Unicode Locale Data Markup Language (LDML)</a>. The following pattern letters are
+ * currently available:</p>
* <blockquote>
- * <pre>
- * Symbol Meaning Presentation Example
- * ------ ------- ------------ -------
- * G era designator (Text) AD
- * y† year (Number) 1996
- * Y* year (week of year) (Number) 1997
- * u* extended year (Number) 4601
- * M month in year (Text & Number) July & 07
- * d day in month (Number) 10
- * h hour in am/pm (1~12) (Number) 12
- * H hour in day (0~23) (Number) 0
- * m minute in hour (Number) 30
- * s second in minute (Number) 55
- * S fractional second (Number) 978
- * E day of week (Text) Tuesday
- * e* day of week (local 1~7) (Text & Number) Tuesday & 2
- * D day in year (Number) 189
- * F day of week in month (Number) 2 (2nd Wed in July)
- * w week in year (Number) 27
- * W week in month (Number) 2
- * a am/pm marker (Text) PM
- * k hour in day (1~24) (Number) 24
- * K hour in am/pm (0~11) (Number) 0
- * z time zone (Text) Pacific Standard Time
- * Z time zone (RFC 822) (Number) -0800
- * v time zone (generic) (Text) Pacific Time
- * V time zone (location) (Text) United States (Los Angeles)
- * g* Julian day (Number) 2451334
- * A* milliseconds in day (Number) 69540000
- * Q* quarter in year (Text & Number) Q1 & 01
- * c* stand alone day of week (Text & Number) Tuesday & 2
- * L* stand alone month (Text & Number) July & 07
- * q* stand alone quarter (Text & Number) Q1 & 01
- * ' escape for text (Delimiter) 'Date='
- * '' single quote (Literal) 'o''clock'
- * </pre>
+ * <table border="1">
+ * <tr>
+ * <th>Field</th>
+ * <th style="text-align: center">Sym.</th>
+ * <th style="text-align: center">No.</th>
+ * <th>Example</th>
+ * <th>Description</th>
+ * </tr>
+ * <tr>
+ * <th rowspan="3">era</th>
+ * <td style="text-align: center" rowspan="3">G</td>
+ * <td style="text-align: center">1..3</td>
+ * <td>AD</td>
+ * <td rowspan="3">Era - Replaced with the Era string for the current date. One to three letters for the
+ * abbreviated form, four letters for the long form, five for the narrow form.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>Anno Domini</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>A</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="6">year</th>
+ * <td style="text-align: center">y</td>
+ * <td style="text-align: center">1..n</td>
+ * <td>1996</td>
+ * <td>Year. Normally the length specifies the padding, but for two letters it also specifies the maximum
+ * length. Example:<div align="center">
+ * <center>
+ * <table border="1" cellpadding="2" cellspacing="0">
+ * <tr>
+ * <th>Year</th>
+ * <th style="text-align: right">y</th>
+ * <th style="text-align: right">yy</th>
+ * <th style="text-align: right">yyy</th>
+ * <th style="text-align: right">yyyy</th>
+ * <th style="text-align: right">yyyyy</th>
+ * </tr>
+ * <tr>
+ * <td>AD 1</td>
+ * <td style="text-align: right">1</td>
+ * <td style="text-align: right">01</td>
+ * <td style="text-align: right">001</td>
+ * <td style="text-align: right">0001</td>
+ * <td style="text-align: right">00001</td>
+ * </tr>
+ * <tr>
+ * <td>AD 12</td>
+ * <td style="text-align: right">12</td>
+ * <td style="text-align: right">12</td>
+ * <td style="text-align: right">012</td>
+ * <td style="text-align: right">0012</td>
+ * <td style="text-align: right">00012</td>
+ * </tr>
+ * <tr>
+ * <td>AD 123</td>
+ * <td style="text-align: right">123</td>
+ * <td style="text-align: right">23</td>
+ * <td style="text-align: right">123</td>
+ * <td style="text-align: right">0123</td>
+ * <td style="text-align: right">00123</td>
+ * </tr>
+ * <tr>
+ * <td>AD 1234</td>
+ * <td style="text-align: right">1234</td>
+ * <td style="text-align: right">34</td>
+ * <td style="text-align: right">1234</td>
+ * <td style="text-align: right">1234</td>
+ * <td style="text-align: right">01234</td>
+ * </tr>
+ * <tr>
+ * <td>AD 12345</td>
+ * <td style="text-align: right">12345</td>
+ * <td style="text-align: right">45</td>
+ * <td style="text-align: right">12345</td>
+ * <td style="text-align: right">12345</td>
+ * <td style="text-align: right">12345</td>
+ * </tr>
+ * </table>
+ * </center></div>
+ * </td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">Y</td>
+ * <td style="text-align: center">1..n</td>
+ * <td>1997</td>
+ * <td>Year (in "Week of Year" based calendars). Normally the length specifies the padding,
+ * but for two letters it also specifies the maximum length. This year designation is used in ISO
+ * year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems
+ * where week date processing is desired. May not always be the same value as calendar year.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">u</td>
+ * <td style="text-align: center">1..n</td>
+ * <td>4601</td>
+ * <td>Extended year. This is a single number designating the year of this calendar system, encompassing
+ * all supra-year fields. For example, for the Julian calendar system, year numbers are positive, with an
+ * era of BCE or CE. An extended year value for the Julian calendar system assigns positive values to CE
+ * years and negative values to BCE years, with 1 BCE being year 0.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center" rowspan="3">U</td>
+ * <td style="text-align: center">1..3</td>
+ * <td>甲子</td>
+ * <td rowspan="3">Cyclic year name. Calendars such as the Chinese lunar calendar (and related calendars)
+ * and the Hindu calendars use 60-year cycles of year names. Use one through three letters for the abbreviated
+ * name, four for the full name, or five for the narrow name (currently the data only provides abbreviated names,
+ * which will be used for all requested name widths). If the calendar does not provide cyclic year name data,
+ * or if the year value to be formatted is out of the range of years for which cyclic name data is provided,
+ * then numeric formatting is used (behaves like 'y').</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>(currently also 甲子)</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>(currently also 甲子)</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="6">quarter</th>
+ * <td rowspan="3" style="text-align: center">Q</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>02</td>
+ * <td rowspan="3">Quarter - Use one or two for the numerical quarter, three for the abbreviation, or four
+ * for the full name.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>Q2</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>2nd quarter</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="3" style="text-align: center">q</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>02</td>
+ * <td rowspan="3"><b>Stand-Alone</b> Quarter - Use one or two for the numerical quarter, three for the abbreviation,
+ * or four for the full name.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>Q2</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>2nd quarter</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="8">month</th>
+ * <td rowspan="4" style="text-align: center">M</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>09</td>
+ * <td rowspan="4">Month - Use one or two for the numerical month, three for the abbreviation, four for
+ * the full name, or five for the narrow name.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>Sept</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>September</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>S</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="4" style="text-align: center">L</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>09</td>
+ * <td rowspan="4"><b>Stand-Alone</b> Month - Use one or two for the numerical month, three for the abbreviation,
+ * or four for the full name, or 5 for the narrow name.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>Sept</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>September</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>S</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="2">week</th>
+ * <td style="text-align: center">w</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>27</td>
+ * <td>Week of Year.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">W</td>
+ * <td style="text-align: center">1</td>
+ * <td>3</td>
+ * <td>Week of Month</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="4">day</th>
+ * <td style="text-align: center">d</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>1</td>
+ * <td>Date - Day of the month</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">D</td>
+ * <td style="text-align: center">1..3</td>
+ * <td>345</td>
+ * <td>Day of year</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">F</td>
+ * <td style="text-align: center">1</td>
+ * <td>2</td>
+ * <td>Day of Week in Month. The example is for the 2nd Wed in July</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">g</td>
+ * <td style="text-align: center">1..n</td>
+ * <td>2451334</td>
+ * <td>Modified Julian day. This is different from the conventional Julian day number in two regards.
+ * First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number;
+ * that is, it depends on the local time zone. It can be thought of as a single number that encompasses
+ * all the date-related fields.</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="14">week<br>
+ * day</th>
+ * <td rowspan="4" style="text-align: center">E</td>
+ * <td style="text-align: center">1..3</td>
+ * <td>Tues</td>
+ * <td rowspan="4">Day of week - Use one through three letters for the short day, or four for the full name,
+ * five for the narrow name, or six for the short name.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>Tuesday</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>T</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">6</td>
+ * <td>Tu</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="5" style="text-align: center">e</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>2</td>
+ * <td rowspan="5">Local day of week. Same as E except adds a numeric value that will depend on the local
+ * starting day of the week, using one or two letters. For this example, Monday is the first day of the week.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>Tues</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>Tuesday</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>T</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">6</td>
+ * <td>Tu</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="5" style="text-align: center">c</td>
+ * <td style="text-align: center">1</td>
+ * <td>2</td>
+ * <td rowspan="5"><b>Stand-Alone</b> local day of week - Use one letter for the local numeric value (same
+ * as 'e'), three for the short day, four for the full name, five for the narrow name, or six for
+ * the short name.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>Tues</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>Tuesday</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>T</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">6</td>
+ * <td>Tu</td>
+ * </tr>
+ * <tr>
+ * <th>period</th>
+ * <td style="text-align: center">a</td>
+ * <td style="text-align: center">1</td>
+ * <td>AM</td>
+ * <td>AM or PM</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="4">hour</th>
+ * <td style="text-align: center">h</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>11</td>
+ * <td>Hour [1-12]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern
+ * generation, it should match the 12-hour-cycle format preferred by the locale (h or K); it should not match
+ * a 24-hour-cycle format (H or k). Use hh for zero padding.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">H</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>13</td>
+ * <td>Hour [0-23]. When used in skeleton data or in a skeleton passed in an API for flexible data pattern
+ * generation, it should match the 24-hour-cycle format preferred by the locale (H or k); it should not match a
+ * 12-hour-cycle format (h or K). Use HH for zero padding.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">K</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>0</td>
+ * <td>Hour [0-11]. When used in a skeleton, only matches K or h, see above. Use KK for zero padding.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">k</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>24</td>
+ * <td>Hour [1-24]. When used in a skeleton, only matches k or H, see above. Use kk for zero padding.</td>
+ * </tr>
+ * <tr>
+ * <th>minute</th>
+ * <td style="text-align: center">m</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>59</td>
+ * <td>Minute. Use one or two for zero padding.</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="3">second</th>
+ * <td style="text-align: center">s</td>
+ * <td style="text-align: center">1..2</td>
+ * <td>12</td>
+ * <td>Second. Use one or two for zero padding.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">S</td>
+ * <td style="text-align: center">1..n</td>
+ * <td>3456</td>
+ * <td>Fractional Second - truncates (like other time fields) to the count of letters.
+ * (example shows display using pattern SSSS for seconds value 12.34567)</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">A</td>
+ * <td style="text-align: center">1..n</td>
+ * <td>69540000</td>
+ * <td>Milliseconds in day. This field behaves <i>exactly</i> like a composite of all time-related fields,
+ * not including the zone fields. As such, it also reflects discontinuities of those fields on DST transition
+ * days. On a day of DST onset, it will jump forward. On a day of DST cessation, it will jump backward. This
+ * reflects the fact that is must be combined with the offset field to obtain a unique local time value.</td>
+ * </tr>
+ * <tr>
+ * <th rowspan="23">zone</th>
+ * <td rowspan="2" style="text-align: center">z</td>
+ * <td style="text-align: center">1..3</td>
+ * <td>PDT</td>
+ * <td>The <i>short specific non-location format</i>.
+ * Where that is unavailable, falls back to the <i>short localized GMT format</i> ("O").</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>Pacific Daylight Time</td>
+ * <td>The <i>long specific non-location format</i>.
+ * Where that is unavailable, falls back to the <i>long localized GMT format</i> ("OOOO").</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="3" style="text-align: center">Z</td>
+ * <td style="text-align: center">1..3</td>
+ * <td>-0800</td>
+ * <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
+ * The format is equivalent to RFC 822 zone format (when optional seconds field is absent).
+ * This is equivalent to the "xxxx" specifier.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>GMT-8:00</td>
+ * <td>The <i>long localized GMT format</i>.
+ * This is equivalent to the "OOOO" specifier.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>-08:00<br>
+ * -07:52:58</td>
+ * <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
+ * The ISO8601 UTC indicator "Z" is used when local time offset is 0.
+ * This is equivalent to the "XXXXX" specifier.</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="2" style="text-align: center">O</td>
+ * <td style="text-align: center">1</td>
+ * <td>GMT-8</td>
+ * <td>The <i>short localized GMT format</i>.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>GMT-08:00</td>
+ * <td>The <i>long localized GMT format</i>.</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="2" style="text-align: center">v</td>
+ * <td style="text-align: center">1</td>
+ * <td>PT</td>
+ * <td>The <i>short generic non-location format</i>.
+ * Where that is unavailable, falls back to the <i>generic location format</i> ("VVVV"),
+ * then the <i>short localized GMT format</i> as the final fallback.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>Pacific Time</td>
+ * <td>The <i>long generic non-location format</i>.
+ * Where that is unavailable, falls back to <i>generic location format</i> ("VVVV").
+ * </tr>
+ * <tr>
+ * <td rowspan="4" style="text-align: center">V</td>
+ * <td style="text-align: center">1</td>
+ * <td>uslax</td>
+ * <td>The short time zone ID.
+ * Where that is unavailable, the special short time zone ID <i>unk</i> (Unknown Zone) is used.<br>
+ * <i><b>Note</b>: This specifier was originally used for a variant of the short specific non-location format,
+ * but it was deprecated in the later version of the LDML specification. In CLDR 23/ICU 51, the definition of
+ * the specifier was changed to designate a short time zone ID.</i></td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">2</td>
+ * <td>America/Los_Angeles</td>
+ * <td>The long time zone ID.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>Los Angeles</td>
+ * <td>The exemplar city (location) for the time zone.
+ * Where that is unavailable, the localized exemplar city name for the special zone <i>Etc/Unknown</i> is used
+ * as the fallback (for example, "Unknown City"). </td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>Los Angeles Time</td>
+ * <td>The <i>generic location format</i>.
+ * Where that is unavailable, falls back to the <i>long localized GMT format</i> ("OOOO";
+ * Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.)<br>
+ * This is especially useful when presenting possible timezone choices for user selection,
+ * since the naming is more uniform than the "v" format.</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="5" style="text-align: center">X</td>
+ * <td style="text-align: center">1</td>
+ * <td>-08<br>
+ * +0530<br>
+ * Z</td>
+ * <td>The <i>ISO8601 basic format</i> with hours field and optional minutes field.
+ * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">2</td>
+ * <td>-0800<br>
+ * Z</td>
+ * <td>The <i>ISO8601 basic format</i> with hours and minutes fields.
+ * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>-08:00<br>
+ * Z</td>
+ * <td>The <i>ISO8601 extended format</i> with hours and minutes fields.
+ * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>-0800<br>
+ * -075258<br>
+ * Z</td>
+ * <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
+ * (Note: The seconds field is not supported by the ISO8601 specification.)
+ * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>-08:00<br>
+ * -07:52:58<br>
+ * Z</td>
+ * <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
+ * (Note: The seconds field is not supported by the ISO8601 specification.)
+ * The ISO8601 UTC indicator "Z" is used when local time offset is 0.</td>
+ * </tr>
+ * <tr>
+ * <td rowspan="5" style="text-align: center">x</td>
+ * <td style="text-align: center">1</td>
+ * <td>-08<br>
+ * +0530</td>
+ * <td>The <i>ISO8601 basic format</i> with hours field and optional minutes field.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">2</td>
+ * <td>-0800</td>
+ * <td>The <i>ISO8601 basic format</i> with hours and minutes fields.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">3</td>
+ * <td>-08:00</td>
+ * <td>The <i>ISO8601 extended format</i> with hours and minutes fields.</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">4</td>
+ * <td>-0800<br>
+ * -075258</td>
+ * <td>The <i>ISO8601 basic format</i> with hours, minutes and optional seconds fields.
+ * (Note: The seconds field is not supported by the ISO8601 specification.)</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center">5</td>
+ * <td>-08:00<br>
+ * -07:52:58</td>
+ * <td>The <i>ISO8601 extended format</i> with hours, minutes and optional seconds fields.
+ * (Note: The seconds field is not supported by the ISO8601 specification.)</td>
+ * </tr>
+ * </table>
+ *
* </blockquote>
- * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
- * <tt><b>†</b></tt> ICU interprets a single 'y' differently than Java.</p>
- * <p>
- * The count of pattern letters determine the format.
- * <p>
- * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
- * < 4--use short or abbreviated form if one exists.
- * <p>
- * <strong>(Number)</strong>: the minimum number of digits. Shorter
- * numbers are zero-padded to this amount. Year is handled specially;
- * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
- * (e.g., if "yyyy" produces "1997", "yy" produces "97".)
- * Unlike other fields, fractional seconds are padded on the right with zero.
- * <p>
- * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
* <p>
* Any characters in the pattern that are not in the ranges of ['a'..'z']
* and ['A'..'Z'] will be treated as quoted text. For instance, characters
* @see DateFormat
* @see DateFormatSymbols
* @see DecimalFormat
+ * @see TimeZoneFormat
* @author Mark Davis, Chen-Lieh Huang, Alan Liu
* @stable ICU 2.0
*/
// the internal serial version which says which version was written
// - 0 (default) for version up to JDK 1.1.3
// - 1 for version from JDK 1.1.4, which includes a new field
- static final int currentSerialVersion = 1;
+ // - 2 we write additional int for capitalizationContext
+ static final int currentSerialVersion = 2;
static boolean DelayedHebrewMonthCheck = false;
*/
private static final int[] PATTERN_CHAR_TO_LEVEL =
{
- // A B C D E F G H I J K L M N O
- -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, -1,
- // P Q R S T U V W X Y Z
- -1, 20, -1, 80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,
- // a b c d e f g h i j k l m n o
- -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,
- // p q r s t u v w x y z
- -1, 20, -1, 70, -1, 10, 0, 20, -1, 10, 0, -1, -1, -1, -1, -1
+ // A B C D E F G H I J K L M N O
+ -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, 0,
+ // P Q R S T U V W X Y Z
+ -1, 20, -1, 80, -1, 10, 0, 30, 0, 10, 0, -1, -1, -1, -1, -1,
+ // a b c d e f g h i j k l m n o
+ -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,
+ // p q r s t u v w x y z
+ -1, 20, -1, 70, -1, 10, 0, 20, 0, 10, 0, -1, -1, -1, -1, -1
};
+ // When calendar uses hebr numbering (i.e. he@calendar=hebrew),
+ // offset the years within the current millenium down to 1-999
+ private static final int HEBREW_CAL_CUR_MILLENIUM_START_YEAR = 5000;
+ private static final int HEBREW_CAL_CUR_MILLENIUM_END_YEAR = 6000;
/**
* The version of the serialized data on the stream. Possible values:
* has no <code>defaultCenturyStart</code> on stream.
* <li><b>1</b> JDK 1.1.4 or later. This version adds
* <code>defaultCenturyStart</code>.
+ * <li><b>2</b> This version writes an additional int for
+ * <code>capitalizationContext</code>.
* </ul>
* When streaming out this class, the most recent format
* and the highest allowable <code>serialVersionOnStream</code>
// We need to preserve time zone type when parsing specific
// time zone text (xxx Standard Time vs xxx Daylight Time)
- private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;
- private transient int tztype = TZTYPE_UNK;
+ private transient TimeType tztype = TimeType.UNKNOWN;
private static final int millisPerHour = 60 * 60 * 1000;
*/
private volatile TimeZoneFormat tzFormat;
+ /*
+ * Capitalization setting, introduced in ICU 50
+ * Special serialization, see writeObject & readObject below
+ */
+ private transient DisplayContext capitalizationSetting;
+
+ /*
+ * Old defaultCapitalizationContext field
+ * from ICU 49.1:
+ */
+ //private ContextValue defaultCapitalizationContext;
+ /**
+ * Old ContextValue enum, preserved only to avoid
+ * deserialization errs from ICU 49.1.
+ */
+ @SuppressWarnings("unused")
+ private enum ContextValue {
+ UNKNOWN,
+ CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE,
+ CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE,
+ CAPITALIZATION_FOR_UI_LIST_OR_MENU,
+ CAPITALIZATION_FOR_STANDALONE
+ }
+
/**
* Constructs a SimpleDateFormat using the default pattern for the default <code>FORMAT</code>
* locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
}
/**
- * Creates an instance of SimpleDateForamt for the given format configuration
+ * Creates an instance of SimpleDateFormat for the given format configuration
* @param formatConfig the format configuration
* @return A SimpleDateFormat instance
* @internal
if (override != null) {
initNumberFormatters(locale);
}
+
+ capitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
}
calendar.setTimeZone(cal.getTimeZone());
cal = calendar;
}
- StringBuffer result = format(cal, toAppendTo, pos, null);
+ StringBuffer result = format(cal, capitalizationSetting, toAppendTo, pos, null);
if (backupTZ != null) {
// Restore the original time zone
calendar.setTimeZone(backupTZ);
// The actual method to format date. If List attributes is not null,
// then attribute information will be recorded.
- private StringBuffer format(Calendar cal, StringBuffer toAppendTo,
- FieldPosition pos, List<FieldPosition> attributes) {
+ private StringBuffer format(Calendar cal, DisplayContext capitalizationContext,
+ StringBuffer toAppendTo, FieldPosition pos, List<FieldPosition> attributes) {
// Initialize
pos.setBeginIndex(0);
pos.setEndIndex(0);
start = toAppendTo.length();
}
if (useFastFormat) {
- subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);
+ subFormat(toAppendTo, item.type, item.length, toAppendTo.length(),
+ i, capitalizationContext, pos, cal);
} else {
- toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos,
- formatData, cal));
+ toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(),
+ i, capitalizationContext, pos, cal));
}
if (attributes != null) {
// Check the sub format length
private static final int[] PATTERN_CHAR_TO_INDEX =
{
// A B C D E F G H I J K L M N O
- -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1,
+ -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, 31,
// P Q R S T U V W X Y Z
- -1, 27, -1, 8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,
+ -1, 27, -1, 8, -1, 30, 29, 13, 32, 18, 23, -1, -1, -1, -1, -1,
// a b c d e f g h i j k l m n o
-1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,
// p q r s t u v w x y z
- -1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1
+ -1, 28, -1, 7, -1, 20, 24, 12, 33, 1, 17, -1, -1, -1, -1, -1
};
// Map pattern character index to Calendar field number
/*L*/ Calendar.MONTH,
/*Qq*/ Calendar.MONTH, Calendar.MONTH,
/*V*/ Calendar.ZONE_OFFSET,
+ /*U*/ Calendar.YEAR,
+ /*O*/ Calendar.ZONE_OFFSET,
+ /*Xx*/ Calendar.ZONE_OFFSET, Calendar.ZONE_OFFSET,
};
// Map pattern character index to DateFormat field number
/*L*/ DateFormat.STANDALONE_MONTH_FIELD,
/*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
/*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD,
+ /*U*/ DateFormat.YEAR_NAME_FIELD,
+ /*O*/ DateFormat.TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD,
+ /*Xx*/ DateFormat.TIMEZONE_ISO_FIELD, DateFormat.TIMEZONE_ISO_LOCAL_FIELD,
};
// Map pattern character index to DateFormat.Field
/*L*/ DateFormat.Field.MONTH,
/*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
/*V*/ DateFormat.Field.TIME_ZONE,
+ /*U*/ DateFormat.Field.YEAR,
+ /*O*/ DateFormat.Field.TIME_ZONE,
+ /*Xx*/ DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE,
};
/**
throws IllegalArgumentException
{
// Note: formatData is ignored
+ return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal);
+ }
+
+ /**
+ * Formats a single field. This is the version called internally; it
+ * adds fieldNum and capitalizationContext parameters.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected String subFormat(char ch, int count, int beginOffset,
+ int fieldNum, DisplayContext capitalizationContext,
+ FieldPosition pos,
+ Calendar cal)
+ {
StringBuffer buf = new StringBuffer();
- subFormat(buf, ch, count, beginOffset, pos, cal);
+ subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal);
return buf.toString();
}
- /**
+ /**
* Formats a single field; useFastFormat variant. Reuses a
* StringBuffer for results instead of creating a String on the
* heap for each call.
@SuppressWarnings("fallthrough")
protected void subFormat(StringBuffer buf,
char ch, int count, int beginOffset,
+ int fieldNum, DisplayContext capitalizationContext,
FieldPosition pos,
Calendar cal) {
}
if (patternCharIndex == -1) {
- throw new IllegalArgumentException("Illegal pattern character " +
- "'" + ch + "' in \"" +
- pattern + '"');
+ if (ch == 'l') { // (SMALL LETTER L) deprecated placeholder for leap month marker, ignore
+ return;
+ } else {
+ throw new IllegalArgumentException("Illegal pattern character " +
+ "'" + ch + "' in \"" +
+ pattern + '"');
+ }
}
final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
int value = cal.get(field);
NumberFormat currentNumberFormat = getNumberFormat(ch);
+ DateFormatSymbols.CapitalizationContextUsage capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.OTHER;
switch (patternCharIndex) {
case 0: // 'G' - ERA
- if (count == 5) {
- safeAppend(formatData.narrowEras, value, buf);
- } else if (count == 4) {
- safeAppend(formatData.eraNames, value, buf);
+ if ( cal.getType().equals("chinese") || cal.getType().equals("dangi") ) {
+ // moved from ChineseDateFormat
+ zeroPaddingNumber(currentNumberFormat, buf, value, 1, 9);
} else {
- safeAppend(formatData.eras, value, buf);
+ if (count == 5) {
+ safeAppend(formatData.narrowEras, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_NARROW;
+ } else if (count == 4) {
+ safeAppend(formatData.eraNames, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_WIDE;
+ } else {
+ safeAppend(formatData.eras, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_ABBREV;
+ }
}
break;
+ case 30: // 'U' - YEAR_NAME_FIELD
+ if (formatData.shortYearNames != null && value <= formatData.shortYearNames.length) {
+ safeAppend(formatData.shortYearNames, value-1, buf);
+ break;
+ }
+ // else fall through to numeric year handling, do not break here
case 1: // 'y' - YEAR
+ case 18: // 'Y' - YEAR_WOY
+ if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) &&
+ value > HEBREW_CAL_CUR_MILLENIUM_START_YEAR && value < HEBREW_CAL_CUR_MILLENIUM_END_YEAR ) {
+ value -= HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
+ }
/* According to the specification, if the number of pattern letters ('y') is 2,
* the year is truncated to 2 digits; otherwise it is interpreted as a number.
* But the original code process 'y', 'yy', 'yyy' in the same way. and process
}
break;
case 2: // 'M' - MONTH
+ case 26: // 'L' - STANDALONE MONTH
if ( cal.getType().equals("hebrew")) {
boolean isLeap = HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR));
if (isLeap && value == 6 && count >= 3 ) {
value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7.
}
}
+ int isLeapMonth = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT)?
+ cal.get(Calendar.IS_LEAP_MONTH): 0;
+ // should consolidate the next section by using arrays of pointers & counts for the right symbols...
if (count == 5) {
- safeAppend(formatData.narrowMonths, value, buf);
+ if (patternCharIndex == 2) {
+ safeAppendWithMonthPattern(formatData.narrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_NARROW]: null);
+ } else {
+ safeAppendWithMonthPattern(formatData.standaloneNarrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW]: null);
+ }
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_NARROW;
} else if (count == 4) {
- safeAppend(formatData.months, value, buf);
+ if (patternCharIndex == 2) {
+ safeAppendWithMonthPattern(formatData.months, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
+ } else {
+ safeAppendWithMonthPattern(formatData.standaloneMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
+ }
} else if (count == 3) {
- safeAppend(formatData.shortMonths, value, buf);
+ if (patternCharIndex == 2) {
+ safeAppendWithMonthPattern(formatData.shortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT;
+ } else {
+ safeAppendWithMonthPattern(formatData.standaloneShortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE;
+ }
} else {
- zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
+ StringBuffer monthNumber = new StringBuffer();
+ zeroPaddingNumber(currentNumberFormat, monthNumber, value+1, count, maxIntCount);
+ String[] monthNumberStrings = new String[1];
+ monthNumberStrings[0] = monthNumber.toString();
+ safeAppendWithMonthPattern(monthNumberStrings, 0, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC]: null);
}
break;
case 4: // 'k' - HOUR_OF_DAY (1..24)
case 9: // 'E' - DAY_OF_WEEK
if (count == 5) {
safeAppend(formatData.narrowWeekdays, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
} else if (count == 4) {
safeAppend(formatData.weekdays, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
+ } else if (count == 6 && formatData.shorterWeekdays != null) {
+ safeAppend(formatData.shorterWeekdays, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
} else {// count <= 3, use abbreviated form if exists
safeAppend(formatData.shortWeekdays, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT;
}
break;
case 14: // 'a' - AM_PM
zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
}
break;
- case 17: // 'z' - ZONE_OFFSET
+
+ case 17: // 'z' - TIMEZONE_FIELD
if (count < 4) {
// "z", "zz", "zzz"
- result = tzFormat().format(Style.SPECIFIC_SHORT_COMMONLY_USED, tz, date);
+ result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
} else {
result = tzFormat().format(Style.SPECIFIC_LONG, tz, date);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
}
buf.append(result);
break;
- case 23: // 'Z' - TIMEZONE_RFC
- {
+ case 23: // 'Z' - TIMEZONE_RFC_FIELD
if (count < 4) {
- // RFC822 format
- result = tzFormat().format(Style.RFC822, tz, date);
+ // RFC822 format - equivalent to ISO 8601 local offset fixed width format
+ result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
+ } else if (count == 5) {
+ // ISO 8601 extended format
+ result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
} else {
// long form, localized GMT pattern
result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
}
buf.append(result);
break;
- }
- case 24: // 'v' - TIMEZONE_GENERIC
+ case 24: // 'v' - TIMEZONE_GENERIC_FIELD
if (count == 1) {
// "v"
result = tzFormat().format(Style.GENERIC_SHORT, tz, date);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT;
} else if (count == 4) {
// "vvvv"
result = tzFormat().format(Style.GENERIC_LONG, tz, date);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG;
+ }
+ buf.append(result);
+ break;
+ case 29: // 'V' - TIMEZONE_SPECIAL_FIELD
+ if (count == 1) {
+ // "V"
+ result = tzFormat().format(Style.ZONE_ID_SHORT, tz, date);
+ } else if (count == 2) {
+ // "VV"
+ result = tzFormat().format(Style.ZONE_ID, tz, date);
+ } else if (count == 3) {
+ // "VVV"
+ result = tzFormat().format(Style.EXEMPLAR_LOCATION, tz, date);
+ } else if (count == 4) {
+ // "VVVV"
+ result = tzFormat().format(Style.GENERIC_LOCATION, tz, date);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ZONE_LONG;
+ }
+ buf.append(result);
+ break;
+ case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
+ if (count == 1) {
+ // "O" - Short Localized GMT format
+ result = tzFormat().format(Style.LOCALIZED_GMT_SHORT, tz, date);
+ } else if (count == 4) {
+ // "OOOO" - Localized GMT format
+ result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
+ }
+ buf.append(result);
+ break;
+ case 32: // 'X' - TIMEZONE_ISO_FIELD
+ if (count == 1) {
+ // "X" - ISO Basic/Short
+ result = tzFormat().format(Style.ISO_BASIC_SHORT, tz, date);
+ } else if (count == 2) {
+ // "XX" - ISO Basic/Fixed
+ result = tzFormat().format(Style.ISO_BASIC_FIXED, tz, date);
+ } else if (count == 3) {
+ // "XXX" - ISO Extended/Fixed
+ result = tzFormat().format(Style.ISO_EXTENDED_FIXED, tz, date);
+ } else if (count == 4) {
+ // "XXXX" - ISO Basic/Optional second field
+ result = tzFormat().format(Style.ISO_BASIC_FULL, tz, date);
+ } else if (count == 5) {
+ // "XXXXX" - ISO Extended/Optional second field
+ result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date);
+ }
+ buf.append(result);
+ break;
+ case 33: // 'x' - TIMEZONE_ISO_LOCAL_FIELD
+ if (count == 1) {
+ // "x" - ISO Local Basic/Short
+ result = tzFormat().format(Style.ISO_BASIC_LOCAL_SHORT, tz, date);
+ } else if (count == 2) {
+ // "x" - ISO Local Basic/Fixed
+ result = tzFormat().format(Style.ISO_BASIC_LOCAL_FIXED, tz, date);
+ } else if (count == 3) {
+ // "xxx" - ISO Local Extended/Fixed
+ result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FIXED, tz, date);
+ } else if (count == 4) {
+ // "xxxx" - ISO Local Basic/Optional second field
+ result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date);
+ } else if (count == 5) {
+ // "xxxxx" - ISO Local Extended/Optional second field
+ result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FULL, tz, date);
}
buf.append(result);
break;
value = cal.get(Calendar.DAY_OF_WEEK);
if (count == 5) {
safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW;
} else if (count == 4) {
safeAppend(formatData.standaloneWeekdays, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
+ } else if (count == 6 && formatData.standaloneShorterWeekdays != null) {
+ safeAppend(formatData.standaloneShorterWeekdays, value, buf);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
} else { // count == 3
safeAppend(formatData.standaloneShortWeekdays, value, buf);
- }
- break;
- case 26: // 'L' - STANDALONE MONTH
- if (count == 5) {
- safeAppend(formatData.standaloneNarrowMonths, value, buf);
- } else if (count == 4) {
- safeAppend(formatData.standaloneMonths, value, buf);
- } else if (count == 3) {
- safeAppend(formatData.standaloneShortMonths, value, buf);
- } else {
- zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
+ capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE;
}
break;
case 27: // 'Q' - QUARTER
zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
}
break;
- case 29: // 'V' - TIMEZONE_SPECIAL
- if (count == 1) {
- // "V"
- result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date);
- } else if (count == 4) {
- // "VVVV"
- result = tzFormat().format(Style.GENERIC_LOCATION, tz, date);
- }
- buf.append(result);
- break;
default:
// case 3: // 'd' - DATE
// case 5: // 'H' - HOUR_OF_DAY (0..23)
// case 12: // 'w' - WEEK_OF_YEAR
// case 13: // 'W' - WEEK_OF_MONTH
// case 16: // 'K' - HOUR (0..11)
- // case 18: // 'Y' - YEAR_WOY
// case 20: // 'u' - EXTENDED_YEAR
// case 21: // 'g' - JULIAN_DAY
// case 22: // 'A' - MILLISECONDS_IN_DAY
break;
} // switch (patternCharIndex)
+ if (fieldNum == 0) {
+ boolean titlecase = false;
+ if (capitalizationContext != null) {
+ switch (capitalizationContext) {
+ case CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE:
+ titlecase = true;
+ break;
+ case CAPITALIZATION_FOR_UI_LIST_OR_MENU:
+ case CAPITALIZATION_FOR_STANDALONE:
+ if (formatData.capitalization != null) {
+ boolean[] transforms = formatData.capitalization.get(capContextUsageType);
+ titlecase = (capitalizationContext==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)?
+ transforms[0]: transforms[1];
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (titlecase) {
+ String firstField = buf.substring(bufstart); // bufstart or beginOffset, should be the same
+ String firstFieldTitleCase = UCharacter.toTitleCase(locale, firstField, null,
+ UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
+ buf.replace(bufstart, buf.length(), firstFieldTitleCase);
+ }
+ }
+
// Set the FieldPosition (for the first occurrence only)
if (pos.getBeginIndex() == pos.getEndIndex()) {
if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
}
}
+ private static void safeAppendWithMonthPattern(String[] array, int value, StringBuffer appendTo, String monthPattern) {
+ if (array != null && value >= 0 && value < array.length) {
+ if (monthPattern == null) {
+ appendTo.append(array[value]);
+ } else {
+ appendTo.append(MessageFormat.format(monthPattern, array[value]));
+ }
+ }
+ }
+
/*
* PatternItem store parsed date/time field pattern information.
*/
int start = pos;
// Reset tztype
- tztype = TZTYPE_UNK;
+ tztype = TimeType.UNKNOWN;
boolean[] ambiguousYear = { false };
// item index for the first numeric field within a contiguous numeric run
int numericFieldLength = 0;
// start index of numeric text run in the input text
int numericStartPos = 0;
+
+ MessageFormat numericLeapMonthFormatter = null;
+ if (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT) {
+ numericLeapMonthFormatter = new MessageFormat(formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC], locale);
+ }
Object[] items = getPatternItems();
int i = 0;
// Parse a numeric field
pos = subParse(text, pos, field.type, len,
- true, false, ambiguousYear, cal);
+ true, false, ambiguousYear, cal, numericLeapMonthFormatter);
if (pos < 0) {
// If the parse fails anywhere in the numeric run, back up to the
continue;
}
- } else {
+ } else if (field.type != 'l') { // (SMALL LETTER L) obsolete pattern char just gets ignored
// Handle a non-numeric field or a non-abutting numeric field
numericFieldStart = -1;
int s = pos;
pos = subParse(text, pos, field.type, field.length,
- false, true, ambiguousYear, cal);
+ false, true, ambiguousYear, cal, numericLeapMonthFormatter);
if (pos < 0) {
if (pos == ISOSpecialEra) {
if (i+1 < items.length) {
+ String patl = null;
+ // if it will cause a class cast exception to String, we can't use it
+ try {
+ patl = (String)items[i+1];
+ } catch(ClassCastException cce) {
+ parsePos.setIndex(start);
+ parsePos.setErrorIndex(s);
+ if (backupTZ != null) {
+ calendar.setTimeZone(backupTZ);
+ }
+ return;
+ }
+
// get next item in pattern
- String patl = (String)items[i+1];
+ if(patl == null)
+ patl = (String)items[i+1];
int plen = patl.length();
int idx=0;
} else {
// Handle literal pattern text literal
numericFieldStart = -1;
-
- String patl = (String)items[i];
- int plen = patl.length();
- int tlen = text.length();
- int idx = 0;
- while (idx < plen && pos < tlen) {
- char pch = patl.charAt(idx);
- char ich = text.charAt(pos);
- if (PatternProps.isWhiteSpace(pch)
- && PatternProps.isWhiteSpace(ich)) {
- // White space characters found in both patten and input.
- // Skip contiguous white spaces.
- while ((idx + 1) < plen &&
- PatternProps.isWhiteSpace(patl.charAt(idx + 1))) {
- ++idx;
- }
- while ((pos + 1) < tlen &&
- PatternProps.isWhiteSpace(text.charAt(pos + 1))) {
- ++pos;
- }
- } else if (pch != ich) {
- break;
- }
- ++idx;
- ++pos;
- }
- if (idx != plen) {
+ boolean[] complete = new boolean[1];
+ pos = matchLiteral(text, pos, items, i, complete);
+ if (!complete[0]) {
// Set the position of mismatch
parsePos.setIndex(start);
parsePos.setErrorIndex(pos);
}
++i;
}
+
+ // Special hack for trailing "." after non-numeric field.
+ if (pos < text.length()) {
+ char extra = text.charAt(pos);
+ if (extra == '.' && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && items.length != 0) {
+ // only do if the last field is not numeric
+ Object lastItem = items[items.length - 1];
+ if (lastItem instanceof PatternItem && !((PatternItem)lastItem).isNumeric) {
+ pos++; // skip the extra "."
+ }
+ }
+ }
// At this point the fields of Calendar have been set. Calendar
// will fill in default values for missing fields when the time
// front or the back of the default century. This only works because we adjust
// the year correctly to start with in other cases -- see subParse().
try {
- if (ambiguousYear[0] || tztype != TZTYPE_UNK) {
+ if (ambiguousYear[0] || tztype != TimeType.UNKNOWN) {
// We need a copy of the fields, and we need to avoid triggering a call to
// complete(), which will recalculate the fields. Since we can't access
// the fields[] array in Calendar, we clone the entire object. This will
cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
}
}
- if (tztype != TZTYPE_UNK) {
+ if (tztype != TimeType.UNKNOWN) {
copy = (Calendar)cal.clone();
TimeZone tz = copy.getTimeZone();
BasicTimeZone btz = null;
// matches the rule used by the parsed time zone.
int[] offsets = new int[2];
if (btz != null) {
- if (tztype == TZTYPE_STD) {
+ if (tztype == TimeType.STANDARD) {
btz.getOffsetFromLocal(localMillis,
BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
} else {
// but following code work in most case.
tz.getOffset(localMillis, true, offsets);
- if (tztype == TZTYPE_STD && offsets[1] != 0
- || tztype == TZTYPE_DST && offsets[1] == 0) {
+ if (tztype == TimeType.STANDARD && offsets[1] != 0
+ || tztype == TimeType.DAYLIGHT && offsets[1] == 0) {
// Roll back one day and try it again.
// Note: This code assumes 1. timezone transition only happens
// once within 24 hours at max
// Now, compare the results with parsed type, either standard or
// daylight saving time
int resolvedSavings = offsets[1];
- if (tztype == TZTYPE_STD) {
+ if (tztype == TimeType.STANDARD) {
if (offsets[1] != 0) {
// Override DST_OFFSET = 0 in the result calendar
resolvedSavings = 0;
}
}
+ /**
+ * Matches text (starting at pos) with patl. Returns the new pos, and sets complete[0]
+ * if it matched the entire text. Whitespace sequences are treated as singletons.
+ * <p>If isLenient and if we fail to match the first time, some special hacks are put into place.
+ * <ul><li>we are between date and time fields, then one or more whitespace characters
+ * in the text are accepted instead.</li>
+ * <ul><li>we are after a non-numeric field, and the text starts with a ".", we skip it.</li>
+ * </ul>
+ */
+ private int matchLiteral(String text, int pos, Object[] items, int itemIndex, boolean[] complete) {
+ int originalPos = pos;
+ String patternLiteral = (String)items[itemIndex];
+ int plen = patternLiteral.length();
+ int tlen = text.length();
+ int idx = 0;
+ while (idx < plen && pos < tlen) {
+ char pch = patternLiteral.charAt(idx);
+ char ich = text.charAt(pos);
+ if (PatternProps.isWhiteSpace(pch)
+ && PatternProps.isWhiteSpace(ich)) {
+ // White space characters found in both patten and input.
+ // Skip contiguous white spaces.
+ while ((idx + 1) < plen &&
+ PatternProps.isWhiteSpace(patternLiteral.charAt(idx + 1))) {
+ ++idx;
+ }
+ while ((pos + 1) < tlen &&
+ PatternProps.isWhiteSpace(text.charAt(pos + 1))) {
+ ++pos;
+ }
+ } else if (pch != ich) {
+ if (ich == '.' && pos == originalPos && 0 < itemIndex && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
+ Object before = items[itemIndex-1];
+ if (before instanceof PatternItem) {
+ boolean isNumeric = ((PatternItem) before).isNumeric;
+ if (!isNumeric) {
+ ++pos; // just update pos
+ continue;
+ }
+ }
+ } else if ((pch == ' ' || pch == '.') && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
+ ++idx;
+ continue;
+ }
+ break;
+ }
+ ++idx;
+ ++pos;
+ }
+ complete[0] = idx == plen;
+ if (complete[0] == false && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && 0 < itemIndex && itemIndex < items.length - 1) {
+ // If fully lenient, accept " "* for any text between a date and a time field
+ // We don't go more lenient, because we don't want to accept "12/31" for "12:31".
+ // People may be trying to parse for a date, then for a time.
+ if (originalPos < tlen) {
+ Object before = items[itemIndex-1];
+ Object after = items[itemIndex+1];
+ if (before instanceof PatternItem && after instanceof PatternItem) {
+ char beforeType = ((PatternItem) before).type;
+ char afterType = ((PatternItem) after).type;
+ if (DATE_PATTERN_TYPE.contains(beforeType) != DATE_PATTERN_TYPE.contains(afterType)) {
+ int newPos = originalPos;
+ while (true) {
+ char ich = text.charAt(newPos);
+ if (!PatternProps.isWhiteSpace(ich)) {
+ break;
+ }
+ ++newPos;
+ }
+ complete[0] = newPos > originalPos;
+ pos = newPos;
+ }
+ }
+ }
+ }
+ return pos;
+ }
+
+ static final UnicodeSet DATE_PATTERN_TYPE = new UnicodeSet("[GyYuUQqMLlwWd]").freeze();
+
/**
* Attempt to match the text at a given position against an array of
* strings. Since multiple strings in the array may match (for
* @param start where to start parsing.
* @param field the date field being parsed.
* @param data the string array to parsed.
+ * @param cal
* @return the new start position if matching succeeded; a negative
* number indicating matching failure, otherwise. As a side effect,
* sets the <code>cal</code> field <code>field</code> to the index
* @stable ICU 2.0
*/
protected int matchString(String text, int start, int field, String[] data, Calendar cal)
+ {
+ return matchString(text, start, field, data, null, cal);
+ }
+
+ /**
+ * Attempt to match the text at a given position against an array of
+ * strings. Since multiple strings in the array may match (for
+ * example, if the array contains "a", "ab", and "abc", all will match
+ * the input string "abcd") the longest match is returned. As a side
+ * effect, the given field of <code>cal</code> is set to the index
+ * of the best match, if there is one.
+ * @param text the time text being parsed.
+ * @param start where to start parsing.
+ * @param field the date field being parsed.
+ * @param data the string array to parsed.
+ * @param monthPattern leap month pattern, or null if none.
+ * @param cal
+ * @return the new start position if matching succeeded; a negative
+ * number indicating matching failure, otherwise. As a side effect,
+ * sets the <code>cal</code> field <code>field</code> to the index
+ * of the best match, if matching succeeded.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ private int matchString(String text, int start, int field, String[] data, String monthPattern, Calendar cal)
{
int i = 0;
int count = data.length;
// We keep track of the longest match, and return that. Note that this
// unfortunately requires us to test all array elements.
int bestMatchLength = 0, bestMatch = -1;
+ int isLeapMonth = 0;
+ int matchLength = 0;
+
for (; i<count; ++i)
{
int length = data[i].length();
// Always compare if we have no match yet; otherwise only compare
// against potentially better matches (longer strings).
if (length > bestMatchLength &&
- text.regionMatches(true, start, data[i], 0, length))
+ (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0)
{
bestMatch = i;
- bestMatchLength = length;
+ bestMatchLength = matchLength;
+ isLeapMonth = 0;
}
+ if (monthPattern != null) {
+ String leapMonthName = MessageFormat.format(monthPattern, data[i]);
+ length = leapMonthName.length();
+ if (length > bestMatchLength &&
+ (matchLength = regionMatchesWithOptionalDot(text, start, leapMonthName, length)) >= 0)
+ {
+ bestMatch = i;
+ bestMatchLength = matchLength;
+ isLeapMonth = 1;
+ }
+ }
}
if (bestMatch >= 0)
{
+ if (field == Calendar.YEAR) {
+ bestMatch++; // only get here for cyclic year names, which match 1-based years 1-60
+ }
cal.set(field, bestMatch);
+ if (monthPattern != null) {
+ cal.set(Calendar.IS_LEAP_MONTH, isLeapMonth);
+ }
return start + bestMatchLength;
}
- return -start;
+ return ~start;
+ }
+
+ private int regionMatchesWithOptionalDot(String text, int start, String data, int length) {
+ boolean matches = text.regionMatches(true, start, data, 0, length);
+ if (matches) {
+ return length;
+ }
+ if (data.length() > 0 && data.charAt(data.length()-1) == '.') {
+ if (text.regionMatches(true, start, data, 0, length-1)) {
+ return length - 1;
+ }
+ }
+ return -1;
}
/**
// We keep track of the longest match, and return that. Note that this
// unfortunately requires us to test all array elements.
int bestMatchLength = 0, bestMatch = -1;
+ int matchLength = 0;
for (; i<count; ++i) {
int length = data[i].length();
// Always compare if we have no match yet; otherwise only compare
// against potentially better matches (longer strings).
if (length > bestMatchLength &&
- text.regionMatches(true, start, data[i], 0, length)) {
+ (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) {
+
bestMatch = i;
- bestMatchLength = length;
+ bestMatchLength = matchLength;
}
}
* and we should use the count to know when to stop parsing.
* @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
* is true, then a two-digit year was parsed and may need to be readjusted.
+ * @param cal
* @return the new start position if matching succeeded; a negative
* number indicating matching failure, otherwise. As a side effect,
* set the appropriate field of <code>cal</code> with the parsed
protected int subParse(String text, int start, char ch, int count,
boolean obeyCount, boolean allowNegative,
boolean[] ambiguousYear, Calendar cal)
+ {
+ return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null);
+ }
+
+ /**
+ * Protected method that converts one field of the input string into a
+ * numeric field value in <code>cal</code>. Returns -start (for
+ * ParsePosition) if failed. Subclasses may override this method to
+ * modify or add parsing capabilities.
+ * @param text the time text to be parsed.
+ * @param start where to start parsing.
+ * @param ch the pattern character for the date field text to be parsed.
+ * @param count the count of a pattern character.
+ * @param obeyCount if true, then the next field directly abuts this one,
+ * and we should use the count to know when to stop parsing.
+ * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
+ * is true, then a two-digit year was parsed and may need to be readjusted.
+ * @param cal
+ * @param numericLeapMonthFormatter if non-null, used to parse numeric leap months.
+ * @return the new start position if matching succeeded; a negative
+ * number indicating matching failure, otherwise. As a side effect,
+ * set the appropriate field of <code>cal</code> with the parsed
+ * value.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ private int subParse(String text, int start, char ch, int count,
+ boolean obeyCount, boolean allowNegative,
+ boolean[] ambiguousYear, Calendar cal, MessageFormat numericLeapMonthFormatter)
{
Number number = null;
NumberFormat currentNumberFormat = null;
}
if (patternCharIndex == -1) {
- return -start;
+ return ~start;
}
currentNumberFormat = getNumberFormat(ch);
int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+
+ if (numericLeapMonthFormatter != null) {
+ numericLeapMonthFormatter.setFormatByArgumentIndex(0, currentNumberFormat);
+ }
+ boolean isChineseCalendar = ( cal.getType().equals("chinese") || cal.getType().equals("dangi") );
// If there are any spaces here, skip over them. If we hit the end
// of the string, then fail.
for (;;) {
if (start >= text.length()) {
- return -start;
+ return ~start;
}
int c = UTF16.charAt(text, start);
if (!UCharacter.isUWhiteSpace(c) || !PatternProps.isWhiteSpace(c)) {
// a number value. We handle further, more generic cases below. We need
// to handle some of them here because some fields require extra processing on
// the parsed value.
- if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
- patternCharIndex == 15 /*HOUR1_FIELD*/ ||
- (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
- patternCharIndex == 1 ||
- patternCharIndex == 8)
+ if (patternCharIndex == 4 /*'k' HOUR_OF_DAY1_FIELD*/ ||
+ patternCharIndex == 15 /*'h' HOUR1_FIELD*/ ||
+ (patternCharIndex == 2 /*'M' MONTH_FIELD*/ && count <= 2) ||
+ (patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ && count <= 2) ||
+ patternCharIndex == 1 /*'y' YEAR */ || patternCharIndex == 18 /*'Y' YEAR_WOY */ ||
+ patternCharIndex == 30 /*'U' YEAR_NAME_FIELD, falls back to numeric */ ||
+ (patternCharIndex == 0 /*'G' ERA */ && isChineseCalendar) ||
+ patternCharIndex == 8 /*'S' FRACTIONAL_SECOND */ )
{
// It would be good to unify this with the obeyCount logic below,
// but that's going to be difficult.
- if (obeyCount) {
- if ((start+count) > text.length()) return -start;
+
+ boolean parsedNumericLeapMonth = false;
+ if (numericLeapMonthFormatter != null && (patternCharIndex == 2 || patternCharIndex == 26)) {
+ // First see if we can parse month number with leap month pattern
+ Object[] args = numericLeapMonthFormatter.parse(text, pos);
+ if (args != null && pos.getIndex() > start && (args[0] instanceof Number)) {
+ parsedNumericLeapMonth = true;
+ number = (Number)args[0];
+ cal.set(Calendar.IS_LEAP_MONTH, 1);
+ } else {
+ pos.setIndex(start);
+ cal.set(Calendar.IS_LEAP_MONTH, 0);
+ }
+ }
+
+ if (!parsedNumericLeapMonth) {
+ if (obeyCount) {
+ if ((start+count) > text.length()) {
+ return ~start;
+ }
number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
- } else {
- number = parseInt(text, pos, allowNegative,currentNumberFormat);
+ } else {
+ number = parseInt(text, pos, allowNegative,currentNumberFormat);
+ }
+ if (number == null && patternCharIndex != 30) {
+ return ~start;
+ }
}
- if (number == null) {
- return -start;
+
+ if (number != null) {
+ value = number.intValue();
}
- value = number.intValue();
}
switch (patternCharIndex)
{
case 0: // 'G' - ERA
+ if ( isChineseCalendar ) {
+ // Numeric era handling moved from ChineseDateFormat,
+ // If we didn't have a number, already returned -start above
+ cal.set(Calendar.ERA, value);
+ return pos.getIndex();
+ }
int ps = 0;
if (count == 5) {
- ps = matchString(text, start, Calendar.ERA, formatData.narrowEras, cal);
+ ps = matchString(text, start, Calendar.ERA, formatData.narrowEras, null, cal);
} else if (count == 4) {
- ps = matchString(text, start, Calendar.ERA, formatData.eraNames, cal);
+ ps = matchString(text, start, Calendar.ERA, formatData.eraNames, null, cal);
} else {
- ps = matchString(text, start, Calendar.ERA, formatData.eras, cal);
+ ps = matchString(text, start, Calendar.ERA, formatData.eras, null, cal);
}
// check return position, if it equals -start, then matchString error
// special case the return code so we don't necessarily fail out until we
// verify no year information also
- if (ps == -start)
+ if (ps == ~start)
ps = ISOSpecialEra;
return ps;
case 1: // 'y' - YEAR
+ case 18: // 'Y' - YEAR_WOY
// If there are 3 or more YEAR pattern characters, this indicates
// that the year value is to be treated literally, without any
// two-digit year adjustments (e.g., from "01" to 2001). Otherwise
// century, for parsed strings from "00" to "99". Any other string
// is treated literally: "2250", "-1", "1", "002".
/* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
- if (count == 2 && (pos.getIndex() - start) == 2
+ /* Skip this for Chinese calendar, moved from ChineseDateFormat */
+ if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) && value < 1000 ) {
+ value += HEBREW_CAL_CUR_MILLENIUM_START_YEAR;
+ } else if (count == 2 && (pos.getIndex() - start) == 2 && !isChineseCalendar
&& UCharacter.isDigit(text.charAt(start))
&& UCharacter.isDigit(text.charAt(start+1)))
{
value += (getDefaultCenturyStartYear()/100)*100 +
(value < ambiguousTwoDigitYear ? 100 : 0);
}
- cal.set(Calendar.YEAR, value);
+ cal.set(field, value);
// Delayed checking for adjustment of Hebrew month numbers in non-leap years.
if (DelayedHebrewMonthCheck) {
DelayedHebrewMonthCheck = false;
}
return pos.getIndex();
+ case 30: // 'U' - YEAR_NAME_FIELD
+ if (formatData.shortYearNames != null) {
+ int newStart = matchString(text, start, Calendar.YEAR, formatData.shortYearNames, null, cal);
+ if (newStart > 0) {
+ return newStart;
+ }
+ }
+ if ( number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC) || formatData.shortYearNames == null || value > formatData.shortYearNames.length) ) {
+ cal.set(Calendar.YEAR, value);
+ return pos.getIndex();
+ }
+ return ~start;
case 2: // 'M' - MONTH
- if (count <= 2) { // i.e., M or MM.
+ case 26: // 'L' - STAND_ALONE_MONTH
+ if (count <= 2) { // i.e., M/MM, L/LL
// Don't want to parse the month if it is a string
- // while pattern uses numeric style: M or MM.
+ // while pattern uses numeric style: M/MM, L/LL.
// [We computed 'value' above.]
cal.set(Calendar.MONTH, value - 1);
// When parsing month numbers from the Hebrew Calendar, we might need
}
return pos.getIndex();
} else {
- // count >= 3 // i.e., MMM or MMMM
+ // count >= 3 // i.e., MMM/MMMM or LLL/LLLL
// Want to be able to parse both short and long forms.
+ boolean haveMonthPat = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT);
// Try count == 4 first:
- int newStart = matchString(text, start, Calendar.MONTH,
- formatData.months, cal);
+ int newStart = (patternCharIndex == 2)?
+ matchString(text, start, Calendar.MONTH, formatData.months,
+ (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null, cal):
+ matchString(text, start, Calendar.MONTH, formatData.standaloneMonths,
+ (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null, cal);
if (newStart > 0) {
return newStart;
} else { // count == 4 failed, now try count == 3
- return matchString(text, start, Calendar.MONTH,
- formatData.shortMonths, cal);
- }
- }
- case 26: // 'L' - STAND_ALONE_MONTH
- if (count <= 2) { // i.e., M or MM.
- // Don't want to parse the month if it is a string
- // while pattern uses numeric style: M or MM.
- // [We computed 'value' above.]
- cal.set(Calendar.MONTH, value - 1);
- return pos.getIndex();
- } else {
- // count >= 3 // i.e., MMM or MMMM
- // Want to be able to parse both short and long forms.
- // Try count == 4 first:
- int newStart = matchString(text, start, Calendar.MONTH,
- formatData.standaloneMonths, cal);
- if (newStart > 0) {
- return newStart;
- } else { // count == 4 failed, now try count == 3
- return matchString(text, start, Calendar.MONTH,
- formatData.standaloneShortMonths, cal);
+ return (patternCharIndex == 2)?
+ matchString(text, start, Calendar.MONTH, formatData.shortMonths,
+ (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null, cal):
+ matchString(text, start, Calendar.MONTH, formatData.standaloneShortMonths,
+ (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null, cal);
}
}
case 4: // 'k' - HOUR_OF_DAY (1..24)
a *= 10;
i--;
}
- value = (value + (a>>1)) / a;
+ value /= a;
}
cal.set(Calendar.MILLISECOND, value);
return pos.getIndex();
case 9: { // 'E' - DAY_OF_WEEK
- // Want to be able to parse both short and long forms.
- // Try count == 4 (EEEE) first:
- int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
- formatData.weekdays, cal);
+ // Want to be able to parse at least wide, abbrev, short forms.
+ int newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.weekdays, null, cal); // try EEEE wide
if (newStart > 0) {
return newStart;
- } else { // EEEE failed, now try EEE
- return matchString(text, start, Calendar.DAY_OF_WEEK,
- formatData.shortWeekdays, cal);
+ } else if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shortWeekdays, null, cal)) > 0) { // try EEE abbrev
+ return newStart;
+ } else if (formatData.shorterWeekdays != null) {
+ return matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shorterWeekdays, null, cal); // try EEEEEE short
}
+ return newStart;
}
case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
- // Want to be able to parse both short and long forms.
- // Try count == 4 (cccc) first:
- int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
- formatData.standaloneWeekdays, cal);
+ // Want to be able to parse at least wide, abbrev, short forms.
+ int newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneWeekdays, null, cal); // try cccc wide
if (newStart > 0) {
return newStart;
- } else { // cccc failed, now try ccc
- return matchString(text, start, Calendar.DAY_OF_WEEK,
- formatData.standaloneShortWeekdays, cal);
+ } else if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShortWeekdays, null, cal)) > 0) { // try ccc abbrev
+ return newStart;
+ } else if (formatData.standaloneShorterWeekdays != null) {
+ return matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShorterWeekdays, null, cal); // try cccccc short
}
+ return newStart;
}
case 14: // 'a' - AM_PM
- return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);
+ return matchString(text, start, Calendar.AM_PM, formatData.ampms, null, cal);
case 15: // 'h' - HOUR (1..12)
// [We computed 'value' above.]
if (value == cal.getLeastMaximum(Calendar.HOUR)+1) {
case 17: // 'z' - ZONE_OFFSET
{
Output<TimeType> tzTimeType = new Output<TimeType>();
- Style style = (count < 4) ? Style.SPECIFIC_SHORT_COMMONLY_USED : Style.SPECIFIC_LONG;
+ Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.SPECIFIC_LONG;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
- if (tzTimeType.value == TimeType.STANDARD) {
- tztype = TZTYPE_STD;
- } else if (tzTimeType.value == TimeType.DAYLIGHT) {
- tztype = TZTYPE_DST;
- }
+ tztype = tzTimeType.value;
cal.setTimeZone(tz);
return pos.getIndex();
}
- return -start;
- }
+ return ~start;
+ }
case 23: // 'Z' - TIMEZONE_RFC
{
Output<TimeType> tzTimeType = new Output<TimeType>();
- Style style = (count < 4) ? Style.RFC822 : Style.LOCALIZED_GMT;
+ Style style = (count < 4) ? Style.ISO_BASIC_LOCAL_FULL : ((count == 5) ? Style.ISO_EXTENDED_FULL : Style.LOCALIZED_GMT);
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
- if (tzTimeType.value == TimeType.STANDARD) {
- tztype = TZTYPE_STD;
- } else if (tzTimeType.value == TimeType.DAYLIGHT) {
- tztype = TZTYPE_DST;
- }
+ tztype = tzTimeType.value;
cal.setTimeZone(tz);
return pos.getIndex();
}
- return -start;
+ return ~start;
}
case 24: // 'v' - TIMEZONE_GENERIC
{
Style style = (count < 4) ? Style.GENERIC_SHORT : Style.GENERIC_LONG;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
- if (tzTimeType.value == TimeType.STANDARD) {
- tztype = TZTYPE_STD;
- } else if (tzTimeType.value == TimeType.DAYLIGHT) {
- tztype = TZTYPE_DST;
- }
+ tztype = tzTimeType.value;
cal.setTimeZone(tz);
return pos.getIndex();
}
- return -start;
+ return ~start;
}
case 29: // 'V' - TIMEZONE_SPECIAL
{
Output<TimeType> tzTimeType = new Output<TimeType>();
- // Note: 'v' only supports count 1 and 4
- Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.GENERIC_LOCATION;
+ Style style = null;
+ switch (count) {
+ case 1:
+ style = Style.ZONE_ID_SHORT;
+ break;
+ case 2:
+ style = Style.ZONE_ID;
+ break;
+ case 3:
+ style = Style.EXEMPLAR_LOCATION;
+ break;
+ default:
+ style = Style.GENERIC_LOCATION;
+ break;
+ }
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
- if (tzTimeType.value == TimeType.STANDARD) {
- tztype = TZTYPE_STD;
- } else if (tzTimeType.value == TimeType.DAYLIGHT) {
- tztype = TZTYPE_DST;
+ tztype = tzTimeType.value;
+ cal.setTimeZone(tz);
+ return pos.getIndex();
}
+ return ~start;
+ }
+ case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET
+ {
+ Output<TimeType> tzTimeType = new Output<TimeType>();
+ Style style = (count < 4) ? Style.LOCALIZED_GMT_SHORT : Style.LOCALIZED_GMT;
+ TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
+ if (tz != null) {
+ tztype = tzTimeType.value;
cal.setTimeZone(tz);
return pos.getIndex();
}
- return -start;
+ return ~start;
+ }
+ case 32: // 'X' - TIMEZONE_ISO
+ {
+ Output<TimeType> tzTimeType = new Output<TimeType>();
+ Style style;
+ switch (count) {
+ case 1:
+ style = Style.ISO_BASIC_SHORT;
+ break;
+ case 2:
+ style = Style.ISO_BASIC_FIXED;
+ break;
+ case 3:
+ style = Style.ISO_EXTENDED_FIXED;
+ break;
+ case 4:
+ style = Style.ISO_BASIC_FULL;
+ break;
+ default: // count >= 5
+ style = Style.ISO_EXTENDED_FULL;
+ break;
+ }
+ TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
+ if (tz != null) {
+ tztype = tzTimeType.value;
+ cal.setTimeZone(tz);
+ return pos.getIndex();
+ }
+ return ~start;
+ }
+ case 33: // 'x' - TIMEZONE_ISO_LOCAL
+ {
+ Output<TimeType> tzTimeType = new Output<TimeType>();
+ Style style;
+ switch (count) {
+ case 1:
+ style = Style.ISO_BASIC_LOCAL_SHORT;
+ break;
+ case 2:
+ style = Style.ISO_BASIC_LOCAL_FIXED;
+ break;
+ case 3:
+ style = Style.ISO_EXTENDED_LOCAL_FIXED;
+ break;
+ case 4:
+ style = Style.ISO_BASIC_LOCAL_FULL;
+ break;
+ default: // count >= 5
+ style = Style.ISO_EXTENDED_LOCAL_FULL;
+ break;
+ }
+ TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
+ if (tz != null) {
+ tztype = tzTimeType.value;
+ cal.setTimeZone(tz);
+ return pos.getIndex();
+ }
+ return ~start;
}
case 27: // 'Q' - QUARTER
if (count <= 2) { // i.e., Q or QQ.
// case 12: // 'w' - WEEK_OF_YEAR
// case 13: // 'W' - WEEK_OF_MONTH
// case 16: // 'K' - HOUR (0..11)
- // case 18: // 'Y' - YEAR_WOY
// case 19: // 'e' - DOW_LOCAL
// case 20: // 'u' - EXTENDED_YEAR
// case 21: // 'g' - JULIAN_DAY
cal.set(field, number.intValue());
return pos.getIndex();
}
- return -start;
+ return ~start;
}
}
nDigits--;
}
pos.setIndex(oldPos + maxDigits);
- number = new Integer((int)val);
+ number = Integer.valueOf((int)val);
}
}
return number;
*
* @return the time zone formatter which this date/time
* formatter uses.
- * @internal ICU 4.8 technology preview
- * @deprecated This API might change or be removed in a future release.
+ * @stable ICU 49
*/
public TimeZoneFormat getTimeZoneFormat() {
return tzFormat().freeze();
}
/**
- * {@icu} Allows you to set the time zoen formatter.
+ * {@icu} Allows you to set the time zone formatter.
*
* @param tzfmt the new time zone formatter
- * @internal ICU 4.8 technology preview
- * @deprecated This API might change or be removed in a future release.
+ * @stable ICU 49
*/
public void setTimeZoneFormat(TimeZoneFormat tzfmt) {
if (tzfmt.isFrozen()) {
}
}
+ /**
+ * {@icu} Set a particular DisplayContext value in the formatter,
+ * such as CAPITALIZATION_FOR_STANDALONE.
+ *
+ * @param context The DisplayContext value to set.
+ * @draft ICU 51
+ * @provisional This API might change or be removed in a future release.
+ */
+ public void setContext(DisplayContext context) {
+ if (context.type() == DisplayContext.Type.CAPITALIZATION) {
+ capitalizationSetting = context;
+ }
+ }
+
+ /**
+ * {@icu} Get the formatter's DisplayContext value for the specified DisplayContext.Type,
+ * such as CAPITALIZATION.
+ *
+ * @param type the DisplayContext.Type whose value to return
+ * @return the current DisplayContext setting for the specified type
+ * @draft ICU 51
+ * @provisional This API might change or be removed in a future release.
+ */
+ public DisplayContext getContext(DisplayContext.Type type) {
+ return (type == DisplayContext.Type.CAPITALIZATION && capitalizationSetting != null)?
+ capitalizationSetting: DisplayContext.CAPITALIZATION_NONE;
+ }
+
/**
* Overrides Cloneable
* @stable ICU 2.0
/**
* Override writeObject.
+ * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectOutputStream.html
*/
private void writeObject(ObjectOutputStream stream) throws IOException{
if (defaultCenturyStart == null) {
}
initializeTimeZoneFormat(false);
stream.defaultWriteObject();
+ stream.writeInt(capitalizationSetting.value());
}
/**
* Override readObject.
+ * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectInputStream.html
*/
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
+ int capitalizationSettingValue = (serialVersionOnStream > 1)? stream.readInt(): -1;
///CLOVER:OFF
// don't have old serial data to test with
if (serialVersionOnStream < 1) {
}
initLocalZeroPaddingNumberFormat();
+
+ capitalizationSetting = DisplayContext.CAPITALIZATION_NONE;
+ if (capitalizationSettingValue >= 0) {
+ for (DisplayContext context: DisplayContext.values()) {
+ if (context.value() == capitalizationSettingValue) {
+ capitalizationSetting = context;
+ break;
+ }
+ }
+ }
}
/**
StringBuffer toAppendTo = new StringBuffer();
FieldPosition pos = new FieldPosition(0);
List<FieldPosition> attributes = new ArrayList<FieldPosition>();
- format(cal, toAppendTo, pos, attributes);
+ format(cal, capitalizationSetting, toAppendTo, pos, attributes);
AttributedString as = new AttributedString(toAppendTo.toString());
} else {
PatternItem item = (PatternItem)items[i];
if (useFastFormat) {
- subFormat(appendTo, item.type, item.length, appendTo.length(), pos,
- fromCalendar);
+ subFormat(appendTo, item.type, item.length, appendTo.length(),
+ i, capitalizationSetting, pos, fromCalendar);
} else {
- appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos,
- formatData, fromCalendar));
+ appendTo.append(subFormat(item.type, item.length, appendTo.length(),
+ i, capitalizationSetting, pos, fromCalendar));
}
}
}
} else {
PatternItem item = (PatternItem)items[i];
if (useFastFormat) {
- subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);
+ subFormat(appendTo, item.type, item.length, appendTo.length(),
+ i, capitalizationSetting, pos, toCalendar);
} else {
- appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos,
- formatData, toCalendar));
+ appendTo.append(subFormat(item.type, item.length, appendTo.length(),
+ i, capitalizationSetting, pos, toCalendar));
}
}
}
protected NumberFormat getNumberFormat(char ch) {
Character ovrField;
- ovrField = new Character(ch);
+ ovrField = Character.valueOf(ch);
if (overrideMap != null && overrideMap.containsKey(ovrField)) {
String nsName = overrideMap.get(ovrField).toString();
NumberFormat nf = numberFormatters.get(nsName);
fullOverride = true;
} else { // Field specific override string such as "y=hebrew"
nsName = currentString.substring(equalSignPosition+1);
- ovrField = new Character(currentString.charAt(0));
+ ovrField = Character.valueOf(currentString.charAt(0));
overrideMap.put(ovrField,nsName);
fullOverride = false;
}