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 String digitString = ns.getDescription(); 1119 // DateNumberFormat does not support non-BMP digits at this moment. 1120 if (ns.isAlgorithmic() || digitString.length() != 10) { 1121 numberFormat = NumberFormat.getInstance(locale); 1122 } else { 1123 String nsName = ns.getName(); 1124 // Use a NumberFormat optimized for date formatting 1125 numberFormat = new DateNumberFormat(locale, digitString, nsName); 1126 } 1127 } 1128 if (numberFormat instanceof DecimalFormat) { 1129 fixNumberFormatForDates(numberFormat); 1130 } 1131 1132 // Note: deferring calendar calculation until when we really need it. 1133 // Instead, we just record time of construction for backward compatibility. 1134 defaultCenturyBase = System.currentTimeMillis(); 1135 1136 setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE)); 1137 initLocalZeroPaddingNumberFormat(); 1138 1139 if (override != null) { 1140 initNumberFormatters(locale); 1141 } 1142 1143 parsePattern(); 1144 } 1145 1146 /** 1147 * Private method lazily instantiate the TimeZoneFormat field 1148 * @param bForceUpdate when true, check if tzFormat is synchronized with 1149 * the current numberFormat and update its digits if necessary. When false, 1150 * this check is skipped. 1151 */ 1152 private synchronized void initializeTimeZoneFormat(boolean bForceUpdate) { 1153 if (bForceUpdate || tzFormat == null) { 1154 tzFormat = TimeZoneFormat.getInstance(locale); 1155 1156 String digits = null; 1157 if (numberFormat instanceof DecimalFormat) { 1158 DecimalFormatSymbols decsym = ((DecimalFormat) numberFormat).getDecimalFormatSymbols(); 1159 String[] strDigits = decsym.getDigitStringsLocal(); 1160 // Note: TimeZoneFormat#setGMTOffsetDigits() does not support string array, 1161 // so we need to concatenate digits to make a single string. 1162 StringBuilder digitsBuf = new StringBuilder(); 1163 for (String digit : strDigits) { 1164 digitsBuf.append(digit); 1165 } 1166 digits = digitsBuf.toString(); 1167 } else if (numberFormat instanceof DateNumberFormat) { 1168 digits = new String(((DateNumberFormat)numberFormat).getDigits()); 1169 } 1170 1171 if (digits != null) { 1172 if (!tzFormat.getGMTOffsetDigits().equals(digits)) { 1173 if (tzFormat.isFrozen()) { 1174 tzFormat = tzFormat.cloneAsThawed(); 1175 } 1176 tzFormat.setGMTOffsetDigits(digits); 1177 } 1178 } 1179 } 1180 } 1181 1182 /** 1183 * Private method, returns non-null TimeZoneFormat. 1184 * @return the TimeZoneFormat used by this formatter. 1185 */ 1186 private TimeZoneFormat tzFormat() { 1187 if (tzFormat == null) { 1188 initializeTimeZoneFormat(false); 1189 } 1190 return tzFormat; 1191 } 1192 1193 // privates for the default pattern 1194 private static ULocale cachedDefaultLocale = null; 1195 private static String cachedDefaultPattern = null; 1196 private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm"; 1197 1198 /* 1199 * Returns the default date and time pattern (SHORT) for the default locale. 1200 * This method is only used by the default SimpleDateFormat constructor. 1201 */ 1202 private static synchronized String getDefaultPattern() { 1203 ULocale defaultLocale = ULocale.getDefault(Category.FORMAT); 1204 if (!defaultLocale.equals(cachedDefaultLocale)) { 1205 cachedDefaultLocale = defaultLocale; 1206 Calendar cal = Calendar.getInstance(cachedDefaultLocale); 1207 1208 try { 1209 // Load the calendar data directly. 1210 ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance( 1211 ICUData.ICU_BASE_NAME, cachedDefaultLocale); 1212 String resourcePath = "calendar/" + cal.getType() + "/DateTimePatterns"; 1213 ICUResourceBundle patternsRb= rb.findWithFallback(resourcePath); 1214 1215 if (patternsRb == null) { 1216 patternsRb = rb.findWithFallback("calendar/gregorian/DateTimePatterns"); 1217 } 1218 if (patternsRb == null || patternsRb.getSize() < 9) { 1219 cachedDefaultPattern = FALLBACKPATTERN; 1220 } else { 1221 int defaultIndex = 8; 1222 if (patternsRb.getSize() >= 13) { 1223 defaultIndex += (SHORT + 1); 1224 } 1225 String basePattern = patternsRb.getString(defaultIndex); 1226 1227 cachedDefaultPattern = SimpleFormatterImpl.formatRawPattern( 1228 basePattern, 2, 2, 1229 patternsRb.getString(SHORT), patternsRb.getString(SHORT + 4)); 1230 } 1231 } catch (MissingResourceException e) { 1232 cachedDefaultPattern = FALLBACKPATTERN; 1233 } 1234 } 1235 return cachedDefaultPattern; 1236 } 1237 1238 /* Define one-century window into which to disambiguate dates using 1239 * two-digit years. 1240 */ 1241 private void parseAmbiguousDatesAsAfter(Date startDate) { 1242 defaultCenturyStart = startDate; 1243 calendar.setTime(startDate); 1244 defaultCenturyStartYear = calendar.get(Calendar.YEAR); 1245 } 1246 1247 /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time. 1248 * The default start time is 80 years before the creation time of this object. 1249 */ 1250 private void initializeDefaultCenturyStart(long baseTime) { 1251 defaultCenturyBase = baseTime; 1252 // clone to avoid messing up date stored in calendar object 1253 // when this method is called while parsing 1254 Calendar tmpCal = (Calendar)calendar.clone(); 1255 tmpCal.setTimeInMillis(baseTime); 1256 tmpCal.add(Calendar.YEAR, -80); 1257 defaultCenturyStart = tmpCal.getTime(); 1258 defaultCenturyStartYear = tmpCal.get(Calendar.YEAR); 1259 } 1260 1261 /* Gets the default century start date for this object */ 1262 private Date getDefaultCenturyStart() { 1263 if (defaultCenturyStart == null) { 1264 // not yet initialized 1265 initializeDefaultCenturyStart(defaultCenturyBase); 1266 } 1267 return defaultCenturyStart; 1268 } 1269 1270 /* Gets the default century start year for this object */ 1271 private int getDefaultCenturyStartYear() { 1272 if (defaultCenturyStart == null) { 1273 // not yet initialized 1274 initializeDefaultCenturyStart(defaultCenturyBase); 1275 } 1276 return defaultCenturyStartYear; 1277 } 1278 1279 /** 1280 * Sets the 100-year period 2-digit years will be interpreted as being in 1281 * to begin on the date the user specifies. 1282 * @param startDate During parsing, two digit years will be placed in the range 1283 * <code>startDate</code> to <code>startDate + 100 years</code>. 1284 * @stable ICU 2.0 1285 */ 1286 public void set2DigitYearStart(Date startDate) { 1287 parseAmbiguousDatesAsAfter(startDate); 1288 } 1289 1290 /** 1291 * Returns the beginning date of the 100-year period 2-digit years are interpreted 1292 * as being within. 1293 * @return the start of the 100-year period into which two digit years are 1294 * parsed 1295 * @stable ICU 2.0 1296 */ 1297 public Date get2DigitYearStart() { 1298 return getDefaultCenturyStart(); 1299 } 1300 1301 /** 1302 * {@icu} Set a particular DisplayContext value in the formatter, 1303 * such as CAPITALIZATION_FOR_STANDALONE. Note: For getContext, see 1304 * DateFormat. 1305 * 1306 * @param context The DisplayContext value to set. 1307 * @stable ICU 53 1308 */ 1309 // Here we override the DateFormat implementation in order to lazily initialize relevant items 1310 @Override 1311 public void setContext(DisplayContext context) { 1312 super.setContext(context); 1313 if (capitalizationBrkIter == null && (context==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE || 1314 context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU || 1315 context==DisplayContext.CAPITALIZATION_FOR_STANDALONE)) { 1316 capitalizationBrkIter = BreakIterator.getSentenceInstance(locale); 1317 } 1318 } 1319 1320 /** 1321 * Formats a date or time, which is the standard millis 1322 * since January 1, 1970, 00:00:00 GMT. 1323 * <p>Example: using the US locale: 1324 * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT 1325 * @param cal the calendar whose date-time value is to be formatted into a date-time string 1326 * @param toAppendTo where the new date-time text is to be appended 1327 * @param pos the formatting position. On input: an alignment field, 1328 * if desired. On output: the offsets of the alignment field. 1329 * @return the formatted date-time string. 1330 * @see DateFormat 1331 * @stable ICU 2.0 1332 */ 1333 @Override 1334 public StringBuffer format(Calendar cal, StringBuffer toAppendTo, 1335 FieldPosition pos) { 1336 TimeZone backupTZ = null; 1337 if (cal != calendar && !cal.getType().equals(calendar.getType())) { 1338 // Different calendar type 1339 // We use the time and time zone from the input calendar, but 1340 // do not use the input calendar for field calculation. 1341 calendar.setTimeInMillis(cal.getTimeInMillis()); 1342 backupTZ = calendar.getTimeZone(); 1343 calendar.setTimeZone(cal.getTimeZone()); 1344 cal = calendar; 1345 } 1346 StringBuffer result = format(cal, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, null); 1347 if (backupTZ != null) { 1348 // Restore the original time zone 1349 calendar.setTimeZone(backupTZ); 1350 } 1351 return result; 1352 } 1353 1354 // The actual method to format date. If List attributes is not null, 1355 // then attribute information will be recorded. 1356 private StringBuffer format(Calendar cal, DisplayContext capitalizationContext, 1357 StringBuffer toAppendTo, FieldPosition pos, List<FieldPosition> attributes) { 1358 // Initialize 1359 pos.setBeginIndex(0); 1360 pos.setEndIndex(0); 1361 1362 // Careful: For best performance, minimize the number of calls 1363 // to StringBuffer.append() by consolidating appends when 1364 // possible. 1365 1366 Object[] items = getPatternItems(); 1367 for (int i = 0; i < items.length; i++) { 1368 if (items[i] instanceof String) { 1369 toAppendTo.append((String)items[i]); 1370 } else { 1371 PatternItem item = (PatternItem)items[i]; 1372 int start = 0; 1373 if (attributes != null) { 1374 // Save the current length 1375 start = toAppendTo.length(); 1376 } 1377 if (useFastFormat) { 1378 subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), 1379 i, capitalizationContext, pos, cal); 1380 } else { 1381 toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), 1382 i, capitalizationContext, pos, cal)); 1383 } 1384 if (attributes != null) { 1385 // Check the sub format length 1386 int end = toAppendTo.length(); 1387 if (end - start > 0) { 1388 // Append the attribute to the list 1389 DateFormat.Field attr = patternCharToDateFormatField(item.type); 1390 FieldPosition fp = new FieldPosition(attr); 1391 fp.setBeginIndex(start); 1392 fp.setEndIndex(end); 1393 attributes.add(fp); 1394 } 1395 } 1396 } 1397 } 1398 return toAppendTo; 1399 1400 } 1401 1402 // Map pattern character to index 1403 private static final int[] PATTERN_CHAR_TO_INDEX = 1404 { 1405 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1406 // 1407 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1408 // ! " # $ % & ' ( ) * + , - . / 1409 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1410 // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 1411 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1412 // @ A B C D E F G H I J K L M N O 1413 -1, 22, 36, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, 31, 1414 // P Q R S T U V W X Y Z [ \ ] ^ _ 1415 -1, 27, -1, 8, -1, 30, 29, 13, 32, 18, 23, -1, -1, -1, -1, -1, 1416 // ` a b c d e f g h i j k l m n o 1417 -1, 14, 35, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1, 1418 // p q r s t u v w x y z { | } ~ 1419 -1, 28, 34, 7, -1, 20, 24, 12, 33, 1, 17, -1, -1, -1, -1, -1, 1420 }; 1421 1422 private static int getIndexFromChar(char ch) { 1423 return ch < PATTERN_CHAR_TO_INDEX.length ? PATTERN_CHAR_TO_INDEX[ch & 0xff] : -1; 1424 } 1425 1426 // Map pattern character index to Calendar field number 1427 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = 1428 { 1429 /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH, 1430 /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, 1431 /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND, 1432 /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH, 1433 /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM, 1434 /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET, 1435 /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR, 1436 /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET /* also DST_OFFSET */, 1437 /*v*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, 1438 /*c*/ Calendar.DOW_LOCAL, 1439 /*L*/ Calendar.MONTH, 1440 /*Qq*/ Calendar.MONTH, Calendar.MONTH, 1441 /*V*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, 1442 /*U*/ Calendar.YEAR, 1443 /*O*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, 1444 /*Xx*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, Calendar.ZONE_OFFSET /* also DST_OFFSET */, 1445 /*r*/ Calendar.EXTENDED_YEAR /* not an exact match */, 1446 /*bB*/ -1, -1 /* am/pm/midnight/noon and flexible day period fields; no mapping to calendar fields */ 1447 /*:*/ -1, /* => no useful mapping to any calendar field, can't use protected Calendar.BASE_FIELD_COUNT */ 1448 }; 1449 1450 // Map pattern character index to DateFormat field number 1451 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { 1452 /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD, 1453 /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD, 1454 /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD, 1455 /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, 1456 /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD, 1457 /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD, 1458 /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD, 1459 /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD, 1460 /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD, 1461 /*c*/ DateFormat.STANDALONE_DAY_FIELD, 1462 /*L*/ DateFormat.STANDALONE_MONTH_FIELD, 1463 /*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD, 1464 /*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD, 1465 /*U*/ DateFormat.YEAR_NAME_FIELD, 1466 /*O*/ DateFormat.TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD, 1467 /*Xx*/ DateFormat.TIMEZONE_ISO_FIELD, DateFormat.TIMEZONE_ISO_LOCAL_FIELD, 1468 /*r*/ DateFormat.RELATED_YEAR, 1469 /*bB*/ DateFormat.AM_PM_MIDNIGHT_NOON_FIELD, DateFormat.FLEXIBLE_DAY_PERIOD_FIELD, 1470 /*(no pattern character defined for this)*/ DateFormat.TIME_SEPARATOR, 1471 }; 1472 1473 // Map pattern character index to DateFormat.Field 1474 private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = { 1475 /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH, 1476 /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0, 1477 /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND, 1478 /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH, 1479 /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM, 1480 /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE, 1481 /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR, 1482 /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE, 1483 /*v*/ DateFormat.Field.TIME_ZONE, 1484 /*c*/ DateFormat.Field.DAY_OF_WEEK, 1485 /*L*/ DateFormat.Field.MONTH, 1486 /*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER, 1487 /*V*/ DateFormat.Field.TIME_ZONE, 1488 /*U*/ DateFormat.Field.YEAR, 1489 /*O*/ DateFormat.Field.TIME_ZONE, 1490 /*Xx*/ DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE, 1491 /*r*/ DateFormat.Field.RELATED_YEAR, 1492 /*bB*/ DateFormat.Field.AM_PM_MIDNIGHT_NOON, DateFormat.Field.FLEXIBLE_DAY_PERIOD, 1493 /*(no pattern character defined for this)*/ DateFormat.Field.TIME_SEPARATOR, 1494 }; 1495 1496 /** 1497 * Returns a DateFormat.Field constant associated with the specified format pattern 1498 * character. 1499 * 1500 * @param ch The pattern character 1501 * @return DateFormat.Field associated with the pattern character 1502 * 1503 * @stable ICU 3.8 1504 */ 1505 protected DateFormat.Field patternCharToDateFormatField(char ch) { 1506 int patternCharIndex = getIndexFromChar(ch); 1507 if (patternCharIndex != -1) { 1508 return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]; 1509 } 1510 return null; 1511 } 1512 1513 /** 1514 * Formats a single field, given its pattern character. Subclasses may 1515 * override this method in order to modify or add formatting 1516 * capabilities. 1517 * @param ch the pattern character 1518 * @param count the number of times ch is repeated in the pattern 1519 * @param beginOffset the offset of the output string at the start of 1520 * this field; used to set pos when appropriate 1521 * @param pos receives the position of a field, when appropriate 1522 * @param fmtData the symbols for this formatter 1523 * @stable ICU 2.0 1524 */ 1525 protected String subFormat(char ch, int count, int beginOffset, 1526 FieldPosition pos, DateFormatSymbols fmtData, 1527 Calendar cal) 1528 throws IllegalArgumentException 1529 { 1530 // Note: formatData is ignored 1531 return subFormat(ch, count, beginOffset, 0, DisplayContext.CAPITALIZATION_NONE, pos, cal); 1532 } 1533 1534 /** 1535 * Formats a single field. This is the version called internally; it 1536 * adds fieldNum and capitalizationContext parameters. 1537 * 1538 * @internal 1539 * @deprecated This API is ICU internal only. 1540 */ 1541 @Deprecated 1542 protected String subFormat(char ch, int count, int beginOffset, 1543 int fieldNum, DisplayContext capitalizationContext, 1544 FieldPosition pos, 1545 Calendar cal) 1546 { 1547 StringBuffer buf = new StringBuffer(); 1548 subFormat(buf, ch, count, beginOffset, fieldNum, capitalizationContext, pos, cal); 1549 return buf.toString(); 1550 } 1551 1552 /** 1553 * Formats a single field; useFastFormat variant. Reuses a 1554 * StringBuffer for results instead of creating a String on the 1555 * heap for each call. 1556 * 1557 * NOTE We don't really need the beginOffset parameter, EXCEPT for 1558 * the need to support the slow subFormat variant (above) which 1559 * has to pass it in to us. 1560 * 1561 * @internal 1562 * @deprecated This API is ICU internal only. 1563 */ 1564 @Deprecated 1565 @SuppressWarnings("fallthrough") 1566 protected void subFormat(StringBuffer buf, 1567 char ch, int count, int beginOffset, 1568 int fieldNum, DisplayContext capitalizationContext, 1569 FieldPosition pos, 1570 Calendar cal) { 1571 1572 final int maxIntCount = Integer.MAX_VALUE; 1573 final int bufstart = buf.length(); 1574 TimeZone tz = cal.getTimeZone(); 1575 long date = cal.getTimeInMillis(); 1576 String result = null; 1577 1578 int patternCharIndex = getIndexFromChar(ch); 1579 if (patternCharIndex == -1) { 1580 if (ch == 'l') { // (SMALL LETTER L) deprecated placeholder for leap month marker, ignore 1581 return; 1582 } else { 1583 throw new IllegalArgumentException("Illegal pattern character " + 1584 "'" + ch + "' in \"" + 1585 pattern + '"'); 1586 } 1587 } 1588 1589 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 1590 int value = 0; 1591 // Don't get value unless it is useful 1592 if (field >= 0) { 1593 value = (patternCharIndex != DateFormat.RELATED_YEAR)? cal.get(field): cal.getRelatedYear(); 1594 } 1595 1596 NumberFormat currentNumberFormat = getNumberFormat(ch); 1597 DateFormatSymbols.CapitalizationContextUsage capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.OTHER; 1598 1599 switch (patternCharIndex) { 1600 case 0: // 'G' - ERA 1601 if ( cal.getType().equals("chinese") || cal.getType().equals("dangi") ) { 1602 // moved from ChineseDateFormat 1603 zeroPaddingNumber(currentNumberFormat, buf, value, 1, 9); 1604 } else { 1605 if (count == 5) { 1606 safeAppend(formatData.narrowEras, value, buf); 1607 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_NARROW; 1608 } else if (count == 4) { 1609 safeAppend(formatData.eraNames, value, buf); 1610 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_WIDE; 1611 } else { 1612 safeAppend(formatData.eras, value, buf); 1613 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ERA_ABBREV; 1614 } 1615 } 1616 break; 1617 case 30: // 'U' - YEAR_NAME_FIELD 1618 if (formatData.shortYearNames != null && value <= formatData.shortYearNames.length) { 1619 safeAppend(formatData.shortYearNames, value-1, buf); 1620 break; 1621 } 1622 // else fall through to numeric year handling, do not break here 1623 case 1: // 'y' - YEAR 1624 case 18: // 'Y' - YEAR_WOY 1625 if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) && 1626 value > HEBREW_CAL_CUR_MILLENIUM_START_YEAR && value < HEBREW_CAL_CUR_MILLENIUM_END_YEAR ) { 1627 value -= HEBREW_CAL_CUR_MILLENIUM_START_YEAR; 1628 } 1629 /* According to the specification, if the number of pattern letters ('y') is 2, 1630 * the year is truncated to 2 digits; otherwise it is interpreted as a number. 1631 * But the original code process 'y', 'yy', 'yyy' in the same way. and process 1632 * patterns with 4 or more than 4 'y' characters in the same way. 1633 * So I change the codes to meet the specification. [Richard/GCl] 1634 */ 1635 if (count == 2) { 1636 zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96 1637 } else { //count = 1 or count > 2 1638 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); 1639 } 1640 break; 1641 case 2: // 'M' - MONTH 1642 case 26: // 'L' - STANDALONE MONTH 1643 if ( cal.getType().equals("hebrew")) { 1644 boolean isLeap = HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR)); 1645 if (isLeap && value == 6 && count >= 3 ) { 1646 value = 13; // Show alternate form for Adar II in leap years in Hebrew calendar. 1647 } 1648 if (!isLeap && value >= 6 && count < 3 ) { 1649 value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7. 1650 } 1651 } 1652 int isLeapMonth = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT)? 1653 cal.get(Calendar.IS_LEAP_MONTH): 0; 1654 // should consolidate the next section by using arrays of pointers & counts for the right symbols... 1655 if (count == 5) { 1656 if (patternCharIndex == 2) { 1657 safeAppendWithMonthPattern(formatData.narrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_NARROW]: null); 1658 } else { 1659 safeAppendWithMonthPattern(formatData.standaloneNarrowMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW]: null); 1660 } 1661 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_NARROW; 1662 } else if (count == 4) { 1663 if (patternCharIndex == 2) { 1664 safeAppendWithMonthPattern(formatData.months, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null); 1665 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT; 1666 } else { 1667 safeAppendWithMonthPattern(formatData.standaloneMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null); 1668 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE; 1669 } 1670 } else if (count == 3) { 1671 if (patternCharIndex == 2) { 1672 safeAppendWithMonthPattern(formatData.shortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null); 1673 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_FORMAT; 1674 } else { 1675 safeAppendWithMonthPattern(formatData.standaloneShortMonths, value, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null); 1676 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.MONTH_STANDALONE; 1677 } 1678 } else { 1679 StringBuffer monthNumber = new StringBuffer(); 1680 zeroPaddingNumber(currentNumberFormat, monthNumber, value+1, count, maxIntCount); 1681 String[] monthNumberStrings = new String[1]; 1682 monthNumberStrings[0] = monthNumber.toString(); 1683 safeAppendWithMonthPattern(monthNumberStrings, 0, buf, (isLeapMonth!=0)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC]: null); 1684 } 1685 break; 1686 case 4: // 'k' - HOUR_OF_DAY (1..24) 1687 if (value == 0) { 1688 zeroPaddingNumber(currentNumberFormat,buf, 1689 cal.getMaximum(Calendar.HOUR_OF_DAY)+1, 1690 count, maxIntCount); 1691 } else { 1692 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); 1693 } 1694 break; 1695 case 8: // 'S' - FRACTIONAL_SECOND 1696 // Fractional seconds left-justify 1697 { 1698 numberFormat.setMinimumIntegerDigits(Math.min(3, count)); 1699 numberFormat.setMaximumIntegerDigits(maxIntCount); 1700 if (count == 1) { 1701 value /= 100; 1702 } else if (count == 2) { 1703 value /= 10; 1704 } 1705 FieldPosition p = new FieldPosition(-1); 1706 numberFormat.format(value, buf, p); 1707 if (count > 3) { 1708 numberFormat.setMinimumIntegerDigits(count - 3); 1709 numberFormat.format(0L, buf, p); 1710 } 1711 } 1712 break; 1713 case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names) 1714 if (count < 3) { 1715 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); 1716 break; 1717 } 1718 // For alpha day-of-week, we don't want DOW_LOCAL, 1719 // we need the standard DAY_OF_WEEK. 1720 value = cal.get(Calendar.DAY_OF_WEEK); 1721 // fall through, do not break here 1722 case 9: // 'E' - DAY_OF_WEEK 1723 if (count == 5) { 1724 safeAppend(formatData.narrowWeekdays, value, buf); 1725 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW; 1726 } else if (count == 4) { 1727 safeAppend(formatData.weekdays, value, buf); 1728 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT; 1729 } else if (count == 6 && formatData.shorterWeekdays != null) { 1730 safeAppend(formatData.shorterWeekdays, value, buf); 1731 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT; 1732 } else {// count <= 3, use abbreviated form if exists 1733 safeAppend(formatData.shortWeekdays, value, buf); 1734 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_FORMAT; 1735 } 1736 break; 1737 case 14: // 'a' - AM_PM 1738 // formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version 1739 if (count < 5 || formatData.ampmsNarrow == null) { 1740 safeAppend(formatData.ampms, value, buf); 1741 } else { 1742 safeAppend(formatData.ampmsNarrow, value, buf); 1743 } 1744 break; 1745 case 15: // 'h' - HOUR (1..12) 1746 if (value == 0) { 1747 zeroPaddingNumber(currentNumberFormat,buf, 1748 cal.getLeastMaximum(Calendar.HOUR)+1, 1749 count, maxIntCount); 1750 } else { 1751 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); 1752 } 1753 break; 1754 1755 case 17: // 'z' - TIMEZONE_FIELD 1756 if (count < 4) { 1757 // "z", "zz", "zzz" 1758 result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date); 1759 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT; 1760 } else { 1761 result = tzFormat().format(Style.SPECIFIC_LONG, tz, date); 1762 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG; 1763 } 1764 buf.append(result); 1765 break; 1766 case 23: // 'Z' - TIMEZONE_RFC_FIELD 1767 if (count < 4) { 1768 // RFC822 format - equivalent to ISO 8601 local offset fixed width format 1769 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date); 1770 } else if (count == 5) { 1771 // ISO 8601 extended format 1772 result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date); 1773 } else { 1774 // long form, localized GMT pattern 1775 result = tzFormat().format(Style.LOCALIZED_GMT, tz, date); 1776 } 1777 buf.append(result); 1778 break; 1779 case 24: // 'v' - TIMEZONE_GENERIC_FIELD 1780 if (count == 1) { 1781 // "v" 1782 result = tzFormat().format(Style.GENERIC_SHORT, tz, date); 1783 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_SHORT; 1784 } else if (count == 4) { 1785 // "vvvv" 1786 result = tzFormat().format(Style.GENERIC_LONG, tz, date); 1787 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.METAZONE_LONG; 1788 } 1789 buf.append(result); 1790 break; 1791 case 29: // 'V' - TIMEZONE_SPECIAL_FIELD 1792 if (count == 1) { 1793 // "V" 1794 result = tzFormat().format(Style.ZONE_ID_SHORT, tz, date); 1795 } else if (count == 2) { 1796 // "VV" 1797 result = tzFormat().format(Style.ZONE_ID, tz, date); 1798 } else if (count == 3) { 1799 // "VVV" 1800 result = tzFormat().format(Style.EXEMPLAR_LOCATION, tz, date); 1801 } else if (count == 4) { 1802 // "VVVV" 1803 result = tzFormat().format(Style.GENERIC_LOCATION, tz, date); 1804 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.ZONE_LONG; 1805 } 1806 buf.append(result); 1807 break; 1808 case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD 1809 if (count == 1) { 1810 // "O" - Short Localized GMT format 1811 result = tzFormat().format(Style.LOCALIZED_GMT_SHORT, tz, date); 1812 } else if (count == 4) { 1813 // "OOOO" - Localized GMT format 1814 result = tzFormat().format(Style.LOCALIZED_GMT, tz, date); 1815 } 1816 buf.append(result); 1817 break; 1818 case 32: // 'X' - TIMEZONE_ISO_FIELD 1819 if (count == 1) { 1820 // "X" - ISO Basic/Short 1821 result = tzFormat().format(Style.ISO_BASIC_SHORT, tz, date); 1822 } else if (count == 2) { 1823 // "XX" - ISO Basic/Fixed 1824 result = tzFormat().format(Style.ISO_BASIC_FIXED, tz, date); 1825 } else if (count == 3) { 1826 // "XXX" - ISO Extended/Fixed 1827 result = tzFormat().format(Style.ISO_EXTENDED_FIXED, tz, date); 1828 } else if (count == 4) { 1829 // "XXXX" - ISO Basic/Optional second field 1830 result = tzFormat().format(Style.ISO_BASIC_FULL, tz, date); 1831 } else if (count == 5) { 1832 // "XXXXX" - ISO Extended/Optional second field 1833 result = tzFormat().format(Style.ISO_EXTENDED_FULL, tz, date); 1834 } 1835 buf.append(result); 1836 break; 1837 case 33: // 'x' - TIMEZONE_ISO_LOCAL_FIELD 1838 if (count == 1) { 1839 // "x" - ISO Local Basic/Short 1840 result = tzFormat().format(Style.ISO_BASIC_LOCAL_SHORT, tz, date); 1841 } else if (count == 2) { 1842 // "x" - ISO Local Basic/Fixed 1843 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FIXED, tz, date); 1844 } else if (count == 3) { 1845 // "xxx" - ISO Local Extended/Fixed 1846 result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FIXED, tz, date); 1847 } else if (count == 4) { 1848 // "xxxx" - ISO Local Basic/Optional second field 1849 result = tzFormat().format(Style.ISO_BASIC_LOCAL_FULL, tz, date); 1850 } else if (count == 5) { 1851 // "xxxxx" - ISO Local Extended/Optional second field 1852 result = tzFormat().format(Style.ISO_EXTENDED_LOCAL_FULL, tz, date); 1853 } 1854 buf.append(result); 1855 break; 1856 1857 case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone) 1858 if (count < 3) { 1859 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount); 1860 break; 1861 } 1862 // For alpha day-of-week, we don't want DOW_LOCAL, 1863 // we need the standard DAY_OF_WEEK. 1864 value = cal.get(Calendar.DAY_OF_WEEK); 1865 if (count == 5) { 1866 safeAppend(formatData.standaloneNarrowWeekdays, value, buf); 1867 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_NARROW; 1868 } else if (count == 4) { 1869 safeAppend(formatData.standaloneWeekdays, value, buf); 1870 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE; 1871 } else if (count == 6 && formatData.standaloneShorterWeekdays != null) { 1872 safeAppend(formatData.standaloneShorterWeekdays, value, buf); 1873 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE; 1874 } else { // count == 3 1875 safeAppend(formatData.standaloneShortWeekdays, value, buf); 1876 capContextUsageType = DateFormatSymbols.CapitalizationContextUsage.DAY_STANDALONE; 1877 } 1878 break; 1879 case 27: // 'Q' - QUARTER 1880 if (count >= 4) { 1881 safeAppend(formatData.quarters, value/3, buf); 1882 } else if (count == 3) { 1883 safeAppend(formatData.shortQuarters, value/3, buf); 1884 } else { 1885 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount); 1886 } 1887 break; 1888 case 28: // 'q' - STANDALONE QUARTER 1889 if (count >= 4) { 1890 safeAppend(formatData.standaloneQuarters, value/3, buf); 1891 } else if (count == 3) { 1892 safeAppend(formatData.standaloneShortQuarters, value/3, buf); 1893 } else { 1894 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount); 1895 } 1896 break; 1897 case 35: // 'b' - am/pm/noon/midnight 1898 { 1899 // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day. 1900 // For ICU 57 output of "midnight" is temporarily suppressed. 1901 1902 int hour = cal.get(Calendar.HOUR_OF_DAY); 1903 String toAppend = null; 1904 1905 // For "midnight" and "noon": 1906 // Time, as displayed, must be exactly noon or midnight. 1907 // This means minutes and seconds, if present, must be zero. 1908 if ((/*hour == 0 ||*/ hour == 12) && 1909 (!hasMinute || cal.get(Calendar.MINUTE) == 0) && 1910 (!hasSecond || cal.get(Calendar.SECOND) == 0)) { 1911 // Stealing am/pm value to use as our array index. 1912 // It works out: am/midnight are both 0, pm/noon are both 1, 1913 // 12 am is 12 midnight, and 12 pm is 12 noon. 1914 value = cal.get(Calendar.AM_PM); 1915 1916 if (count <= 3) { 1917 toAppend = formatData.abbreviatedDayPeriods[value]; 1918 } else if (count == 4 || count > 5) { 1919 toAppend = formatData.wideDayPeriods[value]; 1920 } else { // count == 5 1921 toAppend = formatData.narrowDayPeriods[value]; 1922 } 1923 } 1924 1925 if (toAppend == null) { 1926 // Time isn't exactly midnight or noon (as displayed) or localized string doesn't 1927 // exist for requested period. Fall back to am/pm instead. 1928 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); 1929 } else { 1930 buf.append(toAppend); 1931 } 1932 1933 break; 1934 } 1935 case 36: // 'B' - flexible day period 1936 { 1937 // TODO: Maybe fetch the DayperiodRules during initialization (instead of at the first 1938 // loading of an instance) if a relevant pattern character (b or B) is used. 1939 DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale()); 1940 if (ruleSet == null) { 1941 // Data doesn't exist for the locale we're looking for. 1942 // Fall back to am/pm. 1943 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); 1944 break; 1945 } 1946 1947 // Get current display time. 1948 int hour = cal.get(Calendar.HOUR_OF_DAY); 1949 int minute = 0; 1950 int second = 0; 1951 if (hasMinute) { minute = cal.get(Calendar.MINUTE); } 1952 if (hasSecond) { second = cal.get(Calendar.SECOND); } 1953 1954 // Determine day period. 1955 DayPeriodRules.DayPeriod periodType; 1956 if (hour == 0 && minute == 0 && second == 0 && ruleSet.hasMidnight()) { 1957 periodType = DayPeriodRules.DayPeriod.MIDNIGHT; 1958 } else if (hour == 12 && minute == 0 && second == 0 && ruleSet.hasNoon()) { 1959 periodType = DayPeriodRules.DayPeriod.NOON; 1960 } else { 1961 periodType = ruleSet.getDayPeriodForHour(hour); 1962 } 1963 1964 // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day. 1965 // For ICU 57 output of "midnight" is temporarily suppressed. 1966 1967 // Rule set exists, therefore periodType can't be null. 1968 // Get localized string. 1969 assert(periodType != null); 1970 String toAppend = null; 1971 int index; 1972 1973 if (periodType != DayPeriodRules.DayPeriod.AM && periodType != DayPeriodRules.DayPeriod.PM && 1974 periodType != DayPeriodRules.DayPeriod.MIDNIGHT) { 1975 index = periodType.ordinal(); 1976 if (count <= 3) { 1977 toAppend = formatData.abbreviatedDayPeriods[index]; // i.e. short 1978 } else if (count == 4 || count > 5) { 1979 toAppend = formatData.wideDayPeriods[index]; 1980 } else { // count == 5 1981 toAppend = formatData.narrowDayPeriods[index]; 1982 } 1983 } 1984 1985 1986 // Fallback schedule: 1987 // Midnight/Noon -> General Periods -> AM/PM. 1988 1989 // Midnight/Noon -> General Periods. 1990 if (toAppend == null && 1991 (periodType == DayPeriodRules.DayPeriod.MIDNIGHT || 1992 periodType == DayPeriodRules.DayPeriod.NOON)) { 1993 periodType = ruleSet.getDayPeriodForHour(hour); 1994 index = periodType.ordinal(); 1995 1996 if (count <= 3) { 1997 toAppend = formatData.abbreviatedDayPeriods[index]; // i.e. short 1998 } else if (count == 4 || count > 5) { 1999 toAppend = formatData.wideDayPeriods[index]; 2000 } else { // count == 5 2001 toAppend = formatData.narrowDayPeriods[index]; 2002 } 2003 } 2004 2005 // General Periods -> AM/PM. 2006 if (periodType == DayPeriodRules.DayPeriod.AM || 2007 periodType == DayPeriodRules.DayPeriod.PM || 2008 toAppend == null) { 2009 subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); 2010 } 2011 else { 2012 buf.append(toAppend); 2013 } 2014 2015 break; 2016 } 2017 case 37: // TIME SEPARATOR (no pattern character currently defined, we should 2018 // not get here but leave support in for future definition. 2019 buf.append(formatData.getTimeSeparatorString()); 2020 break; 2021 default: 2022 // case 3: // 'd' - DATE 2023 // case 5: // 'H' - HOUR_OF_DAY (0..23) 2024 // case 6: // 'm' - MINUTE 2025 // case 7: // 's' - SECOND 2026 // case 10: // 'D' - DAY_OF_YEAR 2027 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH 2028 // case 12: // 'w' - WEEK_OF_YEAR 2029 // case 13: // 'W' - WEEK_OF_MONTH 2030 // case 16: // 'K' - HOUR (0..11) 2031 // case 20: // 'u' - EXTENDED_YEAR 2032 // case 21: // 'g' - JULIAN_DAY 2033 // case 22: // 'A' - MILLISECONDS_IN_DAY 2034 2035 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount); 2036 break; 2037 } // switch (patternCharIndex) 2038 2039 if (fieldNum == 0 && capitalizationContext != null && UCharacter.isLowerCase(buf.codePointAt(bufstart))) { 2040 boolean titlecase = false; 2041 switch (capitalizationContext) { 2042 case CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE: 2043 titlecase = true; 2044 break; 2045 case CAPITALIZATION_FOR_UI_LIST_OR_MENU: 2046 case CAPITALIZATION_FOR_STANDALONE: 2047 if (formatData.capitalization != null) { 2048 boolean[] transforms = formatData.capitalization.get(capContextUsageType); 2049 titlecase = (capitalizationContext==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)? 2050 transforms[0]: transforms[1]; 2051 } 2052 break; 2053 default: 2054 break; 2055 } 2056 if (titlecase) { 2057 if (capitalizationBrkIter == null) { 2058 // should only happen when deserializing, etc. 2059 capitalizationBrkIter = BreakIterator.getSentenceInstance(locale); 2060 } 2061 String firstField = buf.substring(bufstart); // bufstart or beginOffset, should be the same 2062 String firstFieldTitleCase = UCharacter.toTitleCase(locale, firstField, capitalizationBrkIter, 2063 UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT); 2064 buf.replace(bufstart, buf.length(), firstFieldTitleCase); 2065 } 2066 } 2067 2068 // Set the FieldPosition (for the first occurrence only) 2069 if (pos.getBeginIndex() == pos.getEndIndex()) { 2070 if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) { 2071 pos.setBeginIndex(beginOffset); 2072 pos.setEndIndex(beginOffset + buf.length() - bufstart); 2073 } else if (pos.getFieldAttribute() == 2074 PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) { 2075 pos.setBeginIndex(beginOffset); 2076 pos.setEndIndex(beginOffset + buf.length() - bufstart); 2077 } 2078 } 2079 } 2080 2081 private static void safeAppend(String[] array, int value, StringBuffer appendTo) { 2082 if (array != null && value >= 0 && value < array.length) { 2083 appendTo.append(array[value]); 2084 } 2085 } 2086 2087 private static void safeAppendWithMonthPattern(String[] array, int value, StringBuffer appendTo, String monthPattern) { 2088 if (array != null && value >= 0 && value < array.length) { 2089 if (monthPattern == null) { 2090 appendTo.append(array[value]); 2091 } else { 2092 String s = SimpleFormatterImpl.formatRawPattern(monthPattern, 1, 1, array[value]); 2093 appendTo.append(s); 2094 } 2095 } 2096 } 2097 2098 /* 2099 * PatternItem store parsed date/time field pattern information. 2100 */ 2101 private static class PatternItem { 2102 final char type; 2103 final int length; 2104 final boolean isNumeric; 2105 2106 PatternItem(char type, int length) { 2107 this.type = type; 2108 this.length = length; 2109 isNumeric = isNumeric(type, length); 2110 } 2111 } 2112 2113 private static ICUCache<String, Object[]> PARSED_PATTERN_CACHE = 2114 new SimpleCache<String, Object[]>(); 2115 private transient Object[] patternItems; 2116 2117 /* 2118 * Returns parsed pattern items. Each item is either String or 2119 * PatternItem. 2120 */ 2121 private Object[] getPatternItems() { 2122 if (patternItems != null) { 2123 return patternItems; 2124 } 2125 2126 patternItems = PARSED_PATTERN_CACHE.get(pattern); 2127 if (patternItems != null) { 2128 return patternItems; 2129 } 2130 2131 boolean isPrevQuote = false; 2132 boolean inQuote = false; 2133 StringBuilder text = new StringBuilder(); 2134 char itemType = 0; // 0 for string literal, otherwise date/time pattern character 2135 int itemLength = 1; 2136 2137 List<Object> items = new ArrayList<Object>(); 2138 2139 for (int i = 0; i < pattern.length(); i++) { 2140 char ch = pattern.charAt(i); 2141 if (ch == '\'') { 2142 if (isPrevQuote) { 2143 text.append('\''); 2144 isPrevQuote = false; 2145 } else { 2146 isPrevQuote = true; 2147 if (itemType != 0) { 2148 items.add(new PatternItem(itemType, itemLength)); 2149 itemType = 0; 2150 } 2151 } 2152 inQuote = !inQuote; 2153 } else { 2154 isPrevQuote = false; 2155 if (inQuote) { 2156 text.append(ch); 2157 } else { 2158 if (isSyntaxChar(ch)) { 2159 // a date/time pattern character 2160 if (ch == itemType) { 2161 itemLength++; 2162 } else { 2163 if (itemType == 0) { 2164 if (text.length() > 0) { 2165 items.add(text.toString()); 2166 text.setLength(0); 2167 } 2168 } else { 2169 items.add(new PatternItem(itemType, itemLength)); 2170 } 2171 itemType = ch; 2172 itemLength = 1; 2173 } 2174 } else { 2175 // a string literal 2176 if (itemType != 0) { 2177 items.add(new PatternItem(itemType, itemLength)); 2178 itemType = 0; 2179 } 2180 text.append(ch); 2181 } 2182 } 2183 } 2184 } 2185 // handle last item 2186 if (itemType == 0) { 2187 if (text.length() > 0) { 2188 items.add(text.toString()); 2189 text.setLength(0); 2190 } 2191 } else { 2192 items.add(new PatternItem(itemType, itemLength)); 2193 } 2194 2195 patternItems = items.toArray(new Object[items.size()]); 2196 2197 PARSED_PATTERN_CACHE.put(pattern, patternItems); 2198 2199 return patternItems; 2200 } 2201 2202 /** 2203 * Internal high-speed method. Reuses a StringBuffer for results 2204 * instead of creating a String on the heap for each call. 2205 * @internal 2206 * @deprecated This API is ICU internal only. 2207 */ 2208 @Deprecated 2209 protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value, 2210 int minDigits, int maxDigits) { 2211 // Note: Indian calendar uses negative value for a calendar 2212 // field. fastZeroPaddingNumber cannot handle negative numbers. 2213 // BTW, it looks like a design bug in the Indian calendar... 2214 if (useLocalZeroPaddingNumberFormat && value >= 0) { 2215 fastZeroPaddingNumber(buf, value, minDigits, maxDigits); 2216 } else { 2217 nf.setMinimumIntegerDigits(minDigits); 2218 nf.setMaximumIntegerDigits(maxDigits); 2219 nf.format(value, buf, new FieldPosition(-1)); 2220 } 2221 } 2222 2223 /** 2224 * Overrides superclass method and 2225 * This method also clears per field NumberFormat instances 2226 * previously set by {@link #setNumberFormat(String, NumberFormat)} 2227 * 2228 * @stable ICU 2.0 2229 */ 2230 @Override 2231 public void setNumberFormat(NumberFormat newNumberFormat) { 2232 // Override this method to update local zero padding number formatter 2233 super.setNumberFormat(newNumberFormat); 2234 initLocalZeroPaddingNumberFormat(); 2235 initializeTimeZoneFormat(true); 2236 2237 if (numberFormatters != null) { 2238 numberFormatters = null; 2239 } 2240 if (overrideMap != null) { 2241 overrideMap = null; 2242 } 2243 } 2244 2245 /* 2246 * Initializes transient fields for fast simple numeric formatting 2247 * code. This method should be called whenever number format is updated. 2248 */ 2249 private void initLocalZeroPaddingNumberFormat() { 2250 if (numberFormat instanceof DecimalFormat) { 2251 DecimalFormatSymbols tmpDecfs = ((DecimalFormat)numberFormat).getDecimalFormatSymbols(); 2252 String[] tmpDigits = tmpDecfs.getDigitStringsLocal(); 2253 useLocalZeroPaddingNumberFormat = true; 2254 decDigits = new char[10]; 2255 for (int i = 0; i < 10; i++) { 2256 if (tmpDigits[i].length() > 1) { 2257 useLocalZeroPaddingNumberFormat = false; 2258 break; 2259 } 2260 decDigits[i] = tmpDigits[i].charAt(0); 2261 } 2262 } else if (numberFormat instanceof DateNumberFormat) { 2263 decDigits = ((DateNumberFormat)numberFormat).getDigits(); 2264 useLocalZeroPaddingNumberFormat = true; 2265 } else { 2266 useLocalZeroPaddingNumberFormat = false; 2267 } 2268 2269 if (useLocalZeroPaddingNumberFormat) { 2270 decimalBuf = new char[DECIMAL_BUF_SIZE]; 2271 } 2272 } 2273 2274 // If true, use local version of zero padding number format 2275 private transient boolean useLocalZeroPaddingNumberFormat; 2276 private transient char[] decDigits; // read-only - can be shared by multiple instances 2277 private transient char[] decimalBuf; // mutable - one per instance 2278 private static final int DECIMAL_BUF_SIZE = 10; // sufficient for int numbers 2279 2280 /* 2281 * Lightweight zero padding integer number format function. 2282 * 2283 * Note: This implementation is almost equivalent to format method in DateNumberFormat. 2284 * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat, 2285 * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative 2286 * date format test case, having local implementation is ~10% faster than using one in 2287 * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference. 2288 * 2289 * -Yoshito 2290 */ 2291 private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) { 2292 int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits; 2293 int index = limit - 1; 2294 while (true) { 2295 decimalBuf[index] = decDigits[(value % 10)]; 2296 value /= 10; 2297 if (index == 0 || value == 0) { 2298 break; 2299 } 2300 index--; 2301 } 2302 int padding = minDigits - (limit - index); 2303 while (padding > 0 && index > 0) { 2304 decimalBuf[--index] = decDigits[0]; 2305 padding--; 2306 } 2307 while (padding > 0) { 2308 // when pattern width is longer than decimalBuf, need extra 2309 // leading zeros - ticke#7595 2310 buf.append(decDigits[0]); 2311 padding--; 2312 } 2313 buf.append(decimalBuf, index, limit - index); 2314 } 2315 2316 /** 2317 * Formats a number with the specified minimum and maximum number of digits. 2318 * @stable ICU 2.0 2319 */ 2320 protected String zeroPaddingNumber(long value, int minDigits, int maxDigits) 2321 { 2322 numberFormat.setMinimumIntegerDigits(minDigits); 2323 numberFormat.setMaximumIntegerDigits(maxDigits); 2324 return numberFormat.format(value); 2325 } 2326 2327 /** 2328 * Format characters that indicate numeric fields always. 2329 */ 2330 private static final String NUMERIC_FORMAT_CHARS = "ADdFgHhKkmrSsuWwYy"; 2331 2332 /** 2333 * Format characters that indicate numeric fields when pattern lengh 2334 * is up to 2. 2335 */ 2336 private static final String NUMERIC_FORMAT_CHARS2 = "ceLMQq"; 2337 2338 /** 2339 * Return true if the given format character, occuring count 2340 * times, represents a numeric field. 2341 */ 2342 private static final boolean isNumeric(char formatChar, int count) { 2343 return NUMERIC_FORMAT_CHARS.indexOf(formatChar) >= 0 2344 || (count <= 2 && NUMERIC_FORMAT_CHARS2.indexOf(formatChar) >= 0); 2345 } 2346 2347 /** 2348 * Overrides DateFormat 2349 * @see DateFormat 2350 * @stable ICU 2.0 2351 */ 2352 @Override 2353 public void parse(String text, Calendar cal, ParsePosition parsePos) 2354 { 2355 TimeZone backupTZ = null; 2356 Calendar resultCal = null; 2357 if (cal != calendar && !cal.getType().equals(calendar.getType())) { 2358 // Different calendar type 2359 // We use the time/zone from the input calendar, but 2360 // do not use the input calendar for field calculation. 2361 calendar.setTimeInMillis(cal.getTimeInMillis()); 2362 backupTZ = calendar.getTimeZone(); 2363 calendar.setTimeZone(cal.getTimeZone()); 2364 resultCal = cal; 2365 cal = calendar; 2366 } 2367 2368 int pos = parsePos.getIndex(); 2369 if(pos < 0) { 2370 parsePos.setErrorIndex(0); 2371 return; 2372 } 2373 int start = pos; 2374 2375 // Hold the day period until everything else is parsed, because we need 2376 // the hour to interpret time correctly. 2377 // Using an one-element array for output parameter. 2378 Output<DayPeriodRules.DayPeriod> dayPeriod = new Output<DayPeriodRules.DayPeriod>(null); 2379 2380 Output<TimeType> tzTimeType = new Output<TimeType>(TimeType.UNKNOWN); 2381 boolean[] ambiguousYear = { false }; 2382 2383 // item index for the first numeric field within a contiguous numeric run 2384 int numericFieldStart = -1; 2385 // item length for the first numeric field within a contiguous numeric run 2386 int numericFieldLength = 0; 2387 // start index of numeric text run in the input text 2388 int numericStartPos = 0; 2389 2390 MessageFormat numericLeapMonthFormatter = null; 2391 if (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT) { 2392 numericLeapMonthFormatter = new MessageFormat(formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC], locale); 2393 } 2394 2395 Object[] items = getPatternItems(); 2396 int i = 0; 2397 while (i < items.length) { 2398 if (items[i] instanceof PatternItem) { 2399 // Handle pattern field 2400 PatternItem field = (PatternItem)items[i]; 2401 if (field.isNumeric) { 2402 // Handle fields within a run of abutting numeric fields. Take 2403 // the pattern "HHmmss" as an example. We will try to parse 2404 // 2/2/2 characters of the input text, then if that fails, 2405 // 1/2/2. We only adjust the width of the leftmost field; the 2406 // others remain fixed. This allows "123456" => 12:34:56, but 2407 // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we 2408 // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2. 2409 if (numericFieldStart == -1) { 2410 // check if this field is followed by abutting another numeric field 2411 if ((i + 1) < items.length 2412 && (items[i + 1] instanceof PatternItem) 2413 && ((PatternItem)items[i + 1]).isNumeric) { 2414 // record the first numeric field within a numeric text run 2415 numericFieldStart = i; 2416 numericFieldLength = field.length; 2417 numericStartPos = pos; 2418 } 2419 } 2420 } 2421 if (numericFieldStart != -1) { 2422 // Handle a numeric field within abutting numeric fields 2423 int len = field.length; 2424 if (numericFieldStart == i) { 2425 len = numericFieldLength; 2426 } 2427 2428 // Parse a numeric field 2429 pos = subParse(text, pos, field.type, len, 2430 true, false, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType); 2431 2432 if (pos < 0) { 2433 // If the parse fails anywhere in the numeric run, back up to the 2434 // start of the run and use shorter pattern length for the first 2435 // numeric field. 2436 --numericFieldLength; 2437 if (numericFieldLength == 0) { 2438 // can not make shorter any more 2439 parsePos.setIndex(start); 2440 parsePos.setErrorIndex(pos); 2441 if (backupTZ != null) { 2442 calendar.setTimeZone(backupTZ); 2443 } 2444 return; 2445 } 2446 i = numericFieldStart; 2447 pos = numericStartPos; 2448 continue; 2449 } 2450 2451 } else if (field.type != 'l') { // (SMALL LETTER L) obsolete pattern char just gets ignored 2452 // Handle a non-numeric field or a non-abutting numeric field 2453 numericFieldStart = -1; 2454 2455 int s = pos; 2456 pos = subParse(text, pos, field.type, field.length, 2457 false, true, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType, dayPeriod); 2458 2459 if (pos < 0) { 2460 if (pos == ISOSpecialEra) { 2461 // era not present, in special cases allow this to continue 2462 pos = s; 2463 2464 if (i+1 < items.length) { 2465 2466 String patl = null; 2467 // if it will cause a class cast exception to String, we can't use it 2468 try { 2469 patl = (String)items[i+1]; 2470 } catch(ClassCastException cce) { 2471 parsePos.setIndex(start); 2472 parsePos.setErrorIndex(s); 2473 if (backupTZ != null) { 2474 calendar.setTimeZone(backupTZ); 2475 } 2476 return; 2477 } 2478 2479 // get next item in pattern 2480 if(patl == null) 2481 patl = (String)items[i+1]; 2482 int plen = patl.length(); 2483 int idx=0; 2484 2485 // White space characters found in pattern. 2486 // Skip contiguous white spaces. 2487 while (idx < plen) { 2488 2489 char pch = patl.charAt(idx); 2490 if (PatternProps.isWhiteSpace(pch)) 2491 idx++; 2492 else 2493 break; 2494 } 2495 2496 // if next item in pattern is all whitespace, skip it 2497 if (idx == plen) { 2498 i++; 2499 } 2500 2501 } 2502 } else { 2503 parsePos.setIndex(start); 2504 parsePos.setErrorIndex(s); 2505 if (backupTZ != null) { 2506 calendar.setTimeZone(backupTZ); 2507 } 2508 return; 2509 } 2510 } 2511 2512 } 2513 } else { 2514 // Handle literal pattern text literal 2515 numericFieldStart = -1; 2516 boolean[] complete = new boolean[1]; 2517 pos = matchLiteral(text, pos, items, i, complete); 2518 if (!complete[0]) { 2519 // Set the position of mismatch 2520 parsePos.setIndex(start); 2521 parsePos.setErrorIndex(pos); 2522 if (backupTZ != null) { 2523 calendar.setTimeZone(backupTZ); 2524 } 2525 return; 2526 } 2527 } 2528 ++i; 2529 } 2530 2531 // Special hack for trailing "." after non-numeric field. 2532 if (pos < text.length()) { 2533 char extra = text.charAt(pos); 2534 if (extra == '.' && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && items.length != 0) { 2535 // only do if the last field is not numeric 2536 Object lastItem = items[items.length - 1]; 2537 if (lastItem instanceof PatternItem && !((PatternItem)lastItem).isNumeric) { 2538 pos++; // skip the extra "." 2539 } 2540 } 2541 } 2542 2543 // If dayPeriod is set, use it in conjunction with hour-of-day to determine am/pm. 2544 if (dayPeriod.value != null) { 2545 DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale()); 2546 2547 if (!cal.isSet(Calendar.HOUR) && !cal.isSet(Calendar.HOUR_OF_DAY)) { 2548 // If hour is not set, set time to the midpoint of current day period, overwriting 2549 // minutes if it's set. 2550 double midPoint = ruleSet.getMidPointForDayPeriod(dayPeriod.value); 2551 2552 // Truncate midPoint toward zero to get the hour. 2553 // Any leftover means it was a half-hour. 2554 int midPointHour = (int) midPoint; 2555 int midPointMinute = (midPoint - midPointHour) > 0 ? 30 : 0; 2556 2557 // No need to set am/pm because hour-of-day is set last therefore takes precedence. 2558 cal.set(Calendar.HOUR_OF_DAY, midPointHour); 2559 cal.set(Calendar.MINUTE, midPointMinute); 2560 } else { 2561 int hourOfDay; 2562 2563 if (cal.isSet(Calendar.HOUR_OF_DAY)) { // Hour is parsed in 24-hour format. 2564 hourOfDay = cal.get(Calendar.HOUR_OF_DAY); 2565 } else { // Hour is parsed in 12-hour format. 2566 hourOfDay = cal.get(Calendar.HOUR); 2567 // cal.get() turns 12 to 0 for 12-hour time; change 0 to 12 2568 // so 0 unambiguously means a 24-hour time from above. 2569 if (hourOfDay == 0) { hourOfDay = 12; } 2570 } 2571 assert(0 <= hourOfDay && hourOfDay <= 23); 2572 2573 2574 // If hour-of-day is 0 or 13 thru 23 then input time in unambiguously in 24-hour format. 2575 if (hourOfDay == 0 || (13 <= hourOfDay && hourOfDay <= 23)) { 2576 // Make hour-of-day take precedence over (hour + am/pm) by setting it again. 2577 cal.set(Calendar.HOUR_OF_DAY, hourOfDay); 2578 } else { 2579 // We have a 12-hour time and need to choose between am and pm. 2580 // Behave as if dayPeriod spanned 6 hours each way from its center point. 2581 // This will parse correctly for consistent time + period (e.g. 10 at night) as 2582 // well as provide a reasonable recovery for inconsistent time + period (e.g. 2583 // 9 in the afternoon). 2584 2585 // Assume current time is in the AM. 2586 // - Change 12 back to 0 for easier handling of 12am. 2587 // - Append minutes as fractional hours because e.g. 8:15 and 8:45 could be parsed 2588 // into different half-days if center of dayPeriod is at 14:30. 2589 // - cal.get(MINUTE) will return 0 if MINUTE is unset, which works. 2590 if (hourOfDay == 12) { hourOfDay = 0; } 2591 double currentHour = hourOfDay + cal.get(Calendar.MINUTE) / 60.0; 2592 double midPointHour = ruleSet.getMidPointForDayPeriod(dayPeriod.value); 2593 2594 double hoursAheadMidPoint = currentHour - midPointHour; 2595 2596 // Assume current time is in the AM. 2597 if (-6 <= hoursAheadMidPoint && hoursAheadMidPoint < 6) { 2598 // Assumption holds; set time as such. 2599 cal.set(Calendar.AM_PM, 0); 2600 } else { 2601 cal.set(Calendar.AM_PM, 1); 2602 } 2603 } 2604 } 2605 } 2606 2607 // At this point the fields of Calendar have been set. Calendar 2608 // will fill in default values for missing fields when the time 2609 // is computed. 2610 2611 parsePos.setIndex(pos); 2612 2613 // This part is a problem: When we call parsedDate.after, we compute the time. 2614 // Take the date April 3 2004 at 2:30 am. When this is first set up, the year 2615 // will be wrong if we're parsing a 2-digit year pattern. It will be 1904. 2616 // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am 2617 // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am 2618 // on that day. It is therefore parsed out to fields as 3:30 am. Then we 2619 // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is 2620 // a Saturday, so it can have a 2:30 am -- and it should. [LIU] 2621 /* 2622 Date parsedDate = cal.getTime(); 2623 if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) { 2624 cal.add(Calendar.YEAR, 100); 2625 parsedDate = cal.getTime(); 2626 } 2627 */ 2628 // Because of the above condition, save off the fields in case we need to readjust. 2629 // The procedure we use here is not particularly efficient, but there is no other 2630 // way to do this given the API restrictions present in Calendar. We minimize 2631 // inefficiency by only performing this computation when it might apply, that is, 2632 // when the two-digit year is equal to the start year, and thus might fall at the 2633 // front or the back of the default century. This only works because we adjust 2634 // the year correctly to start with in other cases -- see subParse(). 2635 try { 2636 TimeType tztype = tzTimeType.value; 2637 if (ambiguousYear[0] || tztype != TimeType.UNKNOWN) { 2638 // We need a copy of the fields, and we need to avoid triggering a call to 2639 // complete(), which will recalculate the fields. Since we can't access 2640 // the fields[] array in Calendar, we clone the entire object. This will 2641 // stop working if Calendar.clone() is ever rewritten to call complete(). 2642 Calendar copy; 2643 if (ambiguousYear[0]) { // the two-digit year == the default start year 2644 copy = (Calendar)cal.clone(); 2645 Date parsedDate = copy.getTime(); 2646 if (parsedDate.before(getDefaultCenturyStart())) { 2647 // We can't use add here because that does a complete() first. 2648 cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100); 2649 } 2650 } 2651 if (tztype != TimeType.UNKNOWN) { 2652 copy = (Calendar)cal.clone(); 2653 TimeZone tz = copy.getTimeZone(); 2654 BasicTimeZone btz = null; 2655 if (tz instanceof BasicTimeZone) { 2656 btz = (BasicTimeZone)tz; 2657 } 2658 2659 // Get local millis 2660 copy.set(Calendar.ZONE_OFFSET, 0); 2661 copy.set(Calendar.DST_OFFSET, 0); 2662 long localMillis = copy.getTimeInMillis(); 2663 2664 // Make sure parsed time zone type (Standard or Daylight) 2665 // matches the rule used by the parsed time zone. 2666 int[] offsets = new int[2]; 2667 if (btz != null) { 2668 if (tztype == TimeType.STANDARD) { 2669 btz.getOffsetFromLocal(localMillis, 2670 BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets); 2671 } else { 2672 btz.getOffsetFromLocal(localMillis, 2673 BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets); 2674 } 2675 } else { 2676 // No good way to resolve ambiguous time at transition, 2677 // but following code work in most case. 2678 tz.getOffset(localMillis, true, offsets); 2679 2680 if (tztype == TimeType.STANDARD && offsets[1] != 0 2681 || tztype == TimeType.DAYLIGHT && offsets[1] == 0) { 2682 // Roll back one day and try it again. 2683 // Note: This code assumes 1. timezone transition only happens 2684 // once within 24 hours at max 2685 // 2. the difference of local offsets at the transition is 2686 // less than 24 hours. 2687 tz.getOffset(localMillis - (24*60*60*1000), true, offsets); 2688 } 2689 } 2690 2691 // Now, compare the results with parsed type, either standard or 2692 // daylight saving time 2693 int resolvedSavings = offsets[1]; 2694 if (tztype == TimeType.STANDARD) { 2695 if (offsets[1] != 0) { 2696 // Override DST_OFFSET = 0 in the result calendar 2697 resolvedSavings = 0; 2698 } 2699 } else { // tztype == TZTYPE_DST 2700 if (offsets[1] == 0) { 2701 if (btz != null) { 2702 long time = localMillis + offsets[0]; 2703 // We use the nearest daylight saving time rule. 2704 TimeZoneTransition beforeTrs, afterTrs; 2705 long beforeT = time, afterT = time; 2706 int beforeSav = 0, afterSav = 0; 2707 2708 // Search for DST rule before or on the time 2709 while (true) { 2710 beforeTrs = btz.getPreviousTransition(beforeT, true); 2711 if (beforeTrs == null) { 2712 break; 2713 } 2714 beforeT = beforeTrs.getTime() - 1; 2715 beforeSav = beforeTrs.getFrom().getDSTSavings(); 2716 if (beforeSav != 0) { 2717 break; 2718 } 2719 } 2720 2721 // Search for DST rule after the time 2722 while (true) { 2723 afterTrs = btz.getNextTransition(afterT, false); 2724 if (afterTrs == null) { 2725 break; 2726 } 2727 afterT = afterTrs.getTime(); 2728 afterSav = afterTrs.getTo().getDSTSavings(); 2729 if (afterSav != 0) { 2730 break; 2731 } 2732 } 2733 2734 if (beforeTrs != null && afterTrs != null) { 2735 if (time - beforeT > afterT - time) { 2736 resolvedSavings = afterSav; 2737 } else { 2738 resolvedSavings = beforeSav; 2739 } 2740 } else if (beforeTrs != null && beforeSav != 0) { 2741 resolvedSavings = beforeSav; 2742 } else if (afterTrs != null && afterSav != 0) { 2743 resolvedSavings = afterSav; 2744 } else { 2745 resolvedSavings = btz.getDSTSavings(); 2746 } 2747 } else { 2748 resolvedSavings = tz.getDSTSavings(); 2749 } 2750 if (resolvedSavings == 0) { 2751 // Final fallback 2752 resolvedSavings = millisPerHour; 2753 } 2754 } 2755 } 2756 cal.set(Calendar.ZONE_OFFSET, offsets[0]); 2757 cal.set(Calendar.DST_OFFSET, resolvedSavings); 2758 } 2759 } 2760 } 2761 // An IllegalArgumentException will be thrown by Calendar.getTime() 2762 // if any fields are out of range, e.g., MONTH == 17. 2763 catch (IllegalArgumentException e) { 2764 parsePos.setErrorIndex(pos); 2765 parsePos.setIndex(start); 2766 if (backupTZ != null) { 2767 calendar.setTimeZone(backupTZ); 2768 } 2769 return; 2770 } 2771 // Set the parsed result if local calendar is used 2772 // instead of the input calendar 2773 if (resultCal != null) { 2774 resultCal.setTimeZone(cal.getTimeZone()); 2775 resultCal.setTimeInMillis(cal.getTimeInMillis()); 2776 } 2777 // Restore the original time zone if required 2778 if (backupTZ != null) { 2779 calendar.setTimeZone(backupTZ); 2780 } 2781 } 2782 2783 /** 2784 * Matches text (starting at pos) with patl. Returns the new pos, and sets complete[0] 2785 * if it matched the entire text. Whitespace sequences are treated as singletons. 2786 * <p>If isLenient and if we fail to match the first time, some special hacks are put into place. 2787 * <ul><li>we are between date and time fields, then one or more whitespace characters 2788 * in the text are accepted instead.</li> 2789 * <ul><li>we are after a non-numeric field, and the text starts with a ".", we skip it.</li> 2790 * </ul> 2791 */ 2792 private int matchLiteral(String text, int pos, Object[] items, int itemIndex, boolean[] complete) { 2793 int originalPos = pos; 2794 String patternLiteral = (String)items[itemIndex]; 2795 int plen = patternLiteral.length(); 2796 int tlen = text.length(); 2797 int idx = 0; 2798 while (idx < plen && pos < tlen) { 2799 char pch = patternLiteral.charAt(idx); 2800 char ich = text.charAt(pos); 2801 if (PatternProps.isWhiteSpace(pch) 2802 && PatternProps.isWhiteSpace(ich)) { 2803 // White space characters found in both patten and input. 2804 // Skip contiguous white spaces. 2805 while ((idx + 1) < plen && 2806 PatternProps.isWhiteSpace(patternLiteral.charAt(idx + 1))) { 2807 ++idx; 2808 } 2809 while ((pos + 1) < tlen && 2810 PatternProps.isWhiteSpace(text.charAt(pos + 1))) { 2811 ++pos; 2812 } 2813 } else if (pch != ich) { 2814 if (ich == '.' && pos == originalPos && 0 < itemIndex && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) { 2815 Object before = items[itemIndex-1]; 2816 if (before instanceof PatternItem) { 2817 boolean isNumeric = ((PatternItem) before).isNumeric; 2818 if (!isNumeric) { 2819 ++pos; // just update pos 2820 continue; 2821 } 2822 } 2823 } else if ((pch == ' ' || pch == '.') && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) { 2824 ++idx; 2825 continue; 2826 } else if (pos != originalPos && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH)) { 2827 ++idx; 2828 continue; 2829 } 2830 break; 2831 } 2832 ++idx; 2833 ++pos; 2834 } 2835 complete[0] = idx == plen; 2836 if (complete[0] == false && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE) && 0 < itemIndex && itemIndex < items.length - 1) { 2837 // If fully lenient, accept " "* for any text between a date and a time field 2838 // We don't go more lenient, because we don't want to accept "12/31" for "12:31". 2839 // People may be trying to parse for a date, then for a time. 2840 if (originalPos < tlen) { 2841 Object before = items[itemIndex-1]; 2842 Object after = items[itemIndex+1]; 2843 if (before instanceof PatternItem && after instanceof PatternItem) { 2844 char beforeType = ((PatternItem) before).type; 2845 char afterType = ((PatternItem) after).type; 2846 if (DATE_PATTERN_TYPE.contains(beforeType) != DATE_PATTERN_TYPE.contains(afterType)) { 2847 int newPos = originalPos; 2848 while (true) { 2849 char ich = text.charAt(newPos); 2850 if (!PatternProps.isWhiteSpace(ich)) { 2851 break; 2852 } 2853 ++newPos; 2854 } 2855 complete[0] = newPos > originalPos; 2856 pos = newPos; 2857 } 2858 } 2859 } 2860 } 2861 return pos; 2862 } 2863 2864 static final UnicodeSet DATE_PATTERN_TYPE = new UnicodeSet("[GyYuUQqMLlwWd]").freeze(); 2865 2866 /** 2867 * Attempt to match the text at a given position against an array of 2868 * strings. Since multiple strings in the array may match (for 2869 * example, if the array contains "a", "ab", and "abc", all will match 2870 * the input string "abcd") the longest match is returned. As a side 2871 * effect, the given field of <code>cal</code> is set to the index 2872 * of the best match, if there is one. 2873 * @param text the time text being parsed. 2874 * @param start where to start parsing. 2875 * @param field the date field being parsed. 2876 * @param data the string array to parsed. 2877 * @param cal 2878 * @return the new start position if matching succeeded; a negative 2879 * number indicating matching failure, otherwise. As a side effect, 2880 * sets the <code>cal</code> field <code>field</code> to the index 2881 * of the best match, if matching succeeded. 2882 * @stable ICU 2.0 2883 */ 2884 protected int matchString(String text, int start, int field, String[] data, Calendar cal) 2885 { 2886 return matchString(text, start, field, data, null, cal); 2887 } 2888 2889 /** 2890 * Attempt to match the text at a given position against an array of 2891 * strings. Since multiple strings in the array may match (for 2892 * example, if the array contains "a", "ab", and "abc", all will match 2893 * the input string "abcd") the longest match is returned. As a side 2894 * effect, the given field of <code>cal</code> is set to the index 2895 * of the best match, if there is one. 2896 * @param text the time text being parsed. 2897 * @param start where to start parsing. 2898 * @param field the date field being parsed. 2899 * @param data the string array to parsed. 2900 * @param monthPattern leap month pattern, or null if none. 2901 * @param cal 2902 * @return the new start position if matching succeeded; a negative 2903 * number indicating matching failure, otherwise. As a side effect, 2904 * sets the <code>cal</code> field <code>field</code> to the index 2905 * of the best match, if matching succeeded. 2906 * @internal 2907 * @deprecated This API is ICU internal only. 2908 */ 2909 @Deprecated 2910 private int matchString(String text, int start, int field, String[] data, String monthPattern, Calendar cal) 2911 { 2912 int i = 0; 2913 int count = data.length; 2914 2915 if (field == Calendar.DAY_OF_WEEK) i = 1; 2916 2917 // There may be multiple strings in the data[] array which begin with 2918 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). 2919 // We keep track of the longest match, and return that. Note that this 2920 // unfortunately requires us to test all array elements. 2921 int bestMatchLength = 0, bestMatch = -1; 2922 int isLeapMonth = 0; 2923 int matchLength = 0; 2924 2925 for (; i<count; ++i) 2926 { 2927 int length = data[i].length(); 2928 // Always compare if we have no match yet; otherwise only compare 2929 // against potentially better matches (longer strings). 2930 if (length > bestMatchLength && 2931 (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) 2932 { 2933 bestMatch = i; 2934 bestMatchLength = matchLength; 2935 isLeapMonth = 0; 2936 } 2937 if (monthPattern != null) { 2938 String leapMonthName = SimpleFormatterImpl.formatRawPattern( 2939 monthPattern, 1, 1, data[i]); 2940 length = leapMonthName.length(); 2941 if (length > bestMatchLength && 2942 (matchLength = regionMatchesWithOptionalDot(text, start, leapMonthName, length)) >= 0) 2943 { 2944 bestMatch = i; 2945 bestMatchLength = matchLength; 2946 isLeapMonth = 1; 2947 } 2948 } 2949 } 2950 if (bestMatch >= 0) 2951 { 2952 if (field >= 0) { 2953 if (field == Calendar.YEAR) { 2954 bestMatch++; // only get here for cyclic year names, which match 1-based years 1-60 2955 } 2956 cal.set(field, bestMatch); 2957 if (monthPattern != null) { 2958 cal.set(Calendar.IS_LEAP_MONTH, isLeapMonth); 2959 } 2960 } 2961 return start + bestMatchLength; 2962 } 2963 return ~start; 2964 } 2965 2966 private int regionMatchesWithOptionalDot(String text, int start, String data, int length) { 2967 boolean matches = text.regionMatches(true, start, data, 0, length); 2968 if (matches) { 2969 return length; 2970 } 2971 if (data.length() > 0 && data.charAt(data.length()-1) == '.') { 2972 if (text.regionMatches(true, start, data, 0, length-1)) { 2973 return length - 1; 2974 } 2975 } 2976 return -1; 2977 } 2978 2979 /** 2980 * Attempt to match the text at a given position against an array of quarter 2981 * strings. Since multiple strings in the array may match (for 2982 * example, if the array contains "a", "ab", and "abc", all will match 2983 * the input string "abcd") the longest match is returned. As a side 2984 * effect, the given field of <code>cal</code> is set to the index 2985 * of the best match, if there is one. 2986 * @param text the time text being parsed. 2987 * @param start where to start parsing. 2988 * @param field the date field being parsed. 2989 * @param data the string array to parsed. 2990 * @return the new start position if matching succeeded; a negative 2991 * number indicating matching failure, otherwise. As a side effect, 2992 * sets the <code>cal</code> field <code>field</code> to the index 2993 * of the best match, if matching succeeded. 2994 * @stable ICU 2.0 2995 */ 2996 protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal) 2997 { 2998 int i = 0; 2999 int count = data.length; 3000 3001 // There may be multiple strings in the data[] array which begin with 3002 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). 3003 // We keep track of the longest match, and return that. Note that this 3004 // unfortunately requires us to test all array elements. 3005 int bestMatchLength = 0, bestMatch = -1; 3006 int matchLength = 0; 3007 for (; i<count; ++i) { 3008 int length = data[i].length(); 3009 // Always compare if we have no match yet; otherwise only compare 3010 // against potentially better matches (longer strings). 3011 if (length > bestMatchLength && 3012 (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) { 3013 3014 bestMatch = i; 3015 bestMatchLength = matchLength; 3016 } 3017 } 3018 3019 if (bestMatch >= 0) { 3020 cal.set(field, bestMatch * 3); 3021 return start + bestMatchLength; 3022 } 3023 3024 return -start; 3025 } 3026 3027 /** 3028 * Similar to matchQuarterString but customized for day periods. 3029 */ 3030 private int matchDayPeriodString(String text, int start, String[] data, int dataLength, 3031 Output<DayPeriodRules.DayPeriod> dayPeriod) 3032 { 3033 int bestMatchLength = 0, bestMatch = -1; 3034 int matchLength = 0; 3035 for (int i = 0; i < dataLength; ++i) { 3036 // Only try matching if the string exists. 3037 if (data[i] != null) { 3038 int length = data[i].length(); 3039 if (length > bestMatchLength && 3040 (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) { 3041 bestMatch = i; 3042 bestMatchLength = matchLength; 3043 } 3044 } 3045 } 3046 3047 if (bestMatch >= 0) { 3048 dayPeriod.value = DayPeriodRules.DayPeriod.VALUES[bestMatch]; 3049 return start + bestMatchLength; 3050 } 3051 3052 return -start; 3053 } 3054 3055 /** 3056 * Protected method that converts one field of the input string into a 3057 * numeric field value in <code>cal</code>. Returns -start (for 3058 * ParsePosition) if failed. Subclasses may override this method to 3059 * modify or add parsing capabilities. 3060 * @param text the time text to be parsed. 3061 * @param start where to start parsing. 3062 * @param ch the pattern character for the date field text to be parsed. 3063 * @param count the count of a pattern character. 3064 * @param obeyCount if true, then the next field directly abuts this one, 3065 * and we should use the count to know when to stop parsing. 3066 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] 3067 * is true, then a two-digit year was parsed and may need to be readjusted. 3068 * @param cal 3069 * @return the new start position if matching succeeded; a negative 3070 * number indicating matching failure, otherwise. As a side effect, 3071 * set the appropriate field of <code>cal</code> with the parsed 3072 * value. 3073 * @stable ICU 2.0 3074 */ 3075 protected int subParse(String text, int start, char ch, int count, 3076 boolean obeyCount, boolean allowNegative, 3077 boolean[] ambiguousYear, Calendar cal) 3078 { 3079 return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null); 3080 } 3081 3082 /** 3083 * Overloading to provide default argument (null) for day period. 3084 */ 3085 private int subParse(String text, int start, char ch, int count, 3086 boolean obeyCount, boolean allowNegative, 3087 boolean[] ambiguousYear, Calendar cal, 3088 MessageFormat numericLeapMonthFormatter, Output<TimeType> tzTimeType) { 3089 return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null, null); 3090 } 3091 3092 /** 3093 * Protected method that converts one field of the input string into a 3094 * numeric field value in <code>cal</code>. Returns -start (for 3095 * ParsePosition) if failed. Subclasses may override this method to 3096 * modify or add parsing capabilities. 3097 * @param text the time text to be parsed. 3098 * @param start where to start parsing. 3099 * @param ch the pattern character for the date field text to be parsed. 3100 * @param count the count of a pattern character. 3101 * @param obeyCount if true, then the next field directly abuts this one, 3102 * and we should use the count to know when to stop parsing. 3103 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] 3104 * is true, then a two-digit year was parsed and may need to be readjusted. 3105 * @param cal 3106 * @param numericLeapMonthFormatter if non-null, used to parse numeric leap months. 3107 * @param tzTimeType the type of parsed time zone - standard, daylight or unknown (output). 3108 * This parameter can be null if caller does not need the information. 3109 * @return the new start position if matching succeeded; a negative 3110 * number indicating matching failure, otherwise. As a side effect, 3111 * set the appropriate field of <code>cal</code> with the parsed 3112 * value. 3113 * @internal 3114 * @deprecated This API is ICU internal only. 3115 */ 3116 @Deprecated 3117 @SuppressWarnings("fallthrough") 3118 private int subParse(String text, int start, char ch, int count, 3119 boolean obeyCount, boolean allowNegative, 3120 boolean[] ambiguousYear, Calendar cal, 3121 MessageFormat numericLeapMonthFormatter, Output<TimeType> tzTimeType, 3122 Output<DayPeriodRules.DayPeriod> dayPeriod) 3123 { 3124 Number number = null; 3125 NumberFormat currentNumberFormat = null; 3126 int value = 0; 3127 int i; 3128 ParsePosition pos = new ParsePosition(0); 3129 3130 int patternCharIndex = getIndexFromChar(ch); 3131 if (patternCharIndex == -1) { 3132 return ~start; 3133 } 3134 3135 currentNumberFormat = getNumberFormat(ch); 3136 3137 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; // -1 if irrelevant 3138 3139 if (numericLeapMonthFormatter != null) { 3140 numericLeapMonthFormatter.setFormatByArgumentIndex(0, currentNumberFormat); 3141 } 3142 boolean isChineseCalendar = ( cal.getType().equals("chinese") || cal.getType().equals("dangi") ); 3143 3144 // If there are any spaces here, skip over them. If we hit the end 3145 // of the string, then fail. 3146 for (;;) { 3147 if (start >= text.length()) { 3148 return ~start; 3149 } 3150 int c = UTF16.charAt(text, start); 3151 if (!UCharacter.isUWhiteSpace(c) || !PatternProps.isWhiteSpace(c)) { 3152 break; 3153 } 3154 start += UTF16.getCharCount(c); 3155 } 3156 pos.setIndex(start); 3157 3158 // We handle a few special cases here where we need to parse 3159 // a number value. We handle further, more generic cases below. We need 3160 // to handle some of them here because some fields require extra processing on 3161 // the parsed value. 3162 if (patternCharIndex == 4 /*'k' HOUR_OF_DAY1_FIELD*/ || 3163 patternCharIndex == 15 /*'h' HOUR1_FIELD*/ || 3164 (patternCharIndex == 2 /*'M' MONTH_FIELD*/ && count <= 2) || 3165 patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ || 3166 patternCharIndex == 19 /*'e' DOW_LOCAL*/ || 3167 patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ || 3168 patternCharIndex == 1 /*'y' YEAR */ || patternCharIndex == 18 /*'Y' YEAR_WOY */ || 3169 patternCharIndex == 30 /*'U' YEAR_NAME_FIELD, falls back to numeric */ || 3170 (patternCharIndex == 0 /*'G' ERA */ && isChineseCalendar) || 3171 patternCharIndex == 27 /* 'Q' - QUARTER*/ || 3172 patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/ || 3173 patternCharIndex == 8 /*'S' FRACTIONAL_SECOND */ ) 3174 { 3175 // It would be good to unify this with the obeyCount logic below, 3176 // but that's going to be difficult. 3177 3178 boolean parsedNumericLeapMonth = false; 3179 if (numericLeapMonthFormatter != null && (patternCharIndex == 2 || patternCharIndex == 26)) { 3180 // First see if we can parse month number with leap month pattern 3181 Object[] args = numericLeapMonthFormatter.parse(text, pos); 3182 if (args != null && pos.getIndex() > start && (args[0] instanceof Number)) { 3183 parsedNumericLeapMonth = true; 3184 number = (Number)args[0]; 3185 cal.set(Calendar.IS_LEAP_MONTH, 1); 3186 } else { 3187 pos.setIndex(start); 3188 cal.set(Calendar.IS_LEAP_MONTH, 0); 3189 } 3190 } 3191 3192 if (!parsedNumericLeapMonth) { 3193 if (obeyCount) { 3194 if ((start+count) > text.length()) { 3195 return ~start; 3196 } 3197 number = parseInt(text, count, pos, allowNegative,currentNumberFormat); 3198 } else { 3199 number = parseInt(text, pos, allowNegative,currentNumberFormat); 3200 } 3201 if (number == null && !allowNumericFallback(patternCharIndex)) { 3202 // only return if pattern is NOT one that allows numeric fallback 3203 return ~start; 3204 } 3205 } 3206 3207 if (number != null) { 3208 value = number.intValue(); 3209 } 3210 } 3211 3212 switch (patternCharIndex) 3213 { 3214 case 0: // 'G' - ERA 3215 if ( isChineseCalendar ) { 3216 // Numeric era handling moved from ChineseDateFormat, 3217 // If we didn't have a number, already returned -start above 3218 cal.set(Calendar.ERA, value); 3219 return pos.getIndex(); 3220 } 3221 int ps = 0; 3222 if (count == 5) { 3223 ps = matchString(text, start, Calendar.ERA, formatData.narrowEras, null, cal); 3224 } else if (count == 4) { 3225 ps = matchString(text, start, Calendar.ERA, formatData.eraNames, null, cal); 3226 } else { 3227 ps = matchString(text, start, Calendar.ERA, formatData.eras, null, cal); 3228 } 3229 3230 // check return position, if it equals -start, then matchString error 3231 // special case the return code so we don't necessarily fail out until we 3232 // verify no year information also 3233 if (ps == ~start) 3234 ps = ISOSpecialEra; 3235 3236 return ps; 3237 3238 case 1: // 'y' - YEAR 3239 case 18: // 'Y' - YEAR_WOY 3240 // If there are 3 or more YEAR pattern characters, this indicates 3241 // that the year value is to be treated literally, without any 3242 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise 3243 // we made adjustments to place the 2-digit year in the proper 3244 // century, for parsed strings from "00" to "99". Any other string 3245 // is treated literally: "2250", "-1", "1", "002". 3246 /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/ 3247 /* Skip this for Chinese calendar, moved from ChineseDateFormat */ 3248 if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) && value < 1000 ) { 3249 value += HEBREW_CAL_CUR_MILLENIUM_START_YEAR; 3250 } else if (count == 2 && countDigits(text, start, pos.getIndex()) == 2 && cal.haveDefaultCentury()) { 3251 // Assume for example that the defaultCenturyStart is 6/18/1903. 3252 // This means that two-digit years will be forced into the range 3253 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 3254 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond 3255 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the 3256 // other fields specify a date before 6/18, or 1903 if they specify a 3257 // date afterwards. As a result, 03 is an ambiguous year. All other 3258 // two-digit years are unambiguous. 3259 int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100; 3260 ambiguousYear[0] = value == ambiguousTwoDigitYear; 3261 value += (getDefaultCenturyStartYear()/100)*100 + 3262 (value < ambiguousTwoDigitYear ? 100 : 0); 3263 } 3264 cal.set(field, value); 3265 3266 // Delayed checking for adjustment of Hebrew month numbers in non-leap years. 3267 if (DelayedHebrewMonthCheck) { 3268 if (!HebrewCalendar.isLeapYear(value)) { 3269 cal.add(Calendar.MONTH,1); 3270 } 3271 DelayedHebrewMonthCheck = false; 3272 } 3273 return pos.getIndex(); 3274 case 30: // 'U' - YEAR_NAME_FIELD 3275 if (formatData.shortYearNames != null) { 3276 int newStart = matchString(text, start, Calendar.YEAR, formatData.shortYearNames, null, cal); 3277 if (newStart > 0) { 3278 return newStart; 3279 } 3280 } 3281 if ( number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC) || formatData.shortYearNames == null || value > formatData.shortYearNames.length) ) { 3282 cal.set(Calendar.YEAR, value); 3283 return pos.getIndex(); 3284 } 3285 return ~start; 3286 case 2: // 'M' - MONTH 3287 case 26: // 'L' - STAND_ALONE_MONTH 3288 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { 3289 // i.e., M/MM, L/LL or lenient & have a number 3290 // Don't want to parse the month if it is a string 3291 // while pattern uses numeric style: M/MM, L/LL. 3292 // [We computed 'value' above.] 3293 cal.set(Calendar.MONTH, value - 1); 3294 // When parsing month numbers from the Hebrew Calendar, we might need 3295 // to adjust the month depending on whether or not it was a leap year. 3296 // We may or may not yet know what year it is, so might have to delay 3297 // checking until the year is parsed. 3298 if (cal.getType().equals("hebrew") && value >= 6) { 3299 if (cal.isSet(Calendar.YEAR)) { 3300 if (!HebrewCalendar.isLeapYear(cal.get(Calendar.YEAR))) { 3301 cal.set(Calendar.MONTH, value); 3302 } 3303 } else { 3304 DelayedHebrewMonthCheck = true; 3305 } 3306 } 3307 return pos.getIndex(); 3308 } else { 3309 // count >= 3 // i.e., MMM/MMMM or LLL/LLLL 3310 // Want to be able to parse both short and long forms. 3311 boolean haveMonthPat = (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT); 3312 // Try count == 4 first:, unless we're strict 3313 int newStart = 0; 3314 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3315 newStart = (patternCharIndex == 2)? 3316 matchString(text, start, Calendar.MONTH, formatData.months, 3317 (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_WIDE]: null, cal): 3318 matchString(text, start, Calendar.MONTH, formatData.standaloneMonths, 3319 (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_WIDE]: null, cal); 3320 if (newStart > 0) { 3321 return newStart; 3322 } 3323 } 3324 // count == 4 failed, now try count == 3 3325 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { 3326 return (patternCharIndex == 2)? 3327 matchString(text, start, Calendar.MONTH, formatData.shortMonths, 3328 (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_FORMAT_ABBREV]: null, cal): 3329 matchString(text, start, Calendar.MONTH, formatData.standaloneShortMonths, 3330 (haveMonthPat)? formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_STANDALONE_ABBREV]: null, cal); 3331 } 3332 return newStart; 3333 } 3334 case 4: // 'k' - HOUR_OF_DAY (1..24) 3335 // [We computed 'value' above.] 3336 if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) { 3337 value = 0; 3338 } 3339 cal.set(Calendar.HOUR_OF_DAY, value); 3340 return pos.getIndex(); 3341 case 8: // 'S' - FRACTIONAL_SECOND 3342 // Fractional seconds left-justify 3343 i = countDigits(text, start, pos.getIndex()); 3344 if (i < 3) { 3345 while (i < 3) { 3346 value *= 10; 3347 i++; 3348 } 3349 } else { 3350 int a = 1; 3351 while (i > 3) { 3352 a *= 10; 3353 i--; 3354 } 3355 value /= a; 3356 } 3357 cal.set(Calendar.MILLISECOND, value); 3358 return pos.getIndex(); 3359 case 19: // 'e' - DOW_LOCAL 3360 if(count <= 2 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) { 3361 // i.e. e/ee or lenient and have a number 3362 cal.set(field, value); 3363 return pos.getIndex(); 3364 } 3365 // else for eee-eeeeee, fall through to EEE-EEEEEE handling 3366 //$FALL-THROUGH$ 3367 case 9: { // 'E' - DAY_OF_WEEK 3368 // Want to be able to parse at least wide, abbrev, short, and narrow forms. 3369 int newStart = 0; 3370 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3371 if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.weekdays, null, cal)) > 0) { // try EEEE wide 3372 return newStart; 3373 } 3374 } 3375 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { 3376 if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shortWeekdays, null, cal)) > 0) { // try EEE abbrev 3377 return newStart; 3378 } 3379 } 3380 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) { 3381 if (formatData.shorterWeekdays != null) { 3382 if((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shorterWeekdays, null, cal)) > 0) { // try EEEEEE short 3383 return newStart; 3384 } 3385 } 3386 } 3387 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 5) { 3388 if (formatData.narrowWeekdays != null) { 3389 if((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.narrowWeekdays, null, cal)) > 0) { // try EEEEE narrow 3390 return newStart; 3391 } 3392 } 3393 } 3394 return newStart; 3395 } 3396 case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK 3397 if(count == 1 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) { 3398 // i.e. c or lenient and have a number 3399 cal.set(field, value); 3400 return pos.getIndex(); 3401 } 3402 // Want to be able to parse at least wide, abbrev, short forms. 3403 int newStart = 0; 3404 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3405 if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneWeekdays, null, cal)) > 0) { // try cccc wide 3406 return newStart; 3407 } 3408 } 3409 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { 3410 if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShortWeekdays, null, cal)) > 0) { // try ccc abbrev 3411 return newStart; 3412 } 3413 } 3414 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) { 3415 if (formatData.standaloneShorterWeekdays != null) { 3416 return matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShorterWeekdays, null, cal); // try cccccc short 3417 } 3418 } 3419 return newStart; 3420 } 3421 case 14: { // 'a' - AM_PM 3422 // Optionally try both wide/abbrev and narrow forms. 3423 // formatData.ampmsNarrow may be null when deserializing DateFormatSymbolsfrom old version, 3424 // in which case our only option is wide form 3425 int newStart = 0; 3426 // try wide/abbrev a-aaaa 3427 if(formatData.ampmsNarrow == null || count < 5 || getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH)) { 3428 if ((newStart = matchString(text, start, Calendar.AM_PM, formatData.ampms, null, cal)) > 0) { 3429 return newStart; 3430 } 3431 } 3432 // try narrow aaaaa 3433 if(formatData.ampmsNarrow != null && (count >= 5 || getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH))) { 3434 if ((newStart = matchString(text, start, Calendar.AM_PM, formatData.ampmsNarrow, null, cal)) > 0) { 3435 return newStart; 3436 } 3437 } 3438 // no matches for given options 3439 return ~start; 3440 } 3441 case 15: // 'h' - HOUR (1..12) 3442 // [We computed 'value' above.] 3443 if (value == cal.getLeastMaximum(Calendar.HOUR)+1) { 3444 value = 0; 3445 } 3446 cal.set(Calendar.HOUR, value); 3447 return pos.getIndex(); 3448 case 17: // 'z' - ZONE_OFFSET 3449 { 3450 Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.SPECIFIC_LONG; 3451 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); 3452 if (tz != null) { 3453 cal.setTimeZone(tz); 3454 return pos.getIndex(); 3455 } 3456 return ~start; 3457 } 3458 case 23: // 'Z' - TIMEZONE_RFC 3459 { 3460 Style style = (count < 4) ? Style.ISO_BASIC_LOCAL_FULL : ((count == 5) ? Style.ISO_EXTENDED_FULL : Style.LOCALIZED_GMT); 3461 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); 3462 if (tz != null) { 3463 cal.setTimeZone(tz); 3464 return pos.getIndex(); 3465 } 3466 return ~start; 3467 } 3468 case 24: // 'v' - TIMEZONE_GENERIC 3469 { 3470 // Note: 'v' only supports count 1 and 4 3471 Style style = (count < 4) ? Style.GENERIC_SHORT : Style.GENERIC_LONG; 3472 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); 3473 if (tz != null) { 3474 cal.setTimeZone(tz); 3475 return pos.getIndex(); 3476 } 3477 return ~start; 3478 } 3479 case 29: // 'V' - TIMEZONE_SPECIAL 3480 { 3481 Style style = null; 3482 switch (count) { 3483 case 1: 3484 style = Style.ZONE_ID_SHORT; 3485 break; 3486 case 2: 3487 style = Style.ZONE_ID; 3488 break; 3489 case 3: 3490 style = Style.EXEMPLAR_LOCATION; 3491 break; 3492 default: 3493 style = Style.GENERIC_LOCATION; 3494 break; 3495 } 3496 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); 3497 if (tz != null) { 3498 cal.setTimeZone(tz); 3499 return pos.getIndex(); 3500 } 3501 return ~start; 3502 } 3503 case 31: // 'O' - TIMEZONE_LOCALIZED_GMT_OFFSET 3504 { 3505 Style style = (count < 4) ? Style.LOCALIZED_GMT_SHORT : Style.LOCALIZED_GMT; 3506 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); 3507 if (tz != null) { 3508 cal.setTimeZone(tz); 3509 return pos.getIndex(); 3510 } 3511 return ~start; 3512 } 3513 case 32: // 'X' - TIMEZONE_ISO 3514 { 3515 Style style; 3516 switch (count) { 3517 case 1: 3518 style = Style.ISO_BASIC_SHORT; 3519 break; 3520 case 2: 3521 style = Style.ISO_BASIC_FIXED; 3522 break; 3523 case 3: 3524 style = Style.ISO_EXTENDED_FIXED; 3525 break; 3526 case 4: 3527 style = Style.ISO_BASIC_FULL; 3528 break; 3529 default: // count >= 5 3530 style = Style.ISO_EXTENDED_FULL; 3531 break; 3532 } 3533 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); 3534 if (tz != null) { 3535 cal.setTimeZone(tz); 3536 return pos.getIndex(); 3537 } 3538 return ~start; 3539 } 3540 case 33: // 'x' - TIMEZONE_ISO_LOCAL 3541 { 3542 Style style; 3543 switch (count) { 3544 case 1: 3545 style = Style.ISO_BASIC_LOCAL_SHORT; 3546 break; 3547 case 2: 3548 style = Style.ISO_BASIC_LOCAL_FIXED; 3549 break; 3550 case 3: 3551 style = Style.ISO_EXTENDED_LOCAL_FIXED; 3552 break; 3553 case 4: 3554 style = Style.ISO_BASIC_LOCAL_FULL; 3555 break; 3556 default: // count >= 5 3557 style = Style.ISO_EXTENDED_LOCAL_FULL; 3558 break; 3559 } 3560 TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType); 3561 if (tz != null) { 3562 cal.setTimeZone(tz); 3563 return pos.getIndex(); 3564 } 3565 return ~start; 3566 } 3567 case 27: // 'Q' - QUARTER 3568 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { 3569 // i.e., Q or QQ. or lenient & have number 3570 // Don't want to parse the quarter if it is a string 3571 // while pattern uses numeric style: Q or QQ. 3572 // [We computed 'value' above.] 3573 cal.set(Calendar.MONTH, (value - 1) * 3); 3574 return pos.getIndex(); 3575 } else { 3576 // count >= 3 // i.e., QQQ or QQQQ 3577 // Want to be able to parse both short and long forms. 3578 // Try count == 4 first: 3579 int newStart = 0; 3580 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3581 if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.quarters, cal)) > 0) { 3582 return newStart; 3583 } 3584 } 3585 // count == 4 failed, now try count == 3 3586 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { 3587 return matchQuarterString(text, start, Calendar.MONTH, 3588 formatData.shortQuarters, cal); 3589 } 3590 return newStart; 3591 } 3592 3593 case 28: // 'q' - STANDALONE QUARTER 3594 if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { 3595 // i.e., q or qq. or lenient & have number 3596 // Don't want to parse the quarter if it is a string 3597 // while pattern uses numeric style: q or qq. 3598 // [We computed 'value' above.] 3599 cal.set(Calendar.MONTH, (value - 1) * 3); 3600 return pos.getIndex(); 3601 } else { 3602 // count >= 3 // i.e., qqq or qqqq 3603 // Want to be able to parse both short and long forms. 3604 // Try count == 4 first: 3605 int newStart = 0; 3606 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3607 if((newStart = matchQuarterString(text, start, Calendar.MONTH, formatData.standaloneQuarters, cal)) > 0) { 3608 return newStart; 3609 } 3610 } 3611 // count == 4 failed, now try count == 3 3612 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { 3613 return matchQuarterString(text, start, Calendar.MONTH, 3614 formatData.standaloneShortQuarters, cal); 3615 } 3616 return newStart; 3617 } 3618 3619 case 37: // TIME SEPARATOR (no pattern character currently defined, we should 3620 // not get here but leave support in for future definition. 3621 { 3622 // Try matching a time separator. 3623 ArrayList<String> data = new ArrayList<String>(3); 3624 data.add(formatData.getTimeSeparatorString()); 3625 3626 // Add the default, if different from the locale. 3627 if (!formatData.getTimeSeparatorString().equals(DateFormatSymbols.DEFAULT_TIME_SEPARATOR)) { 3628 data.add(DateFormatSymbols.DEFAULT_TIME_SEPARATOR); 3629 } 3630 3631 // If lenient, add also the alternate, if different from the locale. 3632 if (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH) && 3633 !formatData.getTimeSeparatorString().equals(DateFormatSymbols.ALTERNATE_TIME_SEPARATOR)) { 3634 data.add(DateFormatSymbols.ALTERNATE_TIME_SEPARATOR); 3635 } 3636 3637 return matchString(text, start, -1 /* => nothing to set */, data.toArray(new String[0]), cal); 3638 } 3639 3640 case 35: // 'b' -- fixed day period (am/pm/midnight/noon) 3641 { 3642 int ampmStart = subParse(text, start, 'a', count, obeyCount, allowNegative, ambiguousYear, cal, 3643 numericLeapMonthFormatter, tzTimeType, dayPeriod); 3644 3645 if (ampmStart > 0) { 3646 return ampmStart; 3647 } else { 3648 int newStart = 0; 3649 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { 3650 if ((newStart = matchDayPeriodString( 3651 text, start, formatData.abbreviatedDayPeriods, 2, dayPeriod)) > 0) { 3652 return newStart; 3653 } 3654 } 3655 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3656 if ((newStart = matchDayPeriodString( 3657 text, start, formatData.wideDayPeriods, 2, dayPeriod)) > 0) { 3658 return newStart; 3659 } 3660 } 3661 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3662 if ((newStart = matchDayPeriodString( 3663 text, start, formatData.narrowDayPeriods, 2, dayPeriod)) > 0) { 3664 return newStart; 3665 } 3666 } 3667 3668 return newStart; 3669 } 3670 } 3671 3672 case 36: // 'B' -- flexible day period 3673 { 3674 int newStart = 0; 3675 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { 3676 if ((newStart = matchDayPeriodString( 3677 text, start, formatData.abbreviatedDayPeriods, 3678 formatData.abbreviatedDayPeriods.length, dayPeriod)) > 0) { 3679 return newStart; 3680 } 3681 } 3682 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3683 if ((newStart = matchDayPeriodString( 3684 text, start, formatData.wideDayPeriods, 3685 formatData.wideDayPeriods.length, dayPeriod)) > 0) { 3686 return newStart; 3687 } 3688 } 3689 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { 3690 if ((newStart = matchDayPeriodString( 3691 text, start, formatData.narrowDayPeriods, 3692 formatData.narrowDayPeriods.length, dayPeriod)) > 0) { 3693 return newStart; 3694 } 3695 } 3696 3697 return newStart; 3698 } 3699 3700 default: 3701 // case 3: // 'd' - DATE 3702 // case 5: // 'H' - HOUR_OF_DAY (0..23) 3703 // case 6: // 'm' - MINUTE 3704 // case 7: // 's' - SECOND 3705 // case 10: // 'D' - DAY_OF_YEAR 3706 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH 3707 // case 12: // 'w' - WEEK_OF_YEAR 3708 // case 13: // 'W' - WEEK_OF_MONTH 3709 // case 16: // 'K' - HOUR (0..11) 3710 // case 20: // 'u' - EXTENDED_YEAR 3711 // case 21: // 'g' - JULIAN_DAY 3712 // case 22: // 'A' - MILLISECONDS_IN_DAY 3713 // case 34: // 3714 3715 // Handle "generic" fields 3716 if (obeyCount) { 3717 if ((start+count) > text.length()) return -start; 3718 number = parseInt(text, count, pos, allowNegative,currentNumberFormat); 3719 } else { 3720 number = parseInt(text, pos, allowNegative,currentNumberFormat); 3721 } 3722 if (number != null) { 3723 if (patternCharIndex != DateFormat.RELATED_YEAR) { 3724 cal.set(field, number.intValue()); 3725 } else { 3726 cal.setRelatedYear(number.intValue()); 3727 } 3728 return pos.getIndex(); 3729 } 3730 return ~start; 3731 } 3732 } 3733 3734 /** 3735 * return true if the pattern specified by patternCharIndex is one that allows 3736 * numeric fallback regardless of actual pattern size. 3737 */ 3738 private boolean allowNumericFallback(int patternCharIndex) { 3739 if (patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ || 3740 patternCharIndex == 19 /*'e' DOW_LOCAL*/ || 3741 patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ || 3742 patternCharIndex == 30 /*'U' YEAR_NAME_FIELD*/ || 3743 patternCharIndex == 27 /* 'Q' - QUARTER*/ || 3744 patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/) { 3745 return true; 3746 } 3747 return false; 3748 } 3749 3750 /** 3751 * Parse an integer using numberFormat. This method is semantically 3752 * const, but actually may modify fNumberFormat. 3753 */ 3754 private Number parseInt(String text, 3755 ParsePosition pos, 3756 boolean allowNegative, 3757 NumberFormat fmt) { 3758 return parseInt(text, -1, pos, allowNegative, fmt); 3759 } 3760 3761 /** 3762 * Parse an integer using numberFormat up to maxDigits. 3763 */ 3764 private Number parseInt(String text, 3765 int maxDigits, 3766 ParsePosition pos, 3767 boolean allowNegative, 3768 NumberFormat fmt) { 3769 Number number; 3770 int oldPos = pos.getIndex(); 3771 if (allowNegative) { 3772 number = fmt.parse(text, pos); 3773 } else { 3774 // Invalidate negative numbers 3775 if (fmt instanceof DecimalFormat) { 3776 String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix(); 3777 ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX); 3778 number = fmt.parse(text, pos); 3779 ((DecimalFormat)fmt).setNegativePrefix(oldPrefix); 3780 } else { 3781 boolean dateNumberFormat = (fmt instanceof DateNumberFormat); 3782 if (dateNumberFormat) { 3783 ((DateNumberFormat)fmt).setParsePositiveOnly(true); 3784 } 3785 number = fmt.parse(text, pos); 3786 if (dateNumberFormat) { 3787 ((DateNumberFormat)fmt).setParsePositiveOnly(false); 3788 } 3789 } 3790 } 3791 if (maxDigits > 0) { 3792 // adjust the result to fit into 3793 // the maxDigits and move the position back 3794 int nDigits = pos.getIndex() - oldPos; 3795 if (nDigits > maxDigits) { 3796 double val = number.doubleValue(); 3797 nDigits -= maxDigits; 3798 while (nDigits > 0) { 3799 val /= 10; 3800 nDigits--; 3801 } 3802 pos.setIndex(oldPos + maxDigits); 3803 number = Integer.valueOf((int)val); 3804 } 3805 } 3806 return number; 3807 } 3808 3809 /** 3810 * Counts number of digit code points in the specified text. 3811 * 3812 * @param text input text 3813 * @param start start index, inclusive 3814 * @param end end index, exclusive 3815 * @return number of digits found in the text in the specified range. 3816 */ 3817 private static int countDigits(String text, int start, int end) { 3818 int numDigits = 0; 3819 int idx = start; 3820 while (idx < end) { 3821 int cp = text.codePointAt(idx); 3822 if (UCharacter.isDigit(cp)) { 3823 numDigits++; 3824 } 3825 idx += UCharacter.charCount(cp); 3826 } 3827 return numDigits; 3828 } 3829 3830 /** 3831 * Translate a pattern, mapping each character in the from string to the 3832 * corresponding character in the to string. 3833 */ 3834 private String translatePattern(String pat, String from, String to) { 3835 StringBuilder result = new StringBuilder(); 3836 boolean inQuote = false; 3837 for (int i = 0; i < pat.length(); ++i) { 3838 char c = pat.charAt(i); 3839 if (inQuote) { 3840 if (c == '\'') 3841 inQuote = false; 3842 } else { 3843 if (c == '\'') { 3844 inQuote = true; 3845 } else if (isSyntaxChar(c)) { 3846 int ci = from.indexOf(c); 3847 if (ci != -1) { 3848 c = to.charAt(ci); 3849 } 3850 // do not worry on translatepattern if the character is not listed 3851 // we do the validity check elsewhere 3852 } 3853 } 3854 result.append(c); 3855 } 3856 if (inQuote) { 3857 throw new IllegalArgumentException("Unfinished quote in pattern"); 3858 } 3859 return result.toString(); 3860 } 3861 3862 /** 3863 * Return a pattern string describing this date format. 3864 * @stable ICU 2.0 3865 */ 3866 public String toPattern() { 3867 return pattern; 3868 } 3869 3870 /** 3871 * Return a localized pattern string describing this date format. 3872 * <p> 3873 * <b>Note:</b> This implementation depends on {@link DateFormatSymbols#getLocalPatternChars()} 3874 * to get localized format pattern characters. ICU does not include 3875 * localized pattern character data, therefore, unless user sets localized 3876 * pattern characters manually, this method returns the same result as 3877 * {@link #toPattern()}. 3878 * 3879 * @stable ICU 2.0 3880 */ 3881 public String toLocalizedPattern() { 3882 return translatePattern(pattern, 3883 DateFormatSymbols.patternChars, 3884 formatData.localPatternChars); 3885 } 3886 3887 /** 3888 * Apply the given unlocalized pattern string to this date format. 3889 * @stable ICU 2.0 3890 */ 3891 public void applyPattern(String pat) 3892 { 3893 this.pattern = pat; 3894 parsePattern(); 3895 3896 setLocale(null, null); 3897 // reset parsed pattern items 3898 patternItems = null; 3899 } 3900 3901 /** 3902 * Apply the given localized pattern string to this date format. 3903 * @stable ICU 2.0 3904 */ 3905 public void applyLocalizedPattern(String pat) { 3906 this.pattern = translatePattern(pat, 3907 formatData.localPatternChars, 3908 DateFormatSymbols.patternChars); 3909 setLocale(null, null); 3910 } 3911 3912 /** 3913 * Gets the date/time formatting data. 3914 * @return a copy of the date-time formatting data associated 3915 * with this date-time formatter. 3916 * @stable ICU 2.0 3917 */ 3918 public DateFormatSymbols getDateFormatSymbols() 3919 { 3920 return (DateFormatSymbols)formatData.clone(); 3921 } 3922 3923 /** 3924 * Allows you to set the date/time formatting data. 3925 * @param newFormatSymbols the new symbols 3926 * @stable ICU 2.0 3927 */ 3928 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) 3929 { 3930 this.formatData = (DateFormatSymbols)newFormatSymbols.clone(); 3931 } 3932 3933 /** 3934 * Method for subclasses to access the DateFormatSymbols. 3935 * @stable ICU 2.0 3936 */ 3937 protected DateFormatSymbols getSymbols() { 3938 return formatData; 3939 } 3940 3941 /** 3942 * {@icu} Gets the time zone formatter which this date/time 3943 * formatter uses to format and parse a time zone. 3944 * 3945 * @return the time zone formatter which this date/time 3946 * formatter uses. 3947 * @stable ICU 49 3948 */ 3949 public TimeZoneFormat getTimeZoneFormat() { 3950 return tzFormat().freeze(); 3951 } 3952 3953 /** 3954 * {@icu} Allows you to set the time zone formatter. 3955 * 3956 * @param tzfmt the new time zone formatter 3957 * @stable ICU 49 3958 */ 3959 public void setTimeZoneFormat(TimeZoneFormat tzfmt) { 3960 if (tzfmt.isFrozen()) { 3961 // If frozen, use it as is. 3962 tzFormat = tzfmt; 3963 } else { 3964 // If not frozen, clone and freeze. 3965 tzFormat = tzfmt.cloneAsThawed().freeze(); 3966 } 3967 } 3968 3969 /** 3970 * Overrides Cloneable 3971 * @stable ICU 2.0 3972 */ 3973 @Override 3974 public Object clone() { 3975 SimpleDateFormat other = (SimpleDateFormat) super.clone(); 3976 other.formatData = (DateFormatSymbols) formatData.clone(); 3977 // We must create a new copy of work buffer used by 3978 // the fast numeric field format code. 3979 if (this.decimalBuf != null) { 3980 other.decimalBuf = new char[DECIMAL_BUF_SIZE]; 3981 } 3982 return other; 3983 } 3984 3985 /** 3986 * Override hashCode. 3987 * Generates the hash code for the SimpleDateFormat object 3988 * @stable ICU 2.0 3989 */ 3990 @Override 3991 public int hashCode() 3992 { 3993 return pattern.hashCode(); 3994 // just enough fields for a reasonable distribution 3995 } 3996 3997 /** 3998 * Override equals. 3999 * @stable ICU 2.0 4000 */ 4001 @Override 4002 public boolean equals(Object obj) 4003 { 4004 if (!super.equals(obj)) return false; // super does class check 4005 SimpleDateFormat that = (SimpleDateFormat) obj; 4006 return (pattern.equals(that.pattern) 4007 && formatData.equals(that.formatData)); 4008 } 4009 4010 /** 4011 * Override writeObject. 4012 * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectOutputStream.html 4013 */ 4014 private void writeObject(ObjectOutputStream stream) throws IOException{ 4015 if (defaultCenturyStart == null) { 4016 // if defaultCenturyStart is not yet initialized, 4017 // calculate and set value before serialization. 4018 initializeDefaultCenturyStart(defaultCenturyBase); 4019 } 4020 initializeTimeZoneFormat(false); 4021 stream.defaultWriteObject(); 4022 stream.writeInt(getContext(DisplayContext.Type.CAPITALIZATION).value()); 4023 } 4024 4025 /** 4026 * Override readObject. 4027 * See http://docs.oracle.com/javase/6/docs/api/java/io/ObjectInputStream.html 4028 */ 4029 private void readObject(ObjectInputStream stream) 4030 throws IOException, ClassNotFoundException { 4031 stream.defaultReadObject(); 4032 int capitalizationSettingValue = (serialVersionOnStream > 1)? stream.readInt(): -1; 4033 ///CLOVER:OFF 4034 // don't have old serial data to test with 4035 if (serialVersionOnStream < 1) { 4036 // didn't have defaultCenturyStart field 4037 defaultCenturyBase = System.currentTimeMillis(); 4038 } 4039 ///CLOVER:ON 4040 else { 4041 // fill in dependent transient field 4042 parseAmbiguousDatesAsAfter(defaultCenturyStart); 4043 } 4044 serialVersionOnStream = currentSerialVersion; 4045 locale = getLocale(ULocale.VALID_LOCALE); 4046 if (locale == null) { 4047 // ICU4J 3.6 or older versions did not have UFormat locales 4048 // in the serialized data. This is just for preventing the 4049 // worst case scenario... 4050 locale = ULocale.getDefault(Category.FORMAT); 4051 } 4052 4053 initLocalZeroPaddingNumberFormat(); 4054 4055 setContext(DisplayContext.CAPITALIZATION_NONE); 4056 if (capitalizationSettingValue >= 0) { 4057 for (DisplayContext context: DisplayContext.values()) { 4058 if (context.value() == capitalizationSettingValue) { 4059 setContext(context); 4060 break; 4061 } 4062 } 4063 } 4064 4065 // if serialized pre-56 update & turned off partial match switch to new enum value 4066 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH) == false) { 4067 setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH, false); 4068 } 4069 4070 parsePattern(); 4071 } 4072 4073 /** 4074 * Format the object to an attributed string, and return the corresponding iterator 4075 * Overrides superclass method. 4076 * 4077 * @param obj The object to format 4078 * @return <code>AttributedCharacterIterator</code> describing the formatted value. 4079 * 4080 * @stable ICU 3.8 4081 */ 4082 @Override 4083 public AttributedCharacterIterator formatToCharacterIterator(Object obj) { 4084 Calendar cal = calendar; 4085 if (obj instanceof Calendar) { 4086 cal = (Calendar)obj; 4087 } else if (obj instanceof Date) { 4088 calendar.setTime((Date)obj); 4089 } else if (obj instanceof Number) { 4090 calendar.setTimeInMillis(((Number)obj).longValue()); 4091 } else { 4092 throw new IllegalArgumentException("Cannot format given Object as a Date"); 4093 } 4094 StringBuffer toAppendTo = new StringBuffer(); 4095 FieldPosition pos = new FieldPosition(0); 4096 List<FieldPosition> attributes = new ArrayList<FieldPosition>(); 4097 format(cal, getContext(DisplayContext.Type.CAPITALIZATION), toAppendTo, pos, attributes); 4098 4099 AttributedString as = new AttributedString(toAppendTo.toString()); 4100 4101 // add DateFormat field attributes to the AttributedString 4102 for (int i = 0; i < attributes.size(); i++) { 4103 FieldPosition fp = attributes.get(i); 4104 Format.Field attribute = fp.getFieldAttribute(); 4105 as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex()); 4106 } 4107 // return the CharacterIterator from AttributedString 4108 return as.getIterator(); 4109 } 4110 4111 /** 4112 * Get the locale of this simple date formatter. 4113 * It is package accessible. also used in DateIntervalFormat. 4114 * 4115 * @return locale in this simple date formatter 4116 */ 4117 ULocale getLocale() 4118 { 4119 return locale; 4120 } 4121 4122 4123 4124 /** 4125 * Check whether the 'field' is smaller than all the fields covered in 4126 * pattern, return true if it is. 4127 * The sequence of calendar field, 4128 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,... 4129 * @param field the calendar field need to check against 4130 * @return true if the 'field' is smaller than all the fields 4131 * covered in pattern. false otherwise. 4132 */ 4133 4134 boolean isFieldUnitIgnored(int field) { 4135 return isFieldUnitIgnored(pattern, field); 4136 } 4137 4138 4139 /* 4140 * Check whether the 'field' is smaller than all the fields covered in 4141 * pattern, return true if it is. 4142 * The sequence of calendar field, 4143 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,... 4144 * @param pattern the pattern to check against 4145 * @param field the calendar field need to check against 4146 * @return true if the 'field' is smaller than all the fields 4147 * covered in pattern. false otherwise. 4148 */ 4149 static boolean isFieldUnitIgnored(String pattern, int field) { 4150 int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field]; 4151 int level; 4152 char ch; 4153 boolean inQuote = false; 4154 char prevCh = 0; 4155 int count = 0; 4156 4157 for (int i = 0; i < pattern.length(); ++i) { 4158 ch = pattern.charAt(i); 4159 if (ch != prevCh && count > 0) { 4160 level = getLevelFromChar(prevCh); 4161 if (fieldLevel <= level) { 4162 return false; 4163 } 4164 count = 0; 4165 } 4166 if (ch == '\'') { 4167 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') { 4168 ++i; 4169 } else { 4170 inQuote = ! inQuote; 4171 } 4172 } else if (!inQuote && isSyntaxChar(ch)) { 4173 prevCh = ch; 4174 ++count; 4175 } 4176 } 4177 if (count > 0) { 4178 // last item 4179 level = getLevelFromChar(prevCh); 4180 if (fieldLevel <= level) { 4181 return false; 4182 } 4183 } 4184 return true; 4185 } 4186 4187 4188 /** 4189 * Format date interval by algorithm. 4190 * It is supposed to be used only by CLDR survey tool. 4191 * 4192 * @param fromCalendar calendar set to the from date in date interval 4193 * to be formatted into date interval stirng 4194 * @param toCalendar calendar set to the to date in date interval 4195 * to be formatted into date interval stirng 4196 * @param appendTo Output parameter to receive result. 4197 * Result is appended to existing contents. 4198 * @param pos On input: an alignment field, if desired. 4199 * On output: the offsets of the alignment field. 4200 * @exception IllegalArgumentException when there is non-recognized 4201 * pattern letter 4202 * @return Reference to 'appendTo' parameter. 4203 * @internal 4204 * @deprecated This API is ICU internal only. 4205 */ 4206 @Deprecated 4207 public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar, 4208 Calendar toCalendar, 4209 StringBuffer appendTo, 4210 FieldPosition pos) 4211 throws IllegalArgumentException 4212 { 4213 // not support different calendar types and time zones 4214 if ( !fromCalendar.isEquivalentTo(toCalendar) ) { 4215 throw new IllegalArgumentException("can not format on two different calendars"); 4216 } 4217 4218 Object[] items = getPatternItems(); 4219 int diffBegin = -1; 4220 int diffEnd = -1; 4221 4222 /* look for different formatting string range */ 4223 // look for start of difference 4224 try { 4225 for (int i = 0; i < items.length; i++) { 4226 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) { 4227 diffBegin = i; 4228 break; 4229 } 4230 } 4231 4232 if ( diffBegin == -1 ) { 4233 // no difference, single date format 4234 return format(fromCalendar, appendTo, pos); 4235 } 4236 4237 // look for end of difference 4238 for (int i = items.length-1; i >= diffBegin; i--) { 4239 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) { 4240 diffEnd = i; 4241 break; 4242 } 4243 } 4244 } catch ( IllegalArgumentException e ) { 4245 throw new IllegalArgumentException(e.toString()); 4246 } 4247 4248 // full range is different 4249 if ( diffBegin == 0 && diffEnd == items.length-1 ) { 4250 format(fromCalendar, appendTo, pos); 4251 appendTo.append(" \u2013 "); // default separator 4252 format(toCalendar, appendTo, pos); 4253 return appendTo; 4254 } 4255 4256 4257 /* search for largest calendar field within the different range */ 4258 int highestLevel = 1000; 4259 for (int i = diffBegin; i <= diffEnd; i++) { 4260 if ( items[i] instanceof String) { 4261 continue; 4262 } 4263 PatternItem item = (PatternItem)items[i]; 4264 char ch = item.type; 4265 int patternCharIndex = getIndexFromChar(ch); 4266 if (patternCharIndex == -1) { 4267 throw new IllegalArgumentException("Illegal pattern character " + 4268 "'" + ch + "' in \"" + 4269 pattern + '"'); 4270 } 4271 4272 if ( patternCharIndex < highestLevel ) { 4273 highestLevel = patternCharIndex; 4274 } 4275 } 4276 4277 /* re-calculate diff range, including those calendar field which 4278 is in lower level than the largest calendar field covered 4279 in diff range calculated. */ 4280 try { 4281 for (int i = 0; i < diffBegin; i++) { 4282 if ( lowerLevel(items, i, highestLevel) ) { 4283 diffBegin = i; 4284 break; 4285 } 4286 } 4287 4288 4289 for (int i = items.length-1; i > diffEnd; i--) { 4290 if ( lowerLevel(items, i, highestLevel) ) { 4291 diffEnd = i; 4292 break; 4293 } 4294 } 4295 } catch ( IllegalArgumentException e ) { 4296 throw new IllegalArgumentException(e.toString()); 4297 } 4298 4299 4300 // full range is different 4301 if ( diffBegin == 0 && diffEnd == items.length-1 ) { 4302 format(fromCalendar, appendTo, pos); 4303 appendTo.append(" \u2013 "); // default separator 4304 format(toCalendar, appendTo, pos); 4305 return appendTo; 4306 } 4307 4308 4309 // formatting 4310 // Initialize 4311 pos.setBeginIndex(0); 4312 pos.setEndIndex(0); 4313 DisplayContext capSetting = getContext(DisplayContext.Type.CAPITALIZATION); 4314 4315 // formatting date 1 4316 for (int i = 0; i <= diffEnd; i++) { 4317 if (items[i] instanceof String) { 4318 appendTo.append((String)items[i]); 4319 } else { 4320 PatternItem item = (PatternItem)items[i]; 4321 if (useFastFormat) { 4322 subFormat(appendTo, item.type, item.length, appendTo.length(), 4323 i, capSetting, pos, fromCalendar); 4324 } else { 4325 appendTo.append(subFormat(item.type, item.length, appendTo.length(), 4326 i, capSetting, pos, fromCalendar)); 4327 } 4328 } 4329 } 4330 4331 appendTo.append(" \u2013 "); // default separator 4332 4333 // formatting date 2 4334 for (int i = diffBegin; i < items.length; i++) { 4335 if (items[i] instanceof String) { 4336 appendTo.append((String)items[i]); 4337 } else { 4338 PatternItem item = (PatternItem)items[i]; 4339 if (useFastFormat) { 4340 subFormat(appendTo, item.type, item.length, appendTo.length(), 4341 i, capSetting, pos, toCalendar); 4342 } else { 4343 appendTo.append(subFormat(item.type, item.length, appendTo.length(), 4344 i, capSetting, pos, toCalendar)); 4345 } 4346 } 4347 } 4348 return appendTo; 4349 } 4350 4351 4352 /** 4353 * check whether the i-th item in 2 calendar is in different value. 4354 * 4355 * It is supposed to be used only by CLDR survey tool. 4356 * It is used by intervalFormatByAlgorithm(). 4357 * 4358 * @param fromCalendar one calendar 4359 * @param toCalendar the other calendar 4360 * @param items pattern items 4361 * @param i the i-th item in pattern items 4362 * @exception IllegalArgumentException when there is non-recognized 4363 * pattern letter 4364 * @return true is i-th item in 2 calendar is in different 4365 * value, false otherwise. 4366 */ 4367 private boolean diffCalFieldValue(Calendar fromCalendar, 4368 Calendar toCalendar, 4369 Object[] items, 4370 int i) throws IllegalArgumentException { 4371 if ( items[i] instanceof String) { 4372 return false; 4373 } 4374 PatternItem item = (PatternItem)items[i]; 4375 char ch = item.type; 4376 int patternCharIndex = getIndexFromChar(ch); 4377 if (patternCharIndex == -1) { 4378 throw new IllegalArgumentException("Illegal pattern character " + 4379 "'" + ch + "' in \"" + 4380 pattern + '"'); 4381 } 4382 4383 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 4384 if (field >= 0) { 4385 int value = fromCalendar.get(field); 4386 int value_2 = toCalendar.get(field); 4387 if ( value != value_2 ) { 4388 return true; 4389 } 4390 } 4391 return false; 4392 } 4393 4394 4395 /** 4396 * check whether the i-th item's level is lower than the input 'level' 4397 * 4398 * It is supposed to be used only by CLDR survey tool. 4399 * It is used by intervalFormatByAlgorithm(). 4400 * 4401 * @param items the pattern items 4402 * @param i the i-th item in pattern items 4403 * @param level the level with which the i-th pattern item compared to 4404 * @exception IllegalArgumentException when there is non-recognized 4405 * pattern letter 4406 * @return true if i-th pattern item is lower than 'level', 4407 * false otherwise 4408 */ 4409 private boolean lowerLevel(Object[] items, int i, int level) 4410 throws IllegalArgumentException { 4411 if (items[i] instanceof String) { 4412 return false; 4413 } 4414 PatternItem item = (PatternItem)items[i]; 4415 char ch = item.type; 4416 int patternCharIndex = getLevelFromChar(ch); 4417 if (patternCharIndex == -1) { 4418 throw new IllegalArgumentException("Illegal pattern character " + 4419 "'" + ch + "' in \"" + 4420 pattern + '"'); 4421 } 4422 4423 if (patternCharIndex >= level) { 4424 return true; 4425 } 4426 return false; 4427 } 4428 4429 /** 4430 * allow the user to set the NumberFormat for several fields 4431 * It can be a single field like: "y"(year) or "M"(month) 4432 * It can be several field combined together: "yMd"(year, month and date) 4433 * Note: 4434 * 1 symbol field is enough for multiple symbol fields (so "y" will override "yy", "yyy") 4435 * If the field is not numeric, then override has no effect (like "MMM" will use abbreviation, not numerical field) 4436 * 4437 * @param fields the fields to override 4438 * @param overrideNF the NumbeferFormat used 4439 * @exception IllegalArgumentException when the fields contain invalid field 4440 * @stable ICU 54 4441 */ 4442 public void setNumberFormat(String fields, NumberFormat overrideNF) { 4443 overrideNF.setGroupingUsed(false); 4444 String nsName = "$" + UUID.randomUUID().toString(); 4445 4446 // initialize mapping if not there 4447 if (numberFormatters == null) { 4448 numberFormatters = new HashMap<String, NumberFormat>(); 4449 } 4450 if (overrideMap == null) { 4451 overrideMap = new HashMap<Character, String>(); 4452 } 4453 4454 // separate string into char and add to maps 4455 for (int i = 0; i < fields.length(); i++) { 4456 char field = fields.charAt(i); 4457 if (DateFormatSymbols.patternChars.indexOf(field) == -1) { 4458 throw new IllegalArgumentException("Illegal field character " + "'" + field + "' in setNumberFormat."); 4459 } 4460 overrideMap.put(field, nsName); 4461 numberFormatters.put(nsName, overrideNF); 4462 } 4463 4464 // Since one or more of the override number formatters might be complex, 4465 // we can't rely on the fast numfmt where we have a partial field override. 4466 useLocalZeroPaddingNumberFormat = false; 4467 } 4468 4469 /** 4470 * give the NumberFormat used for the field like 'y'(year) and 'M'(year) 4471 * 4472 * @param field the field the user wants 4473 * @return override NumberFormat used for the field 4474 * @stable ICU 54 4475 */ 4476 public NumberFormat getNumberFormat(char field) { 4477 Character ovrField; 4478 ovrField = Character.valueOf(field); 4479 if (overrideMap != null && overrideMap.containsKey(ovrField)) { 4480 String nsName = overrideMap.get(ovrField).toString(); 4481 NumberFormat nf = numberFormatters.get(nsName); 4482 return nf; 4483 } else { 4484 return numberFormat; 4485 } 4486 } 4487 4488 private void initNumberFormatters(ULocale loc) { 4489 4490 numberFormatters = new HashMap<String, NumberFormat>(); 4491 overrideMap = new HashMap<Character, String>(); 4492 processOverrideString(loc,override); 4493 4494 } 4495 4496 private void processOverrideString(ULocale loc, String str) { 4497 4498 if ( str == null || str.length() == 0 ) 4499 return; 4500 4501 int start = 0; 4502 int end; 4503 String nsName; 4504 Character ovrField; 4505 boolean moreToProcess = true; 4506 boolean fullOverride; 4507 4508 while (moreToProcess) { 4509 int delimiterPosition = str.indexOf(";",start); 4510 if (delimiterPosition == -1) { 4511 moreToProcess = false; 4512 end = str.length(); 4513 } else { 4514 end = delimiterPosition; 4515 } 4516 4517 String currentString = str.substring(start,end); 4518 int equalSignPosition = currentString.indexOf("="); 4519 if (equalSignPosition == -1) { // Simple override string such as "hebrew" 4520 nsName = currentString; 4521 fullOverride = true; 4522 } else { // Field specific override string such as "y=hebrew" 4523 nsName = currentString.substring(equalSignPosition+1); 4524 ovrField = Character.valueOf(currentString.charAt(0)); 4525 overrideMap.put(ovrField,nsName); 4526 fullOverride = false; 4527 } 4528 4529 ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName); 4530 NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE); 4531 nf.setGroupingUsed(false); 4532 4533 if (fullOverride) { 4534 setNumberFormat(nf); 4535 } else { 4536 // Since one or more of the override number formatters might be complex, 4537 // we can't rely on the fast numfmt where we have a partial field override. 4538 useLocalZeroPaddingNumberFormat = false; 4539 } 4540 4541 if (!fullOverride && !numberFormatters.containsKey(nsName)) { 4542 numberFormatters.put(nsName,nf); 4543 } 4544 4545 start = delimiterPosition + 1; 4546 } 4547 } 4548 4549 private void parsePattern() { 4550 hasMinute = false; 4551 hasSecond = false; 4552 4553 boolean inQuote = false; 4554 for (int i = 0; i < pattern.length(); ++i) { 4555 char ch = pattern.charAt(i); 4556 if (ch == '\'') { 4557 inQuote = !inQuote; 4558 } 4559 if (!inQuote) { 4560 if (ch == 'm') { 4561 hasMinute = true; 4562 } 4563 if (ch == 's') { 4564 hasSecond = true; 4565 } 4566 } 4567 } 4568 } 4569 } 4570