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