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