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