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