1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 /* 28 * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved 29 * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved 30 * 31 * The original version of this source code and documentation is copyrighted 32 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These 33 * materials are provided under terms of a License Agreement between Taligent 34 * and Sun. This technology is protected by multiple US and International 35 * patents. This notice and attribution to Taligent may not be removed. 36 * Taligent is a registered trademark of Taligent, Inc. 37 * 38 */ 39 40 package java.text; 41 42 import android.icu.text.TimeZoneFormat; 43 import android.icu.text.TimeZoneNames; 44 import android.icu.util.ULocale; 45 46 import java.io.IOException; 47 import java.io.InvalidObjectException; 48 import java.io.ObjectInputStream; 49 import java.util.Arrays; 50 import java.util.Calendar; 51 import java.util.Collection; 52 import java.util.Collections; 53 import java.util.Date; 54 import java.util.EnumSet; 55 import java.util.GregorianCalendar; 56 import java.util.HashSet; 57 import java.util.Locale; 58 import java.util.Map; 59 import java.util.Set; 60 import java.util.SimpleTimeZone; 61 import java.util.TimeZone; 62 import java.util.concurrent.ConcurrentHashMap; 63 import java.util.concurrent.ConcurrentMap; 64 import libcore.icu.LocaleData; 65 66 import sun.util.calendar.CalendarUtils; 67 68 import static java.text.DateFormatSymbols.*; 69 70 // Android-changed: Added supported API level, removed unnecessary <br> 71 /** 72 * <code>SimpleDateFormat</code> is a concrete class for formatting and 73 * parsing dates in a locale-sensitive manner. It allows for formatting 74 * (date → text), parsing (text → date), and normalization. 75 * 76 * <p> 77 * <code>SimpleDateFormat</code> allows you to start by choosing 78 * any user-defined patterns for date-time formatting. However, you 79 * are encouraged to create a date-time formatter with either 80 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or 81 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each 82 * of these class methods can return a date/time formatter initialized 83 * with a default format pattern. You may modify the format pattern 84 * using the <code>applyPattern</code> methods as desired. 85 * For more information on using these methods, see 86 * {@link DateFormat}. 87 * 88 * <h3>Date and Time Patterns</h3> 89 * <p> 90 * Date and time formats are specified by <em>date and time pattern</em> 91 * strings. 92 * Within date and time pattern strings, unquoted letters from 93 * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to 94 * <code>'z'</code> are interpreted as pattern letters representing the 95 * components of a date or time string. 96 * Text can be quoted using single quotes (<code>'</code>) to avoid 97 * interpretation. 98 * <code>"''"</code> represents a single quote. 99 * All other characters are not interpreted; they're simply copied into the 100 * output string during formatting or matched against the input string 101 * during parsing. 102 * <p> 103 * The following pattern letters are defined (all other characters from 104 * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to 105 * <code>'z'</code> are reserved): 106 * <blockquote> 107 * <table border=0 cellspacing=3 cellpadding=0 summary="Chart shows pattern letters, date/time component, presentation, and examples."> 108 * <tr style="background-color: rgb(204, 204, 255);"> 109 * <th align=left>Letter 110 * <th align=left>Date or Time Component 111 * <th align=left>Presentation 112 * <th align=left>Examples 113 * <th align=left>Supported (API Levels) 114 * <tr> 115 * <td><code>G</code> 116 * <td>Era designator 117 * <td><a href="#text">Text</a> 118 * <td><code>AD</code> 119 * <td>1+</td> 120 * <tr style="background-color: rgb(238, 238, 255);"> 121 * <td><code>y</code> 122 * <td>Year 123 * <td><a href="#year">Year</a> 124 * <td><code>1996</code>; <code>96</code> 125 * <td>1+</td> 126 * <tr> 127 * <td><code>Y</code> 128 * <td>Week year 129 * <td><a href="#year">Year</a> 130 * <td><code>2009</code>; <code>09</code> 131 * <td>1+</td> 132 * <tr style="background-color: rgb(238, 238, 255);"> 133 * <td><code>M</code> 134 * <td>Month in year (context sensitive) 135 * <td><a href="#month">Month</a> 136 * <td><code>July</code>; <code>Jul</code>; <code>07</code> 137 * <td>1+</td> 138 * <tr> 139 * <td><code>w</code> 140 * <td>Week in year 141 * <td><a href="#number">Number</a> 142 * <td><code>27</code> 143 * <td>1+</td> 144 * <tr> 145 * <td><code>W</code> 146 * <td>Week in month 147 * <td><a href="#number">Number</a> 148 * <td><code>2</code> 149 * <td>1+</td> 150 * <tr style="background-color: rgb(238, 238, 255);"> 151 * <td><code>D</code> 152 * <td>Day in year 153 * <td><a href="#number">Number</a> 154 * <td><code>189</code> 155 * <td>1+</td> 156 * <tr> 157 * <td><code>d</code> 158 * <td>Day in month 159 * <td><a href="#number">Number</a> 160 * <td><code>10</code> 161 * <td>1+</td> 162 * <tr style="background-color: rgb(238, 238, 255);"> 163 * <td><code>F</code> 164 * <td>Day of week in month 165 * <td><a href="#number">Number</a> 166 * <td><code>2</code> 167 * <td>1+</td> 168 * <tr> 169 * <td><code>E</code> 170 * <td>Day name in week 171 * <td><a href="#text">Text</a> 172 * <td><code>Tuesday</code>; <code>Tue</code> 173 * <td>1+</td> 174 * <tr style="background-color: rgb(238, 238, 255);"> 175 * <td><code>u</code> 176 * <td>Day number of week (1 = Monday, ..., 7 = Sunday) 177 * <td><a href="#number">Number</a> 178 * <td><code>1</code> 179 * <td>24+</td> 180 * <tr> 181 * <td><code>a</code> 182 * <td>Am/pm marker 183 * <td><a href="#text">Text</a> 184 * <td><code>PM</code> 185 * <td>1+</td> 186 * <tr style="background-color: rgb(238, 238, 255);"> 187 * <td><code>H</code> 188 * <td>Hour in day (0-23) 189 * <td><a href="#number">Number</a> 190 * <td><code>0</code> 191 * <td>1+</td> 192 * <tr> 193 * <td><code>k</code> 194 * <td>Hour in day (1-24) 195 * <td><a href="#number">Number</a> 196 * <td><code>24</code> 197 * <td>1+</td> 198 * <tr style="background-color: rgb(238, 238, 255);"> 199 * <td><code>K</code> 200 * <td>Hour in am/pm (0-11) 201 * <td><a href="#number">Number</a> 202 * <td><code>0</code> 203 * <td>1+</td> 204 * <tr> 205 * <td><code>h</code> 206 * <td>Hour in am/pm (1-12) 207 * <td><a href="#number">Number</a> 208 * <td><code>12</code> 209 * <td>1+</td> 210 * <tr style="background-color: rgb(238, 238, 255);"> 211 * <td><code>m</code> 212 * <td>Minute in hour 213 * <td><a href="#number">Number</a> 214 * <td><code>30</code> 215 * <td>1+</td> 216 * <tr> 217 * <td><code>s</code> 218 * <td>Second in minute 219 * <td><a href="#number">Number</a> 220 * <td><code>55</code> 221 * <td>1+</td> 222 * <tr style="background-color: rgb(238, 238, 255);"> 223 * <td><code>S</code> 224 * <td>Millisecond 225 * <td><a href="#number">Number</a> 226 * <td><code>978</code> 227 * <td>1+</td> 228 * <tr> 229 * <td><code>z</code> 230 * <td>Time zone 231 * <td><a href="#timezone">General time zone</a> 232 * <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code> 233 * <td>1+</td> 234 * <tr style="background-color: rgb(238, 238, 255);"> 235 * <td><code>Z</code> 236 * <td>Time zone 237 * <td><a href="#rfc822timezone">RFC 822 time zone</a> 238 * <td><code>-0800</code> 239 * <td>1+</td> 240 * <tr> 241 * <td><code>X</code> 242 * <td>Time zone 243 * <td><a href="#iso8601timezone">ISO 8601 time zone</a> 244 * <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code> 245 * <td>1+</td> 246 * </table> 247 * </blockquote> 248 * Pattern letters are usually repeated, as their number determines the 249 * exact presentation: 250 * <ul> 251 * <li><strong><a name="text">Text:</a></strong> 252 * For formatting, if the number of pattern letters is 4 or more, 253 * the full form is used; otherwise a short or abbreviated form 254 * is used if available. 255 * For parsing, both forms are accepted, independent of the number 256 * of pattern letters.</li> 257 * <li><strong><a name="number">Number:</a></strong> 258 * For formatting, the number of pattern letters is the minimum 259 * number of digits, and shorter numbers are zero-padded to this amount. 260 * For parsing, the number of pattern letters is ignored unless 261 * it's needed to separate two adjacent fields.</li> 262 * <li><strong><a name="year">Year:</a></strong> 263 * If the formatter's {@link #getCalendar() Calendar} is the Gregorian 264 * calendar, the following rules are applied. 265 * <ul> 266 * <li>For formatting, if the number of pattern letters is 2, the year 267 * is truncated to 2 digits; otherwise it is interpreted as a 268 * <a href="#number">number</a>. 269 * <li>For parsing, if the number of pattern letters is more than 2, 270 * the year is interpreted literally, regardless of the number of 271 * digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to 272 * Jan 11, 12 A.D. 273 * <li>For parsing with the abbreviated year pattern ("y" or "yy"), 274 * <code>SimpleDateFormat</code> must interpret the abbreviated year 275 * relative to some century. It does this by adjusting dates to be 276 * within 80 years before and 20 years after the time the <code>SimpleDateFormat</code> 277 * instance is created. For example, using a pattern of "MM/dd/yy" and a 278 * <code>SimpleDateFormat</code> instance created on Jan 1, 1997, the string 279 * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64" 280 * would be interpreted as May 4, 1964. 281 * During parsing, only strings consisting of exactly two digits, as defined by 282 * {@link Character#isDigit(char)}, will be parsed into the default century. 283 * Any other numeric string, such as a one digit string, a three or more digit 284 * string, or a two digit string that isn't all digits (for example, "-1"), is 285 * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the 286 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC. 287 * </ul> 288 * Otherwise, calendar system specific forms are applied. 289 * For both formatting and parsing, if the number of pattern 290 * letters is 4 or more, a calendar specific {@linkplain 291 * Calendar#LONG long form} is used. Otherwise, a calendar 292 * specific {@linkplain Calendar#SHORT short or abbreviated form} 293 * is used. 294 * <br> 295 * If week year {@code 'Y'} is specified and the {@linkplain 296 * #getCalendar() calendar} doesn't support any <a 297 * href="../util/GregorianCalendar.html#week_year"> week 298 * years</a>, the calendar year ({@code 'y'}) is used instead. The 299 * support of week years can be tested with a call to {@link 300 * DateFormat#getCalendar() getCalendar()}.{@link 301 * java.util.Calendar#isWeekDateSupported() 302 * isWeekDateSupported()}.</li> 303 * <li><strong><a name="month">Month:</a></strong> 304 * If the number of pattern letters is 3 or more, the month is 305 * interpreted as <a href="#text">text</a>; otherwise, 306 * it is interpreted as a <a href="#number">number</a>.</li> 307 * <li><strong><a name="timezone">General time zone:</a></strong> 308 * Time zones are interpreted as <a href="#text">text</a> if they have 309 * names. For time zones representing a GMT offset value, the 310 * following syntax is used: 311 * <pre> 312 * <a name="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a> 313 * <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i> 314 * <i>Sign:</i> one of 315 * <code>+ -</code> 316 * <i>Hours:</i> 317 * <i>Digit</i> 318 * <i>Digit</i> <i>Digit</i> 319 * <i>Minutes:</i> 320 * <i>Digit</i> <i>Digit</i> 321 * <i>Digit:</i> one of 322 * <code>0 1 2 3 4 5 6 7 8 9</code></pre> 323 * <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between 324 * 00 and 59. The format is locale independent and digits must be taken 325 * from the Basic Latin block of the Unicode standard. 326 * <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also 327 * accepted.</li> 328 * <li><strong><a name="rfc822timezone">RFC 822 time zone:</a></strong> 329 * For formatting, the RFC 822 4-digit time zone format is used: 330 * 331 * <pre> 332 * <i>RFC822TimeZone:</i> 333 * <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i> 334 * <i>TwoDigitHours:</i> 335 * <i>Digit Digit</i></pre> 336 * <i>TwoDigitHours</i> must be between 00 and 23. Other definitions 337 * are as for <a href="#timezone">general time zones</a>. 338 * 339 * <p>For parsing, <a href="#timezone">general time zones</a> are also 340 * accepted. 341 * <li><strong><a name="iso8601timezone">ISO 8601 Time zone:</a></strong> 342 * The number of pattern letters designates the format for both formatting 343 * and parsing as follows: 344 * <pre> 345 * <i>ISO8601TimeZone:</i> 346 * <i>OneLetterISO8601TimeZone</i> 347 * <i>TwoLetterISO8601TimeZone</i> 348 * <i>ThreeLetterISO8601TimeZone</i> 349 * <i>OneLetterISO8601TimeZone:</i> 350 * <i>Sign</i> <i>TwoDigitHours</i> 351 * {@code Z} 352 * <i>TwoLetterISO8601TimeZone:</i> 353 * <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i> 354 * {@code Z} 355 * <i>ThreeLetterISO8601TimeZone:</i> 356 * <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i> 357 * {@code Z}</pre> 358 * Other definitions are as for <a href="#timezone">general time zones</a> or 359 * <a href="#rfc822timezone">RFC 822 time zones</a>. 360 * 361 * <p>For formatting, if the offset value from GMT is 0, {@code "Z"} is 362 * produced. If the number of pattern letters is 1, any fraction of an hour 363 * is ignored. For example, if the pattern is {@code "X"} and the time zone is 364 * {@code "GMT+05:30"}, {@code "+05"} is produced. 365 * 366 * <p>For parsing, {@code "Z"} is parsed as the UTC time zone designator. 367 * <a href="#timezone">General time zones</a> are <em>not</em> accepted. 368 * 369 * <p>If the number of pattern letters is 4 or more, {@link 370 * IllegalArgumentException} is thrown when constructing a {@code 371 * SimpleDateFormat} or {@linkplain #applyPattern(String) applying a 372 * pattern}. 373 * </ul> 374 * <code>SimpleDateFormat</code> also supports <em>localized date and time 375 * pattern</em> strings. In these strings, the pattern letters described above 376 * may be replaced with other, locale dependent, pattern letters. 377 * <code>SimpleDateFormat</code> does not deal with the localization of text 378 * other than the pattern letters; that's up to the client of the class. 379 * 380 * <h4>Examples</h4> 381 * 382 * The following examples show how date and time patterns are interpreted in 383 * the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time 384 * in the U.S. Pacific Time time zone. 385 * <blockquote> 386 * <table border=0 cellspacing=3 cellpadding=0 summary="Examples of date and time patterns interpreted in the U.S. locale"> 387 * <tr style="background-color: rgb(204, 204, 255);"> 388 * <th align=left>Date and Time Pattern 389 * <th align=left>Result 390 * <tr> 391 * <td><code>"yyyy.MM.dd G 'at' HH:mm:ss z"</code> 392 * <td><code>2001.07.04 AD at 12:08:56 PDT</code> 393 * <tr style="background-color: rgb(238, 238, 255);"> 394 * <td><code>"EEE, MMM d, ''yy"</code> 395 * <td><code>Wed, Jul 4, '01</code> 396 * <tr> 397 * <td><code>"h:mm a"</code> 398 * <td><code>12:08 PM</code> 399 * <tr style="background-color: rgb(238, 238, 255);"> 400 * <td><code>"hh 'o''clock' a, zzzz"</code> 401 * <td><code>12 o'clock PM, Pacific Daylight Time</code> 402 * <tr> 403 * <td><code>"K:mm a, z"</code> 404 * <td><code>0:08 PM, PDT</code> 405 * <tr style="background-color: rgb(238, 238, 255);"> 406 * <td><code>"yyyyy.MMMMM.dd GGG hh:mm aaa"</code> 407 * <td><code>02001.July.04 AD 12:08 PM</code> 408 * <tr> 409 * <td><code>"EEE, d MMM yyyy HH:mm:ss Z"</code> 410 * <td><code>Wed, 4 Jul 2001 12:08:56 -0700</code> 411 * <tr style="background-color: rgb(238, 238, 255);"> 412 * <td><code>"yyMMddHHmmssZ"</code> 413 * <td><code>010704120856-0700</code> 414 * <tr> 415 * <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSZ"</code> 416 * <td><code>2001-07-04T12:08:56.235-0700</code> 417 * <tr style="background-color: rgb(238, 238, 255);"> 418 * <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"</code> 419 * <td><code>2001-07-04T12:08:56.235-07:00</code> 420 * <tr> 421 * <td><code>"YYYY-'W'ww-u"</code> 422 * <td><code>2001-W27-3</code> 423 * </table> 424 * </blockquote> 425 * 426 * <h4><a name="synchronization">Synchronization</a></h4> 427 * 428 * <p> 429 * Date formats are not synchronized. 430 * It is recommended to create separate format instances for each thread. 431 * If multiple threads access a format concurrently, it must be synchronized 432 * externally. 433 * 434 * @see <a href="https://docs.oracle.com/javase/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a> 435 * @see java.util.Calendar 436 * @see java.util.TimeZone 437 * @see DateFormat 438 * @see DateFormatSymbols 439 * @author Mark Davis, Chen-Lieh Huang, Alan Liu 440 */ 441 public class SimpleDateFormat extends DateFormat { 442 443 // the official serial version ID which says cryptically 444 // which version we're compatible with 445 static final long serialVersionUID = 4774881970558875024L; 446 447 // the internal serial version which says which version was written 448 // - 0 (default) for version up to JDK 1.1.3 449 // - 1 for version from JDK 1.1.4, which includes a new field 450 static final int currentSerialVersion = 1; 451 452 /** 453 * The version of the serialized data on the stream. Possible values: 454 * <ul> 455 * <li><b>0</b> or not present on stream: JDK 1.1.3. This version 456 * has no <code>defaultCenturyStart</code> on stream. 457 * <li><b>1</b> JDK 1.1.4 or later. This version adds 458 * <code>defaultCenturyStart</code>. 459 * </ul> 460 * When streaming out this class, the most recent format 461 * and the highest allowable <code>serialVersionOnStream</code> 462 * is written. 463 * @serial 464 * @since JDK1.1.4 465 */ 466 private int serialVersionOnStream = currentSerialVersion; 467 468 /** 469 * The pattern string of this formatter. This is always a non-localized 470 * pattern. May not be null. See class documentation for details. 471 * @serial 472 */ 473 private String pattern; 474 475 /** 476 * Saved numberFormat and pattern. 477 * @see SimpleDateFormat#checkNegativeNumberExpression 478 */ 479 transient private NumberFormat originalNumberFormat; 480 transient private String originalNumberPattern; 481 482 /** 483 * The minus sign to be used with format and parse. 484 */ 485 transient private char minusSign = '-'; 486 487 /** 488 * True when a negative sign follows a number. 489 * (True as default in Arabic.) 490 */ 491 transient private boolean hasFollowingMinusSign = false; 492 493 /** 494 * The compiled pattern. 495 */ 496 transient private char[] compiledPattern; 497 498 /** 499 * Tags for the compiled pattern. 500 */ 501 private final static int TAG_QUOTE_ASCII_CHAR = 100; 502 private final static int TAG_QUOTE_CHARS = 101; 503 504 /** 505 * Locale dependent digit zero. 506 * @see #zeroPaddingNumber 507 * @see java.text.DecimalFormatSymbols#getZeroDigit 508 */ 509 transient private char zeroDigit; 510 511 /** 512 * The symbols used by this formatter for week names, month names, 513 * etc. May not be null. 514 * @serial 515 * @see java.text.DateFormatSymbols 516 */ 517 private DateFormatSymbols formatData; 518 519 /** 520 * We map dates with two-digit years into the century starting at 521 * <code>defaultCenturyStart</code>, which may be any date. May 522 * not be null. 523 * @serial 524 * @since JDK1.1.4 525 */ 526 private Date defaultCenturyStart; 527 528 transient private int defaultCenturyStartYear; 529 530 private static final int MILLIS_PER_MINUTE = 60 * 1000; 531 532 // For time zones that have no names, use strings GMT+minutes and 533 // GMT-minutes. For instance, in France the time zone is GMT+60. 534 private static final String GMT = "GMT"; 535 536 /** 537 * Cache NumberFormat instances with Locale key. 538 */ 539 private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData 540 = new ConcurrentHashMap<>(3); 541 542 /** 543 * The Locale used to instantiate this 544 * <code>SimpleDateFormat</code>. The value may be null if this object 545 * has been created by an older <code>SimpleDateFormat</code> and 546 * deserialized. 547 * 548 * @serial 549 * @since 1.6 550 */ 551 private Locale locale; 552 553 /** 554 * Indicates whether this <code>SimpleDateFormat</code> should use 555 * the DateFormatSymbols. If true, the format and parse methods 556 * use the DateFormatSymbols values. If false, the format and 557 * parse methods call Calendar.getDisplayName or 558 * Calendar.getDisplayNames. 559 */ 560 transient boolean useDateFormatSymbols; 561 562 /** 563 * ICU TimeZoneNames used to format and parse time zone names. 564 */ 565 private transient TimeZoneNames timeZoneNames; 566 567 /** 568 * Constructs a <code>SimpleDateFormat</code> using the default pattern and 569 * date format symbols for the default 570 * {@link java.util.Locale.Category#FORMAT FORMAT} locale. 571 * <b>Note:</b> This constructor may not support all locales. 572 * For full coverage, use the factory methods in the {@link DateFormat} 573 * class. 574 */ SimpleDateFormat()575 public SimpleDateFormat() { 576 this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT)); 577 } 578 579 /** 580 * Constructs a <code>SimpleDateFormat</code> using the given pattern and 581 * the default date format symbols for the default 582 * {@link java.util.Locale.Category#FORMAT FORMAT} locale. 583 * <b>Note:</b> This constructor may not support all locales. 584 * For full coverage, use the factory methods in the {@link DateFormat} 585 * class. 586 * <p>This is equivalent to calling 587 * {@link #SimpleDateFormat(String, Locale) 588 * SimpleDateFormat(pattern, Locale.getDefault(Locale.Category.FORMAT))}. 589 * 590 * @see java.util.Locale#getDefault(java.util.Locale.Category) 591 * @see java.util.Locale.Category#FORMAT 592 * @param pattern the pattern describing the date and time format 593 * @exception NullPointerException if the given pattern is null 594 * @exception IllegalArgumentException if the given pattern is invalid 595 */ SimpleDateFormat(String pattern)596 public SimpleDateFormat(String pattern) 597 { 598 this(pattern, Locale.getDefault(Locale.Category.FORMAT)); 599 } 600 601 /** 602 * Constructs a <code>SimpleDateFormat</code> using the given pattern and 603 * the default date format symbols for the given locale. 604 * <b>Note:</b> This constructor may not support all locales. 605 * For full coverage, use the factory methods in the {@link DateFormat} 606 * class. 607 * 608 * @param pattern the pattern describing the date and time format 609 * @param locale the locale whose date format symbols should be used 610 * @exception NullPointerException if the given pattern or locale is null 611 * @exception IllegalArgumentException if the given pattern is invalid 612 */ SimpleDateFormat(String pattern, Locale locale)613 public SimpleDateFormat(String pattern, Locale locale) 614 { 615 if (pattern == null || locale == null) { 616 throw new NullPointerException(); 617 } 618 619 initializeCalendar(locale); 620 this.pattern = pattern; 621 this.formatData = DateFormatSymbols.getInstanceRef(locale); 622 this.locale = locale; 623 initialize(locale); 624 } 625 626 /** 627 * Constructs a <code>SimpleDateFormat</code> using the given pattern and 628 * date format symbols. 629 * 630 * @param pattern the pattern describing the date and time format 631 * @param formatSymbols the date format symbols to be used for formatting 632 * @exception NullPointerException if the given pattern or formatSymbols is null 633 * @exception IllegalArgumentException if the given pattern is invalid 634 */ SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)635 public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) 636 { 637 if (pattern == null || formatSymbols == null) { 638 throw new NullPointerException(); 639 } 640 641 this.pattern = pattern; 642 this.formatData = (DateFormatSymbols) formatSymbols.clone(); 643 this.locale = Locale.getDefault(Locale.Category.FORMAT); 644 initializeCalendar(this.locale); 645 initialize(this.locale); 646 useDateFormatSymbols = true; 647 } 648 649 /* Package-private, called by DateFormat factory methods */ SimpleDateFormat(int timeStyle, int dateStyle, Locale loc)650 SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) { 651 if (loc == null) { 652 throw new NullPointerException(); 653 } 654 655 this.locale = loc; 656 // initialize calendar and related fields 657 initializeCalendar(loc); 658 659 formatData = DateFormatSymbols.getInstanceRef(loc); 660 LocaleData localeData = LocaleData.get(loc); 661 if ((timeStyle >= 0) && (dateStyle >= 0)) { 662 Object[] dateTimeArgs = { 663 localeData.getDateFormat(dateStyle), 664 localeData.getTimeFormat(timeStyle), 665 }; 666 pattern = MessageFormat.format("{0} {1}", dateTimeArgs); 667 } 668 else if (timeStyle >= 0) { 669 pattern = localeData.getTimeFormat(timeStyle); 670 } 671 else if (dateStyle >= 0) { 672 pattern = localeData.getDateFormat(dateStyle); 673 } 674 else { 675 throw new IllegalArgumentException("No date or time style specified"); 676 } 677 678 initialize(loc); 679 } 680 681 /* Initialize compiledPattern and numberFormat fields */ initialize(Locale loc)682 private void initialize(Locale loc) { 683 // Verify and compile the given pattern. 684 compiledPattern = compile(pattern); 685 686 /* try the cache first */ 687 numberFormat = cachedNumberFormatData.get(loc); 688 if (numberFormat == null) { /* cache miss */ 689 numberFormat = NumberFormat.getIntegerInstance(loc); 690 numberFormat.setGroupingUsed(false); 691 692 /* update cache */ 693 cachedNumberFormatData.putIfAbsent(loc, numberFormat); 694 } 695 numberFormat = (NumberFormat) numberFormat.clone(); 696 697 initializeDefaultCentury(); 698 } 699 initializeCalendar(Locale loc)700 private void initializeCalendar(Locale loc) { 701 if (calendar == null) { 702 assert loc != null; 703 // The format object must be constructed using the symbols for this zone. 704 // However, the calendar should use the current default TimeZone. 705 // If this is not contained in the locale zone strings, then the zone 706 // will be formatted using generic GMT+/-H:MM nomenclature. 707 calendar = Calendar.getInstance(TimeZone.getDefault(), loc); 708 } 709 } 710 711 /** 712 * Returns the compiled form of the given pattern. The syntax of 713 * the compiled pattern is: 714 * <blockquote> 715 * CompiledPattern: 716 * EntryList 717 * EntryList: 718 * Entry 719 * EntryList Entry 720 * Entry: 721 * TagField 722 * TagField data 723 * TagField: 724 * Tag Length 725 * TaggedData 726 * Tag: 727 * pattern_char_index 728 * TAG_QUOTE_CHARS 729 * Length: 730 * short_length 731 * long_length 732 * TaggedData: 733 * TAG_QUOTE_ASCII_CHAR ascii_char 734 * 735 * </blockquote> 736 * 737 * where `short_length' is an 8-bit unsigned integer between 0 and 738 * 254. `long_length' is a sequence of an 8-bit integer 255 and a 739 * 32-bit signed integer value which is split into upper and lower 740 * 16-bit fields in two char's. `pattern_char_index' is an 8-bit 741 * integer between 0 and 18. `ascii_char' is an 7-bit ASCII 742 * character value. `data' depends on its Tag value. 743 * <p> 744 * If Length is short_length, Tag and short_length are packed in a 745 * single char, as illustrated below. 746 * <blockquote> 747 * char[0] = (Tag << 8) | short_length; 748 * </blockquote> 749 * 750 * If Length is long_length, Tag and 255 are packed in the first 751 * char and a 32-bit integer, as illustrated below. 752 * <blockquote> 753 * char[0] = (Tag << 8) | 255; 754 * char[1] = (char) (long_length >>> 16); 755 * char[2] = (char) (long_length & 0xffff); 756 * </blockquote> 757 * <p> 758 * If Tag is a pattern_char_index, its Length is the number of 759 * pattern characters. For example, if the given pattern is 760 * "yyyy", Tag is 1 and Length is 4, followed by no data. 761 * <p> 762 * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's 763 * following the TagField. For example, if the given pattern is 764 * "'o''clock'", Length is 7 followed by a char sequence of 765 * <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>. 766 * <p> 767 * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII 768 * character in place of Length. For example, if the given pattern 769 * is "'o'", the TaggedData entry is 770 * <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>. 771 * 772 * @exception NullPointerException if the given pattern is null 773 * @exception IllegalArgumentException if the given pattern is invalid 774 */ compile(String pattern)775 private char[] compile(String pattern) { 776 int length = pattern.length(); 777 boolean inQuote = false; 778 StringBuilder compiledCode = new StringBuilder(length * 2); 779 StringBuilder tmpBuffer = null; 780 int count = 0; 781 int lastTag = -1; 782 783 for (int i = 0; i < length; i++) { 784 char c = pattern.charAt(i); 785 786 if (c == '\'') { 787 // '' is treated as a single quote regardless of being 788 // in a quoted section. 789 if ((i + 1) < length) { 790 c = pattern.charAt(i + 1); 791 if (c == '\'') { 792 i++; 793 if (count != 0) { 794 encode(lastTag, count, compiledCode); 795 lastTag = -1; 796 count = 0; 797 } 798 if (inQuote) { 799 tmpBuffer.append(c); 800 } else { 801 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); 802 } 803 continue; 804 } 805 } 806 if (!inQuote) { 807 if (count != 0) { 808 encode(lastTag, count, compiledCode); 809 lastTag = -1; 810 count = 0; 811 } 812 if (tmpBuffer == null) { 813 tmpBuffer = new StringBuilder(length); 814 } else { 815 tmpBuffer.setLength(0); 816 } 817 inQuote = true; 818 } else { 819 int len = tmpBuffer.length(); 820 if (len == 1) { 821 char ch = tmpBuffer.charAt(0); 822 if (ch < 128) { 823 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch)); 824 } else { 825 compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | 1)); 826 compiledCode.append(ch); 827 } 828 } else { 829 encode(TAG_QUOTE_CHARS, len, compiledCode); 830 compiledCode.append(tmpBuffer); 831 } 832 inQuote = false; 833 } 834 continue; 835 } 836 if (inQuote) { 837 tmpBuffer.append(c); 838 continue; 839 } 840 if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { 841 if (count != 0) { 842 encode(lastTag, count, compiledCode); 843 lastTag = -1; 844 count = 0; 845 } 846 if (c < 128) { 847 // In most cases, c would be a delimiter, such as ':'. 848 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); 849 } else { 850 // Take any contiguous non-ASCII alphabet characters and 851 // put them in a single TAG_QUOTE_CHARS. 852 int j; 853 for (j = i + 1; j < length; j++) { 854 char d = pattern.charAt(j); 855 if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) { 856 break; 857 } 858 } 859 compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | (j - i))); 860 for (; i < j; i++) { 861 compiledCode.append(pattern.charAt(i)); 862 } 863 i--; 864 } 865 continue; 866 } 867 868 int tag; 869 if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) { 870 throw new IllegalArgumentException("Illegal pattern character " + 871 "'" + c + "'"); 872 } 873 if (lastTag == -1 || lastTag == tag) { 874 lastTag = tag; 875 count++; 876 continue; 877 } 878 encode(lastTag, count, compiledCode); 879 lastTag = tag; 880 count = 1; 881 } 882 883 if (inQuote) { 884 throw new IllegalArgumentException("Unterminated quote"); 885 } 886 887 if (count != 0) { 888 encode(lastTag, count, compiledCode); 889 } 890 891 // Copy the compiled pattern to a char array 892 int len = compiledCode.length(); 893 char[] r = new char[len]; 894 compiledCode.getChars(0, len, r, 0); 895 return r; 896 } 897 898 /** 899 * Encodes the given tag and length and puts encoded char(s) into buffer. 900 */ encode(int tag, int length, StringBuilder buffer)901 private static void encode(int tag, int length, StringBuilder buffer) { 902 if (tag == PATTERN_ISO_ZONE && length >= 4) { 903 throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length); 904 } 905 if (length < 255) { 906 buffer.append((char)(tag << 8 | length)); 907 } else { 908 buffer.append((char)((tag << 8) | 0xff)); 909 buffer.append((char)(length >>> 16)); 910 buffer.append((char)(length & 0xffff)); 911 } 912 } 913 914 /* Initialize the fields we use to disambiguate ambiguous years. Separate 915 * so we can call it from readObject(). 916 */ initializeDefaultCentury()917 private void initializeDefaultCentury() { 918 calendar.setTimeInMillis(System.currentTimeMillis()); 919 calendar.add( Calendar.YEAR, -80 ); 920 parseAmbiguousDatesAsAfter(calendar.getTime()); 921 } 922 923 /* Define one-century window into which to disambiguate dates using 924 * two-digit years. 925 */ parseAmbiguousDatesAsAfter(Date startDate)926 private void parseAmbiguousDatesAsAfter(Date startDate) { 927 defaultCenturyStart = startDate; 928 calendar.setTime(startDate); 929 defaultCenturyStartYear = calendar.get(Calendar.YEAR); 930 } 931 932 /** 933 * Sets the 100-year period 2-digit years will be interpreted as being in 934 * to begin on the date the user specifies. 935 * 936 * @param startDate During parsing, two digit years will be placed in the range 937 * <code>startDate</code> to <code>startDate + 100 years</code>. 938 * @see #get2DigitYearStart 939 * @since 1.2 940 */ set2DigitYearStart(Date startDate)941 public void set2DigitYearStart(Date startDate) { 942 parseAmbiguousDatesAsAfter(new Date(startDate.getTime())); 943 } 944 945 /** 946 * Returns the beginning date of the 100-year period 2-digit years are interpreted 947 * as being within. 948 * 949 * @return the start of the 100-year period into which two digit years are 950 * parsed 951 * @see #set2DigitYearStart 952 * @since 1.2 953 */ get2DigitYearStart()954 public Date get2DigitYearStart() { 955 return (Date) defaultCenturyStart.clone(); 956 } 957 958 /** 959 * Formats the given <code>Date</code> into a date/time string and appends 960 * the result to the given <code>StringBuffer</code>. 961 * 962 * @param date the date-time value to be formatted into a date-time string. 963 * @param toAppendTo where the new date-time text is to be appended. 964 * @param pos the formatting position. On input: an alignment field, 965 * if desired. On output: the offsets of the alignment field. 966 * @return the formatted date-time string. 967 * @exception NullPointerException if the given {@code date} is {@code null}. 968 */ 969 @Override format(Date date, StringBuffer toAppendTo, FieldPosition pos)970 public StringBuffer format(Date date, StringBuffer toAppendTo, 971 FieldPosition pos) 972 { 973 pos.beginIndex = pos.endIndex = 0; 974 return format(date, toAppendTo, pos.getFieldDelegate()); 975 } 976 977 // Called from Format after creating a FieldDelegate format(Date date, StringBuffer toAppendTo, FieldDelegate delegate)978 private StringBuffer format(Date date, StringBuffer toAppendTo, 979 FieldDelegate delegate) { 980 // Convert input date to time field list 981 calendar.setTime(date); 982 983 boolean useDateFormatSymbols = useDateFormatSymbols(); 984 985 for (int i = 0; i < compiledPattern.length; ) { 986 int tag = compiledPattern[i] >>> 8; 987 int count = compiledPattern[i++] & 0xff; 988 if (count == 255) { 989 count = compiledPattern[i++] << 16; 990 count |= compiledPattern[i++]; 991 } 992 993 switch (tag) { 994 case TAG_QUOTE_ASCII_CHAR: 995 toAppendTo.append((char)count); 996 break; 997 998 case TAG_QUOTE_CHARS: 999 toAppendTo.append(compiledPattern, i, count); 1000 i += count; 1001 break; 1002 1003 default: 1004 subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); 1005 break; 1006 } 1007 } 1008 return toAppendTo; 1009 } 1010 1011 /** 1012 * Formats an Object producing an <code>AttributedCharacterIterator</code>. 1013 * You can use the returned <code>AttributedCharacterIterator</code> 1014 * to build the resulting String, as well as to determine information 1015 * about the resulting String. 1016 * <p> 1017 * Each attribute key of the AttributedCharacterIterator will be of type 1018 * <code>DateFormat.Field</code>, with the corresponding attribute value 1019 * being the same as the attribute key. 1020 * 1021 * @exception NullPointerException if obj is null. 1022 * @exception IllegalArgumentException if the Format cannot format the 1023 * given object, or if the Format's pattern string is invalid. 1024 * @param obj The object to format 1025 * @return AttributedCharacterIterator describing the formatted value. 1026 * @since 1.4 1027 */ 1028 @Override formatToCharacterIterator(Object obj)1029 public AttributedCharacterIterator formatToCharacterIterator(Object obj) { 1030 StringBuffer sb = new StringBuffer(); 1031 CharacterIteratorFieldDelegate delegate = new 1032 CharacterIteratorFieldDelegate(); 1033 1034 if (obj instanceof Date) { 1035 format((Date)obj, sb, delegate); 1036 } 1037 else if (obj instanceof Number) { 1038 format(new Date(((Number)obj).longValue()), sb, delegate); 1039 } 1040 else if (obj == null) { 1041 throw new NullPointerException( 1042 "formatToCharacterIterator must be passed non-null object"); 1043 } 1044 else { 1045 throw new IllegalArgumentException( 1046 "Cannot format given Object as a Date"); 1047 } 1048 return delegate.getIterator(sb.toString()); 1049 } 1050 1051 // Map index into pattern character string to Calendar field number 1052 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = { 1053 Calendar.ERA, 1054 Calendar.YEAR, 1055 Calendar.MONTH, 1056 Calendar.DATE, 1057 Calendar.HOUR_OF_DAY, 1058 Calendar.HOUR_OF_DAY, 1059 Calendar.MINUTE, 1060 Calendar.SECOND, 1061 Calendar.MILLISECOND, 1062 Calendar.DAY_OF_WEEK, 1063 Calendar.DAY_OF_YEAR, 1064 Calendar.DAY_OF_WEEK_IN_MONTH, 1065 Calendar.WEEK_OF_YEAR, 1066 Calendar.WEEK_OF_MONTH, 1067 Calendar.AM_PM, 1068 Calendar.HOUR, 1069 Calendar.HOUR, 1070 Calendar.ZONE_OFFSET, 1071 Calendar.ZONE_OFFSET, 1072 CalendarBuilder.WEEK_YEAR, // Pseudo Calendar field 1073 CalendarBuilder.ISO_DAY_OF_WEEK, // Pseudo Calendar field 1074 Calendar.ZONE_OFFSET, 1075 // 'L' and 'c', 1076 Calendar.MONTH, 1077 Calendar.DAY_OF_WEEK 1078 }; 1079 1080 // Map index into pattern character string to DateFormat field number 1081 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { 1082 DateFormat.ERA_FIELD, 1083 DateFormat.YEAR_FIELD, 1084 DateFormat.MONTH_FIELD, 1085 DateFormat.DATE_FIELD, 1086 DateFormat.HOUR_OF_DAY1_FIELD, 1087 DateFormat.HOUR_OF_DAY0_FIELD, 1088 DateFormat.MINUTE_FIELD, 1089 DateFormat.SECOND_FIELD, 1090 DateFormat.MILLISECOND_FIELD, 1091 DateFormat.DAY_OF_WEEK_FIELD, 1092 DateFormat.DAY_OF_YEAR_FIELD, 1093 DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, 1094 DateFormat.WEEK_OF_YEAR_FIELD, 1095 DateFormat.WEEK_OF_MONTH_FIELD, 1096 DateFormat.AM_PM_FIELD, 1097 DateFormat.HOUR1_FIELD, 1098 DateFormat.HOUR0_FIELD, 1099 DateFormat.TIMEZONE_FIELD, 1100 DateFormat.TIMEZONE_FIELD, 1101 DateFormat.YEAR_FIELD, 1102 DateFormat.DAY_OF_WEEK_FIELD, 1103 DateFormat.TIMEZONE_FIELD, 1104 // 'L' and 'c' 1105 DateFormat.MONTH_FIELD, 1106 DateFormat.DAY_OF_WEEK_FIELD 1107 }; 1108 1109 // Maps from DecimalFormatSymbols index to Field constant 1110 private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = { 1111 Field.ERA, 1112 Field.YEAR, 1113 Field.MONTH, 1114 Field.DAY_OF_MONTH, 1115 Field.HOUR_OF_DAY1, 1116 Field.HOUR_OF_DAY0, 1117 Field.MINUTE, 1118 Field.SECOND, 1119 Field.MILLISECOND, 1120 Field.DAY_OF_WEEK, 1121 Field.DAY_OF_YEAR, 1122 Field.DAY_OF_WEEK_IN_MONTH, 1123 Field.WEEK_OF_YEAR, 1124 Field.WEEK_OF_MONTH, 1125 Field.AM_PM, 1126 Field.HOUR1, 1127 Field.HOUR0, 1128 Field.TIME_ZONE, 1129 Field.TIME_ZONE, 1130 Field.YEAR, 1131 Field.DAY_OF_WEEK, 1132 Field.TIME_ZONE, 1133 // 'L' and 'c' 1134 Field.MONTH, 1135 Field.DAY_OF_WEEK 1136 }; 1137 1138 private static final String UTC = "UTC"; 1139 1140 /** 1141 * The list of time zone ids formatted as "UTC". 1142 * This mirrors isUtc in libcore_icu_TimeZoneNames.cpp 1143 */ 1144 private static final Set<String> UTC_ZONE_IDS = Collections.unmodifiableSet(new HashSet<>( 1145 Arrays.asList("Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "UCT", "UTC", 1146 "Universal", "Zulu"))); 1147 1148 /** 1149 * Private member function that does the real date/time formatting. 1150 */ subFormat(int patternCharIndex, int count, FieldDelegate delegate, StringBuffer buffer, boolean useDateFormatSymbols)1151 private void subFormat(int patternCharIndex, int count, 1152 FieldDelegate delegate, StringBuffer buffer, 1153 boolean useDateFormatSymbols) 1154 { 1155 int maxIntCount = Integer.MAX_VALUE; 1156 String current = null; 1157 int beginOffset = buffer.length(); 1158 1159 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 1160 int value; 1161 if (field == CalendarBuilder.WEEK_YEAR) { 1162 if (calendar.isWeekDateSupported()) { 1163 value = calendar.getWeekYear(); 1164 } else { 1165 // use calendar year 'y' instead 1166 patternCharIndex = PATTERN_YEAR; 1167 field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 1168 value = calendar.get(field); 1169 } 1170 } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) { 1171 value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK)); 1172 } else { 1173 value = calendar.get(field); 1174 } 1175 1176 int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; 1177 if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) { 1178 current = calendar.getDisplayName(field, style, locale); 1179 } 1180 1181 // Note: zeroPaddingNumber() assumes that maxDigits is either 1182 // 2 or maxIntCount. If we make any changes to this, 1183 // zeroPaddingNumber() must be fixed. 1184 1185 switch (patternCharIndex) { 1186 case PATTERN_ERA: // 'G' 1187 if (useDateFormatSymbols) { 1188 String[] eras = formatData.getEras(); 1189 if (value < eras.length) { 1190 current = eras[value]; 1191 } 1192 } 1193 if (current == null) { 1194 current = ""; 1195 } 1196 break; 1197 1198 case PATTERN_WEEK_YEAR: // 'Y' 1199 case PATTERN_YEAR: // 'y' 1200 if (calendar instanceof GregorianCalendar) { 1201 if (count != 2) { 1202 zeroPaddingNumber(value, count, maxIntCount, buffer); 1203 } else { 1204 zeroPaddingNumber(value, 2, 2, buffer); 1205 } // clip 1996 to 96 1206 } else { 1207 if (current == null) { 1208 zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count, 1209 maxIntCount, buffer); 1210 } 1211 } 1212 break; 1213 1214 case PATTERN_MONTH: // 'M' 1215 { 1216 current = formatMonth(count, value, maxIntCount, buffer, useDateFormatSymbols, 1217 false /* standalone */); 1218 break; 1219 } 1220 1221 case PATTERN_MONTH_STANDALONE: // 'L' 1222 { 1223 current = formatMonth(count, value, maxIntCount, buffer, useDateFormatSymbols, 1224 true /* standalone */); 1225 break; 1226 } 1227 1228 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 1229 if (current == null) { 1230 if (value == 0) { 1231 zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1, 1232 count, maxIntCount, buffer); 1233 } else { 1234 zeroPaddingNumber(value, count, maxIntCount, buffer); 1235 } 1236 } 1237 break; 1238 1239 case PATTERN_DAY_OF_WEEK: // 'E' 1240 { 1241 current = formatWeekday(count, value, useDateFormatSymbols, false /* standalone */); 1242 break; 1243 } 1244 1245 case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c' 1246 { 1247 current = formatWeekday(count, value, useDateFormatSymbols, true /* standalone */); 1248 break; 1249 } 1250 1251 case PATTERN_AM_PM: // 'a' 1252 if (useDateFormatSymbols) { 1253 String[] ampm = formatData.getAmPmStrings(); 1254 current = ampm[value]; 1255 } 1256 break; 1257 1258 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM 1259 if (current == null) { 1260 if (value == 0) { 1261 zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1, 1262 count, maxIntCount, buffer); 1263 } else { 1264 zeroPaddingNumber(value, count, maxIntCount, buffer); 1265 } 1266 } 1267 break; 1268 1269 case PATTERN_ZONE_NAME: // 'z' 1270 if (current == null) { 1271 TimeZone tz = calendar.getTimeZone(); 1272 boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0); 1273 String zoneString; 1274 if (formatData.isZoneStringsSet) { 1275 // DateFormatSymbols.setZoneStrings() has be used, use those values instead of 1276 // ICU code. 1277 int tzstyle = count < 4 ? TimeZone.SHORT : TimeZone.LONG; 1278 zoneString = libcore.icu.TimeZoneNames.getDisplayName( 1279 formatData.getZoneStringsWrapper(), tz.getID(), daylight, tzstyle); 1280 } else { 1281 if (UTC_ZONE_IDS.contains(tz.getID())) { 1282 // ICU doesn't have name strings for UTC, explicitly print it as "UTC". 1283 zoneString = UTC; 1284 } else { 1285 TimeZoneNames.NameType nameType; 1286 if (count < 4) { 1287 nameType = daylight 1288 ? TimeZoneNames.NameType.SHORT_DAYLIGHT 1289 : TimeZoneNames.NameType.SHORT_STANDARD; 1290 } else { 1291 nameType = daylight 1292 ? TimeZoneNames.NameType.LONG_DAYLIGHT 1293 : TimeZoneNames.NameType.LONG_STANDARD; 1294 } 1295 String canonicalID = android.icu.util.TimeZone.getCanonicalID(tz.getID()); 1296 zoneString = getTimeZoneNames() 1297 .getDisplayName(canonicalID, nameType, calendar.getTimeInMillis()); 1298 } 1299 } 1300 if (zoneString != null) { 1301 buffer.append(zoneString); 1302 } else { 1303 int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) + 1304 calendar.get(Calendar.DST_OFFSET); 1305 buffer.append(TimeZone.createGmtOffsetString(true, true, offsetMillis)); 1306 } 1307 } 1308 break; 1309 1310 case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form) 1311 { 1312 value = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 1313 final boolean includeSeparator = (count >= 4); 1314 final boolean includeGmt = (count == 4); 1315 buffer.append(TimeZone.createGmtOffsetString(includeGmt, includeSeparator, value)); 1316 1317 break; 1318 } 1319 1320 case PATTERN_ISO_ZONE: // 'X' 1321 value = calendar.get(Calendar.ZONE_OFFSET) 1322 + calendar.get(Calendar.DST_OFFSET); 1323 1324 if (value == 0) { 1325 buffer.append('Z'); 1326 break; 1327 } 1328 1329 value /= 60000; 1330 if (value >= 0) { 1331 buffer.append('+'); 1332 } else { 1333 buffer.append('-'); 1334 value = -value; 1335 } 1336 1337 CalendarUtils.sprintf0d(buffer, value / 60, 2); 1338 if (count == 1) { 1339 break; 1340 } 1341 1342 if (count == 3) { 1343 buffer.append(':'); 1344 } 1345 CalendarUtils.sprintf0d(buffer, value % 60, 2); 1346 break; 1347 case PATTERN_MILLISECOND: // 'S' 1348 // Fractional seconds must be treated specially. We must always convert the parsed 1349 // value into a fractional second [0, 1) and then widen it out to the appropriate 1350 // formatted size. For example, an initial value of 789 will be converted 1351 // 0.789 and then become ".7" (S) or ".78" (SS) or "0.789" (SSS) or "0.7890" (SSSS) 1352 // in the resulting formatted output. 1353 if (current == null) { 1354 value = (int) (((double) value / 1000) * Math.pow(10, count)); 1355 zeroPaddingNumber(value, count, count, buffer); 1356 } 1357 break; 1358 1359 default: 1360 // case PATTERN_DAY_OF_MONTH: // 'd' 1361 // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 1362 // case PATTERN_MINUTE: // 'm' 1363 // case PATTERN_SECOND: // 's' 1364 // case PATTERN_DAY_OF_YEAR: // 'D' 1365 // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' 1366 // case PATTERN_WEEK_OF_YEAR: // 'w' 1367 // case PATTERN_WEEK_OF_MONTH: // 'W' 1368 // case PATTERN_HOUR0: // 'K' eg, 11PM + 1 hour =>> 0 AM 1369 // case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 7 1370 if (current == null) { 1371 zeroPaddingNumber(value, count, maxIntCount, buffer); 1372 } 1373 break; 1374 } // switch (patternCharIndex) 1375 1376 if (current != null) { 1377 buffer.append(current); 1378 } 1379 1380 int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]; 1381 Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex]; 1382 1383 delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer); 1384 } 1385 formatWeekday(int count, int value, boolean useDateFormatSymbols, boolean standalone)1386 private String formatWeekday(int count, int value, boolean useDateFormatSymbols, 1387 boolean standalone) { 1388 if (useDateFormatSymbols) { 1389 final String[] weekdays; 1390 if (count == 4) { 1391 weekdays = standalone ? formatData.getStandAloneWeekdays() : formatData.getWeekdays(); 1392 } else if (count == 5) { 1393 weekdays = 1394 standalone ? formatData.getTinyStandAloneWeekdays() : formatData.getTinyWeekdays(); 1395 1396 } else { // count < 4, use abbreviated form if exists 1397 weekdays = standalone ? formatData.getShortStandAloneWeekdays() : formatData.getShortWeekdays(); 1398 } 1399 1400 return weekdays[value]; 1401 } 1402 1403 return null; 1404 } 1405 formatMonth(int count, int value, int maxIntCount, StringBuffer buffer, boolean useDateFormatSymbols, boolean standalone)1406 private String formatMonth(int count, int value, int maxIntCount, StringBuffer buffer, 1407 boolean useDateFormatSymbols, boolean standalone) { 1408 String current = null; 1409 if (useDateFormatSymbols) { 1410 final String[] months; 1411 if (count == 4) { 1412 months = standalone ? formatData.getStandAloneMonths() : formatData.getMonths(); 1413 } else if (count == 5) { 1414 months = standalone ? formatData.getTinyStandAloneMonths() : formatData.getTinyMonths(); 1415 } else if (count == 3) { 1416 months = standalone ? formatData.getShortStandAloneMonths() : formatData.getShortMonths(); 1417 } else { 1418 months = null; 1419 } 1420 1421 if (months != null) { 1422 current = months[value]; 1423 } 1424 } else { 1425 if (count < 3) { 1426 current = null; 1427 } 1428 } 1429 1430 if (current == null) { 1431 zeroPaddingNumber(value+1, count, maxIntCount, buffer); 1432 } 1433 1434 return current; 1435 } 1436 1437 /** 1438 * Formats a number with the specified minimum and maximum number of digits. 1439 */ zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)1440 private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer) 1441 { 1442 // Optimization for 1, 2 and 4 digit numbers. This should 1443 // cover most cases of formatting date/time related items. 1444 // Note: This optimization code assumes that maxDigits is 1445 // either 2 or Integer.MAX_VALUE (maxIntCount in format()). 1446 try { 1447 if (zeroDigit == 0) { 1448 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit(); 1449 } 1450 if (value >= 0) { 1451 if (value < 100 && minDigits >= 1 && minDigits <= 2) { 1452 if (value < 10) { 1453 if (minDigits == 2) { 1454 buffer.append(zeroDigit); 1455 } 1456 buffer.append((char)(zeroDigit + value)); 1457 } else { 1458 buffer.append((char)(zeroDigit + value / 10)); 1459 buffer.append((char)(zeroDigit + value % 10)); 1460 } 1461 return; 1462 } else if (value >= 1000 && value < 10000) { 1463 if (minDigits == 4) { 1464 buffer.append((char)(zeroDigit + value / 1000)); 1465 value %= 1000; 1466 buffer.append((char)(zeroDigit + value / 100)); 1467 value %= 100; 1468 buffer.append((char)(zeroDigit + value / 10)); 1469 buffer.append((char)(zeroDigit + value % 10)); 1470 return; 1471 } 1472 if (minDigits == 2 && maxDigits == 2) { 1473 zeroPaddingNumber(value % 100, 2, 2, buffer); 1474 return; 1475 } 1476 } 1477 } 1478 } catch (Exception e) { 1479 } 1480 1481 numberFormat.setMinimumIntegerDigits(minDigits); 1482 numberFormat.setMaximumIntegerDigits(maxDigits); 1483 numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE); 1484 } 1485 1486 1487 /** 1488 * Parses text from a string to produce a <code>Date</code>. 1489 * <p> 1490 * The method attempts to parse text starting at the index given by 1491 * <code>pos</code>. 1492 * If parsing succeeds, then the index of <code>pos</code> is updated 1493 * to the index after the last character used (parsing does not necessarily 1494 * use all characters up to the end of the string), and the parsed 1495 * date is returned. The updated <code>pos</code> can be used to 1496 * indicate the starting point for the next call to this method. 1497 * If an error occurs, then the index of <code>pos</code> is not 1498 * changed, the error index of <code>pos</code> is set to the index of 1499 * the character where the error occurred, and null is returned. 1500 * 1501 * <p>This parsing operation uses the {@link DateFormat#calendar 1502 * calendar} to produce a {@code Date}. All of the {@code 1503 * calendar}'s date-time fields are {@linkplain Calendar#clear() 1504 * cleared} before parsing, and the {@code calendar}'s default 1505 * values of the date-time fields are used for any missing 1506 * date-time information. For example, the year value of the 1507 * parsed {@code Date} is 1970 with {@link GregorianCalendar} if 1508 * no year value is given from the parsing operation. The {@code 1509 * TimeZone} value may be overwritten, depending on the given 1510 * pattern and the time zone value in {@code text}. Any {@code 1511 * TimeZone} value that has previously been set by a call to 1512 * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need 1513 * to be restored for further operations. 1514 * 1515 * @param text A <code>String</code>, part of which should be parsed. 1516 * @param pos A <code>ParsePosition</code> object with index and error 1517 * index information as described above. 1518 * @return A <code>Date</code> parsed from the string. In case of 1519 * error, returns null. 1520 * @exception NullPointerException if <code>text</code> or <code>pos</code> is null. 1521 */ 1522 @Override parse(String text, ParsePosition pos)1523 public Date parse(String text, ParsePosition pos) { 1524 // Make sure the timezone associated with this dateformat instance (set via 1525 // {@code setTimeZone} isn't change as a side-effect of parsing a date. 1526 final TimeZone tz = getTimeZone(); 1527 try { 1528 return parseInternal(text, pos); 1529 } finally { 1530 setTimeZone(tz); 1531 } 1532 } 1533 parseInternal(String text, ParsePosition pos)1534 private Date parseInternal(String text, ParsePosition pos) 1535 { 1536 checkNegativeNumberExpression(); 1537 1538 int start = pos.index; 1539 int oldStart = start; 1540 int textLength = text.length(); 1541 1542 boolean[] ambiguousYear = {false}; 1543 1544 CalendarBuilder calb = new CalendarBuilder(); 1545 1546 for (int i = 0; i < compiledPattern.length; ) { 1547 int tag = compiledPattern[i] >>> 8; 1548 int count = compiledPattern[i++] & 0xff; 1549 if (count == 255) { 1550 count = compiledPattern[i++] << 16; 1551 count |= compiledPattern[i++]; 1552 } 1553 1554 switch (tag) { 1555 case TAG_QUOTE_ASCII_CHAR: 1556 if (start >= textLength || text.charAt(start) != (char)count) { 1557 pos.index = oldStart; 1558 pos.errorIndex = start; 1559 return null; 1560 } 1561 start++; 1562 break; 1563 1564 case TAG_QUOTE_CHARS: 1565 while (count-- > 0) { 1566 if (start >= textLength || text.charAt(start) != compiledPattern[i++]) { 1567 pos.index = oldStart; 1568 pos.errorIndex = start; 1569 return null; 1570 } 1571 start++; 1572 } 1573 break; 1574 1575 default: 1576 // Peek the next pattern to determine if we need to 1577 // obey the number of pattern letters for 1578 // parsing. It's required when parsing contiguous 1579 // digit text (e.g., "20010704") with a pattern which 1580 // has no delimiters between fields, like "yyyyMMdd". 1581 boolean obeyCount = false; 1582 1583 // In Arabic, a minus sign for a negative number is put after 1584 // the number. Even in another locale, a minus sign can be 1585 // put after a number using DateFormat.setNumberFormat(). 1586 // If both the minus sign and the field-delimiter are '-', 1587 // subParse() needs to determine whether a '-' after a number 1588 // in the given text is a delimiter or is a minus sign for the 1589 // preceding number. We give subParse() a clue based on the 1590 // information in compiledPattern. 1591 boolean useFollowingMinusSignAsDelimiter = false; 1592 1593 if (i < compiledPattern.length) { 1594 int nextTag = compiledPattern[i] >>> 8; 1595 if (!(nextTag == TAG_QUOTE_ASCII_CHAR || 1596 nextTag == TAG_QUOTE_CHARS)) { 1597 obeyCount = true; 1598 } 1599 1600 if (hasFollowingMinusSign && 1601 (nextTag == TAG_QUOTE_ASCII_CHAR || 1602 nextTag == TAG_QUOTE_CHARS)) { 1603 int c; 1604 if (nextTag == TAG_QUOTE_ASCII_CHAR) { 1605 c = compiledPattern[i] & 0xff; 1606 } else { 1607 c = compiledPattern[i+1]; 1608 } 1609 1610 if (c == minusSign) { 1611 useFollowingMinusSignAsDelimiter = true; 1612 } 1613 } 1614 } 1615 start = subParse(text, start, tag, count, obeyCount, 1616 ambiguousYear, pos, 1617 useFollowingMinusSignAsDelimiter, calb); 1618 if (start < 0) { 1619 pos.index = oldStart; 1620 return null; 1621 } 1622 } 1623 } 1624 1625 // At this point the fields of Calendar have been set. Calendar 1626 // will fill in default values for missing fields when the time 1627 // is computed. 1628 1629 pos.index = start; 1630 1631 Date parsedDate; 1632 try { 1633 parsedDate = calb.establish(calendar).getTime(); 1634 // If the year value is ambiguous, 1635 // then the two-digit year == the default start year 1636 if (ambiguousYear[0]) { 1637 if (parsedDate.before(defaultCenturyStart)) { 1638 parsedDate = calb.addYear(100).establish(calendar).getTime(); 1639 } 1640 } 1641 } 1642 // An IllegalArgumentException will be thrown by Calendar.getTime() 1643 // if any fields are out of range, e.g., MONTH == 17. 1644 catch (IllegalArgumentException e) { 1645 pos.errorIndex = start; 1646 pos.index = oldStart; 1647 return null; 1648 } 1649 1650 return parsedDate; 1651 } 1652 1653 /** 1654 * Private code-size reduction function used by subParse. 1655 * @param text the time text being parsed. 1656 * @param start where to start parsing. 1657 * @param field the date field being parsed. 1658 * @param data the string array to parsed. 1659 * @return the new start position if matching succeeded; a negative number 1660 * indicating matching failure, otherwise. 1661 */ matchString(String text, int start, int field, String[] data, CalendarBuilder calb)1662 private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb) 1663 { 1664 int i = 0; 1665 int count = data.length; 1666 1667 if (field == Calendar.DAY_OF_WEEK) { 1668 i = 1; 1669 } 1670 1671 // There may be multiple strings in the data[] array which begin with 1672 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). 1673 // We keep track of the longest match, and return that. Note that this 1674 // unfortunately requires us to test all array elements. 1675 int bestMatchLength = 0, bestMatch = -1; 1676 for (; i<count; ++i) 1677 { 1678 int length = data[i].length(); 1679 // Always compare if we have no match yet; otherwise only compare 1680 // against potentially better matches (longer strings). 1681 if (length > bestMatchLength && 1682 text.regionMatches(true, start, data[i], 0, length)) 1683 { 1684 bestMatch = i; 1685 bestMatchLength = length; 1686 } 1687 1688 // When the input option ends with a period (usually an abbreviated form), attempt 1689 // to match all chars up to that period. 1690 if ((data[i].charAt(length - 1) == '.') && 1691 ((length - 1) > bestMatchLength) && 1692 text.regionMatches(true, start, data[i], 0, length - 1)) { 1693 bestMatch = i; 1694 bestMatchLength = (length - 1); 1695 } 1696 } 1697 if (bestMatch >= 0) 1698 { 1699 calb.set(field, bestMatch); 1700 return start + bestMatchLength; 1701 } 1702 return -start; 1703 } 1704 1705 /** 1706 * Performs the same thing as matchString(String, int, int, 1707 * String[]). This method takes a Map<String, Integer> instead of 1708 * String[]. 1709 */ matchString(String text, int start, int field, Map<String,Integer> data, CalendarBuilder calb)1710 private int matchString(String text, int start, int field, 1711 Map<String,Integer> data, CalendarBuilder calb) { 1712 if (data != null) { 1713 String bestMatch = null; 1714 1715 for (String name : data.keySet()) { 1716 int length = name.length(); 1717 if (bestMatch == null || length > bestMatch.length()) { 1718 if (text.regionMatches(true, start, name, 0, length)) { 1719 bestMatch = name; 1720 } 1721 } 1722 } 1723 1724 if (bestMatch != null) { 1725 calb.set(field, data.get(bestMatch)); 1726 return start + bestMatch.length(); 1727 } 1728 } 1729 return -start; 1730 } 1731 matchZoneString(String text, int start, String[] zoneNames)1732 private int matchZoneString(String text, int start, String[] zoneNames) { 1733 for (int i = 1; i <= 4; ++i) { 1734 // Checking long and short zones [1 & 2], 1735 // and long and short daylight [3 & 4]. 1736 String zoneName = zoneNames[i]; 1737 if (text.regionMatches(true, start, 1738 zoneName, 0, zoneName.length())) { 1739 return i; 1740 } 1741 } 1742 return -1; 1743 } 1744 1745 /** 1746 * Parses the string in {@code text} (starting at {@code start}), interpreting it as a time zone 1747 * name. If a time zone is found, the internal calendar is set to that timezone and the index of 1748 * the first character after the time zone name is returned. Otherwise, returns {@code 0}. 1749 * @return the index of the next character to parse or {@code 0} on error. 1750 */ subParseZoneString(String text, int start, CalendarBuilder calb)1751 private int subParseZoneString(String text, int start, CalendarBuilder calb) { 1752 if (formatData.isZoneStringsSet) { 1753 // DateFormatSymbols.setZoneStrings() has be used, use those values instead of ICU code. 1754 return subParseZoneStringFromSymbols(text, start, calb); 1755 } else { 1756 return subParseZoneStringFromICU(text, start, calb); 1757 } 1758 } 1759 getTimeZoneNames()1760 private TimeZoneNames getTimeZoneNames() { 1761 if (timeZoneNames == null) { 1762 timeZoneNames = TimeZoneNames.getInstance(locale); 1763 } 1764 return timeZoneNames; 1765 } 1766 1767 /** 1768 * The set of name types accepted when parsing time zone names. 1769 */ 1770 private static final EnumSet<TimeZoneNames.NameType> NAME_TYPES = 1771 EnumSet.of(TimeZoneNames.NameType.LONG_GENERIC, TimeZoneNames.NameType.LONG_STANDARD, 1772 TimeZoneNames.NameType.LONG_DAYLIGHT, TimeZoneNames.NameType.SHORT_GENERIC, 1773 TimeZoneNames.NameType.SHORT_STANDARD, TimeZoneNames.NameType.SHORT_DAYLIGHT); 1774 1775 /** 1776 * Time zone name types that indicate daylight saving time. 1777 */ 1778 private static final Set<TimeZoneNames.NameType> DST_NAME_TYPES = 1779 Collections.unmodifiableSet(EnumSet.of( 1780 TimeZoneNames.NameType.LONG_DAYLIGHT, TimeZoneNames.NameType.SHORT_DAYLIGHT)); 1781 1782 /** 1783 * Parses the time zone string using the ICU4J class {@link TimeZoneNames}. 1784 */ subParseZoneStringFromICU(String text, int start, CalendarBuilder calb)1785 private int subParseZoneStringFromICU(String text, int start, CalendarBuilder calb) { 1786 String currentTimeZoneID = android.icu.util.TimeZone.getCanonicalID(getTimeZone().getID()); 1787 1788 TimeZoneNames tzNames = getTimeZoneNames(); 1789 TimeZoneNames.MatchInfo bestMatch = null; 1790 // The MetaZones associated with the current time zone are needed in two places, both of 1791 // which are avoided in some cases, so they are computed lazily. 1792 Set<String> currentTzMetaZoneIds = null; 1793 1794 // ICU doesn't parse the string "UTC", so manually check for it. 1795 if (start + UTC.length() <= text.length() && 1796 text.regionMatches(true /* ignoreCase */, start, UTC, 0, UTC.length())) { 1797 bestMatch = new TimeZoneNames.MatchInfo( 1798 TimeZoneNames.NameType.SHORT_GENERIC, UTC, null, UTC.length()); 1799 } else { 1800 Collection<TimeZoneNames.MatchInfo> matches = tzNames.find(text, start, NAME_TYPES); 1801 for (TimeZoneNames.MatchInfo match : matches) { 1802 if (bestMatch == null || bestMatch.matchLength() < match.matchLength()) { 1803 bestMatch = match; 1804 } else if (bestMatch.matchLength() == match.matchLength()) { 1805 if (currentTimeZoneID.equals(match.tzID())) { 1806 // Prefer the currently set timezone over other matches, even if they are 1807 // the same length. 1808 bestMatch = match; 1809 break; 1810 } else if (match.mzID() != null) { 1811 if (currentTzMetaZoneIds == null) { 1812 currentTzMetaZoneIds = 1813 tzNames.getAvailableMetaZoneIDs(currentTimeZoneID); 1814 } 1815 if (currentTzMetaZoneIds.contains(match.mzID())) { 1816 bestMatch = match; 1817 break; 1818 } 1819 } 1820 } 1821 } 1822 if (bestMatch == null) { 1823 // No match found, return error. 1824 return -start; 1825 } 1826 } 1827 1828 String tzId = bestMatch.tzID(); 1829 if (tzId == null) { 1830 if (currentTzMetaZoneIds == null) { 1831 currentTzMetaZoneIds = tzNames.getAvailableMetaZoneIDs(currentTimeZoneID); 1832 } 1833 if (currentTzMetaZoneIds.contains(bestMatch.mzID())) { 1834 tzId = currentTimeZoneID; 1835 } else { 1836 // Match was for a meta-zone, find the matching reference zone. 1837 ULocale uLocale = ULocale.forLocale(locale); 1838 String region = uLocale.getCountry(); 1839 if (region.length() == 0) { 1840 uLocale = ULocale.addLikelySubtags(uLocale); 1841 region = uLocale.getCountry(); 1842 } 1843 tzId = tzNames.getReferenceZoneID(bestMatch.mzID(), region); 1844 } 1845 } 1846 1847 TimeZone newTimeZone = TimeZone.getTimeZone(tzId); 1848 if (!currentTimeZoneID.equals(tzId)) { 1849 setTimeZone(newTimeZone); 1850 } 1851 1852 // Same logic as in subParseZoneStringFromSymbols, see below for details. 1853 boolean isDst = DST_NAME_TYPES.contains(bestMatch.nameType()); 1854 int dstAmount = isDst ? newTimeZone.getDSTSavings() : 0; 1855 if (!isDst || dstAmount != 0) { 1856 calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount); 1857 } 1858 1859 return bestMatch.matchLength() + start; 1860 } 1861 1862 /** 1863 * Parses the time zone string using the information in {@link #formatData}. 1864 */ subParseZoneStringFromSymbols(String text, int start, CalendarBuilder calb)1865 private int subParseZoneStringFromSymbols(String text, int start, CalendarBuilder calb) { 1866 boolean useSameName = false; // true if standard and daylight time use the same abbreviation. 1867 TimeZone currentTimeZone = getTimeZone(); 1868 1869 // At this point, check for named time zones by looking through 1870 // the locale data from the TimeZoneNames strings. 1871 // Want to be able to parse both short and long forms. 1872 int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID()); 1873 TimeZone tz = null; 1874 String[][] zoneStrings = formatData.getZoneStringsWrapper(); 1875 String[] zoneNames = null; 1876 int nameIndex = 0; 1877 if (zoneIndex != -1) { 1878 zoneNames = zoneStrings[zoneIndex]; 1879 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { 1880 if (nameIndex <= 2) { 1881 // Check if the standard name (abbr) and the daylight name are the same. 1882 useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); 1883 } 1884 tz = TimeZone.getTimeZone(zoneNames[0]); 1885 } 1886 } 1887 if (tz == null) { 1888 zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID()); 1889 if (zoneIndex != -1) { 1890 zoneNames = zoneStrings[zoneIndex]; 1891 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { 1892 if (nameIndex <= 2) { 1893 useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); 1894 } 1895 tz = TimeZone.getTimeZone(zoneNames[0]); 1896 } 1897 } 1898 } 1899 1900 if (tz == null) { 1901 int len = zoneStrings.length; 1902 for (int i = 0; i < len; i++) { 1903 zoneNames = zoneStrings[i]; 1904 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { 1905 if (nameIndex <= 2) { 1906 useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); 1907 } 1908 tz = TimeZone.getTimeZone(zoneNames[0]); 1909 break; 1910 } 1911 } 1912 } 1913 if (tz != null) { // Matched any ? 1914 if (!tz.equals(currentTimeZone)) { 1915 setTimeZone(tz); 1916 } 1917 // If the time zone matched uses the same name 1918 // (abbreviation) for both standard and daylight time, 1919 // let the time zone in the Calendar decide which one. 1920 // 1921 // Also if tz.getDSTSaving() returns 0 for DST, use tz to 1922 // determine the local time. (6645292) 1923 int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0; 1924 if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) { 1925 calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount); 1926 } 1927 return (start + zoneNames[nameIndex].length()); 1928 } 1929 return -start; 1930 } 1931 1932 /** 1933 * Parses numeric forms of time zone offset, such as "hh:mm", and 1934 * sets calb to the parsed value. 1935 * 1936 * @param text the text to be parsed 1937 * @param start the character position to start parsing 1938 * @param sign 1: positive; -1: negative 1939 * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's 1940 * @param colonRequired true - colon required between hh and mm; false - no colon required 1941 * @param calb a CalendarBuilder in which the parsed value is stored 1942 * @return updated parsed position, or its negative value to indicate a parsing error 1943 */ subParseNumericZone(String text, int start, int sign, int count, boolean colonRequired, CalendarBuilder calb)1944 private int subParseNumericZone(String text, int start, int sign, int count, 1945 boolean colonRequired, CalendarBuilder calb) { 1946 int index = start; 1947 1948 parse: 1949 try { 1950 char c = text.charAt(index++); 1951 // Parse hh 1952 int hours; 1953 if (!isDigit(c)) { 1954 break parse; 1955 } 1956 hours = c - '0'; 1957 c = text.charAt(index++); 1958 if (isDigit(c)) { 1959 hours = hours * 10 + (c - '0'); 1960 } else { 1961 --index; 1962 } 1963 if (hours > 23) { 1964 break parse; 1965 } 1966 int minutes = 0; 1967 if (count != 1) { 1968 // Proceed with parsing mm 1969 c = text.charAt(index++); 1970 // Intentional change in behavior from OpenJDK. OpenJDK will return an error code 1971 // if a : is found and colonRequired is false, this will return an error code if 1972 // a : is not found and colonRequired is true. 1973 // 1974 // colonRequired | c == ':' | OpenJDK | this 1975 // false | false | ok | ok 1976 // false | true | error | ok 1977 // true | false | ok | error 1978 // true | true | ok | ok 1979 if (c == ':') { 1980 c = text.charAt(index++); 1981 } else if (colonRequired) { 1982 break parse; 1983 } 1984 if (!isDigit(c)) { 1985 break parse; 1986 } 1987 minutes = c - '0'; 1988 c = text.charAt(index++); 1989 if (!isDigit(c)) { 1990 break parse; 1991 } 1992 minutes = minutes * 10 + (c - '0'); 1993 if (minutes > 59) { 1994 break parse; 1995 } 1996 } 1997 minutes += hours * 60; 1998 calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign) 1999 .set(Calendar.DST_OFFSET, 0); 2000 return index; 2001 } catch (IndexOutOfBoundsException e) { 2002 } 2003 return 1 - index; // -(index - 1) 2004 } 2005 isDigit(char c)2006 private boolean isDigit(char c) { 2007 return c >= '0' && c <= '9'; 2008 } 2009 2010 /** 2011 * Private member function that converts the parsed date strings into 2012 * timeFields. Returns -start (for ParsePosition) if failed. 2013 * @param text the time text to be parsed. 2014 * @param start where to start parsing. 2015 * @param patternCharIndex the index of the pattern character. 2016 * @param count the count of a pattern character. 2017 * @param obeyCount if true, then the next field directly abuts this one, 2018 * and we should use the count to know when to stop parsing. 2019 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] 2020 * is true, then a two-digit year was parsed and may need to be readjusted. 2021 * @param origPos origPos.errorIndex is used to return an error index 2022 * at which a parse error occurred, if matching failure occurs. 2023 * @return the new start position if matching succeeded; -1 indicating 2024 * matching failure, otherwise. In case matching failure occurred, 2025 * an error index is set to origPos.errorIndex. 2026 */ subParse(String text, int start, int patternCharIndex, int count, boolean obeyCount, boolean[] ambiguousYear, ParsePosition origPos, boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb)2027 private int subParse(String text, int start, int patternCharIndex, int count, 2028 boolean obeyCount, boolean[] ambiguousYear, 2029 ParsePosition origPos, 2030 boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) { 2031 Number number; 2032 int value = 0; 2033 ParsePosition pos = new ParsePosition(0); 2034 pos.index = start; 2035 if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) { 2036 // use calendar year 'y' instead 2037 patternCharIndex = PATTERN_YEAR; 2038 } 2039 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 2040 2041 // If there are any spaces here, skip over them. If we hit the end 2042 // of the string, then fail. 2043 for (;;) { 2044 if (pos.index >= text.length()) { 2045 origPos.errorIndex = start; 2046 return -1; 2047 } 2048 char c = text.charAt(pos.index); 2049 if (c != ' ' && c != '\t') { 2050 break; 2051 } 2052 ++pos.index; 2053 } 2054 2055 parsing: 2056 { 2057 // We handle a few special cases here where we need to parse 2058 // a number value. We handle further, more generic cases below. We need 2059 // to handle some of them here because some fields require extra processing on 2060 // the parsed value. 2061 if (patternCharIndex == PATTERN_HOUR_OF_DAY1 || 2062 patternCharIndex == PATTERN_HOUR1 || 2063 (patternCharIndex == PATTERN_MONTH && count <= 2) || 2064 patternCharIndex == PATTERN_YEAR || 2065 patternCharIndex == PATTERN_WEEK_YEAR) { 2066 // It would be good to unify this with the obeyCount logic below, 2067 // but that's going to be difficult. 2068 if (obeyCount) { 2069 if ((start+count) > text.length()) { 2070 break parsing; 2071 } 2072 number = numberFormat.parse(text.substring(0, start+count), pos); 2073 } else { 2074 number = numberFormat.parse(text, pos); 2075 } 2076 if (number == null) { 2077 if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) { 2078 break parsing; 2079 } 2080 } else { 2081 value = number.intValue(); 2082 2083 if (useFollowingMinusSignAsDelimiter && (value < 0) && 2084 (((pos.index < text.length()) && 2085 (text.charAt(pos.index) != minusSign)) || 2086 ((pos.index == text.length()) && 2087 (text.charAt(pos.index-1) == minusSign)))) { 2088 value = -value; 2089 pos.index--; 2090 } 2091 } 2092 } 2093 2094 boolean useDateFormatSymbols = useDateFormatSymbols(); 2095 2096 int index; 2097 switch (patternCharIndex) { 2098 case PATTERN_ERA: // 'G' 2099 if (useDateFormatSymbols) { 2100 if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) { 2101 return index; 2102 } 2103 } else { 2104 Map<String, Integer> map = calendar.getDisplayNames(field, 2105 Calendar.ALL_STYLES, 2106 locale); 2107 if ((index = matchString(text, start, field, map, calb)) > 0) { 2108 return index; 2109 } 2110 } 2111 break parsing; 2112 2113 case PATTERN_WEEK_YEAR: // 'Y' 2114 case PATTERN_YEAR: // 'y' 2115 if (!(calendar instanceof GregorianCalendar)) { 2116 // calendar might have text representations for year values, 2117 // such as "\u5143" in JapaneseImperialCalendar. 2118 int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; 2119 Map<String, Integer> map = calendar.getDisplayNames(field, style, locale); 2120 if (map != null) { 2121 if ((index = matchString(text, start, field, map, calb)) > 0) { 2122 return index; 2123 } 2124 } 2125 calb.set(field, value); 2126 return pos.index; 2127 } 2128 2129 // If there are 3 or more YEAR pattern characters, this indicates 2130 // that the year value is to be treated literally, without any 2131 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise 2132 // we made adjustments to place the 2-digit year in the proper 2133 // century, for parsed strings from "00" to "99". Any other string 2134 // is treated literally: "2250", "-1", "1", "002". 2135 if (count <= 2 && (pos.index - start) == 2 2136 && Character.isDigit(text.charAt(start)) 2137 && Character.isDigit(text.charAt(start+1))) { 2138 // Assume for example that the defaultCenturyStart is 6/18/1903. 2139 // This means that two-digit years will be forced into the range 2140 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 2141 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond 2142 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the 2143 // other fields specify a date before 6/18, or 1903 if they specify a 2144 // date afterwards. As a result, 03 is an ambiguous year. All other 2145 // two-digit years are unambiguous. 2146 int ambiguousTwoDigitYear = defaultCenturyStartYear % 100; 2147 ambiguousYear[0] = value == ambiguousTwoDigitYear; 2148 value += (defaultCenturyStartYear/100)*100 + 2149 (value < ambiguousTwoDigitYear ? 100 : 0); 2150 } 2151 calb.set(field, value); 2152 return pos.index; 2153 2154 case PATTERN_MONTH: // 'M' 2155 { 2156 final int idx = parseMonth(text, count, value, start, field, pos, 2157 useDateFormatSymbols, false /* isStandalone */, calb); 2158 if (idx > 0) { 2159 return idx; 2160 } 2161 2162 break parsing; 2163 } 2164 2165 case PATTERN_MONTH_STANDALONE: // 'L'. 2166 { 2167 final int idx = parseMonth(text, count, value, start, field, pos, 2168 useDateFormatSymbols, true /* isStandalone */, calb); 2169 if (idx > 0) { 2170 return idx; 2171 } 2172 break parsing; 2173 } 2174 2175 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 2176 if (!isLenient()) { 2177 // Validate the hour value in non-lenient 2178 if (value < 1 || value > 24) { 2179 break parsing; 2180 } 2181 } 2182 // [We computed 'value' above.] 2183 if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) { 2184 value = 0; 2185 } 2186 calb.set(Calendar.HOUR_OF_DAY, value); 2187 return pos.index; 2188 2189 case PATTERN_DAY_OF_WEEK: // 'E' 2190 { 2191 final int idx = parseWeekday(text, start, field, useDateFormatSymbols, 2192 false /* standalone */, calb); 2193 if (idx > 0) { 2194 return idx; 2195 } 2196 break parsing; 2197 } 2198 2199 case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c' 2200 { 2201 final int idx = parseWeekday(text, start, field, useDateFormatSymbols, 2202 true /* standalone */, calb); 2203 if (idx > 0) { 2204 return idx; 2205 } 2206 2207 break parsing; 2208 } 2209 2210 case PATTERN_AM_PM: // 'a' 2211 if (useDateFormatSymbols) { 2212 if ((index = matchString(text, start, Calendar.AM_PM, 2213 formatData.getAmPmStrings(), calb)) > 0) { 2214 return index; 2215 } 2216 } else { 2217 Map<String,Integer> map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale); 2218 if ((index = matchString(text, start, field, map, calb)) > 0) { 2219 return index; 2220 } 2221 } 2222 break parsing; 2223 2224 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM 2225 if (!isLenient()) { 2226 // Validate the hour value in non-lenient 2227 if (value < 1 || value > 12) { 2228 break parsing; 2229 } 2230 } 2231 // [We computed 'value' above.] 2232 if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) { 2233 value = 0; 2234 } 2235 calb.set(Calendar.HOUR, value); 2236 return pos.index; 2237 2238 case PATTERN_ZONE_NAME: // 'z' 2239 case PATTERN_ZONE_VALUE: // 'Z' 2240 { 2241 int sign = 0; 2242 try { 2243 char c = text.charAt(pos.index); 2244 if (c == '+') { 2245 sign = 1; 2246 } else if (c == '-') { 2247 sign = -1; 2248 } 2249 if (sign == 0) { 2250 // Try parsing a custom time zone "GMT+hh:mm" or "GMT". 2251 if ((c == 'G' || c == 'g') 2252 && (text.length() - start) >= GMT.length() 2253 && text.regionMatches(true, start, GMT, 0, GMT.length())) { 2254 pos.index = start + GMT.length(); 2255 2256 if ((text.length() - pos.index) > 0) { 2257 c = text.charAt(pos.index); 2258 if (c == '+') { 2259 sign = 1; 2260 } else if (c == '-') { 2261 sign = -1; 2262 } 2263 } 2264 2265 if (sign == 0) { /* "GMT" without offset */ 2266 calb.set(Calendar.ZONE_OFFSET, 0) 2267 .set(Calendar.DST_OFFSET, 0); 2268 return pos.index; 2269 } 2270 2271 // Parse the rest as "hh[:]?mm" 2272 int i = subParseNumericZone(text, ++pos.index, sign, 0, 2273 false, calb); 2274 if (i > 0) { 2275 return i; 2276 } 2277 pos.index = -i; 2278 } else { 2279 // Try parsing the text as a time zone 2280 // name or abbreviation. 2281 int i = subParseZoneString(text, pos.index, calb); 2282 if (i > 0) { 2283 return i; 2284 } 2285 pos.index = -i; 2286 } 2287 } else { 2288 // Parse the rest as "hh[:]?mm" (RFC 822) 2289 int i = subParseNumericZone(text, ++pos.index, sign, 0, 2290 false, calb); 2291 if (i > 0) { 2292 return i; 2293 } 2294 pos.index = -i; 2295 } 2296 } catch (IndexOutOfBoundsException e) { 2297 } 2298 } 2299 break parsing; 2300 2301 case PATTERN_ISO_ZONE: // 'X' 2302 { 2303 if ((text.length() - pos.index) <= 0) { 2304 break parsing; 2305 } 2306 2307 int sign; 2308 char c = text.charAt(pos.index); 2309 if (c == 'Z') { 2310 calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0); 2311 return ++pos.index; 2312 } 2313 2314 // parse text as "+/-hh[[:]mm]" based on count 2315 if (c == '+') { 2316 sign = 1; 2317 } else if (c == '-') { 2318 sign = -1; 2319 } else { 2320 ++pos.index; 2321 break parsing; 2322 } 2323 int i = subParseNumericZone(text, ++pos.index, sign, count, 2324 count == 3, calb); 2325 if (i > 0) { 2326 return i; 2327 } 2328 pos.index = -i; 2329 } 2330 break parsing; 2331 2332 default: 2333 // case PATTERN_DAY_OF_MONTH: // 'd' 2334 // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 2335 // case PATTERN_MINUTE: // 'm' 2336 // case PATTERN_SECOND: // 's' 2337 // case PATTERN_MILLISECOND: // 'S' 2338 // case PATTERN_DAY_OF_YEAR: // 'D' 2339 // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' 2340 // case PATTERN_WEEK_OF_YEAR: // 'w' 2341 // case PATTERN_WEEK_OF_MONTH: // 'W' 2342 // case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM 2343 // case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field); 2344 2345 // Handle "generic" fields 2346 int parseStart = pos.getIndex(); 2347 if (obeyCount) { 2348 if ((start+count) > text.length()) { 2349 break parsing; 2350 } 2351 number = numberFormat.parse(text.substring(0, start+count), pos); 2352 } else { 2353 number = numberFormat.parse(text, pos); 2354 } 2355 if (number != null) { 2356 if (patternCharIndex == PATTERN_MILLISECOND) { 2357 // Fractional seconds must be treated specially. We must always 2358 // normalize them to their fractional second value [0, 1) before we attempt 2359 // to parse them. 2360 // 2361 // Case 1: 11.78 seconds is 11 seconds and 780 (not 78) milliseconds. 2362 // Case 2: 11.7890567 seconds is 11 seconds and 789 (not 7890567) milliseconds. 2363 double doubleValue = number.doubleValue(); 2364 int width = pos.getIndex() - parseStart; 2365 final double divisor = Math.pow(10, width); 2366 value = (int) ((doubleValue / divisor) * 1000); 2367 } else { 2368 value = number.intValue(); 2369 } 2370 2371 if (useFollowingMinusSignAsDelimiter && (value < 0) && 2372 (((pos.index < text.length()) && 2373 (text.charAt(pos.index) != minusSign)) || 2374 ((pos.index == text.length()) && 2375 (text.charAt(pos.index-1) == minusSign)))) { 2376 value = -value; 2377 pos.index--; 2378 } 2379 2380 calb.set(field, value); 2381 return pos.index; 2382 } 2383 break parsing; 2384 } 2385 } 2386 2387 // Parsing failed. 2388 origPos.errorIndex = pos.index; 2389 return -1; 2390 } 2391 parseMonth(String text, int count, int value, int start, int field, ParsePosition pos, boolean useDateFormatSymbols, boolean standalone, CalendarBuilder out)2392 private int parseMonth(String text, int count, int value, int start, 2393 int field, ParsePosition pos, boolean useDateFormatSymbols, 2394 boolean standalone, 2395 CalendarBuilder out) { 2396 if (count <= 2) // i.e., M or MM. 2397 { 2398 // Don't want to parse the month if it is a string 2399 // while pattern uses numeric style: M or MM. 2400 // [We computed 'value' above.] 2401 out.set(Calendar.MONTH, value - 1); 2402 return pos.index; 2403 } 2404 2405 int index = -1; 2406 if (useDateFormatSymbols) { 2407 // count >= 3 // i.e., MMM or MMMM 2408 // Want to be able to parse both short and long forms. 2409 // Try count == 4 first: 2410 if ((index = matchString( 2411 text, start, Calendar.MONTH, 2412 standalone ? formatData.getStandAloneMonths() : formatData.getMonths(), 2413 out)) > 0) { 2414 return index; 2415 } 2416 // count == 4 failed, now try count == 3 2417 if ((index = matchString( 2418 text, start, Calendar.MONTH, 2419 standalone ? formatData.getShortStandAloneMonths() : formatData.getShortMonths(), 2420 out)) > 0) { 2421 return index; 2422 } 2423 } else { 2424 Map<String, Integer> map = calendar.getDisplayNames(field, 2425 Calendar.ALL_STYLES, 2426 locale); 2427 if ((index = matchString(text, start, field, map, out)) > 0) { 2428 return index; 2429 } 2430 } 2431 2432 return index; 2433 } 2434 parseWeekday(String text, int start, int field, boolean useDateFormatSymbols, boolean standalone, CalendarBuilder out)2435 private int parseWeekday(String text, int start, int field, boolean useDateFormatSymbols, 2436 boolean standalone, CalendarBuilder out) { 2437 int index = -1; 2438 if (useDateFormatSymbols) { 2439 // Want to be able to parse both short and long forms. 2440 // Try count == 4 (DDDD) first: 2441 if ((index=matchString( 2442 text, start, Calendar.DAY_OF_WEEK, 2443 standalone ? formatData.getStandAloneWeekdays() : formatData.getWeekdays(), 2444 out)) > 0) { 2445 return index; 2446 } 2447 2448 // DDDD failed, now try DDD 2449 if ((index = matchString( 2450 text, start, Calendar.DAY_OF_WEEK, 2451 standalone ? formatData.getShortStandAloneWeekdays() : formatData.getShortWeekdays(), 2452 out)) > 0) { 2453 return index; 2454 } 2455 } else { 2456 int[] styles = { Calendar.LONG, Calendar.SHORT }; 2457 for (int style : styles) { 2458 Map<String,Integer> map = calendar.getDisplayNames(field, style, locale); 2459 if ((index = matchString(text, start, field, map, out)) > 0) { 2460 return index; 2461 } 2462 } 2463 } 2464 2465 return index; 2466 } 2467 2468 getCalendarName()2469 private final String getCalendarName() { 2470 return calendar.getClass().getName(); 2471 } 2472 useDateFormatSymbols()2473 private boolean useDateFormatSymbols() { 2474 if (useDateFormatSymbols) { 2475 return true; 2476 } 2477 return isGregorianCalendar() || locale == null; 2478 } 2479 isGregorianCalendar()2480 private boolean isGregorianCalendar() { 2481 return "java.util.GregorianCalendar".equals(getCalendarName()); 2482 } 2483 2484 /** 2485 * Translates a pattern, mapping each character in the from string to the 2486 * corresponding character in the to string. 2487 * 2488 * @exception IllegalArgumentException if the given pattern is invalid 2489 */ translatePattern(String pattern, String from, String to)2490 private String translatePattern(String pattern, String from, String to) { 2491 StringBuilder result = new StringBuilder(); 2492 boolean inQuote = false; 2493 for (int i = 0; i < pattern.length(); ++i) { 2494 char c = pattern.charAt(i); 2495 if (inQuote) { 2496 if (c == '\'') { 2497 inQuote = false; 2498 } 2499 } 2500 else { 2501 if (c == '\'') { 2502 inQuote = true; 2503 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 2504 int ci = from.indexOf(c); 2505 if (ci >= 0) { 2506 // patternChars is longer than localPatternChars due 2507 // to serialization compatibility. The pattern letters 2508 // unsupported by localPatternChars pass through. 2509 if (ci < to.length()) { 2510 c = to.charAt(ci); 2511 } 2512 } else { 2513 throw new IllegalArgumentException("Illegal pattern " + 2514 " character '" + 2515 c + "'"); 2516 } 2517 } 2518 } 2519 result.append(c); 2520 } 2521 if (inQuote) { 2522 throw new IllegalArgumentException("Unfinished quote in pattern"); 2523 } 2524 return result.toString(); 2525 } 2526 2527 /** 2528 * Returns a pattern string describing this date format. 2529 * 2530 * @return a pattern string describing this date format. 2531 */ toPattern()2532 public String toPattern() { 2533 return pattern; 2534 } 2535 2536 /** 2537 * Returns a localized pattern string describing this date format. 2538 * 2539 * @return a localized pattern string describing this date format. 2540 */ toLocalizedPattern()2541 public String toLocalizedPattern() { 2542 return translatePattern(pattern, 2543 DateFormatSymbols.patternChars, 2544 formatData.getLocalPatternChars()); 2545 } 2546 2547 /** 2548 * Applies the given pattern string to this date format. 2549 * 2550 * @param pattern the new date and time pattern for this date format 2551 * @exception NullPointerException if the given pattern is null 2552 * @exception IllegalArgumentException if the given pattern is invalid 2553 */ applyPattern(String pattern)2554 public void applyPattern(String pattern) 2555 { 2556 compiledPattern = compile(pattern); 2557 this.pattern = pattern; 2558 } 2559 2560 /** 2561 * Applies the given localized pattern string to this date format. 2562 * 2563 * @param pattern a String to be mapped to the new date and time format 2564 * pattern for this format 2565 * @exception NullPointerException if the given pattern is null 2566 * @exception IllegalArgumentException if the given pattern is invalid 2567 */ applyLocalizedPattern(String pattern)2568 public void applyLocalizedPattern(String pattern) { 2569 String p = translatePattern(pattern, 2570 formatData.getLocalPatternChars(), 2571 DateFormatSymbols.patternChars); 2572 compiledPattern = compile(p); 2573 this.pattern = p; 2574 } 2575 2576 /** 2577 * Gets a copy of the date and time format symbols of this date format. 2578 * 2579 * @return the date and time format symbols of this date format 2580 * @see #setDateFormatSymbols 2581 */ getDateFormatSymbols()2582 public DateFormatSymbols getDateFormatSymbols() 2583 { 2584 return (DateFormatSymbols)formatData.clone(); 2585 } 2586 2587 /** 2588 * Sets the date and time format symbols of this date format. 2589 * 2590 * @param newFormatSymbols the new date and time format symbols 2591 * @exception NullPointerException if the given newFormatSymbols is null 2592 * @see #getDateFormatSymbols 2593 */ setDateFormatSymbols(DateFormatSymbols newFormatSymbols)2594 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) 2595 { 2596 this.formatData = (DateFormatSymbols)newFormatSymbols.clone(); 2597 useDateFormatSymbols = true; 2598 } 2599 2600 /** 2601 * Creates a copy of this <code>SimpleDateFormat</code>. This also 2602 * clones the format's date format symbols. 2603 * 2604 * @return a clone of this <code>SimpleDateFormat</code> 2605 */ 2606 @Override clone()2607 public Object clone() { 2608 SimpleDateFormat other = (SimpleDateFormat) super.clone(); 2609 other.formatData = (DateFormatSymbols) formatData.clone(); 2610 return other; 2611 } 2612 2613 /** 2614 * Returns the hash code value for this <code>SimpleDateFormat</code> object. 2615 * 2616 * @return the hash code value for this <code>SimpleDateFormat</code> object. 2617 */ 2618 @Override hashCode()2619 public int hashCode() 2620 { 2621 return pattern.hashCode(); 2622 // just enough fields for a reasonable distribution 2623 } 2624 2625 /** 2626 * Compares the given object with this <code>SimpleDateFormat</code> for 2627 * equality. 2628 * 2629 * @return true if the given object is equal to this 2630 * <code>SimpleDateFormat</code> 2631 */ 2632 @Override equals(Object obj)2633 public boolean equals(Object obj) 2634 { 2635 if (!super.equals(obj)) { 2636 return false; // super does class check 2637 } 2638 SimpleDateFormat that = (SimpleDateFormat) obj; 2639 return (pattern.equals(that.pattern) 2640 && formatData.equals(that.formatData)); 2641 } 2642 2643 /** 2644 * After reading an object from the input stream, the format 2645 * pattern in the object is verified. 2646 * <p> 2647 * @exception InvalidObjectException if the pattern is invalid 2648 */ readObject(ObjectInputStream stream)2649 private void readObject(ObjectInputStream stream) 2650 throws IOException, ClassNotFoundException { 2651 stream.defaultReadObject(); 2652 2653 try { 2654 compiledPattern = compile(pattern); 2655 } catch (Exception e) { 2656 throw new InvalidObjectException("invalid pattern"); 2657 } 2658 2659 if (serialVersionOnStream < 1) { 2660 // didn't have defaultCenturyStart field 2661 initializeDefaultCentury(); 2662 } 2663 else { 2664 // fill in dependent transient field 2665 parseAmbiguousDatesAsAfter(defaultCenturyStart); 2666 } 2667 serialVersionOnStream = currentSerialVersion; 2668 2669 // If the deserialized object has a SimpleTimeZone, try 2670 // to replace it with a ZoneInfo equivalent in order to 2671 // be compatible with the SimpleTimeZone-based 2672 // implementation as much as possible. 2673 TimeZone tz = getTimeZone(); 2674 if (tz instanceof SimpleTimeZone) { 2675 String id = tz.getID(); 2676 TimeZone zi = TimeZone.getTimeZone(id); 2677 if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) { 2678 setTimeZone(zi); 2679 } 2680 } 2681 } 2682 2683 /** 2684 * Analyze the negative subpattern of DecimalFormat and set/update values 2685 * as necessary. 2686 */ checkNegativeNumberExpression()2687 private void checkNegativeNumberExpression() { 2688 if ((numberFormat instanceof DecimalFormat) && 2689 !numberFormat.equals(originalNumberFormat)) { 2690 String numberPattern = ((DecimalFormat)numberFormat).toPattern(); 2691 if (!numberPattern.equals(originalNumberPattern)) { 2692 hasFollowingMinusSign = false; 2693 2694 int separatorIndex = numberPattern.indexOf(';'); 2695 // If the negative subpattern is not absent, we have to analayze 2696 // it in order to check if it has a following minus sign. 2697 if (separatorIndex > -1) { 2698 int minusIndex = numberPattern.indexOf('-', separatorIndex); 2699 if ((minusIndex > numberPattern.lastIndexOf('0')) && 2700 (minusIndex > numberPattern.lastIndexOf('#'))) { 2701 hasFollowingMinusSign = true; 2702 minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign(); 2703 } 2704 } 2705 originalNumberPattern = numberPattern; 2706 } 2707 originalNumberFormat = numberFormat; 2708 } 2709 } 2710 2711 } 2712