packages/core/src/format/DateTimeFormatter.js
/**
* @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
* @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
* @license BSD-3-Clause (see LICENSE in the root directory of this source tree)
*/
import { assert, requireNonNull } from '../assert';
import { DateTimeParseException, NullPointerException } from '../errors';
import { Period } from '../Period';
import { ParsePosition } from './ParsePosition';
import { DateTimeBuilder } from './DateTimeBuilder';
import { DateTimeParseContext } from './DateTimeParseContext';
import { DateTimePrintContext } from './DateTimePrintContext';
import { DateTimeFormatterBuilder } from './DateTimeFormatterBuilder';
import { SignStyle } from './SignStyle';
import { StringBuilder } from './StringBuilder';
import { ResolverStyle } from './ResolverStyle';
import { IsoChronology } from '../chrono/IsoChronology';
import { ChronoField } from '../temporal/ChronoField';
import { createTemporalQuery } from '../temporal/TemporalQuery';
/**
*
* ### Static properties of Class {@link DateTimeFormatter}
*
* DateTimeFormatter.ISO_LOCAL_DATE
*
* DateTimeFormatter.ISO_LOCAL_TIME
*
* DateTimeFormatter.ISO_LOCAL_DATE_TIME
*
*/
export class DateTimeFormatter {
//-----------------------------------------------------------------------
/**
* A query that provides access to the excess days that were parsed.
*
* This returns a singleton {@link TemporalQuery} 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.
*
* * If the {@link ResolverStyle} is {@link LENIENT} and a time is parsed
* without a date, then the complete result of the parse consists of a
* {@link LocalTime} and an excess {@link Period} in days.
* * If the {@link ResolverStyle} is {@link SMART} and a time is parsed
* without a date where the time is 24:00:00, then the complete result of
* the parse consists of a {@link LocalTime} of 00:00:00 and an excess
* {@link Period} of one day.
*
* In both cases, if a complete {@link ChronoLocalDateTime} or {@link Instant}
* is parsed, then the excess days are added to the date part.
* As a result, this query will return a zero period.
*
* The {@link SMART} behaviour handles the common "end of day" 24:00 value.
* Processing in {@link LENIENT} mode also produces the same result:
* <pre>
* 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)
* </pre>
* The query can be used as follows:
* <pre>
* TemporalAccessor parsed = formatter.parse(str);
* LocalTime time = parsed.query(LocalTime.FROM);
* Period extraDays = parsed.query(DateTimeFormatter.parsedExcessDays());
* </pre>
* @return {TemporalQuery} a query that provides access to the excess days that were parsed
*/
static parsedExcessDays() {
return DateTimeFormatter.PARSED_EXCESS_DAYS;
}
/**
* A query that provides access to whether a leap-second was parsed.
*
* This returns a singleton {@link TemporalQuery} 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 one second of excess if it did adjust to remove
* the leap-second, and zero if not. Note that applying a leap-second
* smoothing mechanism, such as UTC-SLS, is the responsibility of the
* application, as follows:
* <pre>
* 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
* }
* </pre>
* @return a query that provides access to whether a leap-second was parsed
*/
static parsedLeapSecond() {
return DateTimeFormatter.PARSED_LEAP_SECOND;
}
/**
* Creates a formatter using the specified pattern.
*
* This method will create a formatter based on a simple pattern of letters and symbols.
*
* The returned formatter will use the default locale, but this can be changed
* using {@link DateTimeFormatter.withLocale}.
*
* All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
* The following pattern letters are defined:
* <pre>
* |Symbol |Meaning |Presentation |Examples
* |--------|----------------------------|------------------|----------------------------------------------------
* | G | era | number/text | 1; 01; AD; Anno Domini
* | u | year | year | 2004; 04
* | y | year-of-era | year | 2004; 04
* | D | day-of-year | number | 189
* | M | month-of-year | number/text | 7; 07; Jul; July; J
* | d | day-of-month | number | 10
* | | | |
* | Q | quarter-of-year | number/text | 3; 03; Q3
* | Y | week-based-year | year | 1996; 96
* | w | week-of-year | number | 27
* | W | week-of-month | number | 27
* | e | localized day-of-week | number | 2; Tue; Tuesday; T
* | E | day-of-week | number/text | 2; 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
* | 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 | |
* </pre>
*
* The count of pattern letters determine the format.
*
* **Text**: The text style is determined based on the number of pattern letters used.
* Less than 4 pattern letters will use the short form `TextStyle.SHORT`.
* Exactly 4 pattern letters will use the full form `TextStyle.FULL`.
* Exactly 5 pattern letters will use the narrow form `TextStyle.NARROW`.
*
* **NOTE**: since text styles require locale support, they are currently not supported in js-joda!
*
* **Number**: If the count of letters is one, then the value is printed using the minimum number
* of digits and without padding as per {@link DateTimeFormatterBuilder.appendValue}.
* Otherwise, the count of digits is used as the width of the output field as per
* {@link DateTimeFormatterBuilder.appendValue}.
*
* **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.
* When parsing in strict mode, the number of parsed digits must match the count of pattern letters.
* When parsing in lenient mode, the number of parsed digits must be at least the count of pattern
* letters, up to 9 digits.
*
* **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}
* 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 `SignStyle.NORMAL`.
* Otherwise, the sign is output if the pad width is exceeded, as per `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 `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 `IllegalArgumentException`.
*
* **NOTE**: since zone ids and name require the iana tzdb, they are currently not supported in js-joda!
*
* **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 `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 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'.
* Four or more letters throws `IllegalArgumentException`.
* The output will be '+0000' when the offset is zero.
*
* **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}.
*
* 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.
*
* @param {String} pattern the pattern to use, not null
* @return {DateTimeFormatter} the formatter based on the pattern, not null
* @throws IllegalArgumentException if the pattern is invalid
* @see DateTimeFormatterBuilder#appendPattern(String)
* @example
* var s = LocalDate.parse('2016-04-01').format(DateTimeFormatter.ofPattern('d MM yyyy'));
* console.log(s); // '1 04 2016'
*
*/
static ofPattern(pattern) {
return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
}
//-----------------------------------------------------------------------
/**
* Constructor.
*
* @param printerParser the printer/parser to use, not null
* @param locale the locale to use, not null
* @param decimalStyle the decimal style to use, not null
* @param resolverStyle the resolver style to use, not null
* @param resolverFields the fields to use during resolving, null for all fields
* @param chrono the chronology to use, null for no override
* @param zone the zone to use, null for no override
* @private
*/
constructor(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono=IsoChronology.INSTANCE, zone) {
assert(printerParser != null);
assert(decimalStyle != null);
assert(resolverStyle != null);
/**
* The printer and/or parser to use, not null.
*/
this._printerParser = printerParser;
/**
* The locale to use for formatting. // nyi
*/
this._locale = locale;
/**
* The symbols to use for formatting, not null.
*/
this._decimalStyle = decimalStyle;
/**
* The resolver style to use, not null.
*/
this._resolverStyle = resolverStyle;
/**
* The fields to use in resolving, null for all fields.
*/
this._resolverFields = resolverFields;
/**
* The chronology to use for formatting, null for no override.
*/
this._chrono = chrono;
/**
* The zone to use for formatting, null for no override. // nyi
*/
this._zone = zone;
}
locale() {
return this._locale;
}
decimalStyle() {
return this._decimalStyle;
}
chronology() {
return this._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 printed or parsed will be affected.
*
* When printing, if the {@link Temporal} object contains a date then it will
* be converted to a date in the override chronology.
* Any time or zone will be retained unless overridden.
* The converted result will behave in a manner equivalent to an implementation
* of {@link ChronoLocalDate},{@link ChronoLocalDateTime} or {@link ChronoZonedDateTime}.
*
* When parsing, the override chronology will be used to interpret the
* {@link ChronoField} into a date unless the
* formatter directly parses a valid chronology.
*
* This instance is immutable and unaffected by this method call.
*
* @param chrono the new chronology, not null
* @return a formatter based on this formatter with the requested override chronology, not null
*/
withChronology(chrono) {
if (this._chrono != null && this._chrono.equals(chrono)) {
return this;
}
return new DateTimeFormatter(this._printerParser, this._locale, this._decimalStyle,
this._resolverStyle, this._resolverFields, chrono, this._zone);
}
/**
* not yet supported
* @returns {DateTimeFormatter}
*/
withLocale(){
return this;
}
/**
* Returns a copy of this formatter with a new resolver style.
* <p>
* 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.
* <p>
* 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 {@link ResolverStyle} for more information on the options available.
* <p>
* This instance is immutable and unaffected by this method call.
*
* @param {ResolverStyle} resolverStyle the new resolver style, not null
* @return {DateTimeFormatter} a formatter based on this formatter with the requested resolver style, not null
*/
withResolverStyle(resolverStyle) {
requireNonNull(resolverStyle, 'resolverStyle');
if (resolverStyle.equals(this._resolverStyle)) {
return this;
}
return new DateTimeFormatter(this._printerParser, this._locale, this._decimalStyle, resolverStyle, this._resolverFields, this._chrono, this._zone);
}
//-----------------------------------------------------------------------
/**
* Formats a date-time object using this formatter.
*
* This formats the date-time to a String using the rules of the formatter.
*
* @param {TemporalAccessor} temporal the temporal object to print, not null
* @return {String} the printed string, not null
* @throws DateTimeException if an error occurs during formatting
*/
format(temporal) {
const buf = new StringBuilder(32);
this._formatTo(temporal, buf);
return buf.toString();
}
//-----------------------------------------------------------------------
/**
* Formats a date-time object to an {@link Appendable} using this formatter.
*
* This formats the date-time to the specified destination.
* {@link Appendable} is a general purpose interface that is implemented by all
* key character output classes including {@link StringBuffer}, {@link StringBuilder},
* {@link PrintStream} and {@link Writer}.
*
* Although {@link Appendable} methods throw an {@link IOException}, this method does not.
* Instead, any {@link IOException} is wrapped in a runtime exception.
*
* @param {TemporalAccessor} temporal - the temporal object to print, not null
* @param {StringBuilder} appendable - the appendable to print to, not null
* @throws DateTimeException if an error occurs during formatting
*/
_formatTo(temporal, appendable) {
requireNonNull(temporal, 'temporal');
requireNonNull(appendable, 'appendable');
const context = new DateTimePrintContext(temporal, this);
this._printerParser.print(context, appendable);
}
/**
* function overloading for {@link DateTimeFormatter.parse}
*
* if called with one arg {@link DateTimeFormatter.parse1} is called
* otherwise {@link DateTimeFormatter.parse2}
*
* @param {string} text
* @param {TemporalQuery} type
* @return {TemporalAccessor}
*/
parse(text, type){
if(arguments.length === 1){
return this.parse1(text);
} else {
return this.parse2(text, type);
}
}
/**
* 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}.
* The result of this method is {@link 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 {String} text the text to parse, not null
* @return {TemporalAccessor} the parsed temporal object, not null
* @throws DateTimeParseException if unable to parse the requested result
*/
parse1(text) {
requireNonNull(text, 'text');
try {
return this._parseToBuilder(text, null).resolve(this._resolverStyle, this._resolverFields);
} catch (ex) {
if(ex instanceof DateTimeParseException){
throw ex;
} else {
throw this._createError(text, 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}.
* The result of this method is {@link 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
* @param type the type to extract, not null
* @return the parsed temporal object, not null
* @throws DateTimeParseException if unable to parse the requested result
*/
parse2(text, type) {
requireNonNull(text, 'text');
requireNonNull(type, 'type');
try {
const builder = this._parseToBuilder(text, null).resolve(this._resolverStyle, this._resolverFields);
return builder.build(type);
} catch (ex) {
if(ex instanceof DateTimeParseException){
throw ex;
} else {
throw this._createError(text, ex);
}
}
}
_createError(text, ex) {
let abbr = '';
if (text.length > 64) {
abbr = `${text.substring(0, 64)}...`;
} else {
abbr = text;
}
return new DateTimeParseException(`Text '${abbr}' could not be parsed: ${ex.message}`, text, 0, ex);
}
/**
* Parses the text to a builder.
*
* This parses to a {@link DateTimeBuilder} ensuring that the text is fully parsed.
* This method throws {@link DateTimeParseException} if unable to parse, or
* some other {@link DateTimeException} if another date/time problem occurs.
*
* @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 engine representing the result of the parse, not null
* @throws DateTimeParseException if the parse fails
*/
_parseToBuilder(text, position) {
const pos = (position != null ? position : new ParsePosition(0));
const result = this._parseUnresolved0(text, pos);
if (result == null || pos.getErrorIndex() >= 0 || (position == null && pos.getIndex() < text.length)) {
let abbr = '';
if (text.length > 64) {
abbr = `${text.substr(0, 64).toString()}...`;
} else {
abbr = text;
}
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 result.toBuilder();
}
/**
* 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 {@link Map} of field to value, a {@link ZoneId} and a {@link 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 {@link 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 {@link ParsePosition}.
* The entire length of the text does not have to be parsed, the {@link ParsePosition}
* will be updated with the index at the end of parsing.
*
* Errors are returned using the error index field of the {@link ParsePosition}
* instead of {@link DateTimeParseException}.
* The returned error index will be set to an index indicative of the error.
* Callers must check for errors before using the context.
*
* 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} 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
*/
parseUnresolved(text, position) {
return this._parseUnresolved0(text, position);
}
_parseUnresolved0(text, position) {
assert(text != null, 'text', NullPointerException);
assert(position != null, 'position', NullPointerException);
const context = new DateTimeParseContext(this);
let pos = position.getIndex();
pos = this._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.toParsed();
}
/**
* Returns the formatter as a composite printer parser.
*
* @param {boolean} optional whether the printer/parser should be optional
* @return {CompositePrinterParser} the printer/parser, not null
*/
_toPrinterParser(optional) {
return this._printerParser.withOptional(optional);
}
/**
*
* @returns {string}
*/
toString() {
const pattern = this._printerParser.toString();
return pattern.indexOf('[') === 0 ? pattern : pattern.substring(1, pattern.length - 1);
}
}
export function _init() {
DateTimeFormatter.ISO_LOCAL_DATE = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-')
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(ChronoField.DAY_OF_MONTH, 2)
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
DateTimeFormatter.ISO_LOCAL_TIME = new DateTimeFormatterBuilder()
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(ChronoField.SECOND_OF_MINUTE, 2)
.optionalStart()
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
.toFormatter(ResolverStyle.STRICT);
DateTimeFormatter.ISO_LOCAL_DATE_TIME = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral('T')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
DateTimeFormatter.ISO_INSTANT = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendInstant()
.toFormatter(ResolverStyle.STRICT);
DateTimeFormatter.ISO_OFFSET_DATE_TIME = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.appendOffsetId()
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
DateTimeFormatter.ISO_ZONED_DATE_TIME = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
.optionalStart()
.appendLiteral('[')
.parseCaseSensitive()
.appendZoneId()
// .appendZoneRegionId()
.appendLiteral(']')
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
DateTimeFormatter.BASIC_ISO_DATE = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendValue(ChronoField.DAY_OF_MONTH, 2)
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
DateTimeFormatter.ISO_OFFSET_DATE = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendOffsetId()
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
DateTimeFormatter.ISO_OFFSET_TIME = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendOffsetId()
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
DateTimeFormatter.ISO_ORDINAL_DATE = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-')
.appendValue(ChronoField.DAY_OF_YEAR)
.toFormatter(ResolverStyle.STRICT);
DateTimeFormatter.ISO_WEEK_DATE = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-W')
.appendValue(ChronoField.ALIGNED_WEEK_OF_YEAR)
.appendLiteral('-')
.appendValue(ChronoField.DAY_OF_WEEK)
.toFormatter(ResolverStyle.STRICT);
DateTimeFormatter.ISO_DATE = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.optionalStart()
.appendOffsetId()
.optionalEnd()
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
DateTimeFormatter.ISO_TIME = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.optionalStart()
.appendOffsetId()
.optionalEnd()
.toFormatter(ResolverStyle.STRICT);
DateTimeFormatter.ISO_DATE_TIME = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.optionalStart()
.appendOffsetId()
.optionalEnd()
.toFormatter(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
// TODO:
// RFC_1123_DATE_TIME - https://www.threeten.org/threetenbp/apidocs/org/threeten/bp/format/DateTimeFormatter.html#RFC_1123_DATE_TIME
DateTimeFormatter.PARSED_EXCESS_DAYS = createTemporalQuery('PARSED_EXCESS_DAYS', (temporal) => {
if (temporal instanceof DateTimeBuilder) {
return temporal.excessDays;
} else {
return Period.ZERO;
}
});
DateTimeFormatter.PARSED_LEAP_SECOND = createTemporalQuery('PARSED_LEAP_SECOND', (temporal) => {
if (temporal instanceof DateTimeBuilder) {
return temporal.leapSecond;
} else {
return false;
}
});
}