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