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