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