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