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