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