/* * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * This file is available under and governed by the GNU General Public * License version 2 only, as published by the Free Software Foundation. * However, the following notice accompanied the original version of this * file: * * Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of JSR-310 nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package java.time.format; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.DAY_OF_WEEK; import static java.time.temporal.ChronoField.DAY_OF_YEAR; import static java.time.temporal.ChronoField.HOUR_OF_DAY; import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; import static java.time.temporal.ChronoField.MONTH_OF_YEAR; import static java.time.temporal.ChronoField.NANO_OF_SECOND; import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; import static java.time.temporal.ChronoField.YEAR; import java.io.IOException; import java.text.FieldPosition; import java.text.Format; import java.text.ParseException; import java.text.ParsePosition; import java.time.DateTimeException; import java.time.Period; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatterBuilder.CompositePrinterParser; import java.time.temporal.ChronoField; import java.time.temporal.IsoFields; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.TemporalQuery; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; /** * Formatter for printing and parsing date-time objects. *
* This class provides the main application entry point for printing and parsing * and provides common implementations of {@code DateTimeFormatter}: *
* More complex formatters are provided by * {@link DateTimeFormatterBuilder DateTimeFormatterBuilder}. * *
* The main date-time classes provide two methods - one for formatting, * {@code format(DateTimeFormatter formatter)}, and one for parsing, * {@code parse(CharSequence text, DateTimeFormatter formatter)}. *
For example: *
** LocalDate date = LocalDate.now(); * String text = date.format(formatter); * LocalDate parsedDate = LocalDate.parse(text, formatter); *
* In addition to the format, formatters can be created with desired Locale, * Chronology, ZoneId, and DecimalStyle. *
* The {@link #withLocale withLocale} method returns a new formatter that * overrides the locale. The locale affects some aspects of formatting and * parsing. For example, the {@link #ofLocalizedDate ofLocalizedDate} provides a * formatter that uses the locale specific date format. *
* The {@link #withChronology withChronology} method returns a new formatter * that overrides the chronology. If overridden, the date-time value is * converted to the chronology before formatting. During parsing the date-time * value is converted to the chronology before it is returned. *
* The {@link #withZone withZone} method returns a new formatter that overrides * the zone. If overridden, the date-time value is converted to a ZonedDateTime * with the requested ZoneId before formatting. During parsing the ZoneId is * applied before the value is returned. *
* The {@link #withDecimalStyle withDecimalStyle} method returns a new formatter that * overrides the {@link DecimalStyle}. The DecimalStyle symbols are used for * formatting and parsing. *
* Some applications may need to use the older {@link Format java.text.Format} * class for formatting. The {@link #toFormat()} method returns an * implementation of {@code java.text.Format}. * *
Formatter | *Description | *Example | *
---|---|---|
{@link #ofLocalizedDate ofLocalizedDate(dateStyle)} | *Formatter with date style from the locale | *'2011-12-03' | *
{@link #ofLocalizedTime ofLocalizedTime(timeStyle)} | *Formatter with time style from the locale | *'10:15:30' | *
{@link #ofLocalizedDateTime ofLocalizedDateTime(dateTimeStyle)} | *Formatter with a style for date and time from the locale | *'3 Jun 2008 11:05:30' | *
{@link #ofLocalizedDateTime ofLocalizedDateTime(dateStyle,timeStyle)} * | *Formatter with date and time styles from the locale | *'3 Jun 2008 11:05' | *
{@link #BASIC_ISO_DATE} | *Basic ISO date | '20111203' | *
{@link #ISO_LOCAL_DATE} | *ISO Local Date | *'2011-12-03' | *
{@link #ISO_OFFSET_DATE} | *ISO Date with offset | *'2011-12-03+01:00' | *
{@link #ISO_DATE} | *ISO Date with or without offset | *'2011-12-03+01:00'; '2011-12-03' | *
{@link #ISO_LOCAL_TIME} | *Time without offset | *'10:15:30' | *
{@link #ISO_OFFSET_TIME} | *Time with offset | *'10:15:30+01:00' | *
{@link #ISO_TIME} | *Time with or without offset | *'10:15:30+01:00'; '10:15:30' | *
{@link #ISO_LOCAL_DATE_TIME} | *ISO Local Date and Time | *'2011-12-03T10:15:30' | *
{@link #ISO_OFFSET_DATE_TIME} | *Date Time with Offset * | 2011-12-03T10:15:30+01:00' | *
{@link #ISO_ZONED_DATE_TIME} | *Zoned Date Time | *'2011-12-03T10:15:30+01:00[Europe/Paris]' | *
{@link #ISO_DATE_TIME} | *Date and time with ZoneId | *'2011-12-03T10:15:30+01:00[Europe/Paris]' | *
{@link #ISO_ORDINAL_DATE} | *Year and day of year | *'2012-337' | *
{@link #ISO_WEEK_DATE} | *Year and Week | *2012-W48-6' |
{@link #ISO_INSTANT} | *Date and Time of an Instant | *'2011-12-03T10:15:30Z' | *
{@link #RFC_1123_DATE_TIME} | *RFC 1123 / RFC 822 | *'Tue, 3 Jun 2008 11:05:30 GMT' | *
* For example: *
** LocalDate date = LocalDate.now(); * DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd"); * String text = date.format(formatter); * LocalDate parsedDate = LocalDate.parse(text, formatter); *
* All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. The * following pattern letters are defined: *
* Symbol Meaning Presentation Examples * ------ ------- ------------ ------- * G era text AD; Anno Domini; A * u year year 2004; 04 * y year-of-era year 2004; 04 * D day-of-year number 189 * M/L month-of-year number/text 7; 07; Jul; July; J * d day-of-month number 10 * * Q/q quarter-of-year number/text 3; 03; Q3; 3rd quarter * Y week-based-year year 1996; 96 * w week-of-week-based-year number 27 * W week-of-month number 4 * E day-of-week text Tue; Tuesday; T * e/c localized day-of-week number/text 2; 02; Tue; Tuesday; T * F week-of-month number 3 * * a am-pm-of-day text PM * h clock-hour-of-am-pm (1-12) number 12 * K hour-of-am-pm (0-11) number 0 * k clock-hour-of-am-pm (1-24) number 0 * * H hour-of-day (0-23) number 0 * m minute-of-hour number 30 * s second-of-minute number 55 * S fraction-of-second fraction 978 * A milli-of-day number 1234 * n nano-of-second number 987654321 * N nano-of-day number 1234000000 * * V time-zone ID zone-id America/Los_Angeles; Z; -08:30 * z time-zone name zone-name Pacific Standard Time; PST * O localized zone-offset offset-O GMT+8; GMT+08:00; UTC-08:00; * X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15; * x zone-offset offset-x +0000; -08; -0830; -08:30; -083015; -08:30:15; * Z zone-offset offset-Z +0000; -0800; -08:00; * * p pad next pad modifier 1 * * ' escape for text delimiter * '' single quote literal ' * [ optional section start * ] optional section end * # reserved for future use * { reserved for future use * } reserved for future use **
* The count of pattern letters determines the format. *
* Text: The text style is determined based on the number of pattern * letters used. Less than 4 pattern letters will use the * {@link TextStyle#SHORT short form}. Exactly 4 pattern letters will use the * {@link TextStyle#FULL full form}. Exactly 5 pattern letters will use the * {@link TextStyle#NARROW narrow form}. * Pattern letters 'L', 'c', and 'q' specify the stand-alone form of the text styles. *
* Number: If the count of letters is one, then the value is output using * the minimum number of digits and without padding. Otherwise, the count of digits * is used as the width of the output field, with the value zero-padded as necessary. * The following pattern letters have constraints on the count of letters. * Only one letter of 'c' and 'F' can be specified. * Up to two letters of 'd', 'H', 'h', 'K', 'k', 'm', and 's' can be specified. * Up to three letters of 'D' can be specified. *
* Number/Text: If the count of pattern letters is 3 or greater, use the * Text rules above. Otherwise use the Number rules above. *
* Fraction: Outputs the nano-of-second field as a fraction-of-second. * The nano-of-second value has nine digits, thus the count of pattern letters * is from 1 to 9. If it is less than 9, then the nano-of-second value is * truncated, with only the most significant digits being output. *
* Year: The count of letters determines the minimum field width below * which padding is used. If the count of letters is two, then a * {@link DateTimeFormatterBuilder#appendValueReduced reduced} two digit form is * used. For printing, this outputs the rightmost two digits. For parsing, this * will parse using the base value of 2000, resulting in a year within the range * 2000 to 2099 inclusive. If the count of letters is less than four (but not * two), then the sign is only output for negative years as per * {@link SignStyle#NORMAL}. Otherwise, the sign is output if the pad width is * exceeded, as per {@link SignStyle#EXCEEDS_PAD}. *
* ZoneId: This outputs the time-zone ID, such as 'Europe/Paris'. If the * count of letters is two, then the time-zone ID is output. Any other count of * letters throws {@code IllegalArgumentException}. *
* Zone names: This outputs the display name of the time-zone ID. If the * count of letters is one, two or three, then the short name is output. If the * count of letters is four, then the full name is output. Five or more letters * throws {@code IllegalArgumentException}. *
* Offset X and x: This formats the offset based on the number of pattern * letters. One letter outputs just the hour, such as '+01', unless the minute * is non-zero in which case the minute is also output, such as '+0130'. Two * letters outputs the hour and minute, without a colon, such as '+0130'. Three * letters outputs the hour and minute, with a colon, such as '+01:30'. Four * letters outputs the hour and minute and optional second, without a colon, * such as '+013015'. Five letters outputs the hour and minute and optional * second, with a colon, such as '+01:30:15'. Six or more letters throws * {@code IllegalArgumentException}. Pattern letter 'X' (upper case) will output * 'Z' when the offset to be output would be zero, whereas pattern letter 'x' * (lower case) will output '+00', '+0000', or '+00:00'. *
* Offset O: This formats the localized offset based on the number of * pattern letters. One letter outputs the {@linkplain TextStyle#SHORT short} * form of the localized offset, which is localized offset text, such as 'GMT', * with hour without leading zero, optional 2-digit minute and second if * non-zero, and colon, for example 'GMT+8'. Four letters outputs the * {@linkplain TextStyle#FULL full} form, which is localized offset text, * such as 'GMT, with 2-digit hour and minute field, optional second field * if non-zero, and colon, for example 'GMT+08:00'. Any other count of letters * throws {@code IllegalArgumentException}. *
* Offset Z: This formats the offset based on the number of pattern * letters. One, two or three letters outputs the hour and minute, without a * colon, such as '+0130'. The output will be '+0000' when the offset is zero. * Four letters outputs the {@linkplain TextStyle#FULL full} form of localized * offset, equivalent to four letters of Offset-O. The output will be the * corresponding localized offset text if the offset is zero. Five * letters outputs the hour, minute, with optional second if non-zero, with * colon. It outputs 'Z' if the offset is zero. * Six or more letters throws {@code IllegalArgumentException}. *
* Optional section: The optional section markers work exactly like * calling {@link DateTimeFormatterBuilder#optionalStart()} and * {@link DateTimeFormatterBuilder#optionalEnd()}. *
* Pad modifier: Modifies the pattern that immediately follows to be * padded with spaces. The pad width is determined by the number of pattern * letters. This is the same as calling * {@link DateTimeFormatterBuilder#padNext(int)}. *
* For example, 'ppH' outputs the hour-of-day padded on the left with spaces to * a width of 2. *
* Any unrecognized letter is an error. Any non-letter character, other than * '[', ']', '{', '}', '#' and the single quote will be output directly. * Despite this, it is recommended to use single quotes around all characters * that you want to output directly to ensure that future changes do not break * your application. * *
* Five parsing methods are supplied by this class. * Four of these perform both the parse and resolve phases. * The fifth method, {@link #parseUnresolved(CharSequence, ParsePosition)}, * only performs the first phase, leaving the result unresolved. * As such, it is essentially a low-level operation. *
* The resolve phase is controlled by two parameters, set on this class. *
* The {@link ResolverStyle} is an enum that offers three different approaches, * strict, smart and lenient. The smart option is the default. * It can be set using {@link #withResolverStyle(ResolverStyle)}. *
* The {@link #withResolverFields(TemporalField...)} parameter allows the * set of fields that will be resolved to be filtered before resolving starts. * For example, if the formatter has parsed a year, month, day-of-month * and day-of-year, then there are two approaches to resolve a date: * (year + month + day-of-month) and (year + day-of-year). * The resolver fields allows one of the two approaches to be selected. * If no resolver fields are set then both approaches must result in the same date. *
* Resolving separate fields to form a complete date and time is a complex * process with behaviour distributed across a number of classes. * It follows these steps: *
* This method will create a formatter based on a simple * pattern of letters and symbols * as described in the class documentation. * For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'. *
* The formatter will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. * This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter * Alternatively use the {@link #ofPattern(String, Locale)} variant of this method. *
* The returned formatter has no override chronology or zone. * It uses {@link ResolverStyle#SMART SMART} resolver style. * * @param pattern the pattern to use, not null * @return the formatter based on the pattern, not null * @throws IllegalArgumentException if the pattern is invalid * @see DateTimeFormatterBuilder#appendPattern(String) */ public static DateTimeFormatter ofPattern(String pattern) { return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(); } /** * Creates a formatter using the specified pattern and locale. *
* This method will create a formatter based on a simple * pattern of letters and symbols * as described in the class documentation. * For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'. *
* The formatter will use the specified locale. * This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter *
* The returned formatter has no override chronology or zone. * It uses {@link ResolverStyle#SMART SMART} resolver style. * * @param pattern the pattern to use, not null * @param locale the locale to use, not null * @return the formatter based on the pattern, not null * @throws IllegalArgumentException if the pattern is invalid * @see DateTimeFormatterBuilder#appendPattern(String) */ public static DateTimeFormatter ofPattern(String pattern, Locale locale) { return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale); } //----------------------------------------------------------------------- /** * Returns a locale specific date format for the ISO chronology. *
* This returns a formatter that will format or parse a date. * The exact format pattern used varies by locale. *
* The locale is determined from the formatter. The formatter returned directly by * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} * on the result of this method. *
* Note that the localized pattern is looked up lazily. * This {@code DateTimeFormatter} holds the style required and the locale, * looking up the pattern required on demand. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. * * @param dateStyle the formatter style to obtain, not null * @return the date formatter, not null */ public static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) { Objects.requireNonNull(dateStyle, "dateStyle"); return new DateTimeFormatterBuilder().appendLocalized(dateStyle, null) .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } /** * Returns a locale specific time format for the ISO chronology. *
* This returns a formatter that will format or parse a time. * The exact format pattern used varies by locale. *
* The locale is determined from the formatter. The formatter returned directly by * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} * on the result of this method. *
* Note that the localized pattern is looked up lazily. * This {@code DateTimeFormatter} holds the style required and the locale, * looking up the pattern required on demand. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. * * @param timeStyle the formatter style to obtain, not null * @return the time formatter, not null */ public static DateTimeFormatter ofLocalizedTime(FormatStyle timeStyle) { Objects.requireNonNull(timeStyle, "timeStyle"); return new DateTimeFormatterBuilder().appendLocalized(null, timeStyle) .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } /** * Returns a locale specific date-time formatter for the ISO chronology. *
* This returns a formatter that will format or parse a date-time. * The exact format pattern used varies by locale. *
* The locale is determined from the formatter. The formatter returned directly by * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}. * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} * on the result of this method. *
* Note that the localized pattern is looked up lazily. * This {@code DateTimeFormatter} holds the style required and the locale, * looking up the pattern required on demand. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. * * @param dateTimeStyle the formatter style to obtain, not null * @return the date-time formatter, not null */ public static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateTimeStyle) { Objects.requireNonNull(dateTimeStyle, "dateTimeStyle"); return new DateTimeFormatterBuilder().appendLocalized(dateTimeStyle, dateTimeStyle) .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } /** * Returns a locale specific date and time format for the ISO chronology. *
* This returns a formatter that will format or parse a date-time. * The exact format pattern used varies by locale. *
* The locale is determined from the formatter. The formatter returned directly by * this method will use the {@link Locale#getDefault() default FORMAT locale}. * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)} * on the result of this method. *
* Note that the localized pattern is looked up lazily. * This {@code DateTimeFormatter} holds the style required and the locale, * looking up the pattern required on demand. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style. * * @param dateStyle the date formatter style to obtain, not null * @param timeStyle the time formatter style to obtain, not null * @return the date, time or date-time formatter, not null */ public static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle) { Objects.requireNonNull(dateStyle, "dateStyle"); Objects.requireNonNull(timeStyle, "timeStyle"); return new DateTimeFormatterBuilder().appendLocalized(dateStyle, timeStyle) .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO date formatter that formats or parses a date without an * offset, such as '2011-12-03'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended local date format. * The format consists of: *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_LOCAL_DATE; static { ISO_LOCAL_DATE = new DateTimeFormatterBuilder() .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) .appendLiteral('-') .appendValue(MONTH_OF_YEAR, 2) .appendLiteral('-') .appendValue(DAY_OF_MONTH, 2) .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO date formatter that formats or parses a date with an * offset, such as '2011-12-03+01:00'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset date format. * The format consists of: *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_OFFSET_DATE; static { ISO_OFFSET_DATE = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(ISO_LOCAL_DATE) .appendOffsetId() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO date formatter that formats or parses a date with the * offset if available, such as '2011-12-03' or '2011-12-03+01:00'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended date format. * The format consists of: *
* As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_DATE; static { ISO_DATE = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(ISO_LOCAL_DATE) .optionalStart() .appendOffsetId() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO time formatter that formats or parses a time without an * offset, such as '10:15' or '10:15:30'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended local time format. * The format consists of: *
* The returned formatter has no override chronology or zone. * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_LOCAL_TIME; static { ISO_LOCAL_TIME = new DateTimeFormatterBuilder() .appendValue(HOUR_OF_DAY, 2) .appendLiteral(':') .appendValue(MINUTE_OF_HOUR, 2) .optionalStart() .appendLiteral(':') .appendValue(SECOND_OF_MINUTE, 2) .optionalStart() .appendFraction(NANO_OF_SECOND, 0, 9, true) .toFormatter(ResolverStyle.STRICT, null); } //----------------------------------------------------------------------- /** * The ISO time formatter that formats or parses a time with an * offset, such as '10:15+01:00' or '10:15:30+01:00'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset time format. * The format consists of: *
* The returned formatter has no override chronology or zone. * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_OFFSET_TIME; static { ISO_OFFSET_TIME = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(ISO_LOCAL_TIME) .appendOffsetId() .toFormatter(ResolverStyle.STRICT, null); } //----------------------------------------------------------------------- /** * The ISO time formatter that formats or parses a time, with the * offset if available, such as '10:15', '10:15:30' or '10:15:30+01:00'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset time format. * The format consists of: *
* As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. *
* The returned formatter has no override chronology or zone. * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_TIME; static { ISO_TIME = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(ISO_LOCAL_TIME) .optionalStart() .appendOffsetId() .toFormatter(ResolverStyle.STRICT, null); } //----------------------------------------------------------------------- /** * The ISO date-time formatter that formats or parses a date-time without * an offset, such as '2011-12-03T10:15:30'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset date-time format. * The format consists of: *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_LOCAL_DATE_TIME; static { ISO_LOCAL_DATE_TIME = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(ISO_LOCAL_DATE) .appendLiteral('T') .append(ISO_LOCAL_TIME) .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO date-time formatter that formats or parses a date-time with an * offset, such as '2011-12-03T10:15:30+01:00'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended offset date-time format. * The format consists of: *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_OFFSET_DATE_TIME; static { ISO_OFFSET_DATE_TIME = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(ISO_LOCAL_DATE_TIME) .appendOffsetId() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO-like date-time formatter that formats or parses a date-time with * offset and zone, such as '2011-12-03T10:15:30+01:00[Europe/Paris]'. *
* This returns an immutable formatter capable of formatting and parsing * a format that extends the ISO-8601 extended offset date-time format * to add the time-zone. * The section in square brackets is not part of the ISO-8601 standard. * The format consists of: *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_ZONED_DATE_TIME; static { ISO_ZONED_DATE_TIME = new DateTimeFormatterBuilder() .append(ISO_OFFSET_DATE_TIME) .optionalStart() .appendLiteral('[') .parseCaseSensitive() .appendZoneRegionId() .appendLiteral(']') .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO-like date-time formatter that formats or parses a date-time with * the offset and zone if available, such as '2011-12-03T10:15:30', * '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended local or offset date-time format, as well as the * extended non-ISO form specifying the time-zone. * The format consists of: *
* As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_DATE_TIME; static { ISO_DATE_TIME = new DateTimeFormatterBuilder() .append(ISO_LOCAL_DATE_TIME) .optionalStart() .appendOffsetId() .optionalStart() .appendLiteral('[') .parseCaseSensitive() .appendZoneRegionId() .appendLiteral(']') .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO date formatter that formats or parses the ordinal date * without an offset, such as '2012-337'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended ordinal date format. * The format consists of: *
* As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_ORDINAL_DATE; static { ISO_ORDINAL_DATE = new DateTimeFormatterBuilder() .parseCaseInsensitive() .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) .appendLiteral('-') .appendValue(DAY_OF_YEAR, 3) .optionalStart() .appendOffsetId() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO date formatter that formats or parses the week-based date * without an offset, such as '2012-W48-6'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 extended week-based date format. * The format consists of: *
* As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_WEEK_DATE; static { ISO_WEEK_DATE = new DateTimeFormatterBuilder() .parseCaseInsensitive() .appendValue(IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD) .appendLiteral("-W") .appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2) .appendLiteral('-') .appendValue(DAY_OF_WEEK, 1) .optionalStart() .appendOffsetId() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The ISO instant formatter that formats or parses an instant in UTC, * such as '2011-12-03T10:15:30Z'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 instant format. * When formatting, the second-of-minute is always output. * The nano-of-second outputs zero, three, six or nine digits digits as necessary. * When parsing, time to at least the seconds field is required. * Fractional seconds from zero to nine are parsed. * The localized decimal style is not used. *
* This is a special case formatter intended to allow a human readable form * of an {@link java.time.Instant}. The {@code Instant} class is designed to * only represent a point in time and internally stores a value in nanoseconds * from a fixed epoch of 1970-01-01Z. As such, an {@code Instant} cannot be * formatted as a date or time without providing some form of time-zone. * This formatter allows the {@code Instant} to be formatted, by providing * a suitable conversion using {@code ZoneOffset.UTC}. *
* The format consists of: *
* The returned formatter has no override chronology or zone. * It uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter ISO_INSTANT; static { ISO_INSTANT = new DateTimeFormatterBuilder() .parseCaseInsensitive() .appendInstant() .toFormatter(ResolverStyle.STRICT, null); } //----------------------------------------------------------------------- /** * The ISO date formatter that formats or parses a date without an * offset, such as '20111203'. *
* This returns an immutable formatter capable of formatting and parsing * the ISO-8601 basic local date format. * The format consists of: *
* As this formatter has an optional element, it may be necessary to parse using * {@link DateTimeFormatter#parseBest}. *
* The returned formatter has a chronology of ISO set to ensure dates in * other calendar systems are correctly converted. * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style. */ public static final DateTimeFormatter BASIC_ISO_DATE; static { BASIC_ISO_DATE = new DateTimeFormatterBuilder() .parseCaseInsensitive() .appendValue(YEAR, 4) .appendValue(MONTH_OF_YEAR, 2) .appendValue(DAY_OF_MONTH, 2) .optionalStart() .appendOffset("+HHMMss", "Z") .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } //----------------------------------------------------------------------- /** * The RFC-1123 date-time formatter, such as 'Tue, 3 Jun 2008 11:05:30 GMT'. *
* This returns an immutable formatter capable of formatting and parsing * most of the RFC-1123 format. * RFC-1123 updates RFC-822 changing the year from two digits to four. * This implementation requires a four digit year. * This implementation also does not handle North American or military zone * names, only 'GMT' and offset amounts. *
* The format consists of: *
* Parsing is case insensitive. *
* The returned formatter has a chronology of ISO set to ensure dates in
* other calendar systems are correctly converted.
* It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style.
*/
public static final DateTimeFormatter RFC_1123_DATE_TIME;
static {
// manually code maps to ensure correct data always used
// (locale data can be changed by application code)
Map
* This returns a singleton {@linkplain TemporalQuery query} that provides
* access to additional information from the parse. The query always returns
* a non-null period, with a zero period returned instead of null.
*
* There are two situations where this query may return a non-zero period.
*
* In both cases, if a complete {@code ChronoLocalDateTime} or {@code Instant}
* is parsed, then the excess days are added to the date part.
* As a result, this query will return a zero period.
*
* The {@code SMART} behaviour handles the common "end of day" 24:00 value.
* Processing in {@code LENIENT} mode also produces the same result:
*
* This returns a singleton {@linkplain TemporalQuery query} that provides
* access to additional information from the parse. The query always returns
* a non-null boolean, true if parsing saw a leap-second, false if not.
*
* Instant parsing handles the special "leap second" time of '23:59:60'.
* Leap seconds occur at '23:59:60' in the UTC time-zone, but at other
* local times in different time-zones. To avoid this potential ambiguity,
* the handling of leap-seconds is limited to
* {@link DateTimeFormatterBuilder#appendInstant()}, as that method
* always parses the instant with the UTC zone offset.
*
* If the time '23:59:60' is received, then a simple conversion is applied,
* replacing the second-of-minute of 60 with 59. This query can be used
* on the parse result to determine if the leap-second adjustment was made.
* The query will return {@code true} if it did adjust to remove the
* leap-second, and {@code false} if not. Note that applying a leap-second
* smoothing mechanism, such as UTC-SLS, is the responsibility of the
* application, as follows:
*
* This is used to lookup any part of the formatter needing specific
* localization, such as the text or localized pattern.
*
* @return the locale of this formatter, not null
*/
public Locale getLocale() {
return locale;
}
/**
* Returns a copy of this formatter with a new locale.
*
* This is used to lookup any part of the formatter needing specific
* localization, such as the text or localized pattern.
*
* This instance is immutable and unaffected by this method call.
*
* @param locale the new locale, not null
* @return a formatter based on this formatter with the requested locale, not null
*/
public DateTimeFormatter withLocale(Locale locale) {
if (this.locale.equals(locale)) {
return this;
}
return new DateTimeFormatter(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono, zone);
}
//-----------------------------------------------------------------------
/**
* Gets the DecimalStyle to be used during formatting.
*
* @return the locale of this formatter, not null
*/
public DecimalStyle getDecimalStyle() {
return decimalStyle;
}
/**
* Returns a copy of this formatter with a new DecimalStyle.
*
* This instance is immutable and unaffected by this method call.
*
* @param decimalStyle the new DecimalStyle, not null
* @return a formatter based on this formatter with the requested DecimalStyle, not null
*/
public DateTimeFormatter withDecimalStyle(DecimalStyle decimalStyle) {
if (this.decimalStyle.equals(decimalStyle)) {
return this;
}
return new DateTimeFormatter(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono, zone);
}
//-----------------------------------------------------------------------
/**
* Gets the overriding chronology to be used during formatting.
*
* This returns the override chronology, used to convert dates.
* By default, a formatter has no override chronology, returning null.
* See {@link #withChronology(Chronology)} for more details on overriding.
*
* @return the override chronology of this formatter, null if no override
*/
public Chronology getChronology() {
return chrono;
}
/**
* Returns a copy of this formatter with a new override chronology.
*
* This returns a formatter with similar state to this formatter but
* with the override chronology set.
* By default, a formatter has no override chronology, returning null.
*
* If an override is added, then any date that is formatted or parsed will be affected.
*
* When formatting, if the temporal object contains a date, then it will
* be converted to a date in the override chronology.
* Whether the temporal contains a date is determined by querying the
* {@link ChronoField#EPOCH_DAY EPOCH_DAY} field.
* Any time or zone will be retained unaltered unless overridden.
*
* If the temporal object does not contain a date, but does contain one
* or more {@code ChronoField} date fields, then a {@code DateTimeException}
* is thrown. In all other cases, the override chronology is added to the temporal,
* replacing any previous chronology, but without changing the date/time.
*
* When parsing, there are two distinct cases to consider.
* If a chronology has been parsed directly from the text, perhaps because
* {@link DateTimeFormatterBuilder#appendChronologyId()} was used, then
* this override chronology has no effect.
* If no zone has been parsed, then this override chronology will be used
* to interpret the {@code ChronoField} values into a date according to the
* date resolving rules of the chronology.
*
* This instance is immutable and unaffected by this method call.
*
* @param chrono the new chronology, null if no override
* @return a formatter based on this formatter with the requested override chronology, not null
*/
public DateTimeFormatter withChronology(Chronology chrono) {
if (Objects.equals(this.chrono, chrono)) {
return this;
}
return new DateTimeFormatter(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono, zone);
}
//-----------------------------------------------------------------------
/**
* Gets the overriding zone to be used during formatting.
*
* This returns the override zone, used to convert instants.
* By default, a formatter has no override zone, returning null.
* See {@link #withZone(ZoneId)} for more details on overriding.
*
* @return the override zone of this formatter, null if no override
*/
public ZoneId getZone() {
return zone;
}
/**
* Returns a copy of this formatter with a new override zone.
*
* This returns a formatter with similar state to this formatter but
* with the override zone set.
* By default, a formatter has no override zone, returning null.
*
* If an override is added, then any instant that is formatted or parsed will be affected.
*
* When formatting, if the temporal object contains an instant, then it will
* be converted to a zoned date-time using the override zone.
* Whether the temporal is an instant is determined by querying the
* {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} field.
* If the input has a chronology then it will be retained unless overridden.
* If the input does not have a chronology, such as {@code Instant}, then
* the ISO chronology will be used.
*
* If the temporal object does not contain an instant, but does contain
* an offset then an additional check is made. If the normalized override
* zone is an offset that differs from the offset of the temporal, then
* a {@code DateTimeException} is thrown. In all other cases, the override
* zone is added to the temporal, replacing any previous zone, but without
* changing the date/time.
*
* When parsing, there are two distinct cases to consider.
* If a zone has been parsed directly from the text, perhaps because
* {@link DateTimeFormatterBuilder#appendZoneId()} was used, then
* this override zone has no effect.
* If no zone has been parsed, then this override zone will be included in
* the result of the parse where it can be used to build instants and date-times.
*
* This instance is immutable and unaffected by this method call.
*
* @param zone the new override zone, null if no override
* @return a formatter based on this formatter with the requested override zone, not null
*/
public DateTimeFormatter withZone(ZoneId zone) {
if (Objects.equals(this.zone, zone)) {
return this;
}
return new DateTimeFormatter(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono, zone);
}
//-----------------------------------------------------------------------
/**
* Gets the resolver style to use during parsing.
*
* This returns the resolver style, used during the second phase of parsing
* when fields are resolved into dates and times.
* By default, a formatter has the {@link ResolverStyle#SMART SMART} resolver style.
* See {@link #withResolverStyle(ResolverStyle)} for more details.
*
* @return the resolver style of this formatter, not null
*/
public ResolverStyle getResolverStyle() {
return resolverStyle;
}
/**
* Returns a copy of this formatter with a new resolver style.
*
* This returns a formatter with similar state to this formatter but
* with the resolver style set. By default, a formatter has the
* {@link ResolverStyle#SMART SMART} resolver style.
*
* Changing the resolver style only has an effect during parsing.
* Parsing a text string occurs in two phases.
* Phase 1 is a basic text parse according to the fields added to the builder.
* Phase 2 resolves the parsed field-value pairs into date and/or time objects.
* The resolver style is used to control how phase 2, resolving, happens.
* See {@code ResolverStyle} for more information on the options available.
*
* This instance is immutable and unaffected by this method call.
*
* @param resolverStyle the new resolver style, not null
* @return a formatter based on this formatter with the requested resolver style, not null
*/
public DateTimeFormatter withResolverStyle(ResolverStyle resolverStyle) {
Objects.requireNonNull(resolverStyle, "resolverStyle");
if (Objects.equals(this.resolverStyle, resolverStyle)) {
return this;
}
return new DateTimeFormatter(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono, zone);
}
//-----------------------------------------------------------------------
/**
* Gets the resolver fields to use during parsing.
*
* This returns the resolver fields, used during the second phase of parsing
* when fields are resolved into dates and times.
* By default, a formatter has no resolver fields, and thus returns null.
* See {@link #withResolverFields(Set)} for more details.
*
* @return the immutable set of resolver fields of this formatter, null if no fields
*/
public Set
* This returns a formatter with similar state to this formatter but with
* the resolver fields set. By default, a formatter has no resolver fields.
*
* Changing the resolver fields only has an effect during parsing.
* Parsing a text string occurs in two phases.
* Phase 1 is a basic text parse according to the fields added to the builder.
* Phase 2 resolves the parsed field-value pairs into date and/or time objects.
* The resolver fields are used to filter the field-value pairs between phase 1 and 2.
*
* This can be used to select between two or more ways that a date or time might
* be resolved. For example, if the formatter consists of year, month, day-of-month
* and day-of-year, then there are two ways to resolve a date.
* Calling this method with the arguments {@link ChronoField#YEAR YEAR} and
* {@link ChronoField#DAY_OF_YEAR DAY_OF_YEAR} will ensure that the date is
* resolved using the year and day-of-year, effectively meaning that the month
* and day-of-month are ignored during the resolving phase.
*
* In a similar manner, this method can be used to ignore secondary fields that
* would otherwise be cross-checked. For example, if the formatter consists of year,
* month, day-of-month and day-of-week, then there is only one way to resolve a
* date, but the parsed value for day-of-week will be cross-checked against the
* resolved date. Calling this method with the arguments {@link ChronoField#YEAR YEAR},
* {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} and
* {@link ChronoField#DAY_OF_MONTH DAY_OF_MONTH} will ensure that the date is
* resolved correctly, but without any cross-check for the day-of-week.
*
* In implementation terms, this method behaves as follows. The result of the
* parsing phase can be considered to be a map of field to value. The behavior
* of this method is to cause that map to be filtered between phase 1 and 2,
* removing all fields other than those specified as arguments to this method.
*
* This instance is immutable and unaffected by this method call.
*
* @param resolverFields the new set of resolver fields, null if no fields
* @return a formatter based on this formatter with the requested resolver style, not null
*/
public DateTimeFormatter withResolverFields(TemporalField... resolverFields) {
Set
* This returns a formatter with similar state to this formatter but with
* the resolver fields set. By default, a formatter has no resolver fields.
*
* Changing the resolver fields only has an effect during parsing.
* Parsing a text string occurs in two phases.
* Phase 1 is a basic text parse according to the fields added to the builder.
* Phase 2 resolves the parsed field-value pairs into date and/or time objects.
* The resolver fields are used to filter the field-value pairs between phase 1 and 2.
*
* This can be used to select between two or more ways that a date or time might
* be resolved. For example, if the formatter consists of year, month, day-of-month
* and day-of-year, then there are two ways to resolve a date.
* Calling this method with the arguments {@link ChronoField#YEAR YEAR} and
* {@link ChronoField#DAY_OF_YEAR DAY_OF_YEAR} will ensure that the date is
* resolved using the year and day-of-year, effectively meaning that the month
* and day-of-month are ignored during the resolving phase.
*
* In a similar manner, this method can be used to ignore secondary fields that
* would otherwise be cross-checked. For example, if the formatter consists of year,
* month, day-of-month and day-of-week, then there is only one way to resolve a
* date, but the parsed value for day-of-week will be cross-checked against the
* resolved date. Calling this method with the arguments {@link ChronoField#YEAR YEAR},
* {@link ChronoField#MONTH_OF_YEAR MONTH_OF_YEAR} and
* {@link ChronoField#DAY_OF_MONTH DAY_OF_MONTH} will ensure that the date is
* resolved correctly, but without any cross-check for the day-of-week.
*
* In implementation terms, this method behaves as follows. The result of the
* parsing phase can be considered to be a map of field to value. The behavior
* of this method is to cause that map to be filtered between phase 1 and 2,
* removing all fields other than those specified as arguments to this method.
*
* This instance is immutable and unaffected by this method call.
*
* @param resolverFields the new set of resolver fields, null if no fields
* @return a formatter based on this formatter with the requested resolver style, not null
*/
public DateTimeFormatter withResolverFields(Set
* This formats the date-time to a String using the rules of the formatter.
*
* @param temporal the temporal object to format, not null
* @return the formatted string, not null
* @throws DateTimeException if an error occurs during formatting
*/
public String format(TemporalAccessor temporal) {
StringBuilder buf = new StringBuilder(32);
formatTo(temporal, buf);
return buf.toString();
}
//-----------------------------------------------------------------------
/**
* Formats a date-time object to an {@code Appendable} using this formatter.
*
* This outputs the formatted date-time to the specified destination.
* {@link Appendable} is a general purpose interface that is implemented by all
* key character output classes including {@code StringBuffer}, {@code StringBuilder},
* {@code PrintStream} and {@code Writer}.
*
* Although {@code Appendable} methods throw an {@code IOException}, this method does not.
* Instead, any {@code IOException} is wrapped in a runtime exception.
*
* @param temporal the temporal object to format, not null
* @param appendable the appendable to format to, not null
* @throws DateTimeException if an error occurs during formatting
*/
public void formatTo(TemporalAccessor temporal, Appendable appendable) {
Objects.requireNonNull(temporal, "temporal");
Objects.requireNonNull(appendable, "appendable");
try {
DateTimePrintContext context = new DateTimePrintContext(temporal, this);
if (appendable instanceof StringBuilder) {
printerParser.format(context, (StringBuilder) appendable);
} else {
// buffer output to avoid writing to appendable in case of error
StringBuilder buf = new StringBuilder(32);
printerParser.format(context, buf);
appendable.append(buf);
}
} catch (IOException ex) {
throw new DateTimeException(ex.getMessage(), ex);
}
}
//-----------------------------------------------------------------------
/**
* Fully parses the text producing a temporal object.
*
* This parses the entire text producing a temporal object.
* It is typically more useful to use {@link #parse(CharSequence, TemporalQuery)}.
* The result of this method is {@code TemporalAccessor} which has been resolved,
* applying basic validation checks to help ensure a valid date-time.
*
* If the parse completes without reading the entire length of the text,
* or a problem occurs during parsing or merging, then an exception is thrown.
*
* @param text the text to parse, not null
* @return the parsed temporal object, not null
* @throws DateTimeParseException if unable to parse the requested result
*/
public TemporalAccessor parse(CharSequence text) {
Objects.requireNonNull(text, "text");
try {
return parseResolved0(text, null);
} catch (DateTimeParseException ex) {
throw ex;
} catch (RuntimeException ex) {
throw createError(text, ex);
}
}
/**
* Parses the text using this formatter, providing control over the text position.
*
* This parses the text without requiring the parse to start from the beginning
* of the string or finish at the end.
* The result of this method is {@code TemporalAccessor} which has been resolved,
* applying basic validation checks to help ensure a valid date-time.
*
* The text will be parsed from the specified start {@code ParsePosition}.
* The entire length of the text does not have to be parsed, the {@code ParsePosition}
* will be updated with the index at the end of parsing.
*
* The operation of this method is slightly different to similar methods using
* {@code ParsePosition} on {@code java.text.Format}. That class will return
* errors using the error index on the {@code ParsePosition}. By contrast, this
* method will throw a {@link DateTimeParseException} if an error occurs, with
* the exception containing the error index.
* This change in behavior is necessary due to the increased complexity of
* parsing and resolving dates/times in this API.
*
* If the formatter parses the same field more than once with different values,
* the result will be an error.
*
* @param text the text to parse, not null
* @param position the position to parse from, updated with length parsed
* and the index of any error, not null
* @return the parsed temporal object, not null
* @throws DateTimeParseException if unable to parse the requested result
* @throws IndexOutOfBoundsException if the position is invalid
*/
public TemporalAccessor parse(CharSequence text, ParsePosition position) {
Objects.requireNonNull(text, "text");
Objects.requireNonNull(position, "position");
try {
return parseResolved0(text, position);
} catch (DateTimeParseException | IndexOutOfBoundsException ex) {
throw ex;
} catch (RuntimeException ex) {
throw createError(text, ex);
}
}
//-----------------------------------------------------------------------
/**
* Fully parses the text producing an object of the specified type.
*
* Most applications should use this method for parsing.
* It parses the entire text to produce the required date-time.
* The query is typically a method reference to a {@code from(TemporalAccessor)} method.
* For example:
*
* This parse method is convenient for use when the parser can handle optional elements.
* For example, a pattern of 'uuuu-MM-dd HH.mm[ VV]' can be fully parsed to a {@code ZonedDateTime},
* or partially parsed to a {@code LocalDateTime}.
* The queries must be specified in order, starting from the best matching full-parse option
* and ending with the worst matching minimal parse option.
* The query is typically a method reference to a {@code from(TemporalAccessor)} method.
*
* The result is associated with the first type that successfully parses.
* Normally, applications will use {@code instanceof} to check the result.
* For example:
*
* This parses to a {@code TemporalAccessor} ensuring that the text is fully parsed.
*
* @param text the text to parse, not null
* @param position the position to parse from, updated with length parsed
* and the index of any error, null if parsing whole string
* @return the resolved result of the parse, not null
* @throws DateTimeParseException if the parse fails
* @throws DateTimeException if an error occurs while resolving the date or time
* @throws IndexOutOfBoundsException if the position is invalid
*/
private TemporalAccessor parseResolved0(final CharSequence text, final ParsePosition position) {
ParsePosition pos = (position != null ? position : new ParsePosition(0));
DateTimeParseContext context = parseUnresolved0(text, pos);
if (context == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length())) {
String abbr;
if (text.length() > 64) {
abbr = text.subSequence(0, 64).toString() + "...";
} else {
abbr = text.toString();
}
if (pos.getErrorIndex() >= 0) {
throw new DateTimeParseException("Text '" + abbr + "' could not be parsed at index " +
pos.getErrorIndex(), text, pos.getErrorIndex());
} else {
throw new DateTimeParseException("Text '" + abbr + "' could not be parsed, unparsed text found at index " +
pos.getIndex(), text, pos.getIndex());
}
}
return context.toResolved(resolverStyle, resolverFields);
}
/**
* Parses the text using this formatter, without resolving the result, intended
* for advanced use cases.
*
* Parsing is implemented as a two-phase operation.
* First, the text is parsed using the layout defined by the formatter, producing
* a {@code Map} of field to value, a {@code ZoneId} and a {@code Chronology}.
* Second, the parsed data is resolved, by validating, combining and
* simplifying the various fields into more useful ones.
* This method performs the parsing stage but not the resolving stage.
*
* The result of this method is {@code TemporalAccessor} which represents the
* data as seen in the input. Values are not validated, thus parsing a date string
* of '2012-00-65' would result in a temporal with three fields - year of '2012',
* month of '0' and day-of-month of '65'.
*
* The text will be parsed from the specified start {@code ParsePosition}.
* The entire length of the text does not have to be parsed, the {@code ParsePosition}
* will be updated with the index at the end of parsing.
*
* Errors are returned using the error index field of the {@code ParsePosition}
* instead of {@code DateTimeParseException}.
* The returned error index will be set to an index indicative of the error.
* Callers must check for errors before using the result.
*
* If the formatter parses the same field more than once with different values,
* the result will be an error.
*
* This method is intended for advanced use cases that need access to the
* internal state during parsing. Typical application code should use
* {@link #parse(CharSequence, TemporalQuery)} or the parse method on the target type.
*
* @param text the text to parse, not null
* @param position the position to parse from, updated with length parsed
* and the index of any error, not null
* @return the parsed text, null if the parse results in an error
* @throws DateTimeException if some problem occurs during parsing
* @throws IndexOutOfBoundsException if the position is invalid
*/
public TemporalAccessor parseUnresolved(CharSequence text, ParsePosition position) {
DateTimeParseContext context = parseUnresolved0(text, position);
if (context == null) {
return null;
}
return context.toUnresolved();
}
private DateTimeParseContext parseUnresolved0(CharSequence text, ParsePosition position) {
Objects.requireNonNull(text, "text");
Objects.requireNonNull(position, "position");
DateTimeParseContext context = new DateTimeParseContext(this);
int pos = position.getIndex();
pos = printerParser.parse(context, text, pos);
if (pos < 0) {
position.setErrorIndex(~pos); // index not updated from input
return null;
}
position.setIndex(pos); // errorIndex not updated from input
return context;
}
//-----------------------------------------------------------------------
/**
* Returns the formatter as a composite printer parser.
*
* @param optional whether the printer/parser should be optional
* @return the printer/parser, not null
*/
CompositePrinterParser toPrinterParser(boolean optional) {
return printerParser.withOptional(optional);
}
/**
* Returns this formatter as a {@code java.text.Format} instance.
*
* The returned {@link Format} instance will format any {@link TemporalAccessor}
* and parses to a resolved {@link TemporalAccessor}.
*
* Exceptions will follow the definitions of {@code Format}, see those methods
* for details about {@code IllegalArgumentException} during formatting and
* {@code ParseException} or null during parsing.
* The format does not support attributing of the returned format string.
*
* @return this formatter as a classic format instance, not null
*/
public Format toFormat() {
return new ClassicFormat(this, null);
}
/**
* Returns this formatter as a {@code java.text.Format} instance that will
* parse using the specified query.
*
* The returned {@link Format} instance will format any {@link TemporalAccessor}
* and parses to the type specified.
* The type must be one that is supported by {@link #parse}.
*
* Exceptions will follow the definitions of {@code Format}, see those methods
* for details about {@code IllegalArgumentException} during formatting and
* {@code ParseException} or null during parsing.
* The format does not support attributing of the returned format string.
*
* @param parseQuery the query defining the type to parse to, not null
* @return this formatter as a classic format instance, not null
*/
public Format toFormat(TemporalQuery> parseQuery) {
Objects.requireNonNull(parseQuery, "parseQuery");
return new ClassicFormat(this, parseQuery);
}
//-----------------------------------------------------------------------
/**
* Returns a description of the underlying formatters.
*
* @return a description of this formatter, not null
*/
@Override
public String toString() {
String pattern = printerParser.toString();
pattern = pattern.startsWith("[") ? pattern : pattern.substring(1, pattern.length() - 1);
return pattern;
// TODO: Fix tests to not depend on toString()
// return "DateTimeFormatter[" + locale +
// (chrono != null ? "," + chrono : "") +
// (zone != null ? "," + zone : "") +
// pattern + "]";
}
//-----------------------------------------------------------------------
/**
* Implements the classic Java Format API.
* @serial exclude
*/
@SuppressWarnings("serial") // not actually serializable
static class ClassicFormat extends Format {
/** The formatter. */
private final DateTimeFormatter formatter;
/** The type to be parsed. */
private final TemporalQuery> parseType;
/** Constructor. */
public ClassicFormat(DateTimeFormatter formatter, TemporalQuery> parseType) {
this.formatter = formatter;
this.parseType = parseType;
}
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
Objects.requireNonNull(obj, "obj");
Objects.requireNonNull(toAppendTo, "toAppendTo");
Objects.requireNonNull(pos, "pos");
if (obj instanceof TemporalAccessor == false) {
throw new IllegalArgumentException("Format target must implement TemporalAccessor");
}
pos.setBeginIndex(0);
pos.setEndIndex(0);
try {
formatter.formatTo((TemporalAccessor) obj, toAppendTo);
} catch (RuntimeException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
return toAppendTo;
}
@Override
public Object parseObject(String text) throws ParseException {
Objects.requireNonNull(text, "text");
try {
if (parseType == null) {
return formatter.parseResolved0(text, null);
}
return formatter.parse(text, parseType);
} catch (DateTimeParseException ex) {
throw new ParseException(ex.getMessage(), ex.getErrorIndex());
} catch (RuntimeException ex) {
throw (ParseException) new ParseException(ex.getMessage(), 0).initCause(ex);
}
}
@Override
public Object parseObject(String text, ParsePosition pos) {
Objects.requireNonNull(text, "text");
DateTimeParseContext context;
try {
context = formatter.parseUnresolved0(text, pos);
} catch (IndexOutOfBoundsException ex) {
if (pos.getErrorIndex() < 0) {
pos.setErrorIndex(0);
}
return null;
}
if (context == null) {
if (pos.getErrorIndex() < 0) {
pos.setErrorIndex(0);
}
return null;
}
try {
TemporalAccessor resolved = context.toResolved(formatter.resolverStyle, formatter.resolverFields);
if (parseType == null) {
return resolved;
}
return resolved.query(parseType);
} catch (RuntimeException ex) {
pos.setErrorIndex(0);
return null;
}
}
}
}
*
*
* Text to parse Parsed object Excess days
* "2012-12-03T00:00" LocalDateTime.of(2012, 12, 3, 0, 0) ZERO
* "2012-12-03T24:00" LocalDateTime.of(2012, 12, 4, 0, 0) ZERO
* "00:00" LocalTime.of(0, 0) ZERO
* "24:00" LocalTime.of(0, 0) Period.ofDays(1)
*
* The query can be used as follows:
*
* TemporalAccessor parsed = formatter.parse(str);
* LocalTime time = parsed.query(LocalTime::from);
* Period extraDays = parsed.query(DateTimeFormatter.parsedExcessDays());
*
* @return a query that provides access to the excess days that were parsed
*/
public static final TemporalQuery
* TemporalAccessor parsed = formatter.parse(str);
* Instant instant = parsed.query(Instant::from);
* if (parsed.query(DateTimeFormatter.parsedLeapSecond())) {
* // validate leap-second is correct and apply correct smoothing
* }
*
* @return a query that provides access to whether a leap-second was parsed
*/
public static final TemporalQuery
* LocalDateTime dt = parser.parse(str, LocalDateTime::from);
*
* If the parse completes without reading the entire length of the text,
* or a problem occurs during parsing or merging, then an exception is thrown.
*
* @param
* TemporalAccessor dt = parser.parseBest(str, ZonedDateTime::from, LocalDateTime::from);
* if (dt instanceof ZonedDateTime) {
* ...
* } else {
* ...
* }
*
* If the parse completes without reading the entire length of the text,
* or a problem occurs during parsing or merging, then an exception is thrown.
*
* @param text the text to parse, not null
* @param queries the queries defining the types to attempt to parse to,
* must implement {@code TemporalAccessor}, not null
* @return the parsed date-time, not null
* @throws IllegalArgumentException if less than 2 types are specified
* @throws DateTimeParseException if unable to parse the requested result
*/
public TemporalAccessor parseBest(CharSequence text, TemporalQuery>... queries) {
Objects.requireNonNull(text, "text");
Objects.requireNonNull(queries, "queries");
if (queries.length < 2) {
throw new IllegalArgumentException("At least two queries must be specified");
}
try {
TemporalAccessor resolved = parseResolved0(text, null);
for (TemporalQuery> query : queries) {
try {
return (TemporalAccessor) resolved.query(query);
} catch (RuntimeException ex) {
// continue
}
}
throw new DateTimeException("Unable to convert parsed text using any of the specified queries");
} catch (DateTimeParseException ex) {
throw ex;
} catch (RuntimeException ex) {
throw createError(text, ex);
}
}
private DateTimeParseException createError(CharSequence text, RuntimeException ex) {
String abbr;
if (text.length() > 64) {
abbr = text.subSequence(0, 64).toString() + "...";
} else {
abbr = text.toString();
}
return new DateTimeParseException("Text '" + abbr + "' could not be parsed: " + ex.getMessage(), text, 0, ex);
}
//-----------------------------------------------------------------------
/**
* Parses and resolves the specified text.
*