001/* 002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos 003 * 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or without 007 * modification, are permitted provided that the following conditions are met: 008 * 009 * * Redistributions of source code must retain the above copyright notice, 010 * this list of conditions and the following disclaimer. 011 * 012 * * Redistributions in binary form must reproduce the above copyright notice, 013 * this list of conditions and the following disclaimer in the documentation 014 * and/or other materials provided with the distribution. 015 * 016 * * Neither the name of JSR-310 nor the names of its contributors 017 * may be used to endorse or promote products derived from this software 018 * without specific prior written permission. 019 * 020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 031 */ 032package org.threeten.bp.format; 033 034import java.io.IOException; 035import java.text.FieldPosition; 036import java.text.Format; 037import java.text.ParseException; 038import java.text.ParsePosition; 039import java.util.Arrays; 040import java.util.Locale; 041import java.util.Objects; 042 043import org.threeten.bp.DateTimeException; 044import org.threeten.bp.ZoneId; 045import org.threeten.bp.format.DateTimeFormatterBuilder.CompositePrinterParser; 046import org.threeten.bp.temporal.Chrono; 047import org.threeten.bp.temporal.ChronoField; 048import org.threeten.bp.temporal.TemporalAccessor; 049 050/** 051 * Formatter for printing and parsing date-time objects. 052 * <p> 053 * This class provides the main application entry point for printing and parsing. 054 * Common instances of {@code DateTimeFormatter} are provided by {@link DateTimeFormatters}. 055 * For more complex formatters, a {@link DateTimeFormatterBuilder builder} is provided. 056 * <p> 057 * In most cases, it is not necessary to use this class directly when formatting. 058 * The main date-time classes provide two methods - one for printing, 059 * {@code toString(DateTimeFormatter formatter)}, and one for parsing, 060 * {@code parse(CharSequence text, DateTimeFormatter formatter)}. 061 * For example: 062 * <pre> 063 * String text = date.toString(formatter); 064 * LocalDate date = LocalDate.parse(text, formatter); 065 * </pre> 066 * Some aspects of printing and parsing are dependent on the locale. 067 * The locale can be changed using the {@link #withLocale(Locale)} method 068 * which returns a new formatter in the requested locale. 069 * <p> 070 * Some applications may need to use the older {@link Format} class for formatting. 071 * The {@link #toFormat()} method returns an implementation of the old API. 072 * 073 * <h3>Specification for implementors</h3> 074 * This class is immutable and thread-safe. 075 */ 076public final class DateTimeFormatter { 077 078 /** 079 * The printer and/or parser to use, not null. 080 */ 081 private final CompositePrinterParser printerParser; 082 /** 083 * The locale to use for formatting, not null. 084 */ 085 private final Locale locale; 086 /** 087 * The symbols to use for formatting, not null. 088 */ 089 private final DateTimeFormatSymbols symbols; 090 /** 091 * The chronology to use for formatting, null for no override. 092 */ 093 private final Chrono<?> chrono; 094 /** 095 * The zone to use for formatting, null for no override. 096 */ 097 private final ZoneId zone; 098 099 /** 100 * Constructor. 101 * 102 * @param printerParser the printer/parser to use, not null 103 * @param locale the locale to use, not null 104 * @param symbols the symbols to use, not null 105 * @param chrono the chronology to use, null for no override 106 * @param zone the zone to use, null for no override 107 */ 108 DateTimeFormatter(CompositePrinterParser printerParser, Locale locale, 109 DateTimeFormatSymbols symbols, Chrono<?> chrono, ZoneId zone) { 110 this.printerParser = Objects.requireNonNull(printerParser, "printerParser"); 111 this.locale = Objects.requireNonNull(locale, "locale"); 112 this.symbols = Objects.requireNonNull(symbols, "symbols"); 113 this.chrono = chrono; 114 this.zone = zone; 115 } 116 117 //----------------------------------------------------------------------- 118 /** 119 * Gets the locale to be used during formatting. 120 * <p> 121 * This is used to lookup any part of the formatter needing specific 122 * localization, such as the text or localized pattern. 123 * 124 * @return the locale of this formatter, not null 125 */ 126 public Locale getLocale() { 127 return locale; 128 } 129 130 /** 131 * Returns a copy of this formatter with a new locale. 132 * <p> 133 * This is used to lookup any part of the formatter needing specific 134 * localization, such as the text or localized pattern. 135 * <p> 136 * This instance is immutable and unaffected by this method call. 137 * 138 * @param locale the new locale, not null 139 * @return a formatter based on this formatter with the requested locale, not null 140 */ 141 public DateTimeFormatter withLocale(Locale locale) { 142 if (this.locale.equals(locale)) { 143 return this; 144 } 145 return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); 146 } 147 148 //----------------------------------------------------------------------- 149 /** 150 * Gets the set of symbols to be used during formatting. 151 * 152 * @return the locale of this formatter, not null 153 */ 154 public DateTimeFormatSymbols getSymbols() { 155 return symbols; 156 } 157 158 /** 159 * Returns a copy of this formatter with a new set of symbols. 160 * <p> 161 * This instance is immutable and unaffected by this method call. 162 * 163 * @param symbols the new symbols, not null 164 * @return a formatter based on this formatter with the requested symbols, not null 165 */ 166 public DateTimeFormatter withSymbols(DateTimeFormatSymbols symbols) { 167 if (this.symbols.equals(symbols)) { 168 return this; 169 } 170 return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); 171 } 172 173 //----------------------------------------------------------------------- 174 /** 175 * Gets the overriding chronology to be used during formatting. 176 * <p> 177 * This returns the override chronology, used to convert dates. 178 * By default, a formatter has no override chronology, returning null. 179 * See {@link #withChrono(Chrono)} for more details on overriding. 180 * 181 * @return the chronology of this formatter, null if no override 182 */ 183 public Chrono<?> getChrono() { 184 return chrono; 185 } 186 187 /** 188 * Returns a copy of this formatter with a new override chronology. 189 * <p> 190 * This returns a formatter with similar state to this formatter but 191 * with the override chronology set. 192 * By default, a formatter has no override chronology, returning null. 193 * <p> 194 * If an override is added, then any date that is printed or parsed will be affected. 195 * <p> 196 * When printing, if the {@code Temporal} object contains a date then it will 197 * be converted to a date in the override chronology. 198 * Any time or zone will be retained unless overridden. 199 * The converted result will behave in a manner equivalent to an implementation 200 * of {@code ChronoLocalDate},{@code ChronoLocalDateTime} or {@code ChronoZonedDateTime}. 201 * <p> 202 * When parsing, the override chronology will be used to interpret the 203 * {@linkplain ChronoField fields} into a date unless the 204 * formatter directly parses a valid chronology. 205 * <p> 206 * This instance is immutable and unaffected by this method call. 207 * 208 * @param chrono the new chronology, not null 209 * @return a formatter based on this formatter with the requested override chronology, not null 210 */ 211 public DateTimeFormatter withChrono(Chrono<?> chrono) { 212 if (Objects.equals(this.chrono, chrono)) { 213 return this; 214 } 215 return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); 216 } 217 218 //----------------------------------------------------------------------- 219 /** 220 * Gets the overriding zone to be used during formatting. 221 * <p> 222 * This returns the override zone, used to convert instants. 223 * By default, a formatter has no override zone, returning null. 224 * See {@link #withZone(ZoneId)} for more details on overriding. 225 * 226 * @return the chronology of this formatter, null if no override 227 */ 228 public ZoneId getZone() { 229 return zone; 230 } 231 232 /** 233 * Returns a copy of this formatter with a new override zone. 234 * <p> 235 * This returns a formatter with similar state to this formatter but 236 * with the override zone set. 237 * By default, a formatter has no override zone, returning null. 238 * <p> 239 * If an override is added, then any instant that is printed or parsed will be affected. 240 * <p> 241 * When printing, if the {@code Temporal} object contains an instant then it will 242 * be converted to a zoned date-time using the override zone. 243 * If the input has a chronology then it will be retained unless overridden. 244 * If the input does not have a chronology, such as {@code Instant}, then 245 * the ISO chronology will be used. 246 * The converted result will behave in a manner equivalent to an implementation 247 * of {@code ChronoZonedDateTime}. 248 * <p> 249 * When parsing, the override zone will be used to interpret the 250 * {@linkplain ChronoField fields} into an instant unless the 251 * formatter directly parses a valid zone. 252 * <p> 253 * This instance is immutable and unaffected by this method call. 254 * 255 * @param zone the new override zone, not null 256 * @return a formatter based on this formatter with the requested override zone, not null 257 */ 258 public DateTimeFormatter withZone(ZoneId zone) { 259 if (Objects.equals(this.zone, zone)) { 260 return this; 261 } 262 return new DateTimeFormatter(printerParser, locale, symbols, chrono, zone); 263 } 264 265 //----------------------------------------------------------------------- 266 /** 267 * Prints a date-time object using this formatter. 268 * <p> 269 * This prints the date-time to a String using the rules of the formatter. 270 * 271 * @param temporal the temporal object to print, not null 272 * @return the printed string, not null 273 * @throws DateTimeException if an error occurs during printing 274 */ 275 public String print(TemporalAccessor temporal) { 276 StringBuilder buf = new StringBuilder(32); 277 printTo(temporal, buf); 278 return buf.toString(); 279 } 280 281 //----------------------------------------------------------------------- 282 /** 283 * Prints a date-time object to an {@code Appendable} using this formatter. 284 * <p> 285 * This prints the date-time to the specified destination. 286 * {@link Appendable} is a general purpose interface that is implemented by all 287 * key character output classes including {@code StringBuffer}, {@code StringBuilder}, 288 * {@code PrintStream} and {@code Writer}. 289 * <p> 290 * Although {@code Appendable} methods throw an {@code IOException}, this method does not. 291 * Instead, any {@code IOException} is wrapped in a runtime exception. 292 * See {@link DateTimePrintException#rethrowIOException()} for a means 293 * to extract the {@code IOException}. 294 * 295 * @param temporal the temporal object to print, not null 296 * @param appendable the appendable to print to, not null 297 * @throws DateTimeException if an error occurs during printing 298 */ 299 public void printTo(TemporalAccessor temporal, Appendable appendable) { 300 Objects.requireNonNull(temporal, "temporal"); 301 Objects.requireNonNull(appendable, "appendable"); 302 try { 303 DateTimePrintContext context = new DateTimePrintContext(temporal, this); 304 if (appendable instanceof StringBuilder) { 305 printerParser.print(context, (StringBuilder) appendable); 306 } else { 307 // buffer output to avoid writing to appendable in case of error 308 StringBuilder buf = new StringBuilder(32); 309 printerParser.print(context, buf); 310 appendable.append(buf); 311 } 312 } catch (IOException ex) { 313 throw new DateTimePrintException(ex.getMessage(), ex); 314 } 315 } 316 317 //----------------------------------------------------------------------- 318 /** 319 * Fully parses the text producing an object of the specified type. 320 * <p> 321 * Most applications should use this method for parsing. 322 * It parses the entire text to produce the required date-time. 323 * For example: 324 * <pre> 325 * LocalDateTime dt = parser.parse(str, LocalDateTime.class); 326 * </pre> 327 * If the parse completes without reading the entire length of the text, 328 * or a problem occurs during parsing or merging, then an exception is thrown. 329 * 330 * @param <T> the type to extract 331 * @param text the text to parse, not null 332 * @param type the type to extract, not null 333 * @return the parsed date-time, not null 334 * @throws DateTimeParseException if the parse fails 335 */ 336 public <T> T parse(CharSequence text, Class<T> type) { 337 Objects.requireNonNull(text, "text"); 338 Objects.requireNonNull(type, "type"); 339 String str = text.toString(); // parsing whole String, so this makes sense 340 try { 341 DateTimeBuilder builder = parseToBuilder(str).resolve(); 342 return builder.build(type); 343 } catch (DateTimeParseException ex) { 344 throw ex; 345 } catch (RuntimeException ex) { 346 throw createError(str, ex); 347 } 348 } 349 350 /** 351 * Fully parses the text producing an object of one of the specified types. 352 * <p> 353 * This parse method is convenient for use when the parser can handle optional elements. 354 * For example, a pattern of 'yyyy-MM[-dd[Z]]' can be fully parsed to an {@code OffsetDate}, 355 * or partially parsed to a {@code LocalDate} or a {@code YearMonth}. 356 * The types must be specified in order, starting from the best matching full-parse option 357 * and ending with the worst matching minimal parse option. 358 * <p> 359 * The result is associated with the first type that successfully parses. 360 * Normally, applications will use {@code instanceof} to check the result. 361 * For example: 362 * <pre> 363 * TemporalAccessor dt = parser.parseBest(str, OffsetDate.class, LocalDate.class); 364 * if (dt instanceof OffsetDate) { 365 * ... 366 * } else { 367 * ... 368 * } 369 * </pre> 370 * If the parse completes without reading the entire length of the text, 371 * or a problem occurs during parsing or merging, then an exception is thrown. 372 * 373 * @param text the text to parse, not null 374 * @param types the types to attempt to parse to, which must implement {@code TemporalAccessor}, not null 375 * @return the parsed date-time, not null 376 * @throws IllegalArgumentException if less than 2 types are specified 377 * @throws DateTimeParseException if the parse fails 378 */ 379 public TemporalAccessor parseBest(CharSequence text, Class<?>... types) { 380 Objects.requireNonNull(text, "text"); 381 Objects.requireNonNull(types, "types"); 382 if (types.length < 2) { 383 throw new IllegalArgumentException("At least two types must be specified"); 384 } 385 String str = text.toString(); // parsing whole String, so this makes sense 386 try { 387 DateTimeBuilder builder = parseToBuilder(str).resolve(); 388 for (Class<?> type : types) { 389 try { 390 return (TemporalAccessor) builder.build(type); 391 } catch (RuntimeException ex) { 392 // continue 393 } 394 } 395 throw new DateTimeException("Unable to convert parsed text to any specified type: " + Arrays.toString(types)); 396 } catch (DateTimeParseException ex) { 397 throw ex; 398 } catch (RuntimeException ex) { 399 throw createError(str, ex); 400 } 401 } 402 403 private DateTimeParseException createError(String str, RuntimeException ex) { 404 String abbr = str; 405 if (abbr.length() > 64) { 406 abbr = abbr.substring(0, 64) + "..."; 407 } 408 return new DateTimeParseException("Text '" + abbr + "' could not be parsed: " + ex.getMessage(), str, 0, ex); 409 } 410 411 //----------------------------------------------------------------------- 412 /** 413 * Parses the text to a builder. 414 * <p> 415 * This parses to a {@code DateTimeBuilder} ensuring that the text is fully parsed. 416 * This method throws {@link DateTimeParseException} if unable to parse, or 417 * some other {@code DateTimeException} if another date/time problem occurs. 418 * 419 * @param text the text to parse, not null 420 * @return the engine representing the result of the parse, not null 421 * @throws DateTimeParseException if the parse fails 422 */ 423 public DateTimeBuilder parseToBuilder(CharSequence text) { 424 Objects.requireNonNull(text, "text"); 425 String str = text.toString(); // parsing whole String, so this makes sense 426 ParsePosition pos = new ParsePosition(0); 427 DateTimeBuilder result = parseToBuilder(str, pos); 428 if (result == null || pos.getErrorIndex() >= 0 || pos.getIndex() < str.length()) { 429 String abbr = str; 430 if (abbr.length() > 64) { 431 abbr = abbr.substring(0, 64) + "..."; 432 } 433 if (pos.getErrorIndex() >= 0) { 434 throw new DateTimeParseException("Text '" + abbr + "' could not be parsed at index " + 435 pos.getErrorIndex(), str, pos.getErrorIndex()); 436 } else { 437 throw new DateTimeParseException("Text '" + abbr + "' could not be parsed, unparsed text found at index " + 438 pos.getIndex(), str, pos.getIndex()); 439 } 440 } 441 return result; 442 } 443 444 /** 445 * Parses the text to a builder. 446 * <p> 447 * This parses to a {@code DateTimeBuilder} but does not require the input to be fully parsed. 448 * <p> 449 * This method does not throw {@link DateTimeParseException}. 450 * Instead, errors are returned within the state of the specified parse position. 451 * Callers must check for errors before using the context. 452 * <p> 453 * This method may throw some other {@code DateTimeException} if a date/time problem occurs. 454 * 455 * @param text the text to parse, not null 456 * @param position the position to parse from, updated with length parsed 457 * and the index of any error, not null 458 * @return the parsed text, null only if the parse results in an error 459 * @throws IndexOutOfBoundsException if the position is invalid 460 */ 461 public DateTimeBuilder parseToBuilder(CharSequence text, ParsePosition position) { 462 Objects.requireNonNull(text, "text"); 463 Objects.requireNonNull(position, "position"); 464 DateTimeParseContext context = new DateTimeParseContext(this); 465 int pos = position.getIndex(); 466 pos = printerParser.parse(context, text, pos); 467 if (pos < 0) { 468 position.setErrorIndex(~pos); 469 return null; 470 } 471 position.setIndex(pos); 472 return context.toBuilder(); 473 } 474 475 //----------------------------------------------------------------------- 476 /** 477 * Returns the formatter as a composite printer parser. 478 * 479 * @param optional whether the printer/parser should be optional 480 * @return the printer/parser, not null 481 */ 482 CompositePrinterParser toPrinterParser(boolean optional) { 483 return printerParser.withOptional(optional); 484 } 485 486 /** 487 * Returns this formatter as a {@code java.text.Format} instance. 488 * <p> 489 * The returned {@link Format} instance will print any {@link TemporalAccessor} 490 * and parses to a resolved {@link DateTimeBuilder}. 491 * <p> 492 * Exceptions will follow the definitions of {@code Format}, see those methods 493 * for details about {@code IllegalArgumentException} during formatting and 494 * {@code ParseException} or null during parsing. 495 * The format does not support attributing of the returned format string. 496 * 497 * @return this formatter as a classic format instance, not null 498 */ 499 public Format toFormat() { 500 return new ClassicFormat(this, null); 501 } 502 503 /** 504 * Returns this formatter as a {@code java.text.Format} instance that will 505 * parse to the specified type. 506 * <p> 507 * The returned {@link Format} instance will print any {@link TemporalAccessor} 508 * and parses to the type specified. 509 * The type must be one that is supported by {@link #parse}. 510 * <p> 511 * Exceptions will follow the definitions of {@code Format}, see those methods 512 * for details about {@code IllegalArgumentException} during formatting and 513 * {@code ParseException} or null during parsing. 514 * The format does not support attributing of the returned format string. 515 * 516 * @param parseType the type to parse to, not null 517 * @return this formatter as a classic format instance, not null 518 */ 519 public Format toFormat(Class<?> parseType) { 520 Objects.requireNonNull(parseType, "parseType"); 521 return new ClassicFormat(this, parseType); 522 } 523 524 //----------------------------------------------------------------------- 525 /** 526 * Returns a description of the underlying formatters. 527 * 528 * @return a description of this formatter, not null 529 */ 530 @Override 531 public String toString() { 532 String pattern = printerParser.toString(); 533 return pattern.startsWith("[") ? pattern : pattern.substring(1, pattern.length() - 1); 534 } 535 536 //----------------------------------------------------------------------- 537 /** 538 * Implements the classic Java Format API. 539 * @serial exclude 540 */ 541 @SuppressWarnings("serial") // not actually serializable 542 static class ClassicFormat extends Format { 543 /** The formatter. */ 544 private final DateTimeFormatter formatter; 545 /** The type to be parsed. */ 546 private final Class<?> parseType; 547 /** Constructor. */ 548 public ClassicFormat(DateTimeFormatter formatter, Class<?> parseType) { 549 this.formatter = formatter; 550 this.parseType = parseType; 551 } 552 553 @Override 554 public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { 555 Objects.requireNonNull(obj, "obj"); 556 Objects.requireNonNull(toAppendTo, "toAppendTo"); 557 Objects.requireNonNull(pos, "pos"); 558 if (obj instanceof TemporalAccessor == false) { 559 throw new IllegalArgumentException("Format target must implement TemporalAccessor"); 560 } 561 pos.setBeginIndex(0); 562 pos.setEndIndex(0); 563 try { 564 formatter.printTo((TemporalAccessor) obj, toAppendTo); 565 } catch (RuntimeException ex) { 566 throw new IllegalArgumentException(ex.getMessage(), ex); 567 } 568 return toAppendTo; 569 } 570 @Override 571 public Object parseObject(String text) throws ParseException { 572 Objects.requireNonNull(text, "text"); 573 try { 574 if (parseType != null) { 575 return formatter.parse(text, parseType); 576 } 577 return formatter.parseToBuilder(text); 578 } catch (DateTimeParseException ex) { 579 throw new ParseException(ex.getMessage(), ex.getErrorIndex()); 580 } catch (RuntimeException ex) { 581 throw (ParseException) new ParseException(ex.getMessage(), 0).initCause(ex); 582 } 583 } 584 @Override 585 public Object parseObject(String text, ParsePosition pos) { 586 Objects.requireNonNull(text, "text"); 587 DateTimeBuilder builder; 588 try { 589 builder = formatter.parseToBuilder(text, pos); 590 } catch (IndexOutOfBoundsException ex) { 591 if (pos.getErrorIndex() < 0) { 592 pos.setErrorIndex(0); 593 } 594 return null; 595 } 596 if (builder == null) { 597 if (pos.getErrorIndex() < 0) { 598 pos.setErrorIndex(0); 599 } 600 return null; 601 } 602 if (parseType == null) { 603 return builder; 604 } 605 try { 606 return builder.resolve().build(parseType); 607 } catch (RuntimeException ex) { 608 pos.setErrorIndex(0); 609 return null; 610 } 611 } 612 } 613 614}