1 /* 2 ******************************************************************************* 3 * Copyright (C) 1996-2015, International Business Machines Corporation and 4 * others. All Rights Reserved. 5 ******************************************************************************* 6 */ 7 8 package com.ibm.icu.text; 9 10 import java.io.IOException; 11 import java.io.ObjectInputStream; 12 import java.io.ObjectOutputStream; 13 import java.text.AttributedCharacterIterator; 14 import java.text.AttributedString; 15 import java.text.FieldPosition; 16 import java.text.Format; 17 import java.text.ParsePosition; 18 import java.util.ArrayList; 19 import java.util.Date; 20 import java.util.HashMap; 21 import java.util.List; 22 import java.util.Locale; 23 import java.util.MissingResourceException; 24 import java.util.UUID; 25 26 import com.ibm.icu.impl.CalendarData; 27 import com.ibm.icu.impl.DateNumberFormat; 28 import com.ibm.icu.impl.ICUCache; 29 import com.ibm.icu.impl.PatternProps; 30 import com.ibm.icu.impl.SimpleCache; 31 import com.ibm.icu.lang.UCharacter; 32 import com.ibm.icu.text.TimeZoneFormat.Style; 33 import com.ibm.icu.text.TimeZoneFormat.TimeType; 34 import com.ibm.icu.util.BasicTimeZone; 35 import com.ibm.icu.util.Calendar; 36 import com.ibm.icu.util.HebrewCalendar; 37 import com.ibm.icu.util.Output; 38 import com.ibm.icu.util.TimeZone; 39 import com.ibm.icu.util.TimeZoneTransition; 40 import com.ibm.icu.util.ULocale; 41 import 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 */ 715 public 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 */ getLevelFromChar(char ch)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 */ isSyntaxChar(char ch)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 */ SimpleDateFormat()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 */ SimpleDateFormat(String pattern)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 */ SimpleDateFormat(String pattern, Locale loc)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 */ SimpleDateFormat(String pattern, ULocale loc)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 */ SimpleDateFormat(String pattern, String override, ULocale loc)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 */ SimpleDateFormat(String pattern, DateFormatSymbols formatData)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 SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)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 */ SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale, boolean useFastFormat, String override)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 */ SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override)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 getInstance(Calendar.FormatConfiguration formatConfig)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 */ initialize()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 */ initializeTimeZoneFormat(boolean bForceUpdate)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 */ tzFormat()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 */ getDefaultPattern()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 */ parseAmbiguousDatesAsAfter(Date startDate)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 */ initializeDefaultCenturyStart(long baseTime)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 */ getDefaultCenturyStart()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 */ getDefaultCenturyStartYear()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 */ set2DigitYearStart(Date startDate)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 */ get2DigitYearStart()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 setContext(DisplayContext context)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 */ format(Calendar cal, StringBuffer toAppendTo, FieldPosition pos)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. format(Calendar cal, DisplayContext capitalizationContext, StringBuffer toAppendTo, FieldPosition pos, List<FieldPosition> attributes)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 getIndexFromChar(char ch)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 */ patternCharToDateFormatField(char ch)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 */ subFormat(char ch, int count, int beginOffset, FieldPosition pos, DateFormatSymbols fmtData, Calendar cal)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 subFormat(char ch, int count, int beginOffset, int fieldNum, DisplayContext capitalizationContext, FieldPosition pos, Calendar cal)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") subFormat(StringBuffer buf, char ch, int count, int beginOffset, int fieldNum, DisplayContext capitalizationContext, FieldPosition pos, Calendar cal)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 safeAppend(String[] array, int value, StringBuffer appendTo)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 safeAppendWithMonthPattern(String[] array, int value, StringBuffer appendTo, String monthPattern)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 PatternItem(char type, int length)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 */ getPatternItems()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 zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value, int minDigits, int maxDigits)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 */ setNumberFormat(NumberFormat newNumberFormat)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 */ initLocalZeroPaddingNumberFormat()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 */ fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits)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 */ zeroPaddingNumber(long value, int minDigits, int maxDigits)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 */ isNumeric(char formatChar, int count)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 */ parse(String text, Calendar cal, ParsePosition parsePos)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 */ matchLiteral(String text, int pos, Object[] items, int itemIndex, boolean[] complete)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 */ matchString(String text, int start, int field, String[] data, Calendar cal)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 matchString(String text, int start, int field, String[] data, String monthPattern, Calendar cal)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 regionMatchesWithOptionalDot(String text, int start, String data, int length)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 */ matchQuarterString(String text, int start, int field, String[] data, Calendar cal)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 */ subParse(String text, int start, char ch, int count, boolean obeyCount, boolean allowNegative, boolean[] ambiguousYear, Calendar cal)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") subParse(String text, int start, char ch, int count, boolean obeyCount, boolean allowNegative, boolean[] ambiguousYear, Calendar cal, MessageFormat numericLeapMonthFormatter, Output<TimeType> tzTimeType)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 */ allowNumericFallback(int patternCharIndex)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 */ parseInt(String text, ParsePosition pos, boolean allowNegative, NumberFormat fmt)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 */ parseInt(String text, int maxDigits, ParsePosition pos, boolean allowNegative, NumberFormat fmt)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 */ translatePattern(String pat, String from, String to)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 */ toPattern()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 */ toLocalizedPattern()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 */ applyPattern(String pat)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 */ applyLocalizedPattern(String pat)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 */ getDateFormatSymbols()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 */ setDateFormatSymbols(DateFormatSymbols newFormatSymbols)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 */ getSymbols()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 */ getTimeZoneFormat()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 */ setTimeZoneFormat(TimeZoneFormat tzfmt)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 */ clone()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 */ hashCode()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 */ equals(Object obj)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 */ writeObject(ObjectOutputStream stream)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 */ readObject(ObjectInputStream stream)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 */ formatToCharacterIterator(Object obj)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 */ getLocale()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 isFieldUnitIgnored(int field)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 */ isFieldUnitIgnored(String pattern, int field)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 intervalFormatByAlgorithm(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos)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 */ diffCalFieldValue(Calendar fromCalendar, Calendar toCalendar, Object[] items, int i)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 */ lowerLevel(Object[] items, int i, int level)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 */ setNumberFormat(String fields, NumberFormat overrideNF)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 */ getNumberFormat(char field)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 initNumberFormatters(ULocale loc)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 processOverrideString(ULocale loc, String str)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