SimpleDateFormat.java revision b7554f779d330037a5cf591fcc8be73db73a0384
1/* 2 * Copyright (c) 1996, 2013, 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 39package java.text; 40 41import java.io.IOException; 42import java.io.InvalidObjectException; 43import java.io.ObjectInputStream; 44import java.util.Calendar; 45import java.util.Date; 46import java.util.GregorianCalendar; 47import java.util.Locale; 48import java.util.Map; 49import java.util.MissingResourceException; 50import java.util.ResourceBundle; 51import java.util.SimpleTimeZone; 52import java.util.TimeZone; 53import java.util.concurrent.ConcurrentHashMap; 54import java.util.concurrent.ConcurrentMap; 55import libcore.icu.LocaleData; 56import sun.util.calendar.CalendarUtils; 57 58import static java.text.DateFormatSymbols.*; 59 60/** 61 * <code>SimpleDateFormat</code> is a concrete class for formatting and 62 * parsing dates in a locale-sensitive manner. It allows for formatting 63 * (date -> text), parsing (text -> date), and normalization. 64 * 65 * <p> 66 * <code>SimpleDateFormat</code> allows you to start by choosing 67 * any user-defined patterns for date-time formatting. However, you 68 * are encouraged to create a date-time formatter with either 69 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or 70 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each 71 * of these class methods can return a date/time formatter initialized 72 * with a default format pattern. You may modify the format pattern 73 * using the <code>applyPattern</code> methods as desired. 74 * For more information on using these methods, see 75 * {@link DateFormat}. 76 * 77 * <h4>Date and Time Patterns</h4> 78 * <p> 79 * Date and time formats are specified by <em>date and time pattern</em> 80 * strings. 81 * Within date and time pattern strings, unquoted letters from 82 * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to 83 * <code>'z'</code> are interpreted as pattern letters representing the 84 * components of a date or time string. 85 * Text can be quoted using single quotes (<code>'</code>) to avoid 86 * interpretation. 87 * <code>"''"</code> represents a single quote. 88 * All other characters are not interpreted; they're simply copied into the 89 * output string during formatting or matched against the input string 90 * during parsing. 91 * <p> 92 * The following pattern letters are defined (all other characters from 93 * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to 94 * <code>'z'</code> are reserved): 95 * <blockquote> 96 * <table border=0 cellspacing=3 cellpadding=0 summary="Chart shows pattern letters, date/time component, presentation, and examples."> 97 * <tr bgcolor="#ccccff"> 98 * <th align=left>Letter 99 * <th align=left>Date or Time Component 100 * <th align=left>Presentation 101 * <th align=left>Examples 102 * <tr> 103 * <td><code>G</code> 104 * <td>Era designator 105 * <td><a href="#text">Text</a> 106 * <td><code>AD</code> 107 * <tr bgcolor="#eeeeff"> 108 * <td><code>y</code> 109 * <td>Year 110 * <td><a href="#year">Year</a> 111 * <td><code>1996</code>; <code>96</code> 112 * <tr> 113 * <td><code>Y</code> 114 * <td>Week year 115 * <td><a href="#year">Year</a> 116 * <td><code>2009</code>; <code>09</code> 117 * <tr bgcolor="#eeeeff"> 118 * <td><code>M</code> 119 * <td>Month in year 120 * <td><a href="#month">Month</a> 121 * <td><code>July</code>; <code>Jul</code>; <code>07</code> 122 * <tr> 123 * <td><code>w</code> 124 * <td>Week in year 125 * <td><a href="#number">Number</a> 126 * <td><code>27</code> 127 * <tr bgcolor="#eeeeff"> 128 * <td><code>W</code> 129 * <td>Week in month 130 * <td><a href="#number">Number</a> 131 * <td><code>2</code> 132 * <tr> 133 * <td><code>D</code> 134 * <td>Day in year 135 * <td><a href="#number">Number</a> 136 * <td><code>189</code> 137 * <tr bgcolor="#eeeeff"> 138 * <td><code>d</code> 139 * <td>Day in month 140 * <td><a href="#number">Number</a> 141 * <td><code>10</code> 142 * <tr> 143 * <td><code>F</code> 144 * <td>Day of week in month 145 * <td><a href="#number">Number</a> 146 * <td><code>2</code> 147 * <tr bgcolor="#eeeeff"> 148 * <td><code>E</code> 149 * <td>Day name in week 150 * <td><a href="#text">Text</a> 151 * <td><code>Tuesday</code>; <code>Tue</code> 152 * <tr> 153 * <td><code>u</code> 154 * <td>Day number of week (1 = Monday, ..., 7 = Sunday) 155 * <td><a href="#number">Number</a> 156 * <td><code>1</code> 157 * <tr bgcolor="#eeeeff"> 158 * <td><code>a</code> 159 * <td>Am/pm marker 160 * <td><a href="#text">Text</a> 161 * <td><code>PM</code> 162 * <tr> 163 * <td><code>H</code> 164 * <td>Hour in day (0-23) 165 * <td><a href="#number">Number</a> 166 * <td><code>0</code> 167 * <tr bgcolor="#eeeeff"> 168 * <td><code>k</code> 169 * <td>Hour in day (1-24) 170 * <td><a href="#number">Number</a> 171 * <td><code>24</code> 172 * <tr> 173 * <td><code>K</code> 174 * <td>Hour in am/pm (0-11) 175 * <td><a href="#number">Number</a> 176 * <td><code>0</code> 177 * <tr bgcolor="#eeeeff"> 178 * <td><code>h</code> 179 * <td>Hour in am/pm (1-12) 180 * <td><a href="#number">Number</a> 181 * <td><code>12</code> 182 * <tr> 183 * <td><code>m</code> 184 * <td>Minute in hour 185 * <td><a href="#number">Number</a> 186 * <td><code>30</code> 187 * <tr bgcolor="#eeeeff"> 188 * <td><code>s</code> 189 * <td>Second in minute 190 * <td><a href="#number">Number</a> 191 * <td><code>55</code> 192 * <tr> 193 * <td><code>S</code> 194 * <td>Millisecond 195 * <td><a href="#number">Number</a> 196 * <td><code>978</code> 197 * <tr bgcolor="#eeeeff"> 198 * <td><code>z</code> 199 * <td>Time zone 200 * <td><a href="#timezone">General time zone</a> 201 * <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code> 202 * <tr> 203 * <td><code>Z</code> 204 * <td>Time zone 205 * <td><a href="#rfc822timezone">RFC 822 time zone</a> 206 * <td><code>-0800</code> 207 * <tr bgcolor="#eeeeff"> 208 * <td><code>X</code> 209 * <td>Time zone 210 * <td><a href="#iso8601timezone">ISO 8601 time zone</a> 211 * <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code> 212 * </table> 213 * </blockquote> 214 * Pattern letters are usually repeated, as their number determines the 215 * exact presentation: 216 * <ul> 217 * <li><strong><a name="text">Text:</a></strong> 218 * For formatting, if the number of pattern letters is 4 or more, 219 * the full form is used; otherwise a short or abbreviated form 220 * is used if available. 221 * For parsing, both forms are accepted, independent of the number 222 * of pattern letters.<br><br></li> 223 * <li><strong><a name="number">Number:</a></strong> 224 * For formatting, the number of pattern letters is the minimum 225 * number of digits, and shorter numbers are zero-padded to this amount. 226 * For parsing, the number of pattern letters is ignored unless 227 * it's needed to separate two adjacent fields.<br><br></li> 228 * <li><strong><a name="year">Year:</a></strong> 229 * If the formatter's {@link #getCalendar() Calendar} is the Gregorian 230 * calendar, the following rules are applied.<br> 231 * <ul> 232 * <li>For formatting, if the number of pattern letters is 2, the year 233 * is truncated to 2 digits; otherwise it is interpreted as a 234 * <a href="#number">number</a>. 235 * <li>For parsing, if the number of pattern letters is more than 2, 236 * the year is interpreted literally, regardless of the number of 237 * digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to 238 * Jan 11, 12 A.D. 239 * <li>For parsing with the abbreviated year pattern ("y" or "yy"), 240 * <code>SimpleDateFormat</code> must interpret the abbreviated year 241 * relative to some century. It does this by adjusting dates to be 242 * within 80 years before and 20 years after the time the <code>SimpleDateFormat</code> 243 * instance is created. For example, using a pattern of "MM/dd/yy" and a 244 * <code>SimpleDateFormat</code> instance created on Jan 1, 1997, the string 245 * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64" 246 * would be interpreted as May 4, 1964. 247 * During parsing, only strings consisting of exactly two digits, as defined by 248 * {@link Character#isDigit(char)}, will be parsed into the default century. 249 * Any other numeric string, such as a one digit string, a three or more digit 250 * string, or a two digit string that isn't all digits (for example, "-1"), is 251 * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the 252 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC. 253 * </ul> 254 * Otherwise, calendar system specific forms are applied. 255 * For both formatting and parsing, if the number of pattern 256 * letters is 4 or more, a calendar specific {@linkplain 257 * Calendar#LONG long form} is used. Otherwise, a calendar 258 * specific {@linkplain Calendar#SHORT short or abbreviated form} 259 * is used.<br> 260 * <br> 261 * If week year {@code 'Y'} is specified and the {@linkplain 262 * #getCalendar() calendar} doesn't support any <a 263 * href="../util/GregorianCalendar.html#week_year"> week 264 * years</a>, the calendar year ({@code 'y'}) is used instead. The 265 * support of week years can be tested with a call to {@link 266 * DateFormat#getCalendar() getCalendar()}.{@link 267 * java.util.Calendar#isWeekDateSupported() 268 * isWeekDateSupported()}.<br><br></li> 269 * <li><strong><a name="month">Month:</a></strong> 270 * If the number of pattern letters is 3 or more, the month is 271 * interpreted as <a href="#text">text</a>; otherwise, 272 * it is interpreted as a <a href="#number">number</a>.<br><br></li> 273 * <li><strong><a name="timezone">General time zone:</a></strong> 274 * Time zones are interpreted as <a href="#text">text</a> if they have 275 * names. For time zones representing a GMT offset value, the 276 * following syntax is used: 277 * <pre> 278 * <a name="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a> 279 * <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i> 280 * <i>Sign:</i> one of 281 * <code>+ -</code> 282 * <i>Hours:</i> 283 * <i>Digit</i> 284 * <i>Digit</i> <i>Digit</i> 285 * <i>Minutes:</i> 286 * <i>Digit</i> <i>Digit</i> 287 * <i>Digit:</i> one of 288 * <code>0 1 2 3 4 5 6 7 8 9</code></pre> 289 * <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between 290 * 00 and 59. The format is locale independent and digits must be taken 291 * from the Basic Latin block of the Unicode standard. 292 * <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also 293 * accepted.<br><br></li> 294 * <li><strong><a name="rfc822timezone">RFC 822 time zone:</a></strong> 295 * For formatting, the RFC 822 4-digit time zone format is used: 296 * 297 * <pre> 298 * <i>RFC822TimeZone:</i> 299 * <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i> 300 * <i>TwoDigitHours:</i> 301 * <i>Digit Digit</i></pre> 302 * <i>TwoDigitHours</i> must be between 00 and 23. Other definitions 303 * are as for <a href="#timezone">general time zones</a>. 304 * 305 * <p>For parsing, <a href="#timezone">general time zones</a> are also 306 * accepted. 307 * <li><strong><a name="iso8601timezone">ISO 8601 Time zone:</a></strong> 308 * The number of pattern letters designates the format for both formatting 309 * and parsing as follows: 310 * <pre> 311 * <i>ISO8601TimeZone:</i> 312 * <i>OneLetterISO8601TimeZone</i> 313 * <i>TwoLetterISO8601TimeZone</i> 314 * <i>ThreeLetterISO8601TimeZone</i> 315 * <i>OneLetterISO8601TimeZone:</i> 316 * <i>Sign</i> <i>TwoDigitHours</i> 317 * {@code Z} 318 * <i>TwoLetterISO8601TimeZone:</i> 319 * <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i> 320 * {@code Z} 321 * <i>ThreeLetterISO8601TimeZone:</i> 322 * <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i> 323 * {@code Z}</pre> 324 * Other definitions are as for <a href="#timezone">general time zones</a> or 325 * <a href="#rfc822timezone">RFC 822 time zones</a>. 326 * 327 * <p>For formatting, if the offset value from GMT is 0, {@code "Z"} is 328 * produced. If the number of pattern letters is 1, any fraction of an hour 329 * is ignored. For example, if the pattern is {@code "X"} and the time zone is 330 * {@code "GMT+05:30"}, {@code "+05"} is produced. 331 * 332 * <p>For parsing, {@code "Z"} is parsed as the UTC time zone designator. 333 * <a href="#timezone">General time zones</a> are <em>not</em> accepted. 334 * 335 * <p>If the number of pattern letters is 4 or more, {@link 336 * IllegalArgumentException} is thrown when constructing a {@code 337 * SimpleDateFormat} or {@linkplain #applyPattern(String) applying a 338 * pattern}. 339 * </ul> 340 * <code>SimpleDateFormat</code> also supports <em>localized date and time 341 * pattern</em> strings. In these strings, the pattern letters described above 342 * may be replaced with other, locale dependent, pattern letters. 343 * <code>SimpleDateFormat</code> does not deal with the localization of text 344 * other than the pattern letters; that's up to the client of the class. 345 * <p> 346 * 347 * <h4>Examples</h4> 348 * 349 * The following examples show how date and time patterns are interpreted in 350 * the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time 351 * in the U.S. Pacific Time time zone. 352 * <blockquote> 353 * <table border=0 cellspacing=3 cellpadding=0 summary="Examples of date and time patterns interpreted in the U.S. locale"> 354 * <tr bgcolor="#ccccff"> 355 * <th align=left>Date and Time Pattern 356 * <th align=left>Result 357 * <tr> 358 * <td><code>"yyyy.MM.dd G 'at' HH:mm:ss z"</code> 359 * <td><code>2001.07.04 AD at 12:08:56 PDT</code> 360 * <tr bgcolor="#eeeeff"> 361 * <td><code>"EEE, MMM d, ''yy"</code> 362 * <td><code>Wed, Jul 4, '01</code> 363 * <tr> 364 * <td><code>"h:mm a"</code> 365 * <td><code>12:08 PM</code> 366 * <tr bgcolor="#eeeeff"> 367 * <td><code>"hh 'o''clock' a, zzzz"</code> 368 * <td><code>12 o'clock PM, Pacific Daylight Time</code> 369 * <tr> 370 * <td><code>"K:mm a, z"</code> 371 * <td><code>0:08 PM, PDT</code> 372 * <tr bgcolor="#eeeeff"> 373 * <td><code>"yyyyy.MMMMM.dd GGG hh:mm aaa"</code> 374 * <td><code>02001.July.04 AD 12:08 PM</code> 375 * <tr> 376 * <td><code>"EEE, d MMM yyyy HH:mm:ss Z"</code> 377 * <td><code>Wed, 4 Jul 2001 12:08:56 -0700</code> 378 * <tr bgcolor="#eeeeff"> 379 * <td><code>"yyMMddHHmmssZ"</code> 380 * <td><code>010704120856-0700</code> 381 * <tr> 382 * <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSZ"</code> 383 * <td><code>2001-07-04T12:08:56.235-0700</code> 384 * <tr bgcolor="#eeeeff"> 385 * <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"</code> 386 * <td><code>2001-07-04T12:08:56.235-07:00</code> 387 * <tr> 388 * <td><code>"YYYY-'W'ww-u"</code> 389 * <td><code>2001-W27-3</code> 390 * </table> 391 * </blockquote> 392 * 393 * <h4><a name="synchronization">Synchronization</a></h4> 394 * 395 * <p> 396 * Date formats are not synchronized. 397 * It is recommended to create separate format instances for each thread. 398 * If multiple threads access a format concurrently, it must be synchronized 399 * externally. 400 * 401 * @see <a href="http://java.sun.com/docs/books/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a> 402 * @see java.util.Calendar 403 * @see java.util.TimeZone 404 * @see DateFormat 405 * @see DateFormatSymbols 406 * @author Mark Davis, Chen-Lieh Huang, Alan Liu 407 */ 408public class SimpleDateFormat extends DateFormat { 409 410 // the official serial version ID which says cryptically 411 // which version we're compatible with 412 static final long serialVersionUID = 4774881970558875024L; 413 414 // the internal serial version which says which version was written 415 // - 0 (default) for version up to JDK 1.1.3 416 // - 1 for version from JDK 1.1.4, which includes a new field 417 static final int currentSerialVersion = 1; 418 419 /** 420 * The version of the serialized data on the stream. Possible values: 421 * <ul> 422 * <li><b>0</b> or not present on stream: JDK 1.1.3. This version 423 * has no <code>defaultCenturyStart</code> on stream. 424 * <li><b>1</b> JDK 1.1.4 or later. This version adds 425 * <code>defaultCenturyStart</code>. 426 * </ul> 427 * When streaming out this class, the most recent format 428 * and the highest allowable <code>serialVersionOnStream</code> 429 * is written. 430 * @serial 431 * @since JDK1.1.4 432 */ 433 private int serialVersionOnStream = currentSerialVersion; 434 435 /** 436 * The pattern string of this formatter. This is always a non-localized 437 * pattern. May not be null. See class documentation for details. 438 * @serial 439 */ 440 private String pattern; 441 442 /** 443 * Saved numberFormat and pattern. 444 * @see SimpleDateFormat#checkNegativeNumberExpression 445 */ 446 transient private NumberFormat originalNumberFormat; 447 transient private String originalNumberPattern; 448 449 /** 450 * The minus sign to be used with format and parse. 451 */ 452 transient private char minusSign = '-'; 453 454 /** 455 * True when a negative sign follows a number. 456 * (True as default in Arabic.) 457 */ 458 transient private boolean hasFollowingMinusSign = false; 459 460 /** 461 * The compiled pattern. 462 */ 463 transient private char[] compiledPattern; 464 465 /** 466 * Tags for the compiled pattern. 467 */ 468 private final static int TAG_QUOTE_ASCII_CHAR = 100; 469 private final static int TAG_QUOTE_CHARS = 101; 470 471 /** 472 * Locale dependent digit zero. 473 * @see #zeroPaddingNumber 474 * @see java.text.DecimalFormatSymbols#getZeroDigit 475 */ 476 transient private char zeroDigit; 477 478 /** 479 * The symbols used by this formatter for week names, month names, 480 * etc. May not be null. 481 * @serial 482 * @see java.text.DateFormatSymbols 483 */ 484 private DateFormatSymbols formatData; 485 486 /** 487 * We map dates with two-digit years into the century starting at 488 * <code>defaultCenturyStart</code>, which may be any date. May 489 * not be null. 490 * @serial 491 * @since JDK1.1.4 492 */ 493 private Date defaultCenturyStart; 494 495 transient private int defaultCenturyStartYear; 496 497 private static final int MILLIS_PER_MINUTE = 60 * 1000; 498 499 // For time zones that have no names, use strings GMT+minutes and 500 // GMT-minutes. For instance, in France the time zone is GMT+60. 501 private static final String GMT = "GMT"; 502 503 /** 504 * Cache to hold the DateTimePatterns of a Locale. 505 */ 506 private static final ConcurrentMap<Locale, String[]> cachedLocaleData 507 = new ConcurrentHashMap<Locale, String[]>(3); 508 509 /** 510 * Cache NumberFormat instances with Locale key. 511 */ 512 private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData 513 = new ConcurrentHashMap<Locale, NumberFormat>(3); 514 515 /** 516 * The Locale used to instantiate this 517 * <code>SimpleDateFormat</code>. The value may be null if this object 518 * has been created by an older <code>SimpleDateFormat</code> and 519 * deserialized. 520 * 521 * @serial 522 * @since 1.6 523 */ 524 private Locale locale; 525 526 /** 527 * Indicates whether this <code>SimpleDateFormat</code> should use 528 * the DateFormatSymbols. If true, the format and parse methods 529 * use the DateFormatSymbols values. If false, the format and 530 * parse methods call Calendar.getDisplayName or 531 * Calendar.getDisplayNames. 532 */ 533 transient boolean useDateFormatSymbols; 534 535 /** 536 * Constructs a <code>SimpleDateFormat</code> using the default pattern and 537 * date format symbols for the default locale. 538 * <b>Note:</b> This constructor may not support all locales. 539 * For full coverage, use the factory methods in the {@link DateFormat} 540 * class. 541 */ 542 public SimpleDateFormat() { 543 this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT)); 544 } 545 546 /** 547 * Constructs a <code>SimpleDateFormat</code> using the given pattern and 548 * the default date format symbols for the default locale. 549 * <b>Note:</b> This constructor may not support all locales. 550 * For full coverage, use the factory methods in the {@link DateFormat} 551 * class. 552 * 553 * @param pattern the pattern describing the date and time format 554 * @exception NullPointerException if the given pattern is null 555 * @exception IllegalArgumentException if the given pattern is invalid 556 */ 557 public SimpleDateFormat(String pattern) 558 { 559 this(pattern, Locale.getDefault(Locale.Category.FORMAT)); 560 } 561 562 /** 563 * Constructs a <code>SimpleDateFormat</code> using the given pattern and 564 * the default date format symbols for the given locale. 565 * <b>Note:</b> This constructor may not support all locales. 566 * For full coverage, use the factory methods in the {@link DateFormat} 567 * class. 568 * 569 * @param pattern the pattern describing the date and time format 570 * @param locale the locale whose date format symbols should be used 571 * @exception NullPointerException if the given pattern or locale is null 572 * @exception IllegalArgumentException if the given pattern is invalid 573 */ 574 public SimpleDateFormat(String pattern, Locale locale) 575 { 576 if (pattern == null || locale == null) { 577 throw new NullPointerException(); 578 } 579 580 initializeCalendar(locale); 581 this.pattern = pattern; 582 this.formatData = DateFormatSymbols.getInstanceRef(locale); 583 this.locale = locale; 584 initialize(locale); 585 } 586 587 /** 588 * Constructs a <code>SimpleDateFormat</code> using the given pattern and 589 * date format symbols. 590 * 591 * @param pattern the pattern describing the date and time format 592 * @param formatSymbols the date format symbols to be used for formatting 593 * @exception NullPointerException if the given pattern or formatSymbols is null 594 * @exception IllegalArgumentException if the given pattern is invalid 595 */ 596 public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) 597 { 598 if (pattern == null || formatSymbols == null) { 599 throw new NullPointerException(); 600 } 601 602 this.pattern = pattern; 603 this.formatData = (DateFormatSymbols) formatSymbols.clone(); 604 this.locale = Locale.getDefault(Locale.Category.FORMAT); 605 initializeCalendar(this.locale); 606 initialize(this.locale); 607 useDateFormatSymbols = true; 608 } 609 610 /* Package-private, called by DateFormat factory methods */ 611 SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) { 612 if (loc == null) { 613 throw new NullPointerException(); 614 } 615 616 this.locale = loc; 617 // initialize calendar and related fields 618 initializeCalendar(loc); 619 620 /* try the cache first */ 621 String[] dateTimePatterns = cachedLocaleData.get(loc); 622 if (dateTimePatterns == null) { /* cache miss */ 623 LocaleData localeData = LocaleData.get(loc); 624 dateTimePatterns = new String[9]; 625 dateTimePatterns[DateFormat.SHORT + 4] = localeData.getDateFormat(DateFormat.SHORT); 626 dateTimePatterns[DateFormat.MEDIUM + 4] = localeData.getDateFormat(DateFormat.MEDIUM); 627 dateTimePatterns[DateFormat.LONG + 4] = localeData.getDateFormat(DateFormat.LONG); 628 dateTimePatterns[DateFormat.FULL + 4] = localeData.getDateFormat(DateFormat.FULL); 629 dateTimePatterns[DateFormat.SHORT] = localeData.getTimeFormat(DateFormat.SHORT); 630 dateTimePatterns[DateFormat.MEDIUM] = localeData.getTimeFormat(DateFormat.MEDIUM); 631 dateTimePatterns[DateFormat.LONG] = localeData.getTimeFormat(DateFormat.LONG); 632 dateTimePatterns[DateFormat.FULL] = localeData.getTimeFormat(DateFormat.FULL); 633 dateTimePatterns[8] = "%s %s"; 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 // 'L' and 'c', 1039 Calendar.MONTH, 1040 Calendar.DAY_OF_WEEK 1041 }; 1042 1043 // Map index into pattern character string to DateFormat field number 1044 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { 1045 DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD, 1046 DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, 1047 DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD, 1048 DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD, 1049 DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, 1050 DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD, 1051 DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD, 1052 DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, 1053 DateFormat.TIMEZONE_FIELD, DateFormat.TIMEZONE_FIELD, 1054 DateFormat.YEAR_FIELD, DateFormat.DAY_OF_WEEK_FIELD, 1055 DateFormat.TIMEZONE_FIELD, 1056 // 'L' and 'c' 1057 DateFormat.MONTH_FIELD, 1058 DateFormat.DAY_OF_WEEK_FIELD 1059 }; 1060 1061 // Maps from DecimalFormatSymbols index to Field constant 1062 private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = { 1063 Field.ERA, Field.YEAR, Field.MONTH, Field.DAY_OF_MONTH, 1064 Field.HOUR_OF_DAY1, Field.HOUR_OF_DAY0, Field.MINUTE, 1065 Field.SECOND, Field.MILLISECOND, Field.DAY_OF_WEEK, 1066 Field.DAY_OF_YEAR, Field.DAY_OF_WEEK_IN_MONTH, 1067 Field.WEEK_OF_YEAR, Field.WEEK_OF_MONTH, 1068 Field.AM_PM, Field.HOUR1, Field.HOUR0, Field.TIME_ZONE, 1069 Field.TIME_ZONE, 1070 Field.YEAR, Field.DAY_OF_WEEK, 1071 Field.TIME_ZONE, 1072 // 'L' and 'c' 1073 Field.MONTH, 1074 Field.DAY_OF_WEEK 1075 }; 1076 1077 /** 1078 * Private member function that does the real date/time formatting. 1079 */ 1080 private void subFormat(int patternCharIndex, int count, 1081 FieldDelegate delegate, StringBuffer buffer, 1082 boolean useDateFormatSymbols) 1083 { 1084 int maxIntCount = Integer.MAX_VALUE; 1085 String current = null; 1086 int beginOffset = buffer.length(); 1087 1088 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 1089 int value; 1090 if (field == CalendarBuilder.WEEK_YEAR) { 1091 if (calendar.isWeekDateSupported()) { 1092 value = calendar.getWeekYear(); 1093 } else { 1094 // use calendar year 'y' instead 1095 patternCharIndex = PATTERN_YEAR; 1096 field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 1097 value = calendar.get(field); 1098 } 1099 } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) { 1100 value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK)); 1101 } else { 1102 value = calendar.get(field); 1103 } 1104 1105 int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; 1106 if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) { 1107 current = calendar.getDisplayName(field, style, locale); 1108 } 1109 1110 // Note: zeroPaddingNumber() assumes that maxDigits is either 1111 // 2 or maxIntCount. If we make any changes to this, 1112 // zeroPaddingNumber() must be fixed. 1113 1114 switch (patternCharIndex) { 1115 case PATTERN_ERA: // 'G' 1116 if (useDateFormatSymbols) { 1117 String[] eras = formatData.getEras(); 1118 if (value < eras.length) 1119 current = eras[value]; 1120 } 1121 if (current == null) 1122 current = ""; 1123 break; 1124 1125 case PATTERN_WEEK_YEAR: // 'Y' 1126 case PATTERN_YEAR: // 'y' 1127 if (calendar instanceof GregorianCalendar) { 1128 if (count != 2) 1129 zeroPaddingNumber(value, count, maxIntCount, buffer); 1130 else // count == 2 1131 zeroPaddingNumber(value, 2, 2, buffer); // clip 1996 to 96 1132 } else { 1133 if (current == null) { 1134 zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count, 1135 maxIntCount, buffer); 1136 } 1137 } 1138 break; 1139 1140 case 'L': // Standalone month. Unsupported for now. 1141 case PATTERN_MONTH: // 'M' 1142 if (useDateFormatSymbols) { 1143 String[] months; 1144 if (count >= 4) { 1145 months = formatData.getMonths(); 1146 current = months[value]; 1147 } else if (count == 3) { 1148 months = formatData.getShortMonths(); 1149 current = months[value]; 1150 } 1151 } else { 1152 if (count < 3) { 1153 current = null; 1154 } 1155 } 1156 if (current == null) { 1157 zeroPaddingNumber(value+1, count, maxIntCount, buffer); 1158 } 1159 break; 1160 1161 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 1162 if (current == null) { 1163 if (value == 0) 1164 zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+1, 1165 count, maxIntCount, buffer); 1166 else 1167 zeroPaddingNumber(value, count, maxIntCount, buffer); 1168 } 1169 break; 1170 1171 case 'c': // Standalone weekday. Unsupported for now. 1172 case PATTERN_DAY_OF_WEEK: // 'E' 1173 if (useDateFormatSymbols) { 1174 String[] weekdays; 1175 if (count >= 4) { 1176 weekdays = formatData.getWeekdays(); 1177 current = weekdays[value]; 1178 } else { // count < 4, use abbreviated form if exists 1179 weekdays = formatData.getShortWeekdays(); 1180 current = weekdays[value]; 1181 } 1182 } 1183 break; 1184 1185 case PATTERN_AM_PM: // 'a' 1186 if (useDateFormatSymbols) { 1187 String[] ampm = formatData.getAmPmStrings(); 1188 current = ampm[value]; 1189 } 1190 break; 1191 1192 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM 1193 if (current == null) { 1194 if (value == 0) 1195 zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1, 1196 count, maxIntCount, buffer); 1197 else 1198 zeroPaddingNumber(value, count, maxIntCount, buffer); 1199 } 1200 break; 1201 1202 case PATTERN_ZONE_NAME: // 'z' 1203 if (current == null) { 1204 TimeZone tz = calendar.getTimeZone(); 1205 boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0); 1206 int tzstyle = count < 4 ? TimeZone.SHORT : TimeZone.LONG; 1207 String zoneString = tz.getDisplayName(daylight, tzstyle, formatData.locale); 1208 if (zoneString != null) { 1209 buffer.append(zoneString); 1210 } else { 1211 int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) + 1212 calendar.get(Calendar.DST_OFFSET); 1213 buffer.append(TimeZone.createGmtOffsetString(true, true, offsetMillis)); 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.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount); 1650 } 1651 return (start + zoneNames[nameIndex].length()); 1652 } 1653 return 0; 1654 } 1655 1656 /** 1657 * Parses numeric forms of time zone offset, such as "hh:mm", and 1658 * sets calb to the parsed value. 1659 * 1660 * @param text the text to be parsed 1661 * @param start the character position to start parsing 1662 * @param sign 1: positive; -1: negative 1663 * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's 1664 * @param colon true - colon required between hh and mm; false - no colon required 1665 * @param calb a CalendarBuilder in which the parsed value is stored 1666 * @return updated parsed position, or its negative value to indicate a parsing error 1667 */ 1668 private int subParseNumericZone(String text, int start, int sign, int count, 1669 boolean colon, CalendarBuilder calb) { 1670 int index = start; 1671 1672 parse: 1673 try { 1674 char c = text.charAt(index++); 1675 // Parse hh 1676 int hours; 1677 if (!isDigit(c)) { 1678 break parse; 1679 } 1680 hours = c - '0'; 1681 c = text.charAt(index++); 1682 if (isDigit(c)) { 1683 hours = hours * 10 + (c - '0'); 1684 } else { 1685 // If no colon in RFC 822 or 'X' (ISO), two digits are 1686 // required. 1687 if (count > 0 || !colon) { 1688 break parse; 1689 } 1690 --index; 1691 } 1692 if (hours > 23) { 1693 break parse; 1694 } 1695 int minutes = 0; 1696 if (count != 1) { 1697 // Proceed with parsing mm 1698 c = text.charAt(index++); 1699 if (colon) { 1700 if (c != ':') { 1701 break parse; 1702 } 1703 c = text.charAt(index++); 1704 } 1705 if (!isDigit(c)) { 1706 break parse; 1707 } 1708 minutes = c - '0'; 1709 c = text.charAt(index++); 1710 if (!isDigit(c)) { 1711 break parse; 1712 } 1713 minutes = minutes * 10 + (c - '0'); 1714 if (minutes > 59) { 1715 break parse; 1716 } 1717 } 1718 minutes += hours * 60; 1719 calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign) 1720 .set(Calendar.DST_OFFSET, 0); 1721 return index; 1722 } catch (IndexOutOfBoundsException e) { 1723 } 1724 return 1 - index; // -(index - 1) 1725 } 1726 1727 private boolean isDigit(char c) { 1728 return c >= '0' && c <= '9'; 1729 } 1730 1731 /** 1732 * Private member function that converts the parsed date strings into 1733 * timeFields. Returns -start (for ParsePosition) if failed. 1734 * @param text the time text to be parsed. 1735 * @param start where to start parsing. 1736 * @param ch the pattern character for the date field text to be parsed. 1737 * @param count the count of a pattern character. 1738 * @param obeyCount if true, then the next field directly abuts this one, 1739 * and we should use the count to know when to stop parsing. 1740 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] 1741 * is true, then a two-digit year was parsed and may need to be readjusted. 1742 * @param origPos origPos.errorIndex is used to return an error index 1743 * at which a parse error occurred, if matching failure occurs. 1744 * @return the new start position if matching succeeded; -1 indicating 1745 * matching failure, otherwise. In case matching failure occurred, 1746 * an error index is set to origPos.errorIndex. 1747 */ 1748 private int subParse(String text, int start, int patternCharIndex, int count, 1749 boolean obeyCount, boolean[] ambiguousYear, 1750 ParsePosition origPos, 1751 boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) { 1752 Number number = null; 1753 int value = 0; 1754 ParsePosition pos = new ParsePosition(0); 1755 pos.index = start; 1756 if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) { 1757 // use calendar year 'y' instead 1758 patternCharIndex = PATTERN_YEAR; 1759 } 1760 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 1761 1762 // If there are any spaces here, skip over them. If we hit the end 1763 // of the string, then fail. 1764 for (;;) { 1765 if (pos.index >= text.length()) { 1766 origPos.errorIndex = start; 1767 return -1; 1768 } 1769 char c = text.charAt(pos.index); 1770 if (c != ' ' && c != '\t') break; 1771 ++pos.index; 1772 } 1773 1774 parsing: 1775 { 1776 // We handle a few special cases here where we need to parse 1777 // a number value. We handle further, more generic cases below. We need 1778 // to handle some of them here because some fields require extra processing on 1779 // the parsed value. 1780 if (patternCharIndex == PATTERN_HOUR_OF_DAY1 || 1781 patternCharIndex == PATTERN_HOUR1 || 1782 (patternCharIndex == PATTERN_MONTH && count <= 2) || 1783 patternCharIndex == PATTERN_YEAR || 1784 patternCharIndex == PATTERN_WEEK_YEAR) { 1785 // It would be good to unify this with the obeyCount logic below, 1786 // but that's going to be difficult. 1787 if (obeyCount) { 1788 if ((start+count) > text.length()) { 1789 break parsing; 1790 } 1791 number = numberFormat.parse(text.substring(0, start+count), pos); 1792 } else { 1793 number = numberFormat.parse(text, pos); 1794 } 1795 if (number == null) { 1796 if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) { 1797 break parsing; 1798 } 1799 } else { 1800 value = number.intValue(); 1801 1802 if (useFollowingMinusSignAsDelimiter && (value < 0) && 1803 (((pos.index < text.length()) && 1804 (text.charAt(pos.index) != minusSign)) || 1805 ((pos.index == text.length()) && 1806 (text.charAt(pos.index-1) == minusSign)))) { 1807 value = -value; 1808 pos.index--; 1809 } 1810 } 1811 } 1812 1813 boolean useDateFormatSymbols = useDateFormatSymbols(); 1814 1815 int index; 1816 switch (patternCharIndex) { 1817 case PATTERN_ERA: // 'G' 1818 if (useDateFormatSymbols) { 1819 if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) { 1820 return index; 1821 } 1822 } else { 1823 Map<String, Integer> map = calendar.getDisplayNames(field, 1824 Calendar.ALL_STYLES, 1825 locale); 1826 if ((index = matchString(text, start, field, map, calb)) > 0) { 1827 return index; 1828 } 1829 } 1830 break parsing; 1831 1832 case PATTERN_WEEK_YEAR: // 'Y' 1833 case PATTERN_YEAR: // 'y' 1834 if (!(calendar instanceof GregorianCalendar)) { 1835 // calendar might have text representations for year values, 1836 // such as "\u5143" in JapaneseImperialCalendar. 1837 int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; 1838 Map<String, Integer> map = calendar.getDisplayNames(field, style, locale); 1839 if (map != null) { 1840 if ((index = matchString(text, start, field, map, calb)) > 0) { 1841 return index; 1842 } 1843 } 1844 calb.set(field, value); 1845 return pos.index; 1846 } 1847 1848 // If there are 3 or more YEAR pattern characters, this indicates 1849 // that the year value is to be treated literally, without any 1850 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise 1851 // we made adjustments to place the 2-digit year in the proper 1852 // century, for parsed strings from "00" to "99". Any other string 1853 // is treated literally: "2250", "-1", "1", "002". 1854 if (count <= 2 && (pos.index - start) == 2 1855 && Character.isDigit(text.charAt(start)) 1856 && Character.isDigit(text.charAt(start+1))) { 1857 // Assume for example that the defaultCenturyStart is 6/18/1903. 1858 // This means that two-digit years will be forced into the range 1859 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 1860 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond 1861 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the 1862 // other fields specify a date before 6/18, or 1903 if they specify a 1863 // date afterwards. As a result, 03 is an ambiguous year. All other 1864 // two-digit years are unambiguous. 1865 int ambiguousTwoDigitYear = defaultCenturyStartYear % 100; 1866 ambiguousYear[0] = value == ambiguousTwoDigitYear; 1867 value += (defaultCenturyStartYear/100)*100 + 1868 (value < ambiguousTwoDigitYear ? 100 : 0); 1869 } 1870 calb.set(field, value); 1871 return pos.index; 1872 1873 case 'L': // Standalone month. Unsupported for now. 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 'c': // Standalone weekday. Unsupported for now. 1922 case PATTERN_DAY_OF_WEEK: // 'E' 1923 { 1924 if (useDateFormatSymbols) { 1925 // Want to be able to parse both short and long forms. 1926 // Try count == 4 (DDDD) first: 1927 int newStart = 0; 1928 if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK, 1929 formatData.getWeekdays(), calb)) > 0) { 1930 return newStart; 1931 } 1932 // DDDD failed, now try DDD 1933 if ((index = matchString(text, start, Calendar.DAY_OF_WEEK, 1934 formatData.getShortWeekdays(), calb)) > 0) { 1935 return index; 1936 } 1937 } else { 1938 int[] styles = { Calendar.LONG, Calendar.SHORT }; 1939 for (int style : styles) { 1940 Map<String,Integer> map = calendar.getDisplayNames(field, style, locale); 1941 if ((index = matchString(text, start, field, map, calb)) > 0) { 1942 return index; 1943 } 1944 } 1945 } 1946 } 1947 break parsing; 1948 1949 case PATTERN_AM_PM: // 'a' 1950 if (useDateFormatSymbols) { 1951 if ((index = matchString(text, start, Calendar.AM_PM, 1952 formatData.getAmPmStrings(), calb)) > 0) { 1953 return index; 1954 } 1955 } else { 1956 Map<String,Integer> map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale); 1957 if ((index = matchString(text, start, field, map, calb)) > 0) { 1958 return index; 1959 } 1960 } 1961 break parsing; 1962 1963 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM 1964 if (!isLenient()) { 1965 // Validate the hour value in non-lenient 1966 if (value < 1 || value > 12) { 1967 break parsing; 1968 } 1969 } 1970 // [We computed 'value' above.] 1971 if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) 1972 value = 0; 1973 calb.set(Calendar.HOUR, value); 1974 return pos.index; 1975 1976 case PATTERN_ZONE_NAME: // 'z' 1977 case PATTERN_ZONE_VALUE: // 'Z' 1978 { 1979 int sign = 0; 1980 try { 1981 char c = text.charAt(pos.index); 1982 if (c == '+') { 1983 sign = 1; 1984 } else if (c == '-') { 1985 sign = -1; 1986 } 1987 if (sign == 0) { 1988 // Try parsing a custom time zone "GMT+hh:mm" or "GMT". 1989 if ((c == 'G' || c == 'g') 1990 && (text.length() - start) >= GMT.length() 1991 && text.regionMatches(true, start, GMT, 0, GMT.length())) { 1992 pos.index = start + GMT.length(); 1993 1994 if ((text.length() - pos.index) > 0) { 1995 c = text.charAt(pos.index); 1996 if (c == '+') { 1997 sign = 1; 1998 } else if (c == '-') { 1999 sign = -1; 2000 } 2001 } 2002 2003 if (sign == 0) { /* "GMT" without offset */ 2004 calb.set(Calendar.ZONE_OFFSET, 0) 2005 .set(Calendar.DST_OFFSET, 0); 2006 return pos.index; 2007 } 2008 2009 // Parse the rest as "hh:mm" 2010 int i = subParseNumericZone(text, ++pos.index, 2011 sign, 0, true, calb); 2012 if (i > 0) { 2013 return i; 2014 } 2015 pos.index = -i; 2016 } else { 2017 // Try parsing the text as a time zone 2018 // name or abbreviation. 2019 int i = subParseZoneString(text, pos.index, calb); 2020 if (i > 0) { 2021 return i; 2022 } 2023 pos.index = -i; 2024 } 2025 } else { 2026 // Parse the rest as "hhmm" (RFC 822) 2027 int i = subParseNumericZone(text, ++pos.index, 2028 sign, 0, false, calb); 2029 if (i > 0) { 2030 return i; 2031 } 2032 pos.index = -i; 2033 } 2034 } catch (IndexOutOfBoundsException e) { 2035 } 2036 } 2037 break parsing; 2038 2039 case PATTERN_ISO_ZONE: // 'X' 2040 { 2041 if ((text.length() - pos.index) <= 0) { 2042 break parsing; 2043 } 2044 2045 int sign = 0; 2046 char c = text.charAt(pos.index); 2047 if (c == 'Z') { 2048 calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0); 2049 return ++pos.index; 2050 } 2051 2052 // parse text as "+/-hh[[:]mm]" based on count 2053 if (c == '+') { 2054 sign = 1; 2055 } else if (c == '-') { 2056 sign = -1; 2057 } else { 2058 ++pos.index; 2059 break parsing; 2060 } 2061 int i = subParseNumericZone(text, ++pos.index, sign, count, 2062 count == 3, calb); 2063 if (i > 0) { 2064 return i; 2065 } 2066 pos.index = -i; 2067 } 2068 break parsing; 2069 2070 default: 2071 // case PATTERN_DAY_OF_MONTH: // 'd' 2072 // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 2073 // case PATTERN_MINUTE: // 'm' 2074 // case PATTERN_SECOND: // 's' 2075 // case PATTERN_MILLISECOND: // 'S' 2076 // case PATTERN_DAY_OF_YEAR: // 'D' 2077 // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' 2078 // case PATTERN_WEEK_OF_YEAR: // 'w' 2079 // case PATTERN_WEEK_OF_MONTH: // 'W' 2080 // case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM 2081 // case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field); 2082 2083 // Handle "generic" fields 2084 if (obeyCount) { 2085 if ((start+count) > text.length()) { 2086 break parsing; 2087 } 2088 number = numberFormat.parse(text.substring(0, start+count), pos); 2089 } else { 2090 number = numberFormat.parse(text, pos); 2091 } 2092 if (number != null) { 2093 value = number.intValue(); 2094 2095 if (useFollowingMinusSignAsDelimiter && (value < 0) && 2096 (((pos.index < text.length()) && 2097 (text.charAt(pos.index) != minusSign)) || 2098 ((pos.index == text.length()) && 2099 (text.charAt(pos.index-1) == minusSign)))) { 2100 value = -value; 2101 pos.index--; 2102 } 2103 2104 calb.set(field, value); 2105 return pos.index; 2106 } 2107 break parsing; 2108 } 2109 } 2110 2111 // Parsing failed. 2112 origPos.errorIndex = pos.index; 2113 return -1; 2114 } 2115 2116 private final String getCalendarName() { 2117 return calendar.getClass().getName(); 2118 } 2119 2120 private boolean useDateFormatSymbols() { 2121 if (useDateFormatSymbols) { 2122 return true; 2123 } 2124 return isGregorianCalendar() || locale == null; 2125 } 2126 2127 private boolean isGregorianCalendar() { 2128 return "java.util.GregorianCalendar".equals(getCalendarName()); 2129 } 2130 2131 /** 2132 * Translates a pattern, mapping each character in the from string to the 2133 * corresponding character in the to string. 2134 * 2135 * @exception IllegalArgumentException if the given pattern is invalid 2136 */ 2137 private String translatePattern(String pattern, String from, String to) { 2138 StringBuilder result = new StringBuilder(); 2139 boolean inQuote = false; 2140 for (int i = 0; i < pattern.length(); ++i) { 2141 char c = pattern.charAt(i); 2142 if (inQuote) { 2143 if (c == '\'') 2144 inQuote = false; 2145 } 2146 else { 2147 if (c == '\'') 2148 inQuote = true; 2149 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 2150 int ci = from.indexOf(c); 2151 if (ci >= 0) { 2152 // patternChars is longer than localPatternChars due 2153 // to serialization compatibility. The pattern letters 2154 // unsupported by localPatternChars pass through. 2155 if (ci < to.length()) { 2156 c = to.charAt(ci); 2157 } 2158 } else { 2159 throw new IllegalArgumentException("Illegal pattern " + 2160 " character '" + 2161 c + "'"); 2162 } 2163 } 2164 } 2165 result.append(c); 2166 } 2167 if (inQuote) 2168 throw new IllegalArgumentException("Unfinished quote in pattern"); 2169 return result.toString(); 2170 } 2171 2172 /** 2173 * Returns a pattern string describing this date format. 2174 * 2175 * @return a pattern string describing this date format. 2176 */ 2177 public String toPattern() { 2178 return pattern; 2179 } 2180 2181 /** 2182 * Returns a localized pattern string describing this date format. 2183 * 2184 * @return a localized pattern string describing this date format. 2185 */ 2186 public String toLocalizedPattern() { 2187 return translatePattern(pattern, 2188 DateFormatSymbols.patternChars, 2189 formatData.getLocalPatternChars()); 2190 } 2191 2192 /** 2193 * Applies the given pattern string to this date format. 2194 * 2195 * @param pattern the new date and time pattern for this date format 2196 * @exception NullPointerException if the given pattern is null 2197 * @exception IllegalArgumentException if the given pattern is invalid 2198 */ 2199 public void applyPattern(String pattern) 2200 { 2201 compiledPattern = compile(pattern); 2202 this.pattern = pattern; 2203 } 2204 2205 /** 2206 * Applies the given localized pattern string to this date format. 2207 * 2208 * @param pattern a String to be mapped to the new date and time format 2209 * pattern for this format 2210 * @exception NullPointerException if the given pattern is null 2211 * @exception IllegalArgumentException if the given pattern is invalid 2212 */ 2213 public void applyLocalizedPattern(String pattern) { 2214 String p = translatePattern(pattern, 2215 formatData.getLocalPatternChars(), 2216 DateFormatSymbols.patternChars); 2217 compiledPattern = compile(p); 2218 this.pattern = p; 2219 } 2220 2221 /** 2222 * Gets a copy of the date and time format symbols of this date format. 2223 * 2224 * @return the date and time format symbols of this date format 2225 * @see #setDateFormatSymbols 2226 */ 2227 public DateFormatSymbols getDateFormatSymbols() 2228 { 2229 return (DateFormatSymbols)formatData.clone(); 2230 } 2231 2232 /** 2233 * Sets the date and time format symbols of this date format. 2234 * 2235 * @param newFormatSymbols the new date and time format symbols 2236 * @exception NullPointerException if the given newFormatSymbols is null 2237 * @see #getDateFormatSymbols 2238 */ 2239 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) 2240 { 2241 this.formatData = (DateFormatSymbols)newFormatSymbols.clone(); 2242 useDateFormatSymbols = true; 2243 } 2244 2245 /** 2246 * Creates a copy of this <code>SimpleDateFormat</code>. This also 2247 * clones the format's date format symbols. 2248 * 2249 * @return a clone of this <code>SimpleDateFormat</code> 2250 */ 2251 public Object clone() { 2252 SimpleDateFormat other = (SimpleDateFormat) super.clone(); 2253 other.formatData = (DateFormatSymbols) formatData.clone(); 2254 return other; 2255 } 2256 2257 /** 2258 * Returns the hash code value for this <code>SimpleDateFormat</code> object. 2259 * 2260 * @return the hash code value for this <code>SimpleDateFormat</code> object. 2261 */ 2262 public int hashCode() 2263 { 2264 return pattern.hashCode(); 2265 // just enough fields for a reasonable distribution 2266 } 2267 2268 /** 2269 * Compares the given object with this <code>SimpleDateFormat</code> for 2270 * equality. 2271 * 2272 * @return true if the given object is equal to this 2273 * <code>SimpleDateFormat</code> 2274 */ 2275 public boolean equals(Object obj) 2276 { 2277 if (!super.equals(obj)) return false; // super does class check 2278 SimpleDateFormat that = (SimpleDateFormat) obj; 2279 return (pattern.equals(that.pattern) 2280 && formatData.equals(that.formatData)); 2281 } 2282 2283 /** 2284 * After reading an object from the input stream, the format 2285 * pattern in the object is verified. 2286 * <p> 2287 * @exception InvalidObjectException if the pattern is invalid 2288 */ 2289 private void readObject(ObjectInputStream stream) 2290 throws IOException, ClassNotFoundException { 2291 stream.defaultReadObject(); 2292 2293 try { 2294 compiledPattern = compile(pattern); 2295 } catch (Exception e) { 2296 throw new InvalidObjectException("invalid pattern"); 2297 } 2298 2299 if (serialVersionOnStream < 1) { 2300 // didn't have defaultCenturyStart field 2301 initializeDefaultCentury(); 2302 } 2303 else { 2304 // fill in dependent transient field 2305 parseAmbiguousDatesAsAfter(defaultCenturyStart); 2306 } 2307 serialVersionOnStream = currentSerialVersion; 2308 2309 // If the deserialized object has a SimpleTimeZone, try 2310 // to replace it with a ZoneInfo equivalent in order to 2311 // be compatible with the SimpleTimeZone-based 2312 // implementation as much as possible. 2313 TimeZone tz = getTimeZone(); 2314 if (tz instanceof SimpleTimeZone) { 2315 String id = tz.getID(); 2316 TimeZone zi = TimeZone.getTimeZone(id); 2317 if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) { 2318 setTimeZone(zi); 2319 } 2320 } 2321 } 2322 2323 /** 2324 * Analyze the negative subpattern of DecimalFormat and set/update values 2325 * as necessary. 2326 */ 2327 private void checkNegativeNumberExpression() { 2328 if ((numberFormat instanceof DecimalFormat) && 2329 !numberFormat.equals(originalNumberFormat)) { 2330 String numberPattern = ((DecimalFormat)numberFormat).toPattern(); 2331 if (!numberPattern.equals(originalNumberPattern)) { 2332 hasFollowingMinusSign = false; 2333 2334 int separatorIndex = numberPattern.indexOf(';'); 2335 // If the negative subpattern is not absent, we have to analayze 2336 // it in order to check if it has a following minus sign. 2337 if (separatorIndex > -1) { 2338 int minusIndex = numberPattern.indexOf('-', separatorIndex); 2339 if ((minusIndex > numberPattern.lastIndexOf('0')) && 2340 (minusIndex > numberPattern.lastIndexOf('#'))) { 2341 hasFollowingMinusSign = true; 2342 minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign(); 2343 } 2344 } 2345 originalNumberPattern = numberPattern; 2346 } 2347 originalNumberFormat = numberFormat; 2348 } 2349 } 2350 2351} 2352