1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.text; 19 20 import java.io.IOException; 21 import java.io.ObjectInputStream; 22 import java.io.ObjectOutputStream; 23 import java.io.ObjectStreamField; 24 import java.util.ArrayList; 25 import java.util.Calendar; 26 import java.util.Date; 27 import java.util.GregorianCalendar; 28 import java.util.List; 29 import java.util.Locale; 30 import java.util.SimpleTimeZone; 31 import java.util.TimeZone; 32 import libcore.icu.LocaleData; 33 import libcore.icu.TimeZoneNames; 34 35 /** 36 * Formats and parses dates in a locale-sensitive manner. Formatting turns a {@link Date} into 37 * a {@link String}, and parsing turns a {@code String} into a {@code Date}. 38 * 39 * <h4>Time Pattern Syntax</h4> 40 * <p>You can supply a Unicode <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> 41 * pattern describing what strings are produced/accepted, but almost all 42 * callers should use {@link DateFormat#getDateInstance}, {@link DateFormat#getDateTimeInstance}, 43 * or {@link DateFormat#getTimeInstance} to get a ready-made instance suitable for the user's 44 * locale. In cases where the system does not provide a suitable pattern, see 45 * {@link android.text.format.DateFormat#getBestDateTimePattern} which lets you specify 46 * the elements you'd like in a pattern and get back a pattern suitable for any given locale. 47 * 48 * <p>The main reason you'd create an instance this class directly is because you need to 49 * format/parse a specific machine-readable format, in which case you almost certainly want 50 * to explicitly ask for {@link Locale#US} to ensure that you get ASCII digits (rather than, 51 * say, Arabic digits). 52 * (See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".) 53 * The most useful non-localized pattern is {@code "yyyy-MM-dd HH:mm:ss.SSSZ"}, which corresponds 54 * to the ISO 8601 international standard date format. 55 * 56 * <p>To specify the time format, use a <i>time pattern</i> string. In this 57 * string, any character from {@code 'A'} to {@code 'Z'} or {@code 'a'} to {@code 'z'} is 58 * treated specially. All other characters are passed through verbatim. The interpretation of each 59 * of the ASCII letters is given in the table below. ASCII letters not appearing in the table are 60 * reserved for future use, and it is an error to attempt to use them. 61 * 62 * <p>The number of consecutive copies (the "count") of a pattern character further influences 63 * the format, as shown in the table. For fields of kind "number", the count is the minimum number 64 * of digits; shorter values are zero-padded to the given width and longer values overflow it. 65 * 66 * <p><table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY=""> 67 * <tr BGCOLOR="#CCCCFF" CLASS="TableHeadingColor"> 68 * <td><B>Symbol</B></td> <td><B>Meaning</B></td> <td><B>Kind</B></td> <td><B>Example</B></td> </tr> 69 * <tr> <td>{@code D}</td> <td>day in year</td> <td>(Number)</td> <td>189</td> </tr> 70 * <tr> <td>{@code E}</td> <td>day of week</td> <td>(Text)</td> <td>{@code E}/{@code EE}/{@code EEE}:Tue, {@code EEEE}:Tuesday, {@code EEEEE}:T</td> </tr> 71 * <tr> <td>{@code F}</td> <td>day of week in month</td> <td>(Number)</td> <td>2 <i>(2nd Wed in July)</i></td> </tr> 72 * <tr> <td>{@code G}</td> <td>era designator</td> <td>(Text)</td> <td>AD</td> </tr> 73 * <tr> <td>{@code H}</td> <td>hour in day (0-23)</td> <td>(Number)</td> <td>0</td> </tr> 74 * <tr> <td>{@code K}</td> <td>hour in am/pm (0-11)</td> <td>(Number)</td> <td>0</td> </tr> 75 * <tr> <td>{@code L}</td> <td>stand-alone month</td> <td>(Text)</td> <td>{@code L}:1 {@code LL}:01 {@code LLL}:Jan {@code LLLL}:January {@code LLLLL}:J</td> </tr> 76 * <tr> <td>{@code M}</td> <td>month in year</td> <td>(Text)</td> <td>{@code M}:1 {@code MM}:01 {@code MMM}:Jan {@code MMMM}:January {@code MMMMM}:J</td> </tr> 77 * <tr> <td>{@code S}</td> <td>fractional seconds</td> <td>(Number)</td> <td>978</td> </tr> 78 * <tr> <td>{@code W}</td> <td>week in month</td> <td>(Number)</td> <td>2</td> </tr> 79 * <tr> <td>{@code Z}</td> <td>time zone (RFC 822)</td> <td>(Time Zone)</td> <td>{@code Z}/{@code ZZ}/{@code ZZZ}:-0800 {@code ZZZZ}:GMT-08:00 {@code ZZZZZ}:-08:00</td> </tr> 80 * <tr> <td>{@code a}</td> <td>am/pm marker</td> <td>(Text)</td> <td>PM</td> </tr> 81 * <tr> <td>{@code c}</td> <td>stand-alone day of week</td> <td>(Text)</td> <td>{@code c}/{@code cc}/{@code ccc}:Tue, {@code cccc}:Tuesday, {@code ccccc}:T</td> </tr> 82 * <tr> <td>{@code d}</td> <td>day in month</td> <td>(Number)</td> <td>10</td> </tr> 83 * <tr> <td>{@code h}</td> <td>hour in am/pm (1-12)</td> <td>(Number)</td> <td>12</td> </tr> 84 * <tr> <td>{@code k}</td> <td>hour in day (1-24)</td> <td>(Number)</td> <td>24</td> </tr> 85 * <tr> <td>{@code m}</td> <td>minute in hour</td> <td>(Number)</td> <td>30</td> </tr> 86 * <tr> <td>{@code s}</td> <td>second in minute</td> <td>(Number)</td> <td>55</td> </tr> 87 * <tr> <td>{@code w}</td> <td>week in year</td> <td>(Number)</td> <td>27</td> </tr> 88 * <tr> <td>{@code y}</td> <td>year</td> <td>(Number)</td> <td>{@code yy}:10 {@code y}/{@code yyy}/{@code yyyy}:2010</td> </tr> 89 * <tr> <td>{@code z}</td> <td>time zone</td> <td>(Time Zone)</td> <td>{@code z}/{@code zz}/{@code zzz}:PST {@code zzzz}:Pacific Standard Time</td> </tr> 90 * <tr> <td>{@code '}</td> <td>escape for text</td> <td>(Delimiter)</td> <td>{@code 'Date='}:Date=</td> </tr> 91 * <tr> <td>{@code ''}</td> <td>single quote</td> <td>(Literal)</td> <td>{@code 'o''clock'}:o'clock</td> </tr> 92 * </table> 93 * 94 * <p>Fractional seconds are handled specially: they're zero-padded on the <i>right</i>. 95 * 96 * <p>The two pattern characters {@code L} and {@code c} are ICU-compatible extensions, not 97 * available in the RI or in Android before Android 2.3 (Gingerbread, API level 9). These 98 * extensions are necessary for correct localization in languages such as Russian 99 * that make a grammatical distinction between, say, the word "June" in the sentence "June" and 100 * in the sentence "June 10th"; the former is the stand-alone form, the latter the regular 101 * form (because the usual case is to format a complete date). The relationship between {@code E} 102 * and {@code c} is equivalent, but for weekday names. 103 * 104 * <p>Five-count patterns (such as "MMMMM") used for the shortest non-numeric 105 * representation of a field were introduced in Android 4.3 (Jelly Bean MR2, API level 18). 106 * 107 * <p>When two numeric fields are directly adjacent with no intervening delimiter 108 * characters, they constitute a run of adjacent numeric fields. Such runs are 109 * parsed specially. For example, the format "HHmmss" parses the input text 110 * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to 111 * parse "1234". In other words, the leftmost field of the run is flexible, 112 * while the others keep a fixed width. If the parse fails anywhere in the run, 113 * then the leftmost field is shortened by one character, and the entire run is 114 * parsed again. This is repeated until either the parse succeeds or the 115 * leftmost field is one character in length. If the parse still fails at that 116 * point, the parse of the run fails. 117 * 118 * <p>See {@link #set2DigitYearStart} for more about handling two-digit years. 119 * 120 * <h4>Sample Code</h4> 121 * <p>If you're formatting for human use, you should use an instance returned from 122 * {@link DateFormat} as described above. This code: 123 * <pre> 124 * DateFormat[] formats = new DateFormat[] { 125 * DateFormat.getDateInstance(), 126 * DateFormat.getDateTimeInstance(), 127 * DateFormat.getTimeInstance(), 128 * }; 129 * for (DateFormat df : formats) { 130 * System.out.println(df.format(new Date(0))); 131 * } 132 * </pre> 133 * 134 * <p>Produces this output when run on an {@code en_US} device in the America/Los_Angeles time zone: 135 * <pre> 136 * Dec 31, 1969 137 * Dec 31, 1969 4:00:00 PM 138 * 4:00:00 PM 139 * </pre> 140 * And will produce similarly appropriate localized human-readable output on any user's system. 141 * 142 * <p>If you're formatting for machine use, consider this code: 143 * <pre> 144 * String[] formats = new String[] { 145 * "yyyy-MM-dd", 146 * "yyyy-MM-dd HH:mm", 147 * "yyyy-MM-dd HH:mmZ", 148 * "yyyy-MM-dd HH:mm:ss.SSSZ", 149 * "yyyy-MM-dd'T'HH:mm:ss.SSSZ", 150 * }; 151 * for (String format : formats) { 152 * SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US); 153 * System.out.format("%30s %s\n", format, sdf.format(new Date(0))); 154 * sdf.setTimeZone(TimeZone.getTimeZone("UTC")); 155 * System.out.format("%30s %s\n", format, sdf.format(new Date(0))); 156 * } 157 * </pre> 158 * 159 * <p>Which produces this output when run in the America/Los_Angeles time zone: 160 * <pre> 161 * yyyy-MM-dd 1969-12-31 162 * yyyy-MM-dd 1970-01-01 163 * yyyy-MM-dd HH:mm 1969-12-31 16:00 164 * yyyy-MM-dd HH:mm 1970-01-01 00:00 165 * yyyy-MM-dd HH:mmZ 1969-12-31 16:00-0800 166 * yyyy-MM-dd HH:mmZ 1970-01-01 00:00+0000 167 * yyyy-MM-dd HH:mm:ss.SSSZ 1969-12-31 16:00:00.000-0800 168 * yyyy-MM-dd HH:mm:ss.SSSZ 1970-01-01 00:00:00.000+0000 169 * yyyy-MM-dd'T'HH:mm:ss.SSSZ 1969-12-31T16:00:00.000-0800 170 * yyyy-MM-dd'T'HH:mm:ss.SSSZ 1970-01-01T00:00:00.000+0000 171 * </pre> 172 * 173 * <p>As this example shows, each {@code SimpleDateFormat} instance has a {@link TimeZone}. 174 * This is because it's called upon to format instances of {@code Date}, which represents an 175 * absolute time in UTC. That is, {@code Date} does not carry time zone information. 176 * By default, {@code SimpleDateFormat} will use the system's default time zone. This is 177 * appropriate for human-readable output (for which, see the previous sample instead), but 178 * generally inappropriate for machine-readable output, where ambiguity is a problem. Note that 179 * in this example, the output that included a time but no time zone cannot be parsed back into 180 * the original {@code Date}. For this 181 * reason it is almost always necessary and desirable to include the timezone in the output. 182 * It may also be desirable to set the formatter's time zone to UTC (to ease comparison, or to 183 * make logs more readable, for example). It is often best to avoid formatting completely when 184 * writing dates/times in machine-readable form. Simply sending the "Unix time" as a {@code long} 185 * or as the string corresponding to the long is cheaper and unambiguous, and can be formatted any 186 * way the recipient deems appropriate. 187 * 188 * <h4>Synchronization</h4> 189 * {@code SimpleDateFormat} is not thread-safe. Users should create a separate instance for 190 * each thread. 191 * 192 * @see java.util.Calendar 193 * @see java.util.Date 194 * @see java.util.TimeZone 195 * @see java.text.DateFormat 196 */ 197 public class SimpleDateFormat extends DateFormat { 198 199 private static final long serialVersionUID = 4774881970558875024L; 200 201 // 'L' and 'c' are ICU-compatible extensions for stand-alone month and stand-alone weekday. 202 static final String PATTERN_CHARS = "GyMdkHmsSEDFwWahKzZLc"; 203 204 // The index of 'Z' in the PATTERN_CHARS string. This pattern character is supported by the RI, 205 // but has no corresponding public constant. 206 private static final int RFC_822_TIMEZONE_FIELD = 18; 207 208 // The index of 'L' (cf. 'M') in the PATTERN_CHARS string. This is an ICU-compatible extension 209 // necessary for correct localization in various languages (http://b/2633414). 210 private static final int STAND_ALONE_MONTH_FIELD = 19; 211 // The index of 'c' (cf. 'E') in the PATTERN_CHARS string. This is an ICU-compatible extension 212 // necessary for correct localization in various languages (http://b/2633414). 213 private static final int STAND_ALONE_DAY_OF_WEEK_FIELD = 20; 214 215 private String pattern; 216 217 private DateFormatSymbols formatData; 218 219 transient private int creationYear; 220 221 private Date defaultCenturyStart; 222 223 /** 224 * Constructs a new {@code SimpleDateFormat} for formatting and parsing 225 * dates and times in the {@code SHORT} style for the user's default locale. 226 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 227 */ SimpleDateFormat()228 public SimpleDateFormat() { 229 this(Locale.getDefault()); 230 this.pattern = defaultPattern(); 231 this.formatData = new DateFormatSymbols(Locale.getDefault()); 232 } 233 234 /** 235 * Constructs a new {@code SimpleDateFormat} using the specified 236 * non-localized pattern and the {@code DateFormatSymbols} and {@code 237 * Calendar} for the user's default locale. 238 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 239 * 240 * @param pattern 241 * the pattern. 242 * @throws NullPointerException 243 * if the pattern is {@code null}. 244 * @throws IllegalArgumentException 245 * if {@code pattern} is not considered to be usable by this 246 * formatter. 247 */ SimpleDateFormat(String pattern)248 public SimpleDateFormat(String pattern) { 249 this(pattern, Locale.getDefault()); 250 } 251 252 /** 253 * Validates the pattern. 254 * 255 * @param template 256 * the pattern to validate. 257 * 258 * @throws NullPointerException 259 * if the pattern is null 260 * @throws IllegalArgumentException 261 * if the pattern is invalid 262 */ validatePattern(String template)263 private void validatePattern(String template) { 264 boolean quote = false; 265 int next, last = -1, count = 0; 266 267 final int patternLength = template.length(); 268 for (int i = 0; i < patternLength; i++) { 269 next = (template.charAt(i)); 270 if (next == '\'') { 271 if (count > 0) { 272 validatePatternCharacter((char) last); 273 count = 0; 274 } 275 if (last == next) { 276 last = -1; 277 } else { 278 last = next; 279 } 280 quote = !quote; 281 continue; 282 } 283 if (!quote 284 && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { 285 if (last == next) { 286 count++; 287 } else { 288 if (count > 0) { 289 validatePatternCharacter((char) last); 290 } 291 last = next; 292 count = 1; 293 } 294 } else { 295 if (count > 0) { 296 validatePatternCharacter((char) last); 297 count = 0; 298 } 299 last = -1; 300 } 301 } 302 if (count > 0) { 303 validatePatternCharacter((char) last); 304 } 305 306 if (quote) { 307 throw new IllegalArgumentException("Unterminated quote"); 308 } 309 } 310 validatePatternCharacter(char format)311 private void validatePatternCharacter(char format) { 312 int index = PATTERN_CHARS.indexOf(format); 313 if (index == -1) { 314 throw new IllegalArgumentException("Unknown pattern character '" + format + "'"); 315 } 316 } 317 318 /** 319 * Constructs a new {@code SimpleDateFormat} using the specified 320 * non-localized pattern and {@code DateFormatSymbols} and the {@code 321 * Calendar} for the user's default locale. 322 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 323 * 324 * @param template 325 * the pattern. 326 * @param value 327 * the DateFormatSymbols. 328 * @throws NullPointerException 329 * if the pattern is {@code null}. 330 * @throws IllegalArgumentException 331 * if the pattern is invalid. 332 */ SimpleDateFormat(String template, DateFormatSymbols value)333 public SimpleDateFormat(String template, DateFormatSymbols value) { 334 this(Locale.getDefault()); 335 validatePattern(template); 336 pattern = template; 337 formatData = (DateFormatSymbols) value.clone(); 338 } 339 340 /** 341 * Constructs a new {@code SimpleDateFormat} using the specified 342 * non-localized pattern and the {@code DateFormatSymbols} and {@code 343 * Calendar} for the specified locale. 344 * 345 * @param template 346 * the pattern. 347 * @param locale 348 * the locale. 349 * @throws NullPointerException 350 * if the pattern is {@code null}. 351 * @throws IllegalArgumentException 352 * if the pattern is invalid. 353 */ SimpleDateFormat(String template, Locale locale)354 public SimpleDateFormat(String template, Locale locale) { 355 this(locale); 356 validatePattern(template); 357 pattern = template; 358 formatData = new DateFormatSymbols(locale); 359 } 360 SimpleDateFormat(Locale locale)361 private SimpleDateFormat(Locale locale) { 362 numberFormat = NumberFormat.getInstance(locale); 363 numberFormat.setParseIntegerOnly(true); 364 numberFormat.setGroupingUsed(false); 365 calendar = new GregorianCalendar(locale); 366 calendar.add(Calendar.YEAR, -80); 367 creationYear = calendar.get(Calendar.YEAR); 368 defaultCenturyStart = calendar.getTime(); 369 } 370 371 /** 372 * Changes the pattern of this simple date format to the specified pattern 373 * which uses localized pattern characters. 374 * 375 * @param template 376 * the localized pattern. 377 */ applyLocalizedPattern(String template)378 public void applyLocalizedPattern(String template) { 379 pattern = convertPattern(template, formatData.getLocalPatternChars(), PATTERN_CHARS, true); 380 } 381 382 /** 383 * Changes the pattern of this simple date format to the specified pattern 384 * which uses non-localized pattern characters. 385 * 386 * @param template 387 * the non-localized pattern. 388 * @throws NullPointerException 389 * if the pattern is {@code null}. 390 * @throws IllegalArgumentException 391 * if the pattern is invalid. 392 */ applyPattern(String template)393 public void applyPattern(String template) { 394 validatePattern(template); 395 pattern = template; 396 } 397 398 /** 399 * Returns a new {@code SimpleDateFormat} with the same pattern and 400 * properties as this simple date format. 401 */ 402 @Override clone()403 public Object clone() { 404 SimpleDateFormat clone = (SimpleDateFormat) super.clone(); 405 clone.formatData = (DateFormatSymbols) formatData.clone(); 406 clone.defaultCenturyStart = new Date(defaultCenturyStart.getTime()); 407 return clone; 408 } 409 defaultPattern()410 private static String defaultPattern() { 411 LocaleData localeData = LocaleData.get(Locale.getDefault()); 412 return localeData.getDateFormat(SHORT) + " " + localeData.getTimeFormat(SHORT); 413 } 414 415 /** 416 * Compares the specified object with this simple date format and indicates 417 * if they are equal. In order to be equal, {@code object} must be an 418 * instance of {@code SimpleDateFormat} and have the same {@code DateFormat} 419 * properties, pattern, {@code DateFormatSymbols} and creation year. 420 * 421 * @param object 422 * the object to compare with this object. 423 * @return {@code true} if the specified object is equal to this simple date 424 * format; {@code false} otherwise. 425 * @see #hashCode 426 */ 427 @Override equals(Object object)428 public boolean equals(Object object) { 429 if (this == object) { 430 return true; 431 } 432 if (!(object instanceof SimpleDateFormat)) { 433 return false; 434 } 435 SimpleDateFormat simple = (SimpleDateFormat) object; 436 return super.equals(object) && pattern.equals(simple.pattern) 437 && formatData.equals(simple.formatData); 438 } 439 440 /** 441 * Formats the specified object using the rules of this simple date format 442 * and returns an {@code AttributedCharacterIterator} with the formatted 443 * date and attributes. 444 * 445 * @param object 446 * the object to format. 447 * @return an {@code AttributedCharacterIterator} with the formatted date 448 * and attributes. 449 * @throws NullPointerException 450 * if the object is {@code null}. 451 * @throws IllegalArgumentException 452 * if the object cannot be formatted by this simple date 453 * format. 454 */ 455 @Override formatToCharacterIterator(Object object)456 public AttributedCharacterIterator formatToCharacterIterator(Object object) { 457 if (object == null) { 458 throw new NullPointerException("object == null"); 459 } 460 if (object instanceof Date) { 461 return formatToCharacterIteratorImpl((Date) object); 462 } 463 if (object instanceof Number) { 464 return formatToCharacterIteratorImpl(new Date(((Number) object).longValue())); 465 } 466 throw new IllegalArgumentException("Bad class: " + object.getClass()); 467 } 468 formatToCharacterIteratorImpl(Date date)469 private AttributedCharacterIterator formatToCharacterIteratorImpl(Date date) { 470 StringBuffer buffer = new StringBuffer(); 471 ArrayList<FieldPosition> fields = new ArrayList<FieldPosition>(); 472 473 // format the date, and find fields 474 formatImpl(date, buffer, null, fields); 475 476 // create and AttributedString with the formatted buffer 477 AttributedString as = new AttributedString(buffer.toString()); 478 479 // add DateFormat field attributes to the AttributedString 480 for (FieldPosition pos : fields) { 481 Format.Field attribute = pos.getFieldAttribute(); 482 as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos.getEndIndex()); 483 } 484 485 // return the CharacterIterator from AttributedString 486 return as.getIterator(); 487 } 488 489 /** 490 * Formats the date. 491 * <p> 492 * If the FieldPosition {@code field} is not null, and the field 493 * specified by this FieldPosition is formatted, set the begin and end index 494 * of the formatted field in the FieldPosition. 495 * <p> 496 * If the list {@code fields} is not null, find fields of this 497 * date, set FieldPositions with these fields, and add them to the fields 498 * vector. 499 * 500 * @param date 501 * Date to Format 502 * @param buffer 503 * StringBuffer to store the resulting formatted String 504 * @param field 505 * FieldPosition to set begin and end index of the field 506 * specified, if it is part of the format for this date 507 * @param fields 508 * list used to store the FieldPositions for each field in this 509 * date 510 * @return the formatted Date 511 * @throws IllegalArgumentException 512 * if the object cannot be formatted by this Format. 513 */ formatImpl(Date date, StringBuffer buffer, FieldPosition field, List<FieldPosition> fields)514 private StringBuffer formatImpl(Date date, StringBuffer buffer, 515 FieldPosition field, List<FieldPosition> fields) { 516 boolean quote = false; 517 int next, last = -1, count = 0; 518 calendar.setTime(date); 519 if (field != null) { 520 field.setBeginIndex(0); 521 field.setEndIndex(0); 522 } 523 524 final int patternLength = pattern.length(); 525 for (int i = 0; i < patternLength; i++) { 526 next = (pattern.charAt(i)); 527 if (next == '\'') { 528 if (count > 0) { 529 append(buffer, field, fields, (char) last, count); 530 count = 0; 531 } 532 if (last == next) { 533 buffer.append('\''); 534 last = -1; 535 } else { 536 last = next; 537 } 538 quote = !quote; 539 continue; 540 } 541 if (!quote 542 && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { 543 if (last == next) { 544 count++; 545 } else { 546 if (count > 0) { 547 append(buffer, field, fields, (char) last, count); 548 } 549 last = next; 550 count = 1; 551 } 552 } else { 553 if (count > 0) { 554 append(buffer, field, fields, (char) last, count); 555 count = 0; 556 } 557 last = -1; 558 buffer.append((char) next); 559 } 560 } 561 if (count > 0) { 562 append(buffer, field, fields, (char) last, count); 563 } 564 return buffer; 565 } 566 append(StringBuffer buffer, FieldPosition position, List<FieldPosition> fields, char format, int count)567 private void append(StringBuffer buffer, FieldPosition position, 568 List<FieldPosition> fields, char format, int count) { 569 int field = -1; 570 int index = PATTERN_CHARS.indexOf(format); 571 if (index == -1) { 572 throw new IllegalArgumentException("Unknown pattern character '" + format + "'"); 573 } 574 575 int beginPosition = buffer.length(); 576 Field dateFormatField = null; 577 switch (index) { 578 case ERA_FIELD: 579 dateFormatField = Field.ERA; 580 buffer.append(formatData.eras[calendar.get(Calendar.ERA)]); 581 break; 582 case YEAR_FIELD: 583 dateFormatField = Field.YEAR; 584 int year = calendar.get(Calendar.YEAR); 585 /* 586 * For 'y' and 'yyy', we're consistent with Unicode and previous releases 587 * of Android. But this means we're inconsistent with the RI. 588 * http://unicode.org/reports/tr35/ 589 */ 590 if (count == 2) { 591 appendNumber(buffer, 2, year % 100); 592 } else { 593 appendNumber(buffer, count, year); 594 } 595 break; 596 case STAND_ALONE_MONTH_FIELD: // 'L' 597 dateFormatField = Field.MONTH; 598 appendMonth(buffer, count, true); 599 break; 600 case MONTH_FIELD: // 'M' 601 dateFormatField = Field.MONTH; 602 appendMonth(buffer, count, false); 603 break; 604 case DATE_FIELD: 605 dateFormatField = Field.DAY_OF_MONTH; 606 field = Calendar.DATE; 607 break; 608 case HOUR_OF_DAY1_FIELD: // 'k' 609 dateFormatField = Field.HOUR_OF_DAY1; 610 int hour = calendar.get(Calendar.HOUR_OF_DAY); 611 appendNumber(buffer, count, hour == 0 ? 24 : hour); 612 break; 613 case HOUR_OF_DAY0_FIELD: // 'H' 614 dateFormatField = Field.HOUR_OF_DAY0; 615 field = Calendar.HOUR_OF_DAY; 616 break; 617 case MINUTE_FIELD: 618 dateFormatField = Field.MINUTE; 619 field = Calendar.MINUTE; 620 break; 621 case SECOND_FIELD: 622 dateFormatField = Field.SECOND; 623 field = Calendar.SECOND; 624 break; 625 case MILLISECOND_FIELD: 626 dateFormatField = Field.MILLISECOND; 627 appendMilliseconds(buffer, count, calendar.get(Calendar.MILLISECOND)); 628 break; 629 case STAND_ALONE_DAY_OF_WEEK_FIELD: 630 dateFormatField = Field.DAY_OF_WEEK; 631 appendDayOfWeek(buffer, count, true); 632 break; 633 case DAY_OF_WEEK_FIELD: 634 dateFormatField = Field.DAY_OF_WEEK; 635 appendDayOfWeek(buffer, count, false); 636 break; 637 case DAY_OF_YEAR_FIELD: 638 dateFormatField = Field.DAY_OF_YEAR; 639 field = Calendar.DAY_OF_YEAR; 640 break; 641 case DAY_OF_WEEK_IN_MONTH_FIELD: 642 dateFormatField = Field.DAY_OF_WEEK_IN_MONTH; 643 field = Calendar.DAY_OF_WEEK_IN_MONTH; 644 break; 645 case WEEK_OF_YEAR_FIELD: 646 dateFormatField = Field.WEEK_OF_YEAR; 647 field = Calendar.WEEK_OF_YEAR; 648 break; 649 case WEEK_OF_MONTH_FIELD: 650 dateFormatField = Field.WEEK_OF_MONTH; 651 field = Calendar.WEEK_OF_MONTH; 652 break; 653 case AM_PM_FIELD: 654 dateFormatField = Field.AM_PM; 655 buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]); 656 break; 657 case HOUR1_FIELD: // 'h' 658 dateFormatField = Field.HOUR1; 659 hour = calendar.get(Calendar.HOUR); 660 appendNumber(buffer, count, hour == 0 ? 12 : hour); 661 break; 662 case HOUR0_FIELD: // 'K' 663 dateFormatField = Field.HOUR0; 664 field = Calendar.HOUR; 665 break; 666 case TIMEZONE_FIELD: // 'z' 667 dateFormatField = Field.TIME_ZONE; 668 appendTimeZone(buffer, count, true); 669 break; 670 case RFC_822_TIMEZONE_FIELD: // 'Z' 671 dateFormatField = Field.TIME_ZONE; 672 appendNumericTimeZone(buffer, count, false); 673 break; 674 } 675 if (field != -1) { 676 appendNumber(buffer, count, calendar.get(field)); 677 } 678 679 if (fields != null) { 680 position = new FieldPosition(dateFormatField); 681 position.setBeginIndex(beginPosition); 682 position.setEndIndex(buffer.length()); 683 fields.add(position); 684 } else { 685 // Set to the first occurrence 686 if ((position.getFieldAttribute() == dateFormatField || (position 687 .getFieldAttribute() == null && position.getField() == index)) 688 && position.getEndIndex() == 0) { 689 position.setBeginIndex(beginPosition); 690 position.setEndIndex(buffer.length()); 691 } 692 } 693 } 694 695 // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts. appendDayOfWeek(StringBuffer buffer, int count, boolean standAlone)696 private void appendDayOfWeek(StringBuffer buffer, int count, boolean standAlone) { 697 String[] days; 698 LocaleData ld = formatData.localeData; 699 if (count == 4) { 700 days = standAlone ? ld.longStandAloneWeekdayNames : formatData.weekdays; 701 } else if (count == 5) { 702 days = standAlone ? ld.tinyStandAloneWeekdayNames : formatData.localeData.tinyWeekdayNames; 703 } else { 704 days = standAlone ? ld.shortStandAloneWeekdayNames : formatData.shortWeekdays; 705 } 706 buffer.append(days[calendar.get(Calendar.DAY_OF_WEEK)]); 707 } 708 709 // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts. appendMonth(StringBuffer buffer, int count, boolean standAlone)710 private void appendMonth(StringBuffer buffer, int count, boolean standAlone) { 711 int month = calendar.get(Calendar.MONTH); 712 if (count <= 2) { 713 appendNumber(buffer, count, month + 1); 714 return; 715 } 716 717 String[] months; 718 LocaleData ld = formatData.localeData; 719 if (count == 4) { 720 months = standAlone ? ld.longStandAloneMonthNames : formatData.months; 721 } else if (count == 5) { 722 months = standAlone ? ld.tinyStandAloneMonthNames : ld.tinyMonthNames; 723 } else { 724 months = standAlone ? ld.shortStandAloneMonthNames : formatData.shortMonths; 725 } 726 buffer.append(months[month]); 727 } 728 729 /** 730 * Append a representation of the time zone of 'calendar' to 'buffer'. 731 * 732 * @param count the number of z or Z characters in the format string; "zzz" would be 3, 733 * for example. 734 * @param generalTimeZone true if we should use a display name ("PDT") if available; 735 * false implies that we should use RFC 822 format ("-0800") instead. This corresponds to 'z' 736 * versus 'Z' in the format string. 737 */ appendTimeZone(StringBuffer buffer, int count, boolean generalTimeZone)738 private void appendTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) { 739 if (generalTimeZone) { 740 TimeZone tz = calendar.getTimeZone(); 741 boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0); 742 int style = count < 4 ? TimeZone.SHORT : TimeZone.LONG; 743 String zoneString = formatData.getTimeZoneDisplayName(tz, daylight, style); 744 if (zoneString != null) { 745 buffer.append(zoneString); 746 return; 747 } 748 } 749 // We didn't find what we were looking for, so default to a numeric time zone. 750 appendNumericTimeZone(buffer, count, generalTimeZone); 751 } 752 753 // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts. 754 // @param generalTimeZone "GMT-08:00" rather than "-0800". 755 private void appendNumericTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) { 756 int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 757 boolean includeGmt = generalTimeZone || count == 4; 758 boolean includeMinuteSeparator = generalTimeZone || count >= 4; 759 buffer.append(TimeZone.createGmtOffsetString(includeGmt, includeMinuteSeparator, 760 offsetMillis)); 761 } 762 appendMilliseconds(StringBuffer buffer, int count, int value)763 private void appendMilliseconds(StringBuffer buffer, int count, int value) { 764 // Unlike other fields, milliseconds are truncated by count. So 361 formatted SS is "36". 765 numberFormat.setMinimumIntegerDigits((count > 3) ? 3 : count); 766 numberFormat.setMaximumIntegerDigits(10); 767 // We need to left-justify. 768 if (count == 1) { 769 value /= 100; 770 } else if (count == 2) { 771 value /= 10; 772 } 773 FieldPosition p = new FieldPosition(0); 774 numberFormat.format(Integer.valueOf(value), buffer, p); 775 if (count > 3) { 776 numberFormat.setMinimumIntegerDigits(count - 3); 777 numberFormat.format(Integer.valueOf(0), buffer, p); 778 } 779 } 780 appendNumber(StringBuffer buffer, int count, int value)781 private void appendNumber(StringBuffer buffer, int count, int value) { 782 // TODO: we could avoid using the NumberFormat in most cases for a significant speedup. 783 // The only problem is that we expose the NumberFormat to third-party code, so we'd have 784 // some work to do to work out when the optimization is valid. 785 int minimumIntegerDigits = numberFormat.getMinimumIntegerDigits(); 786 numberFormat.setMinimumIntegerDigits(count); 787 numberFormat.format(Integer.valueOf(value), buffer, new FieldPosition(0)); 788 numberFormat.setMinimumIntegerDigits(minimumIntegerDigits); 789 } 790 error(ParsePosition position, int offset, TimeZone zone)791 private Date error(ParsePosition position, int offset, TimeZone zone) { 792 position.setErrorIndex(offset); 793 calendar.setTimeZone(zone); 794 return null; 795 } 796 797 /** 798 * Formats the specified date as a string using the pattern of this date 799 * format and appends the string to the specified string buffer. 800 * <p> 801 * If the {@code field} member of {@code field} contains a value specifying 802 * a format field, then its {@code beginIndex} and {@code endIndex} members 803 * will be updated with the position of the first occurrence of this field 804 * in the formatted text. 805 * 806 * @param date 807 * the date to format. 808 * @param buffer 809 * the target string buffer to append the formatted date/time to. 810 * @param fieldPos 811 * on input: an optional alignment field; on output: the offsets 812 * of the alignment field in the formatted text. 813 * @return the string buffer. 814 * @throws IllegalArgumentException 815 * if there are invalid characters in the pattern. 816 */ 817 @Override format(Date date, StringBuffer buffer, FieldPosition fieldPos)818 public StringBuffer format(Date date, StringBuffer buffer, FieldPosition fieldPos) { 819 // Harmony delegates to ICU's SimpleDateFormat, we implement it directly 820 return formatImpl(date, buffer, fieldPos, null); 821 } 822 823 /** 824 * Returns the date which is the start of the one hundred year period for two-digit year values. 825 * See {@link #set2DigitYearStart} for details. 826 */ get2DigitYearStart()827 public Date get2DigitYearStart() { 828 return (Date) defaultCenturyStart.clone(); 829 } 830 831 /** 832 * Returns the {@code DateFormatSymbols} used by this simple date format. 833 * 834 * @return the {@code DateFormatSymbols} object. 835 */ getDateFormatSymbols()836 public DateFormatSymbols getDateFormatSymbols() { 837 return (DateFormatSymbols) formatData.clone(); 838 } 839 840 @Override hashCode()841 public int hashCode() { 842 return super.hashCode() + pattern.hashCode() + formatData.hashCode() + creationYear; 843 } 844 parse(String string, int offset, char format, int count)845 private int parse(String string, int offset, char format, int count) { 846 int index = PATTERN_CHARS.indexOf(format); 847 if (index == -1) { 848 throw new IllegalArgumentException("Unknown pattern character '" + format + "'"); 849 } 850 int field = -1; 851 // TODO: what's 'absolute' for? when is 'count' negative, and why? 852 int absolute = 0; 853 if (count < 0) { 854 count = -count; 855 absolute = count; 856 } 857 switch (index) { 858 case ERA_FIELD: 859 return parseText(string, offset, formatData.eras, Calendar.ERA); 860 case YEAR_FIELD: 861 if (count >= 3) { 862 field = Calendar.YEAR; 863 } else { 864 ParsePosition position = new ParsePosition(offset); 865 Number result = parseNumber(absolute, string, position); 866 if (result == null) { 867 return -position.getErrorIndex() - 1; 868 } 869 int year = result.intValue(); 870 // A two digit year must be exactly two digits, i.e. 01 871 if ((position.getIndex() - offset) == 2 && year >= 0) { 872 year += creationYear / 100 * 100; 873 if (year < creationYear) { 874 year += 100; 875 } 876 } 877 calendar.set(Calendar.YEAR, year); 878 return position.getIndex(); 879 } 880 break; 881 case STAND_ALONE_MONTH_FIELD: // 'L' 882 return parseMonth(string, offset, count, absolute, true); 883 case MONTH_FIELD: // 'M' 884 return parseMonth(string, offset, count, absolute, false); 885 case DATE_FIELD: 886 field = Calendar.DATE; 887 break; 888 case HOUR_OF_DAY1_FIELD: // 'k' 889 ParsePosition position = new ParsePosition(offset); 890 Number result = parseNumber(absolute, string, position); 891 if (result == null) { 892 return -position.getErrorIndex() - 1; 893 } 894 int hour = result.intValue(); 895 if (hour == 24) { 896 hour = 0; 897 } 898 calendar.set(Calendar.HOUR_OF_DAY, hour); 899 return position.getIndex(); 900 case HOUR_OF_DAY0_FIELD: // 'H' 901 field = Calendar.HOUR_OF_DAY; 902 break; 903 case MINUTE_FIELD: 904 field = Calendar.MINUTE; 905 break; 906 case SECOND_FIELD: 907 field = Calendar.SECOND; 908 break; 909 case MILLISECOND_FIELD: 910 return parseFractionalSeconds(string, offset, absolute); 911 case STAND_ALONE_DAY_OF_WEEK_FIELD: 912 return parseDayOfWeek(string, offset, true); 913 case DAY_OF_WEEK_FIELD: 914 return parseDayOfWeek(string, offset, false); 915 case DAY_OF_YEAR_FIELD: 916 field = Calendar.DAY_OF_YEAR; 917 break; 918 case DAY_OF_WEEK_IN_MONTH_FIELD: 919 field = Calendar.DAY_OF_WEEK_IN_MONTH; 920 break; 921 case WEEK_OF_YEAR_FIELD: 922 field = Calendar.WEEK_OF_YEAR; 923 break; 924 case WEEK_OF_MONTH_FIELD: 925 field = Calendar.WEEK_OF_MONTH; 926 break; 927 case AM_PM_FIELD: 928 return parseText(string, offset, formatData.ampms, Calendar.AM_PM); 929 case HOUR1_FIELD: // 'h' 930 position = new ParsePosition(offset); 931 result = parseNumber(absolute, string, position); 932 if (result == null) { 933 return -position.getErrorIndex() - 1; 934 } 935 hour = result.intValue(); 936 if (hour == 12) { 937 hour = 0; 938 } 939 calendar.set(Calendar.HOUR, hour); 940 return position.getIndex(); 941 case HOUR0_FIELD: // 'K' 942 field = Calendar.HOUR; 943 break; 944 case TIMEZONE_FIELD: // 'z' 945 return parseTimeZone(string, offset); 946 case RFC_822_TIMEZONE_FIELD: // 'Z' 947 return parseTimeZone(string, offset); 948 } 949 if (field != -1) { 950 return parseNumber(absolute, string, offset, field, 0); 951 } 952 return offset; 953 } 954 955 /** 956 * Parses the fractional seconds section of a formatted date and assigns 957 * it to the {@code Calendar.MILLISECOND} field. Note that fractional seconds 958 * are somewhat unique, because they are zero suffixed. 959 */ parseFractionalSeconds(String string, int offset, int count)960 private int parseFractionalSeconds(String string, int offset, int count) { 961 final ParsePosition parsePosition = new ParsePosition(offset); 962 final Number fractionalSeconds = parseNumber(count, string, parsePosition); 963 if (fractionalSeconds == null) { 964 return -parsePosition.getErrorIndex() - 1; 965 } 966 967 // NOTE: We could've done this using two parses instead. The first parse 968 // looking at |count| digits (to verify the date matched the format), and 969 // then a second parse that consumed just the first three digits. That 970 // would've avoided the floating point arithmetic, but would've demanded 971 // that we round values ourselves. 972 final double result = fractionalSeconds.doubleValue(); 973 final int numDigitsParsed = parsePosition.getIndex() - offset; 974 final double divisor = Math.pow(10, numDigitsParsed); 975 976 calendar.set(Calendar.MILLISECOND, (int) ((result / divisor) * 1000)); 977 return parsePosition.getIndex(); 978 } 979 parseDayOfWeek(String string, int offset, boolean standAlone)980 private int parseDayOfWeek(String string, int offset, boolean standAlone) { 981 LocaleData ld = formatData.localeData; 982 int index = parseText(string, offset, 983 standAlone ? ld.longStandAloneWeekdayNames : formatData.weekdays, 984 Calendar.DAY_OF_WEEK); 985 if (index < 0) { 986 index = parseText(string, offset, 987 standAlone ? ld.shortStandAloneWeekdayNames : formatData.shortWeekdays, 988 Calendar.DAY_OF_WEEK); 989 } 990 return index; 991 } 992 parseMonth(String string, int offset, int count, int absolute, boolean standAlone)993 private int parseMonth(String string, int offset, int count, int absolute, boolean standAlone) { 994 if (count <= 2) { 995 return parseNumber(absolute, string, offset, Calendar.MONTH, -1); 996 } 997 LocaleData ld = formatData.localeData; 998 int index = parseText(string, offset, 999 standAlone ? ld.longStandAloneMonthNames : formatData.months, 1000 Calendar.MONTH); 1001 if (index < 0) { 1002 index = parseText(string, offset, 1003 standAlone ? ld.shortStandAloneMonthNames : formatData.shortMonths, 1004 Calendar.MONTH); 1005 } 1006 return index; 1007 } 1008 1009 /** 1010 * Parses a date from the specified string starting at the index specified 1011 * by {@code position}. If the string is successfully parsed then the index 1012 * of the {@code ParsePosition} is updated to the index following the parsed 1013 * text. On error, the index is unchanged and the error index of {@code 1014 * ParsePosition} is set to the index where the error occurred. 1015 * 1016 * @param string 1017 * the string to parse using the pattern of this simple date 1018 * format. 1019 * @param position 1020 * input/output parameter, specifies the start index in {@code 1021 * string} from where to start parsing. If parsing is successful, 1022 * it is updated with the index following the parsed text; on 1023 * error, the index is unchanged and the error index is set to 1024 * the index where the error occurred. 1025 * @return the date resulting from the parse, or {@code null} if there is an 1026 * error. 1027 * @throws IllegalArgumentException 1028 * if there are invalid characters in the pattern. 1029 */ 1030 @Override parse(String string, ParsePosition position)1031 public Date parse(String string, ParsePosition position) { 1032 // Harmony delegates to ICU's SimpleDateFormat, we implement it directly 1033 boolean quote = false; 1034 int next, last = -1, count = 0, offset = position.getIndex(); 1035 int length = string.length(); 1036 calendar.clear(); 1037 TimeZone zone = calendar.getTimeZone(); 1038 final int patternLength = pattern.length(); 1039 for (int i = 0; i < patternLength; i++) { 1040 next = pattern.charAt(i); 1041 if (next == '\'') { 1042 if (count > 0) { 1043 if ((offset = parse(string, offset, (char) last, count)) < 0) { 1044 return error(position, -offset - 1, zone); 1045 } 1046 count = 0; 1047 } 1048 if (last == next) { 1049 if (offset >= length || string.charAt(offset) != '\'') { 1050 return error(position, offset, zone); 1051 } 1052 offset++; 1053 last = -1; 1054 } else { 1055 last = next; 1056 } 1057 quote = !quote; 1058 continue; 1059 } 1060 if (!quote 1061 && (last == next || (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { 1062 if (last == next) { 1063 count++; 1064 } else { 1065 if (count > 0) { 1066 if ((offset = parse(string, offset, (char) last, -count)) < 0) { 1067 return error(position, -offset - 1, zone); 1068 } 1069 } 1070 last = next; 1071 count = 1; 1072 } 1073 } else { 1074 if (count > 0) { 1075 if ((offset = parse(string, offset, (char) last, count)) < 0) { 1076 return error(position, -offset - 1, zone); 1077 } 1078 count = 0; 1079 } 1080 last = -1; 1081 if (offset >= length || string.charAt(offset) != next) { 1082 return error(position, offset, zone); 1083 } 1084 offset++; 1085 } 1086 } 1087 if (count > 0) { 1088 if ((offset = parse(string, offset, (char) last, count)) < 0) { 1089 return error(position, -offset - 1, zone); 1090 } 1091 } 1092 Date date; 1093 try { 1094 date = calendar.getTime(); 1095 } catch (IllegalArgumentException e) { 1096 return error(position, offset, zone); 1097 } 1098 position.setIndex(offset); 1099 calendar.setTimeZone(zone); 1100 return date; 1101 } 1102 parseNumber(int max, String string, ParsePosition position)1103 private Number parseNumber(int max, String string, ParsePosition position) { 1104 int length = string.length(); 1105 int index = position.getIndex(); 1106 if (max > 0 && max < length - index) { 1107 length = index + max; 1108 } 1109 while (index < length && (string.charAt(index) == ' ' || string.charAt(index) == '\t')) { 1110 ++index; 1111 } 1112 if (max == 0) { 1113 position.setIndex(index); 1114 return numberFormat.parse(string, position); 1115 } 1116 1117 int result = 0; 1118 int digit; 1119 while (index < length && (digit = Character.digit(string.charAt(index), 10)) != -1) { 1120 result = result * 10 + digit; 1121 ++index; 1122 } 1123 if (index == position.getIndex()) { 1124 position.setErrorIndex(index); 1125 return null; 1126 } 1127 position.setIndex(index); 1128 return Integer.valueOf(result); 1129 } 1130 parseNumber(int max, String string, int offset, int field, int skew)1131 private int parseNumber(int max, String string, int offset, int field, int skew) { 1132 ParsePosition position = new ParsePosition(offset); 1133 Number result = parseNumber(max, string, position); 1134 if (result == null) { 1135 return -position.getErrorIndex() - 1; 1136 } 1137 calendar.set(field, result.intValue() + skew); 1138 return position.getIndex(); 1139 } 1140 parseText(String string, int offset, String[] options, int field)1141 private int parseText(String string, int offset, String[] options, int field) { 1142 // We search for the longest match, in case some entries are substrings of others. 1143 int bestIndex = -1; 1144 int bestLength = -1; 1145 for (int i = 0; i < options.length; ++i) { 1146 String option = options[i]; 1147 int optionLength = option.length(); 1148 if (optionLength == 0) { 1149 continue; 1150 } 1151 if (string.regionMatches(true, offset, option, 0, optionLength)) { 1152 if (bestIndex == -1 || optionLength > bestLength) { 1153 bestIndex = i; 1154 bestLength = optionLength; 1155 } 1156 } else if (option.charAt(optionLength - 1) == '.') { 1157 // If CLDR has abbreviated forms like "Aug.", we should accept "Aug" too. 1158 // https://code.google.com/p/android/issues/detail?id=59383 1159 if (string.regionMatches(true, offset, option, 0, optionLength - 1)) { 1160 if (bestIndex == -1 || optionLength - 1 > bestLength) { 1161 bestIndex = i; 1162 bestLength = optionLength - 1; 1163 } 1164 } 1165 } 1166 } 1167 if (bestIndex != -1) { 1168 calendar.set(field, bestIndex); 1169 return offset + bestLength; 1170 } 1171 return -offset - 1; 1172 } 1173 parseTimeZone(String string, int offset)1174 private int parseTimeZone(String string, int offset) { 1175 boolean foundGMT = string.regionMatches(offset, "GMT", 0, 3); 1176 if (foundGMT) { 1177 offset += 3; 1178 } 1179 1180 // Check for an offset, which may have been preceded by "GMT" 1181 char sign; 1182 if (offset < string.length() && ((sign = string.charAt(offset)) == '+' || sign == '-')) { 1183 ParsePosition position = new ParsePosition(offset + 1); 1184 Number result = numberFormat.parse(string, position); 1185 if (result == null) { 1186 return -position.getErrorIndex() - 1; 1187 } 1188 int hour = result.intValue(); 1189 int raw = hour * 3600000; 1190 int index = position.getIndex(); 1191 if (index < string.length() && string.charAt(index) == ':') { 1192 position.setIndex(index + 1); 1193 result = numberFormat.parse(string, position); 1194 if (result == null) { 1195 return -position.getErrorIndex() - 1; 1196 } 1197 int minute = result.intValue(); 1198 raw += minute * 60000; 1199 } else if (hour >= 24) { 1200 raw = (hour / 100 * 3600000) + (hour % 100 * 60000); 1201 } 1202 if (sign == '-') { 1203 raw = -raw; 1204 } 1205 calendar.setTimeZone(new SimpleTimeZone(raw, "")); 1206 return position.getIndex(); 1207 } 1208 1209 // If there was "GMT" but no offset. 1210 if (foundGMT) { 1211 calendar.setTimeZone(TimeZone.getTimeZone("GMT")); 1212 return offset; 1213 } 1214 1215 // Exhaustively look for the string in this DateFormat's localized time zone strings. 1216 for (String[] row : formatData.internalZoneStrings()) { 1217 for (int i = TimeZoneNames.LONG_NAME; i < TimeZoneNames.NAME_COUNT; ++i) { 1218 if (row[i] == null) { 1219 // If icu4c doesn't have a name, our array contains a null. Normally we'd 1220 // work out the correct GMT offset, but we already handled parsing GMT offsets 1221 // above, so we can just ignore these cases. http://b/8128460. 1222 continue; 1223 } 1224 if (string.regionMatches(true, offset, row[i], 0, row[i].length())) { 1225 TimeZone zone = TimeZone.getTimeZone(row[TimeZoneNames.OLSON_NAME]); 1226 if (zone == null) { 1227 return -offset - 1; 1228 } 1229 int raw = zone.getRawOffset(); 1230 if (i == TimeZoneNames.LONG_NAME_DST || i == TimeZoneNames.SHORT_NAME_DST) { 1231 // Not all time zones use a one-hour difference, so we need to query 1232 // the TimeZone. (Australia/Lord_Howe is the usual example of this.) 1233 int dstSavings = zone.getDSTSavings(); 1234 // One problem with TimeZone.getDSTSavings is that it will return 0 if the 1235 // time zone has stopped using DST, even if we're parsing a date from 1236 // the past. In that case, assume the default. 1237 if (dstSavings == 0) { 1238 // TODO: we should change this to use TimeZone.getOffset(long), 1239 // but that requires the complete date to be parsed first. 1240 dstSavings = 3600000; 1241 } 1242 raw += dstSavings; 1243 } 1244 calendar.setTimeZone(new SimpleTimeZone(raw, "")); 1245 return offset + row[i].length(); 1246 } 1247 } 1248 } 1249 return -offset - 1; 1250 } 1251 1252 /** 1253 * Sets the date which is the start of the one hundred year period for two-digit year values. 1254 * 1255 * <p>When parsing a date string using the abbreviated year pattern {@code yy}, {@code 1256 * SimpleDateFormat} must interpret the abbreviated year relative to some 1257 * century. It does this by adjusting dates to be within 80 years before and 20 1258 * years after the time the {@code SimpleDateFormat} instance was created. For 1259 * example, using a pattern of {@code MM/dd/yy}, an 1260 * instance created on Jan 1, 1997 would interpret the string {@code "01/11/12"} 1261 * as Jan 11, 2012 but interpret the string {@code "05/04/64"} as May 4, 1964. 1262 * During parsing, only strings consisting of exactly two digits, as 1263 * defined by {@link java.lang.Character#isDigit(char)}, will be parsed into the 1264 * default century. Any other numeric string, such as a one digit string, a 1265 * three or more digit string, or a two digit string that isn't all digits (for 1266 * example, {@code "-1"}), is interpreted literally. So using the same pattern, both 1267 * {@code "01/02/3"} and {@code "01/02/003"} are parsed as Jan 2, 3 AD. 1268 * Similarly, {@code "01/02/-3"} is parsed as Jan 2, 4 BC. 1269 * 1270 * <p>If the year pattern does not have exactly two 'y' characters, the year is 1271 * interpreted literally, regardless of the number of digits. So using the 1272 * pattern {@code MM/dd/yyyy}, {@code "01/11/12"} is parsed as Jan 11, 12 A.D. 1273 */ set2DigitYearStart(Date date)1274 public void set2DigitYearStart(Date date) { 1275 defaultCenturyStart = (Date) date.clone(); 1276 Calendar cal = new GregorianCalendar(); 1277 cal.setTime(defaultCenturyStart); 1278 creationYear = cal.get(Calendar.YEAR); 1279 } 1280 1281 /** 1282 * Sets the {@code DateFormatSymbols} used by this simple date format. 1283 * 1284 * @param value 1285 * the new {@code DateFormatSymbols} object. 1286 */ setDateFormatSymbols(DateFormatSymbols value)1287 public void setDateFormatSymbols(DateFormatSymbols value) { 1288 formatData = (DateFormatSymbols) value.clone(); 1289 } 1290 1291 /** 1292 * Returns the pattern of this simple date format using localized pattern 1293 * characters. 1294 * 1295 * @return the localized pattern. 1296 */ toLocalizedPattern()1297 public String toLocalizedPattern() { 1298 return convertPattern(pattern, PATTERN_CHARS, formatData.getLocalPatternChars(), false); 1299 } 1300 convertPattern(String template, String fromChars, String toChars, boolean check)1301 private static String convertPattern(String template, String fromChars, String toChars, boolean check) { 1302 if (!check && fromChars.equals(toChars)) { 1303 return template; 1304 } 1305 boolean quote = false; 1306 StringBuilder output = new StringBuilder(); 1307 int length = template.length(); 1308 for (int i = 0; i < length; i++) { 1309 int index; 1310 char next = template.charAt(i); 1311 if (next == '\'') { 1312 quote = !quote; 1313 } 1314 if (!quote && (index = fromChars.indexOf(next)) != -1) { 1315 output.append(toChars.charAt(index)); 1316 } else if (check && !quote && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { 1317 throw new IllegalArgumentException("Invalid pattern character '" + next + "' in " + "'" + template + "'"); 1318 } else { 1319 output.append(next); 1320 } 1321 } 1322 if (quote) { 1323 throw new IllegalArgumentException("Unterminated quote"); 1324 } 1325 return output.toString(); 1326 } 1327 1328 /** 1329 * Returns the pattern of this simple date format using non-localized 1330 * pattern characters. 1331 * 1332 * @return the non-localized pattern. 1333 */ toPattern()1334 public String toPattern() { 1335 return pattern; 1336 } 1337 1338 private static final ObjectStreamField[] serialPersistentFields = { 1339 new ObjectStreamField("defaultCenturyStart", Date.class), 1340 new ObjectStreamField("formatData", DateFormatSymbols.class), 1341 new ObjectStreamField("pattern", String.class), 1342 new ObjectStreamField("serialVersionOnStream", int.class), 1343 }; 1344 writeObject(ObjectOutputStream stream)1345 private void writeObject(ObjectOutputStream stream) throws IOException { 1346 ObjectOutputStream.PutField fields = stream.putFields(); 1347 fields.put("defaultCenturyStart", defaultCenturyStart); 1348 fields.put("formatData", formatData); 1349 fields.put("pattern", pattern); 1350 fields.put("serialVersionOnStream", 1); 1351 stream.writeFields(); 1352 } 1353 readObject(ObjectInputStream stream)1354 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 1355 ObjectInputStream.GetField fields = stream.readFields(); 1356 int version = fields.get("serialVersionOnStream", 0); 1357 Date date; 1358 if (version > 0) { 1359 date = (Date) fields.get("defaultCenturyStart", new Date()); 1360 } else { 1361 date = new Date(); 1362 } 1363 set2DigitYearStart(date); 1364 formatData = (DateFormatSymbols) fields.get("formatData", null); 1365 pattern = (String) fields.get("pattern", ""); 1366 } 1367 } 1368