src/share/classes/java/time/format/DateTimeFormatterBuilder.java
Print this page
*** 81,95 ****
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;
- import java.time.chrono.JapaneseChronology;
import java.time.format.DateTimeTextProvider.LocaleStore;
import java.time.temporal.ChronoField;
import java.time.temporal.IsoFields;
- import java.time.temporal.Queries;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.TemporalQuery;
import java.time.temporal.ValueRange;
import java.time.temporal.WeekFields;
--- 81,93 ----
*** 109,119 ****
--- 107,120 ----
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+ import sun.util.locale.provider.LocaleProviderAdapter;
+ import sun.util.locale.provider.LocaleResources;
import sun.util.locale.provider.TimeZoneNameUtility;
/**
* Builder to create date-time formatters.
* <p>
*** 127,136 ****
--- 128,139 ----
* outputting fractions to ensure that the fraction is parsed correctly</li>
* <li>Text - the textual equivalent for the value</li>
* <li>OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li>
* <li>ZoneId - the {@linkplain ZoneId time-zone} id</li>
* <li>ZoneText - the name of the time-zone</li>
+ * <li>ChronologyId - the {@linkplain Chronology chronology} id</li>
+ * <li>ChronologyText - the name of the chronology</li>
* <li>Literal - a text literal</li>
* <li>Nested and Optional - formats can be nested or made optional</li>
* <li>Other - the printer and parser interfaces can be used to add user supplied formatting</li>
* </ul><p>
* In addition, any of the elements may be decorated by padding, either with spaces or any other character.
*** 148,158 ****
/**
* Query for a time-zone that is region-only.
*/
private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = (temporal) -> {
! ZoneId zone = temporal.query(Queries.zoneId());
return (zone != null && zone instanceof ZoneOffset == false ? zone : null);
};
/**
* The currently active builder, used by the outermost builder.
--- 151,161 ----
/**
* Query for a time-zone that is region-only.
*/
private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = (temporal) -> {
! ZoneId zone = temporal.query(TemporalQuery.zoneId());
return (zone != null && zone instanceof ZoneOffset == false ? zone : null);
};
/**
* The currently active builder, used by the outermost builder.
*** 286,295 ****
--- 289,332 ----
return this;
}
//-----------------------------------------------------------------------
/**
+ * Appends a default value for a field to the formatter for use in parsing.
+ * <p>
+ * This appends an instruction to the builder to inject a default value
+ * into the parsed result. This is especially useful in conjunction with
+ * optional parts of the formatter.
+ * <p>
+ * For example, consider a formatter that parses the year, followed by
+ * an optional month, with a further optional day-of-month. Using such a
+ * formatter would require the calling code to check whether a full date,
+ * year-month or just a year had been parsed. This method can be used to
+ * default the month and day-of-month to a sensible value, such as the
+ * first of the month, allowing the calling code to always get a date.
+ * <p>
+ * During formatting, this method has no effect.
+ * <p>
+ * During parsing, the current state of the parse is inspected.
+ * If the specified field has no associated value, because it has not been
+ * parsed successfully at that point, then the specified value is injected
+ * into the parse result. Injection is immediate, thus the field-value pair
+ * will be visible to any subsequent elements in the formatter.
+ * As such, this method is normally called at the end of the builder.
+ *
+ * @param field the field to default the value of, not null
+ * @param value the value to default the field to
+ * @return this, for chaining, not null
+ */
+ public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value) {
+ Objects.requireNonNull(field, "field");
+ appendInternal(new DefaultValueParser(field, value));
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
* Appends the value of a date-time field to the formatter using a normal
* output style.
* <p>
* The value of the field will be output during a format.
* If the value cannot be obtained then an exception will be thrown.
*** 653,663 ****
* Appends the zone offset, such as '+01:00', to the formatter.
* <p>
* This appends an instruction to format/parse the offset ID to the builder.
* <p>
* During formatting, the offset is obtained using a mechanism equivalent
! * to querying the temporal with {@link Queries#offset()}.
* It will be printed using the format defined below.
* If the offset cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* During parsing, the offset is parsed using the format defined below.
--- 690,700 ----
* Appends the zone offset, such as '+01:00', to the formatter.
* <p>
* This appends an instruction to format/parse the offset ID to the builder.
* <p>
* During formatting, the offset is obtained using a mechanism equivalent
! * to querying the temporal with {@link TemporalQuery#offset()}.
* It will be printed using the format defined below.
* If the offset cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* During parsing, the offset is parsed using the format defined below.
*** 690,710 ****
public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
return this;
}
//-----------------------------------------------------------------------
/**
* Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter.
* <p>
* This appends an instruction to format/parse the zone ID to the builder.
* The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}.
* By contrast, {@code OffsetDateTime} does not have a zone ID suitable
* for use with this method, see {@link #appendZoneOrOffsetId()}.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link Queries#zoneId()}.
* It will be printed using the result of {@link ZoneId#getId()}.
* If the zone cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* During parsing, the text must match a known zone or offset.
--- 727,785 ----
public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
return this;
}
+ /**
+ * Appends the localized zone offset, such as 'GMT+01:00', to the formatter.
+ * <p>
+ * This appends a localized zone offset to the builder, the format of the
+ * localized offset is controlled by the specified {@link FormatStyle style}
+ * to this method:
+ * <p><ul>
+ * <li>{@link TextStyle#FULL full} - formats with localized offset text, such
+ * as 'GMT, 2-digit hour and minute field, optional second field if non-zero,
+ * and colon.
+ * <li>{@link TextStyle#SHORT short} - formats with localized offset text,
+ * such as 'GMT, hour without leading zero, optional 2-digit minute and
+ * second if non-zero, and colon.
+ * </ul><p>
+ * <p>
+ * During formatting, the offset is obtained using a mechanism equivalent
+ * to querying the temporal with {@link TemporalQuery#offset()}.
+ * If the offset cannot be obtained then an exception is thrown unless the
+ * section of the formatter is optional.
+ * <p>
+ * During parsing, the offset is parsed using the format defined above.
+ * If the offset cannot be parsed then an exception is thrown unless the
+ * section of the formatter is optional.
+ * <p>
+ * @param style the format style to use, not null
+ * @return this, for chaining, not null
+ * @throws IllegalArgumentException if style is neither {@link TextStyle#FULL
+ * full} nor {@link TextStyle#SHORT short}
+ */
+ public DateTimeFormatterBuilder appendLocalizedOffset(TextStyle style) {
+ Objects.requireNonNull(style, "style");
+ if (style != TextStyle.FULL && style != TextStyle.SHORT) {
+ throw new IllegalArgumentException("Style must be either full or short");
+ }
+ appendInternal(new LocalizedOffsetIdPrinterParser(style));
+ return this;
+ }
+
//-----------------------------------------------------------------------
/**
* Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter.
* <p>
* This appends an instruction to format/parse the zone ID to the builder.
* The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}.
* By contrast, {@code OffsetDateTime} does not have a zone ID suitable
* for use with this method, see {@link #appendZoneOrOffsetId()}.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link TemporalQuery#zoneId()}.
* It will be printed using the result of {@link ZoneId#getId()}.
* If the zone cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* During parsing, the text must match a known zone or offset.
*** 723,751 ****
* starts with 'Z', then {@code ZoneOffset.UTC} is selected.
* The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
* <p>
* For example, the following will parse:
* <pre>
! * "Europe/London" -> ZoneId.of("Europe/London")
! * "Z" -> ZoneOffset.UTC
! * "UT" -> ZoneOffset.UTC
! * "UTC" -> ZoneOffset.UTC
! * "GMT" -> ZoneOffset.UTC
! * "UT0" -> ZoneOffset.UTC
! * "UTC0" -> ZoneOffset.UTC
! * "GMT0" -> ZoneOffset.UTC
! * "+01:30" -> ZoneOffset.of("+01:30")
! * "UT+01:30" -> ZoneOffset.of("+01:30")
! * "UTC+01:30" -> ZoneOffset.of("+01:30")
! * "GMT+01:30" -> ZoneOffset.of("+01:30")
* </pre>
*
* @return this, for chaining, not null
* @see #appendZoneRegionId()
*/
public DateTimeFormatterBuilder appendZoneId() {
! appendInternal(new ZoneIdPrinterParser(Queries.zoneId(), "ZoneId()"));
return this;
}
/**
* Appends the time-zone region ID, such as 'Europe/Paris', to the formatter,
--- 798,826 ----
* starts with 'Z', then {@code ZoneOffset.UTC} is selected.
* The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
* <p>
* For example, the following will parse:
* <pre>
! * "Europe/London" -- ZoneId.of("Europe/London")
! * "Z" -- ZoneOffset.UTC
! * "UT" -- ZoneOffset.UTC
! * "UTC" -- ZoneOffset.UTC
! * "GMT" -- ZoneOffset.UTC
! * "UT0" -- ZoneOffset.UTC
! * "UTC0" -- ZoneOffset.UTC
! * "GMT0" -- ZoneOffset.UTC
! * "+01:30" -- ZoneOffset.of("+01:30")
! * "UT+01:30" -- ZoneOffset.of("+01:30")
! * "UTC+01:30" -- ZoneOffset.of("+01:30")
! * "GMT+01:30" -- ZoneOffset.of("+01:30")
* </pre>
*
* @return this, for chaining, not null
* @see #appendZoneRegionId()
*/
public DateTimeFormatterBuilder appendZoneId() {
! appendInternal(new ZoneIdPrinterParser(TemporalQuery.zoneId(), "ZoneId()"));
return this;
}
/**
* Appends the time-zone region ID, such as 'Europe/Paris', to the formatter,
*** 753,763 ****
* <p>
* This appends an instruction to format/parse the zone ID to the builder
* only if it is a region-based ID.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link Queries#zoneId()}.
* If the zone is a {@code ZoneOffset} or it cannot be obtained then
* an exception is thrown unless the section of the formatter is optional.
* If the zone is not an offset, then the zone will be printed using
* the zone ID from {@link ZoneId#getId()}.
* <p>
--- 828,838 ----
* <p>
* This appends an instruction to format/parse the zone ID to the builder
* only if it is a region-based ID.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link TemporalQuery#zoneId()}.
* If the zone is a {@code ZoneOffset} or it cannot be obtained then
* an exception is thrown unless the section of the formatter is optional.
* If the zone is not an offset, then the zone will be printed using
* the zone ID from {@link ZoneId#getId()}.
* <p>
*** 777,798 ****
* starts with 'Z', then {@code ZoneOffset.UTC} is selected.
* The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
* <p>
* For example, the following will parse:
* <pre>
! * "Europe/London" -> ZoneId.of("Europe/London")
! * "Z" -> ZoneOffset.UTC
! * "UT" -> ZoneOffset.UTC
! * "UTC" -> ZoneOffset.UTC
! * "GMT" -> ZoneOffset.UTC
! * "UT0" -> ZoneOffset.UTC
! * "UTC0" -> ZoneOffset.UTC
! * "GMT0" -> ZoneOffset.UTC
! * "+01:30" -> ZoneOffset.of("+01:30")
! * "UT+01:30" -> ZoneOffset.of("+01:30")
! * "UTC+01:30" -> ZoneOffset.of("+01:30")
! * "GMT+01:30" -> ZoneOffset.of("+01:30")
* </pre>
* <p>
* Note that this method is is identical to {@code appendZoneId()} except
* in the mechanism used to obtain the zone.
* Note also that parsing accepts offsets, whereas formatting will never
--- 852,873 ----
* starts with 'Z', then {@code ZoneOffset.UTC} is selected.
* The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
* <p>
* For example, the following will parse:
* <pre>
! * "Europe/London" -- ZoneId.of("Europe/London")
! * "Z" -- ZoneOffset.UTC
! * "UT" -- ZoneOffset.UTC
! * "UTC" -- ZoneOffset.UTC
! * "GMT" -- ZoneOffset.UTC
! * "UT0" -- ZoneOffset.UTC
! * "UTC0" -- ZoneOffset.UTC
! * "GMT0" -- ZoneOffset.UTC
! * "+01:30" -- ZoneOffset.of("+01:30")
! * "UT+01:30" -- ZoneOffset.of("+01:30")
! * "UTC+01:30" -- ZoneOffset.of("+01:30")
! * "GMT+01:30" -- ZoneOffset.of("+01:30")
* </pre>
* <p>
* Note that this method is is identical to {@code appendZoneId()} except
* in the mechanism used to obtain the zone.
* Note also that parsing accepts offsets, whereas formatting will never
*** 815,825 ****
* The zone ID is obtained in a lenient manner that first attempts to
* find a true zone ID, such as that on {@code ZonedDateTime}, and
* then attempts to find an offset, such as that on {@code OffsetDateTime}.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link Queries#zone()}.
* It will be printed using the result of {@link ZoneId#getId()}.
* If the zone cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* During parsing, the text must match a known zone or offset.
--- 890,900 ----
* The zone ID is obtained in a lenient manner that first attempts to
* find a true zone ID, such as that on {@code ZonedDateTime}, and
* then attempts to find an offset, such as that on {@code OffsetDateTime}.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link TemporalQuery#zone()}.
* It will be printed using the result of {@link ZoneId#getId()}.
* If the zone cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* During parsing, the text must match a known zone or offset.
*** 838,880 ****
* starts with 'Z', then {@code ZoneOffset.UTC} is selected.
* The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
* <p>
* For example, the following will parse:
* <pre>
! * "Europe/London" -> ZoneId.of("Europe/London")
! * "Z" -> ZoneOffset.UTC
! * "UT" -> ZoneOffset.UTC
! * "UTC" -> ZoneOffset.UTC
! * "GMT" -> ZoneOffset.UTC
! * "UT0" -> ZoneOffset.UTC
! * "UTC0" -> ZoneOffset.UTC
! * "GMT0" -> ZoneOffset.UTC
! * "+01:30" -> ZoneOffset.of("+01:30")
! * "UT+01:30" -> ZoneOffset.of("+01:30")
! * "UTC+01:30" -> ZoneOffset.of("+01:30")
! * "GMT+01:30" -> ZoneOffset.of("+01:30")
* </pre>
* <p>
* Note that this method is is identical to {@code appendZoneId()} except
* in the mechanism used to obtain the zone.
*
* @return this, for chaining, not null
* @see #appendZoneId()
*/
public DateTimeFormatterBuilder appendZoneOrOffsetId() {
! appendInternal(new ZoneIdPrinterParser(Queries.zone(), "ZoneOrOffsetId()"));
return this;
}
/**
* Appends the time-zone name, such as 'British Summer Time', to the formatter.
* <p>
* This appends an instruction to format/parse the textual name of the zone to
* the builder.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link Queries#zoneId()}.
* If the zone is a {@code ZoneOffset} it will be printed using the
* result of {@link ZoneOffset#getId()}.
* If the zone is not an offset, the textual name will be looked up
* for the locale set in the {@link DateTimeFormatter}.
* If the temporal object being printed represents an instant, then the text
--- 913,955 ----
* starts with 'Z', then {@code ZoneOffset.UTC} is selected.
* The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
* <p>
* For example, the following will parse:
* <pre>
! * "Europe/London" -- ZoneId.of("Europe/London")
! * "Z" -- ZoneOffset.UTC
! * "UT" -- ZoneOffset.UTC
! * "UTC" -- ZoneOffset.UTC
! * "GMT" -- ZoneOffset.UTC
! * "UT0" -- ZoneOffset.UTC
! * "UTC0" -- ZoneOffset.UTC
! * "GMT0" -- ZoneOffset.UTC
! * "+01:30" -- ZoneOffset.of("+01:30")
! * "UT+01:30" -- ZoneOffset.of("+01:30")
! * "UTC+01:30" -- ZoneOffset.of("+01:30")
! * "GMT+01:30" -- ZoneOffset.of("+01:30")
* </pre>
* <p>
* Note that this method is is identical to {@code appendZoneId()} except
* in the mechanism used to obtain the zone.
*
* @return this, for chaining, not null
* @see #appendZoneId()
*/
public DateTimeFormatterBuilder appendZoneOrOffsetId() {
! appendInternal(new ZoneIdPrinterParser(TemporalQuery.zone(), "ZoneOrOffsetId()"));
return this;
}
/**
* Appends the time-zone name, such as 'British Summer Time', to the formatter.
* <p>
* This appends an instruction to format/parse the textual name of the zone to
* the builder.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link TemporalQuery#zoneId()}.
* If the zone is a {@code ZoneOffset} it will be printed using the
* result of {@link ZoneOffset#getId()}.
* If the zone is not an offset, the textual name will be looked up
* for the locale set in the {@link DateTimeFormatter}.
* If the temporal object being printed represents an instant, then the text
*** 906,916 ****
* <p>
* This appends an instruction to format/parse the textual name of the zone to
* the builder.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link Queries#zoneId()}.
* If the zone is a {@code ZoneOffset} it will be printed using the
* result of {@link ZoneOffset#getId()}.
* If the zone is not an offset, the textual name will be looked up
* for the locale set in the {@link DateTimeFormatter}.
* If the temporal object being printed represents an instant, then the text
--- 981,991 ----
* <p>
* This appends an instruction to format/parse the textual name of the zone to
* the builder.
* <p>
* During formatting, the zone is obtained using a mechanism equivalent
! * to querying the temporal with {@link TemporalQuery#zoneId()}.
* If the zone is a {@code ZoneOffset} it will be printed using the
* result of {@link ZoneOffset#getId()}.
* If the zone is not an offset, the textual name will be looked up
* for the locale set in the {@link DateTimeFormatter}.
* If the temporal object being printed represents an instant, then the text
*** 949,959 ****
* Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter.
* <p>
* This appends an instruction to format/parse the chronology ID to the builder.
* <p>
* During formatting, the chronology is obtained using a mechanism equivalent
! * to querying the temporal with {@link Queries#chronology()}.
* It will be printed using the result of {@link Chronology#getId()}.
* If the chronology cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* During parsing, the chronology is parsed and must match one of the chronologies
--- 1024,1034 ----
* Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter.
* <p>
* This appends an instruction to format/parse the chronology ID to the builder.
* <p>
* During formatting, the chronology is obtained using a mechanism equivalent
! * to querying the temporal with {@link TemporalQuery#chronology()}.
* It will be printed using the result of {@link Chronology#getId()}.
* If the chronology cannot be obtained then an exception is thrown unless the
* section of the formatter is optional.
* <p>
* During parsing, the chronology is parsed and must match one of the chronologies
*** 1096,1123 ****
//-----------------------------------------------------------------------
/**
* Appends the elements defined by the specified pattern to the builder.
* <p>
* All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
! * The characters '{' and '}' are reserved for future use.
* The characters '[' and ']' indicate optional patterns.
* The following pattern letters are defined:
* <pre>
* Symbol Meaning Presentation Examples
* ------ ------- ------------ -------
! * G era text A; AD; Anno Domini
! * y year 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
--- 1171,1199 ----
//-----------------------------------------------------------------------
/**
* Appends the elements defined by the specified pattern to the builder.
* <p>
* All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
! * The characters '#', '{' and '}' are reserved for future use.
* The characters '[' and ']' indicate optional patterns.
* The following pattern letters are defined:
* <pre>
* 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
*** 1131,1260 ****
* 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>
* <p>
* The count of pattern letters determine the format.
* <p>
! * <b>Text</b>: 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}.
! * <p>
! * <b>Number</b>: If the count of letters is one, then the value is printed using the minimum number
! * of digits and without padding as per {@link #appendValue(java.time.temporal.TemporalField)}. Otherwise, the
! * count of digits is used as the width of the output field as per {@link #appendValue(java.time.temporal.TemporalField, int)}.
! * <p>
! * <b>Number/Text</b>: If the count of pattern letters is 3 or greater, use the Text rules above.
! * Otherwise use the Number rules above.
! * <p>
! * <b>Fraction</b>: 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.
! * <p>
! * <b>Year</b>: The count of letters determines the minimum field width below which padding is used.
! * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used.
! * For formatting, 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}
! * <p>
! * <b>ZoneId</b>: 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}.
* <pre>
! * Pattern Equivalent builder methods
! * VV appendZoneId()
* </pre>
* <p>
! * <b>Zone names</b>: 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}.
* <pre>
! * Pattern Equivalent builder methods
! * z appendZoneText(TextStyle.SHORT)
! * zz appendZoneText(TextStyle.SHORT)
! * zzz appendZoneText(TextStyle.SHORT)
! * zzzz appendZoneText(TextStyle.FULL)
* </pre>
* <p>
! * <b>Offset X and x</b>: 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'.
* <pre>
! * Pattern Equivalent builder methods
! * X appendOffset("+HHmm","Z")
! * XX appendOffset("+HHMM","Z")
! * XXX appendOffset("+HH:MM","Z")
! * XXXX appendOffset("+HHMMss","Z")
! * XXXXX appendOffset("+HH:MM:ss","Z")
! * x appendOffset("+HHmm","+00")
! * xx appendOffset("+HHMM","+0000")
! * xxx appendOffset("+HH:MM","+00:00")
! * xxxx appendOffset("+HHMMss","+0000")
! * xxxxx appendOffset("+HH:MM:ss","+00:00")
* </pre>
* <p>
! * <b>Offset Z</b>: 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 {@code IllegalArgumentException}.
! * The output will be '+0000' when the offset is zero.
* <pre>
! * Pattern Equivalent builder methods
! * Z appendOffset("+HHMM","+0000")
! * ZZ appendOffset("+HHMM","+0000")
! * ZZZ appendOffset("+HHMM","+0000")
* </pre>
* <p>
! * <b>Optional section</b>: The optional section markers work exactly like calling {@link #optionalStart()}
! * and {@link #optionalEnd()}.
* <p>
! * <b>Pad modifier</b>: 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 #padNext(int)}.
! * <p>
! * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2.
! * <p>
! * 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.
* <p>
* Note that the pattern string is similar, but not identical, to
* {@link java.text.SimpleDateFormat SimpleDateFormat}.
* The pattern string is also similar, but not identical, to that defined by the
* Unicode Common Locale Data Repository (CLDR/LDML).
! * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric.
! * Pattern letters 'X' is aligned with Unicode CLDR/LDML, which affects pattern 'X'.
! * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently.
! * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added.
* Number types will reject large numbers.
*
* @param pattern the pattern to add, not null
* @return this, for chaining, not null
* @throws IllegalArgumentException if the pattern is invalid
--- 1207,1390 ----
* 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
* </pre>
* <p>
* The count of pattern letters determine the format.
+ * See <a href="DateTimeFormatter.html#patterns">DateTimeFormatter</a> for a user-focused description of the patterns.
+ * The following tables define how the pattern letters map to the builder.
* <p>
! * <b>Date fields</b>: Pattern letters to output a date.
* <pre>
! * Pattern Count Equivalent builder methods
! * ------- ----- --------------------------
! * G 1 appendText(ChronoField.ERA, TextStyle.SHORT)
! * GG 2 appendText(ChronoField.ERA, TextStyle.SHORT)
! * GGG 3 appendText(ChronoField.ERA, TextStyle.SHORT)
! * GGGG 4 appendText(ChronoField.ERA, TextStyle.FULL)
! * GGGGG 5 appendText(ChronoField.ERA, TextStyle.NARROW)
! *
! * u 1 appendValue(ChronoField.YEAR, 1, 19, SignStyle.NORMAL);
! * uu 2 appendValueReduced(ChronoField.YEAR, 2, 2000);
! * uuu 3 appendValue(ChronoField.YEAR, 3, 19, SignStyle.NORMAL);
! * u..u 4..n appendValue(ChronoField.YEAR, n, 19, SignStyle.EXCEEDS_PAD);
! * y 1 appendValue(ChronoField.YEAR_OF_ERA, 1, 19, SignStyle.NORMAL);
! * yy 2 appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000);
! * yyy 3 appendValue(ChronoField.YEAR_OF_ERA, 3, 19, SignStyle.NORMAL);
! * y..y 4..n appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD);
! * Y 1 append special localized WeekFields element for numeric week-based-year
! * YY 2 append special localized WeekFields element for reduced numeric week-based-year 2 digits;
! * YYY 3 append special localized WeekFields element for numeric week-based-year (3, 19, SignStyle.NORMAL);
! * Y..Y 4..n append special localized WeekFields element for numeric week-based-year (n, 19, SignStyle.EXCEEDS_PAD);
! *
! * Q 1 appendValue(IsoFields.QUARTER_OF_YEAR);
! * QQ 2 appendValue(IsoFields.QUARTER_OF_YEAR, 2);
! * QQQ 3 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT)
! * QQQQ 4 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL)
! * QQQQQ 5 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW)
! * q 1 appendValue(IsoFields.QUARTER_OF_YEAR);
! * qq 2 appendValue(IsoFields.QUARTER_OF_YEAR, 2);
! * qqq 3 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT_STANDALONE)
! * qqqq 4 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL_STANDALONE)
! * qqqqq 5 appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW_STANDALONE)
! *
! * M 1 appendValue(ChronoField.MONTH_OF_YEAR);
! * MM 2 appendValue(ChronoField.MONTH_OF_YEAR, 2);
! * MMM 3 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
! * MMMM 4 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
! * MMMMM 5 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW)
! * L 1 appendValue(ChronoField.MONTH_OF_YEAR);
! * LL 2 appendValue(ChronoField.MONTH_OF_YEAR, 2);
! * LLL 3 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT_STANDALONE)
! * LLLL 4 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE)
! * LLLLL 5 appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW_STANDALONE)
! *
! * w 1 append special localized WeekFields element for numeric week-of-year
! * ww 1 append special localized WeekFields element for numeric week-of-year, zero-padded
! * W 1 append special localized WeekFields element for numeric week-of-month
! * d 1 appendValue(ChronoField.DAY_OF_MONTH)
! * dd 2 appendValue(ChronoField.DAY_OF_MONTH, 2)
! * D 1 appendValue(ChronoField.DAY_OF_YEAR)
! * DD 2 appendValue(ChronoField.DAY_OF_YEAR, 2)
! * DDD 3 appendValue(ChronoField.DAY_OF_YEAR, 3)
! * F 1 appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
! * E 1 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
! * EE 2 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
! * EEE 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
! * EEEE 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
! * EEEEE 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
! * e 1 append special localized WeekFields element for numeric day-of-week
! * ee 2 append special localized WeekFields element for numeric day-of-week, zero-padded
! * eee 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
! * eeee 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
! * eeeee 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
! * c 1 append special localized WeekFields element for numeric day-of-week
! * ccc 3 appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT_STANDALONE)
! * cccc 4 appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL_STANDALONE)
! * ccccc 5 appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE)
* </pre>
* <p>
! * <b>Time fields</b>: Pattern letters to output a time.
* <pre>
! * Pattern Count Equivalent builder methods
! * ------- ----- --------------------------
! * a 1 appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT)
! * h 1 appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)
! * hh 2 appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 2)
! * H 1 appendValue(ChronoField.HOUR_OF_DAY)
! * HH 2 appendValue(ChronoField.HOUR_OF_DAY, 2)
! * k 1 appendValue(ChronoField.CLOCK_HOUR_OF_DAY)
! * kk 2 appendValue(ChronoField.CLOCK_HOUR_OF_DAY, 2)
! * K 1 appendValue(ChronoField.HOUR_OF_AMPM)
! * KK 2 appendValue(ChronoField.HOUR_OF_AMPM, 2)
! * m 1 appendValue(ChronoField.MINUTE_OF_HOUR)
! * mm 2 appendValue(ChronoField.MINUTE_OF_HOUR, 2)
! * s 1 appendValue(ChronoField.SECOND_OF_MINUTE)
! * ss 2 appendValue(ChronoField.SECOND_OF_MINUTE, 2)
! *
! * S..S 1..n appendFraction(ChronoField.NANO_OF_SECOND, n, n, false)
! * A 1 appendValue(ChronoField.MILLI_OF_DAY)
! * A..A 2..n appendValue(ChronoField.MILLI_OF_DAY, n)
! * n 1 appendValue(ChronoField.NANO_OF_SECOND)
! * n..n 2..n appendValue(ChronoField.NANO_OF_SECOND, n)
! * N 1 appendValue(ChronoField.NANO_OF_DAY)
! * N..N 2..n appendValue(ChronoField.NANO_OF_DAY, n)
* </pre>
* <p>
! * <b>Zone ID</b>: Pattern letters to output {@code ZoneId}.
* <pre>
! * Pattern Count Equivalent builder methods
! * ------- ----- --------------------------
! * VV 2 appendZoneId()
! * z 1 appendZoneText(TextStyle.SHORT)
! * zz 2 appendZoneText(TextStyle.SHORT)
! * zzz 3 appendZoneText(TextStyle.SHORT)
! * zzzz 4 appendZoneText(TextStyle.FULL)
* </pre>
* <p>
! * <b>Zone offset</b>: Pattern letters to output {@code ZoneOffset}.
* <pre>
! * Pattern Count Equivalent builder methods
! * ------- ----- --------------------------
! * O 1 appendLocalizedOffsetPrefixed(TextStyle.SHORT);
! * OOOO 4 appendLocalizedOffsetPrefixed(TextStyle.FULL);
! * X 1 appendOffset("+HHmm","Z")
! * XX 2 appendOffset("+HHMM","Z")
! * XXX 3 appendOffset("+HH:MM","Z")
! * XXXX 4 appendOffset("+HHMMss","Z")
! * XXXXX 5 appendOffset("+HH:MM:ss","Z")
! * x 1 appendOffset("+HHmm","+00")
! * xx 2 appendOffset("+HHMM","+0000")
! * xxx 3 appendOffset("+HH:MM","+00:00")
! * xxxx 4 appendOffset("+HHMMss","+0000")
! * xxxxx 5 appendOffset("+HH:MM:ss","+00:00")
! * Z 1 appendOffset("+HHMM","+0000")
! * ZZ 2 appendOffset("+HHMM","+0000")
! * ZZZ 3 appendOffset("+HHMM","+0000")
! * ZZZZ 4 appendLocalizedOffset(TextStyle.FULL);
! * ZZZZZ 5 appendOffset("+HH:MM:ss","Z")
* </pre>
* <p>
! * <b>Modifiers</b>: Pattern letters that modify the rest of the pattern:
! * <pre>
! * Pattern Count Equivalent builder methods
! * ------- ----- --------------------------
! * [ 1 optionalStart()
! * ] 1 optionalEnd()
! * p..p 1..n padNext(n)
! * </pre>
* <p>
! * Any sequence of letters not specified above, unrecognized letter or
! * reserved character will throw an exception.
! * Future versions may add to the set of patterns.
! * 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.
* <p>
* Note that the pattern string is similar, but not identical, to
* {@link java.text.SimpleDateFormat SimpleDateFormat}.
* The pattern string is also similar, but not identical, to that defined by the
* Unicode Common Locale Data Repository (CLDR/LDML).
! * Pattern letters 'X' and 'u' are aligned with Unicode CLDR/LDML.
! * By contrast, {@code SimpleDateFormat} uses 'u' for the numeric day of week.
! * Pattern letters 'y' and 'Y' parse years of two digits and more than 4 digits differently.
! * Pattern letters 'n', 'A', 'N', and 'p' are added.
* Number types will reject large numbers.
*
* @param pattern the pattern to add, not null
* @return this, for chaining, not null
* @throws IllegalArgumentException if the pattern is invalid
*** 1306,1319 ****
if (count != 2) {
throw new IllegalArgumentException("Pattern letter count must be 2: " + cur);
}
appendZoneId();
} else if (cur == 'Z') {
! if (count > 3) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
! appendOffset("+HHMM", "+0000");
} else if (cur == 'X') {
if (count > 5) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z");
--- 1436,1462 ----
if (count != 2) {
throw new IllegalArgumentException("Pattern letter count must be 2: " + cur);
}
appendZoneId();
} else if (cur == 'Z') {
! if (count < 4) {
! appendOffset("+HHMM", "+0000");
! } else if (count == 4) {
! appendLocalizedOffset(TextStyle.FULL);
! } else if (count == 5) {
! appendOffset("+HH:MM:ss","Z");
! } else {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
! } else if (cur == 'O') {
! if (count == 1) {
! appendLocalizedOffset(TextStyle.SHORT);
! } else if (count == 4) {
! appendLocalizedOffset(TextStyle.FULL);
! } else {
! throw new IllegalArgumentException("Pattern letter count must be 1 or 4: " + cur);
! }
} else if (cur == 'X') {
if (count > 5) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z");
*** 1321,1342 ****
if (count > 5) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00"));
appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero);
! } else if (cur == 'w' || cur == 'e') {
// Fields defined by Locale
if (count > 1) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
appendInternal(new WeekBasedFieldPrinterParser(cur, count));
! } else if (cur == 'W') {
// Fields defined by Locale
if (count > 2) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
appendInternal(new WeekBasedFieldPrinterParser(cur, count));
} else {
throw new IllegalArgumentException("Unknown pattern letter: " + cur);
}
pos--;
--- 1464,1488 ----
if (count > 5) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00"));
appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero);
! } else if (cur == 'W') {
// Fields defined by Locale
if (count > 1) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
appendInternal(new WeekBasedFieldPrinterParser(cur, count));
! } else if (cur == 'w') {
// Fields defined by Locale
if (count > 2) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
appendInternal(new WeekBasedFieldPrinterParser(cur, count));
+ } else if (cur == 'Y') {
+ // Fields defined by Locale
+ appendInternal(new WeekBasedFieldPrinterParser(cur, count));
} else {
throw new IllegalArgumentException("Unknown pattern letter: " + cur);
}
pos--;
*** 1369,1423 ****
if (active.parent == null) {
throw new IllegalArgumentException("Pattern invalid as it contains ] without previous [");
}
optionalEnd();
! } else if (cur == '{' || cur == '}') {
throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'");
} else {
appendLiteral(cur);
}
}
}
private void parseField(char cur, int count, TemporalField field) {
switch (cur) {
case 'y':
- case 'Y':
if (count == 2) {
appendValueReduced(field, 2, 2000);
} else if (count < 4) {
appendValue(field, count, 19, SignStyle.NORMAL);
} else {
appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
}
break;
case 'M':
case 'Q':
case 'E':
switch (count) {
case 1:
- appendValue(field);
- break;
case 2:
appendValue(field, 2);
break;
case 3:
! appendText(field, TextStyle.SHORT);
break;
case 4:
! appendText(field, TextStyle.FULL);
break;
case 5:
! appendText(field, TextStyle.NARROW);
break;
default:
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
break;
- case 'G':
case 'a':
switch (count) {
case 1:
case 2:
case 3:
appendText(field, TextStyle.SHORT);
--- 1515,1595 ----
if (active.parent == null) {
throw new IllegalArgumentException("Pattern invalid as it contains ] without previous [");
}
optionalEnd();
! } else if (cur == '{' || cur == '}' || cur == '#') {
throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'");
} else {
appendLiteral(cur);
}
}
}
+ @SuppressWarnings("fallthrough")
private void parseField(char cur, int count, TemporalField field) {
+ boolean standalone = false;
switch (cur) {
+ case 'u':
case 'y':
if (count == 2) {
appendValueReduced(field, 2, 2000);
} else if (count < 4) {
appendValue(field, count, 19, SignStyle.NORMAL);
} else {
appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
}
break;
+ case 'c':
+ if (count == 2) {
+ throw new IllegalArgumentException("Invalid pattern \"cc\"");
+ }
+ /*fallthrough*/
+ case 'L':
+ case 'q':
+ standalone = true;
+ /*fallthrough*/
case 'M':
case 'Q':
case 'E':
+ case 'e':
switch (count) {
case 1:
case 2:
+ if (cur == 'c' || cur == 'e') {
+ appendInternal(new WeekBasedFieldPrinterParser(cur, count));
+ } else if (cur == 'E') {
+ appendText(field, TextStyle.SHORT);
+ } else {
+ if (count == 1) {
+ appendValue(field);
+ } else {
appendValue(field, 2);
+ }
+ }
break;
case 3:
! appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT);
break;
case 4:
! appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL);
break;
case 5:
! appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW);
break;
default:
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
break;
case 'a':
+ if (count == 1) {
+ appendText(field, TextStyle.SHORT);
+ } else {
+ throw new IllegalArgumentException("Too many pattern letters: " + cur);
+ }
+ break;
+ case 'G':
switch (count) {
case 1:
case 2:
case 3:
appendText(field, TextStyle.SHORT);
*** 1433,1442 ****
--- 1605,1645 ----
}
break;
case 'S':
appendFraction(NANO_OF_SECOND, count, count, false);
break;
+ case 'F':
+ if (count == 1) {
+ appendValue(field);
+ } else {
+ throw new IllegalArgumentException("Too many pattern letters: " + cur);
+ }
+ break;
+ case 'd':
+ case 'h':
+ case 'H':
+ case 'k':
+ case 'K':
+ case 'm':
+ case 's':
+ if (count == 1) {
+ appendValue(field);
+ } else if (count == 2) {
+ appendValue(field, count);
+ } else {
+ throw new IllegalArgumentException("Too many pattern letters: " + cur);
+ }
+ break;
+ case 'D':
+ if (count == 1) {
+ appendValue(field);
+ } else if (count <= 3) {
+ appendValue(field, count);
+ } else {
+ throw new IllegalArgumentException("Too many pattern letters: " + cur);
+ }
+ break;
default:
if (count == 1) {
appendValue(field);
} else {
appendValue(field, count);
*** 1446,1493 ****
}
/** Map of letters to fields. */
private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
static {
! FIELD_MAP.put('G', ChronoField.ERA); // Java, LDML (different to both for 1/2 chars)
! FIELD_MAP.put('y', ChronoField.YEAR); // LDML
! // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // Java, LDML // TODO redefine from above
! // FIELD_MAP.put('u', ChronoField.YEAR); // LDML // TODO
! // FIELD_MAP.put('Y', IsoFields.WEEK_BASED_YEAR); // Java7, LDML (needs localized week number) // TODO
FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR); // LDML (removed quarter from 310)
! FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // Java, LDML
! // FIELD_MAP.put('w', WeekFields.weekOfYear()); // Java, LDML (needs localized week number)
! // FIELD_MAP.put('W', WeekFields.weekOfMonth()); // Java, LDML (needs localized week number)
! FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // Java, LDML
! FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // Java, LDML
! FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH); // Java, LDML
! FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // Java, LDML (different to both for 1/2 chars)
! // FIELD_MAP.put('e', WeekFields.dayOfWeek()); // LDML (needs localized week number)
! FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // Java, LDML
! FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // Java, LDML
! FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // Java, LDML
! FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // Java, LDML
! FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // Java, LDML
! FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // Java, LDML
! FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // Java, LDML
! FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (Java uses milli-of-second number)
FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // LDML
FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 (proposed for LDML)
FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 (proposed for LDML)
// 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4
// 310 - Z - matches SimpleDateFormat and LDML
! // 310 - V - time-zone id, matches proposed LDML
// 310 - p - prefix for padding
! // 310 - X - matches proposed LDML, almost matches JavaSDF for 1, exact match 2&3, extended 4&5
! // 310 - x - matches proposed LDML
! // Java - u - clashes with LDML, go with LDML (year-proleptic) here
// LDML - U - cycle year name, not supported by 310 yet
// LDML - l - deprecated
// LDML - j - not relevant
// LDML - g - modified-julian-day
// LDML - v,V - extended time-zone names
- // LDML - q/c/L - standalone quarter/day-of-week/month
}
//-----------------------------------------------------------------------
/**
* Causes the next added printer/parser to pad to a fixed width using a space.
--- 1649,1695 ----
}
/** Map of letters to fields. */
private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
static {
! // SDF = SimpleDateFormat
! FIELD_MAP.put('G', ChronoField.ERA); // SDF, LDML (different to both for 1/2 chars)
! FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // SDF, LDML
! FIELD_MAP.put('u', ChronoField.YEAR); // LDML (different in SDF)
FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR); // LDML (removed quarter from 310)
! FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR); // LDML (stand-alone)
! FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // SDF, LDML
! FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR); // SDF, LDML (stand-alone)
! FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // SDF, LDML
! FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // SDF, LDML
! FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH); // SDF, LDML
! FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // SDF, LDML (different to both for 1/2 chars)
! FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK); // LDML (stand-alone)
! FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK); // LDML (needs localized week number)
! FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // SDF, LDML
! FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // SDF, LDML
! FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // SDF, LDML
! FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // SDF, LDML
! FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // SDF, LDML
! FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // SDF, LDML
! FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // SDF, LDML
! FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (SDF uses milli-of-second number)
FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // LDML
FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 (proposed for LDML)
FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 (proposed for LDML)
// 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4
// 310 - Z - matches SimpleDateFormat and LDML
! // 310 - V - time-zone id, matches LDML
// 310 - p - prefix for padding
! // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5
! // 310 - x - matches LDML
! // 310 - w, W, and Y are localized forms matching LDML
// LDML - U - cycle year name, not supported by 310 yet
// LDML - l - deprecated
// LDML - j - not relevant
// LDML - g - modified-julian-day
// LDML - v,V - extended time-zone names
}
//-----------------------------------------------------------------------
/**
* Causes the next added printer/parser to pad to a fixed width using a space.
*** 1630,1643 ****
return active.printerParsers.size() - 1;
}
//-----------------------------------------------------------------------
/**
! * Completes this builder by creating the DateTimeFormatter using the default locale.
* <p>
! * This will create a formatter with the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
* Numbers will be printed and parsed using the standard non-localized set of symbols.
* <p>
* Calling this method will end any open optional sections by repeatedly
* calling {@link #optionalEnd()} before creating the formatter.
* <p>
* This builder can still be used after creating the formatter if desired,
--- 1832,1847 ----
return active.printerParsers.size() - 1;
}
//-----------------------------------------------------------------------
/**
! * Completes this builder by creating the {@code DateTimeFormatter}
! * using the default locale.
* <p>
! * This will create a formatter with the {@linkplain Locale#getDefault(Locale.Category) default FORMAT locale}.
* Numbers will be printed and parsed using the standard non-localized set of symbols.
+ * The resolver style will be {@link ResolverStyle#SMART SMART}.
* <p>
* Calling this method will end any open optional sections by repeatedly
* calling {@link #optionalEnd()} before creating the formatter.
* <p>
* This builder can still be used after creating the formatter if desired,
*** 1648,1661 ****
public DateTimeFormatter toFormatter() {
return toFormatter(Locale.getDefault(Locale.Category.FORMAT));
}
/**
! * Completes this builder by creating the DateTimeFormatter using the specified locale.
* <p>
* This will create a formatter with the specified locale.
* Numbers will be printed and parsed using the standard non-localized set of symbols.
* <p>
* Calling this method will end any open optional sections by repeatedly
* calling {@link #optionalEnd()} before creating the formatter.
* <p>
* This builder can still be used after creating the formatter if desired,
--- 1852,1867 ----
public DateTimeFormatter toFormatter() {
return toFormatter(Locale.getDefault(Locale.Category.FORMAT));
}
/**
! * Completes this builder by creating the {@code DateTimeFormatter}
! * using the specified locale.
* <p>
* This will create a formatter with the specified locale.
* Numbers will be printed and parsed using the standard non-localized set of symbols.
+ * The resolver style will be {@link ResolverStyle#SMART SMART}.
* <p>
* Calling this method will end any open optional sections by repeatedly
* calling {@link #optionalEnd()} before creating the formatter.
* <p>
* This builder can still be used after creating the formatter if desired,
*** 1663,1678 ****
*
* @param locale the locale to use for formatting, not null
* @return the created formatter, not null
*/
public DateTimeFormatter toFormatter(Locale locale) {
Objects.requireNonNull(locale, "locale");
while (active.parent != null) {
optionalEnd();
}
CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
! return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null);
}
//-----------------------------------------------------------------------
/**
* Strategy for formatting/parsing date-time information.
--- 1869,1907 ----
*
* @param locale the locale to use for formatting, not null
* @return the created formatter, not null
*/
public DateTimeFormatter toFormatter(Locale locale) {
+ return toFormatter(locale, ResolverStyle.SMART, null);
+ }
+
+ /**
+ * Completes this builder by creating the formatter.
+ * This uses the default locale.
+ *
+ * @param resolverStyle the resolver style to use, not null
+ * @return the created formatter, not null
+ */
+ DateTimeFormatter toFormatter(ResolverStyle resolverStyle, Chronology chrono) {
+ return toFormatter(Locale.getDefault(Locale.Category.FORMAT), resolverStyle, chrono);
+ }
+
+ /**
+ * Completes this builder by creating the formatter.
+ *
+ * @param locale the locale to use for formatting, not null
+ * @param chrono the chronology to use, may be null
+ * @return the created formatter, not null
+ */
+ private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle, Chronology chrono) {
Objects.requireNonNull(locale, "locale");
while (active.parent != null) {
optionalEnd();
}
CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
! return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD,
! resolverStyle, null, chrono, null);
}
//-----------------------------------------------------------------------
/**
* Strategy for formatting/parsing date-time information.
*** 1940,1949 ****
--- 2169,2203 ----
}
}
//-----------------------------------------------------------------------
/**
+ * Defaults a value into the parse if not currently present.
+ */
+ static class DefaultValueParser implements DateTimePrinterParser {
+ private final TemporalField field;
+ private final long value;
+
+ DefaultValueParser(TemporalField field, long value) {
+ this.field = field;
+ this.value = value;
+ }
+
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
+ return true;
+ }
+
+ public int parse(DateTimeParseContext context, CharSequence text, int position) {
+ if (context.getParsed(field) == null) {
+ context.setParsedField(field, value, position, position);
+ }
+ return position;
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
* Prints or parses a character literal.
*/
static final class CharLiteralPrinterParser implements DateTimePrinterParser {
private final char literal;
*** 2102,2118 ****
return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, this.subsequentWidth + subsequentWidth);
}
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
! Chronology chrono = context.getTemporal().query(Queries.chronology());
! Long valueLong;
! if (chrono == JapaneseChronology.INSTANCE && field == ChronoField.YEAR) {
! valueLong = context.getValue(ChronoField.YEAR_OF_ERA);
! } else {
! valueLong = context.getValue(field);
! }
if (valueLong == null) {
return false;
}
long value = getValue(valueLong);
DateTimeFormatSymbols symbols = context.getSymbols();
--- 2356,2366 ----
return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, this.subsequentWidth + subsequentWidth);
}
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
! Long valueLong = context.getValue(field);
if (valueLong == null) {
return false;
}
long value = getValue(valueLong);
DateTimeFormatSymbols symbols = context.getSymbols();
*** 2279,2296 ****
* @param errorPos the position of the field being parsed
* @param successPos the position after the field being parsed
* @return the new position
*/
int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
! TemporalField f = field;
! if (field == ChronoField.YEAR) {
! Chronology chrono = context.getEffectiveChronology();
! if (chrono == JapaneseChronology.INSTANCE) {
! f = ChronoField.YEAR_OF_ERA;
! }
! }
! return context.setParsedField(f, value, errorPos, successPos);
}
@Override
public String toString() {
if (minWidth == 1 && maxWidth == 19 && signStyle == SignStyle.NORMAL) {
--- 2527,2537 ----
* @param errorPos the position of the field being parsed
* @param successPos the position after the field being parsed
* @return the new position
*/
int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
! return context.setParsedField(field, value, errorPos, successPos);
}
@Override
public String toString() {
if (minWidth == 1 && maxWidth == 19 && signStyle == SignStyle.NORMAL) {
*** 2568,2578 ****
Long value = context.getValue(field);
if (value == null) {
return false;
}
String text;
! Chronology chrono = context.getTemporal().query(Queries.chronology());
if (chrono == null || chrono == IsoChronology.INSTANCE) {
text = provider.getText(field, value, textStyle, context.getLocale());
} else {
text = provider.getText(chrono, field, value, textStyle, context.getLocale());
}
--- 2809,2819 ----
Long value = context.getValue(field);
if (value == null) {
return false;
}
String text;
! Chronology chrono = context.getTemporal().query(TemporalQuery.chronology());
if (chrono == null || chrono == IsoChronology.INSTANCE) {
text = provider.getText(field, value, textStyle, context.getLocale());
} else {
text = provider.getText(chrono, field, value, textStyle, context.getLocale());
}
*** 2885,2894 ****
--- 3126,3296 ----
}
}
//-----------------------------------------------------------------------
/**
+ * Prints or parses an offset ID.
+ */
+ static final class LocalizedOffsetIdPrinterParser implements DateTimePrinterParser {
+ private final TextStyle style;
+
+ /**
+ * Constructor.
+ *
+ * @param style the style, not null
+ */
+ LocalizedOffsetIdPrinterParser(TextStyle style) {
+ this.style = style;
+ }
+
+ private static StringBuilder appendHMS(StringBuilder buf, int t) {
+ return buf.append((char)(t / 10 + '0'))
+ .append((char)(t % 10 + '0'));
+ }
+
+ @Override
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
+ Long offsetSecs = context.getValue(OFFSET_SECONDS);
+ if (offsetSecs == null) {
+ return false;
+ }
+ String gmtText = "GMT"; // TODO: get localized version of 'GMT'
+ if (gmtText != null) {
+ buf.append(gmtText);
+ }
+ int totalSecs = Math.toIntExact(offsetSecs);
+ if (totalSecs != 0) {
+ int absHours = Math.abs((totalSecs / 3600) % 100); // anything larger than 99 silently dropped
+ int absMinutes = Math.abs((totalSecs / 60) % 60);
+ int absSeconds = Math.abs(totalSecs % 60);
+ buf.append(totalSecs < 0 ? "-" : "+");
+ if (style == TextStyle.FULL) {
+ appendHMS(buf, absHours);
+ buf.append(':');
+ appendHMS(buf, absMinutes);
+ if (absSeconds != 0) {
+ buf.append(':');
+ appendHMS(buf, absSeconds);
+ }
+ } else {
+ if (absHours >= 10) {
+ buf.append((char)(absHours / 10 + '0'));
+ }
+ buf.append((char)(absHours % 10 + '0'));
+ if (absMinutes != 0 || absSeconds != 0) {
+ buf.append(':');
+ appendHMS(buf, absMinutes);
+ if (absSeconds != 0) {
+ buf.append(':');
+ appendHMS(buf, absSeconds);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ int getDigit(CharSequence text, int position) {
+ char c = text.charAt(position);
+ if (c < '0' || c > '9') {
+ return -1;
+ }
+ return c - '0';
+ }
+
+ @Override
+ public int parse(DateTimeParseContext context, CharSequence text, int position) {
+ int pos = position;
+ int end = pos + text.length();
+ String gmtText = "GMT"; // TODO: get localized version of 'GMT'
+ if (gmtText != null) {
+ if (!context.subSequenceEquals(text, pos, gmtText, 0, gmtText.length())) {
+ return ~position;
+ }
+ pos += gmtText.length();
+ }
+ // parse normal plus/minus offset
+ int negative = 0;
+ if (pos == end) {
+ return context.setParsedField(OFFSET_SECONDS, 0, position, pos);
+ }
+ char sign = text.charAt(pos); // IOOBE if invalid position
+ if (sign == '+') {
+ negative = 1;
+ } else if (sign == '-') {
+ negative = -1;
+ } else {
+ return context.setParsedField(OFFSET_SECONDS, 0, position, pos);
+ }
+ pos++;
+ int h = 0;
+ int m = 0;
+ int s = 0;
+ if (style == TextStyle.FULL) {
+ int h1 = getDigit(text, pos++);
+ int h2 = getDigit(text, pos++);
+ if (h1 < 0 || h2 < 0 || text.charAt(pos++) != ':') {
+ return ~position;
+ }
+ h = h1 * 10 + h2;
+ int m1 = getDigit(text, pos++);
+ int m2 = getDigit(text, pos++);
+ if (m1 < 0 || m2 < 0) {
+ return ~position;
+ }
+ m = m1 * 10 + m2;
+ if (pos + 2 < end && text.charAt(pos) == ':') {
+ int s1 = getDigit(text, pos + 1);
+ int s2 = getDigit(text, pos + 2);
+ if (s1 >= 0 && s2 >= 0) {
+ s = s1 * 10 + s2;
+ pos += 3;
+ }
+ }
+ } else {
+ h = getDigit(text, pos++);
+ if (h < 0) {
+ return ~position;
+ }
+ if (pos < end) {
+ int h2 = getDigit(text, pos);
+ if (h2 >=0) {
+ h = h * 10 + h2;
+ pos++;
+ }
+ if (pos + 2 < end && text.charAt(pos) == ':') {
+ if (pos + 2 < end && text.charAt(pos) == ':') {
+ int m1 = getDigit(text, pos + 1);
+ int m2 = getDigit(text, pos + 2);
+ if (m1 >= 0 && m2 >= 0) {
+ m = m1 * 10 + m2;
+ pos += 3;
+ if (pos + 2 < end && text.charAt(pos) == ':') {
+ int s1 = getDigit(text, pos + 1);
+ int s2 = getDigit(text, pos + 2);
+ if (s1 >= 0 && s2 >= 0) {
+ s = s1 * 10 + s2;
+ pos += 3;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ long offsetSecs = negative * (h * 3600L + m * 60L + s);
+ return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, pos);
+ }
+
+ @Override
+ public String toString() {
+ return "LocalizedOffset(" + style + ")";
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
* Prints or parses a zone ID.
*/
static final class ZoneTextPrinterParser extends ZoneIdPrinterParser {
/** The text style to output. */
*** 2896,2906 ****
/** The preferred zoneid map */
private Set<String> preferredZones;
ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones) {
! super(Queries.zone(), "ZoneText(" + textStyle + ")");
this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
if (preferredZones != null && preferredZones.size() != 0) {
this.preferredZones = new HashSet<>();
for (ZoneId id : preferredZones) {
this.preferredZones.add(id.getId());
--- 3298,3308 ----
/** The preferred zoneid map */
private Set<String> preferredZones;
ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones) {
! super(TemporalQuery.zone(), "ZoneText(" + textStyle + ")");
this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
if (preferredZones != null && preferredZones.size() != 0) {
this.preferredZones = new HashSet<>();
for (ZoneId id : preferredZones) {
this.preferredZones.add(id.getId());
*** 2927,2942 ****
if (names == null) {
return null;
}
names = Arrays.copyOfRange(names, 0, 7);
names[5] =
! TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG,locale);
if (names[5] == null) {
names[5] = names[0]; // use the id
}
names[6] =
! TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT,locale);
if (names[6] == null) {
names[6] = names[0];
}
if (perLocale == null) {
perLocale = new ConcurrentHashMap<>();
--- 3329,3344 ----
if (names == null) {
return null;
}
names = Arrays.copyOfRange(names, 0, 7);
names[5] =
! TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG, locale);
if (names[5] == null) {
names[5] = names[0]; // use the id
}
names[6] =
! TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT, locale);
if (names[6] == null) {
names[6] = names[0];
}
if (perLocale == null) {
perLocale = new ConcurrentHashMap<>();
*** 2944,2963 ****
perLocale.put(locale, names);
cache.put(id, new SoftReference<>(perLocale));
}
switch (type) {
case STD:
! return names[textStyle.ordinal() + 1];
case DST:
! return names[textStyle.ordinal() + 3];
}
! return names[textStyle.ordinal() + 5];
}
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
! ZoneId zone = context.getValue(Queries.zoneId());
if (zone == null) {
return false;
}
String zname = zone.getId();
if (!(zone instanceof ZoneOffset)) {
--- 3346,3365 ----
perLocale.put(locale, names);
cache.put(id, new SoftReference<>(perLocale));
}
switch (type) {
case STD:
! return names[textStyle.zoneNameStyleIndex() + 1];
case DST:
! return names[textStyle.zoneNameStyleIndex() + 3];
}
! return names[textStyle.zoneNameStyleIndex() + 5];
}
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
! ZoneId zone = context.getValue(TemporalQuery.zoneId());
if (zone == null) {
return false;
}
String zname = zone.getId();
if (!(zone instanceof ZoneOffset)) {
*** 3505,3522 ****
this.textStyle = textStyle;
}
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
! Chronology chrono = context.getValue(Queries.chronology());
if (chrono == null) {
return false;
}
if (textStyle == null) {
buf.append(chrono.getId());
} else {
! buf.append(chrono.getId()); // TODO: Use symbols
}
return true;
}
@Override
--- 3907,3924 ----
this.textStyle = textStyle;
}
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
! Chronology chrono = context.getValue(TemporalQuery.chronology());
if (chrono == null) {
return false;
}
if (textStyle == null) {
buf.append(chrono.getId());
} else {
! buf.append(getChronologyName(chrono, context.getLocale()));
}
return true;
}
@Override
*** 3527,3556 ****
}
Set<Chronology> chronos = Chronology.getAvailableChronologies();
Chronology bestMatch = null;
int matchLen = -1;
for (Chronology chrono : chronos) {
! String id = chrono.getId();
! int idLen = id.length();
! if (idLen > matchLen && context.subSequenceEquals(text, position, id, 0, idLen)) {
bestMatch = chrono;
! matchLen = idLen;
}
}
if (bestMatch == null) {
return ~position;
}
context.setParsed(bestMatch);
return position + matchLen;
}
}
//-----------------------------------------------------------------------
/**
* Prints or parses a localized pattern.
*/
static final class LocalizedPrinterParser implements DateTimePrinterParser {
private final FormatStyle dateStyle;
private final FormatStyle timeStyle;
/**
* Constructor.
--- 3929,3982 ----
}
Set<Chronology> chronos = Chronology.getAvailableChronologies();
Chronology bestMatch = null;
int matchLen = -1;
for (Chronology chrono : chronos) {
! String name;
! if (textStyle == null) {
! name = chrono.getId();
! } else {
! name = getChronologyName(chrono, context.getLocale());
! }
! int nameLen = name.length();
! if (nameLen > matchLen && context.subSequenceEquals(text, position, name, 0, nameLen)) {
bestMatch = chrono;
! matchLen = nameLen;
}
}
if (bestMatch == null) {
return ~position;
}
context.setParsed(bestMatch);
return position + matchLen;
}
+
+ /**
+ * Returns the chronology name of the given chrono in the given locale
+ * if available, or the chronology Id otherwise. The regular ResourceBundle
+ * search path is used for looking up the chronology name.
+ *
+ * @param chrono the chronology, not null
+ * @param locale the locale, not null
+ * @return the chronology name of chrono in locale, or the id if no name is available
+ * @throws NullPointerException if chrono or locale is null
+ */
+ private String getChronologyName(Chronology chrono, Locale locale) {
+ String key = "calendarname." + chrono.getCalendarType();
+ String name = DateTimeTextProvider.getLocalizedResource(key, locale);
+ return name != null ? name : chrono.getId();
+ }
}
//-----------------------------------------------------------------------
/**
* Prints or parses a localized pattern.
*/
static final class LocalizedPrinterParser implements DateTimePrinterParser {
+ /** Cache of formatters. */
+ private static final ConcurrentMap<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>(16, 0.75f, 2);
+
private final FormatStyle dateStyle;
private final FormatStyle timeStyle;
/**
* Constructor.
*** 3576,3604 ****
return formatter(context.getLocale(), chrono).toPrinterParser(false).parse(context, text, position);
}
/**
* Gets the formatter to use.
*
* @param locale the locale to use, not null
* @param chrono the chronology to use, not null
* @return the formatter, not null
* @throws IllegalArgumentException if the formatter cannot be found
*/
private DateTimeFormatter formatter(Locale locale, Chronology chrono) {
! return DateTimeFormatStyleProvider.getInstance()
! .getFormatter(dateStyle, timeStyle, chrono, locale);
}
@Override
public String toString() {
return "Localized(" + (dateStyle != null ? dateStyle : "") + "," +
(timeStyle != null ? timeStyle : "") + ")";
}
}
-
//-----------------------------------------------------------------------
/**
* Prints or parses a localized pattern from a localized field.
* The specific formatter and parameters is not selected until the
* the field is to be printed or parsed.
--- 4002,4056 ----
return formatter(context.getLocale(), chrono).toPrinterParser(false).parse(context, text, position);
}
/**
* Gets the formatter to use.
+ * <p>
+ * The formatter will be the most appropriate to use for the date and time style in the locale.
+ * For example, some locales will use the month name while others will use the number.
*
* @param locale the locale to use, not null
* @param chrono the chronology to use, not null
* @return the formatter, not null
* @throws IllegalArgumentException if the formatter cannot be found
*/
private DateTimeFormatter formatter(Locale locale, Chronology chrono) {
! String key = chrono.getId() + '|' + locale.toString() + '|' + dateStyle + timeStyle;
! DateTimeFormatter formatter = FORMATTER_CACHE.get(key);
! if (formatter == null) {
! LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale);
! String pattern = lr.getJavaTimeDateTimePattern(
! convertStyle(timeStyle), convertStyle(dateStyle), chrono.getCalendarType());
! formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);
! DateTimeFormatter old = FORMATTER_CACHE.putIfAbsent(key, formatter);
! if (old != null) {
! formatter = old;
! }
! }
! return formatter;
! }
!
! /**
! * Converts the given FormatStyle to the java.text.DateFormat style.
! *
! * @param style the FormatStyle style
! * @return the int style, or -1 if style is null, indicating unrequired
! */
! private int convertStyle(FormatStyle style) {
! if (style == null) {
! return -1;
! }
! return style.ordinal(); // indices happen to align
}
@Override
public String toString() {
return "Localized(" + (dateStyle != null ? dateStyle : "") + "," +
(timeStyle != null ? timeStyle : "") + ")";
}
}
//-----------------------------------------------------------------------
/**
* Prints or parses a localized pattern from a localized field.
* The specific formatter and parameters is not selected until the
* the field is to be printed or parsed.
*** 3639,3669 ****
*/
private DateTimePrinterParser printerParser(Locale locale) {
WeekFields weekDef = WeekFields.of(locale);
TemporalField field = null;
switch (chr) {
case 'e':
field = weekDef.dayOfWeek();
break;
case 'w':
! field = weekDef.weekOfMonth();
break;
case 'W':
! field = weekDef.weekOfYear();
break;
default:
throw new IllegalStateException("unreachable");
}
return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE);
}
@Override
public String toString() {
! return String.format("WeekBased(%c%d)", chr, count);
}
}
-
//-------------------------------------------------------------------------
/**
* Length comparator.
*/
--- 4091,4160 ----
*/
private DateTimePrinterParser printerParser(Locale locale) {
WeekFields weekDef = WeekFields.of(locale);
TemporalField field = null;
switch (chr) {
+ case 'Y':
+ field = weekDef.weekBasedYear();
+ if (count == 2) {
+ return new ReducedPrinterParser(field, 2, 2000);
+ } else {
+ return new NumberPrinterParser(field, count, 19,
+ (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1);
+ }
case 'e':
+ case 'c':
field = weekDef.dayOfWeek();
break;
case 'w':
! field = weekDef.weekOfWeekBasedYear();
break;
case 'W':
! field = weekDef.weekOfMonth();
break;
default:
throw new IllegalStateException("unreachable");
}
return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE);
}
@Override
public String toString() {
! StringBuilder sb = new StringBuilder(30);
! sb.append("Localized(");
! if (chr == 'Y') {
! if (count == 1) {
! sb.append("WeekBasedYear");
! } else if (count == 2) {
! sb.append("ReducedValue(WeekBasedYear,2,2000)");
! } else {
! sb.append("WeekBasedYear,").append(count).append(",")
! .append(19).append(",")
! .append((count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD);
! }
! } else {
! switch (chr) {
! case 'c':
! case 'e':
! sb.append("DayOfWeek");
! break;
! case 'w':
! sb.append("WeekOfWeekBasedYear");
! break;
! case 'W':
! sb.append("WeekOfMonth");
! break;
! default:
! break;
! }
! sb.append(",");
! sb.append(count);
! }
! sb.append(")");
! return sb.toString();
}
}
//-------------------------------------------------------------------------
/**
* Length comparator.
*/
*** 3671,3677 ****
@Override
public int compare(String str1, String str2) {
return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length();
}
};
-
}
--- 4162,4167 ----