diff -r c588664d547e -r 55b829ca2334 jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java --- a/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Tue Feb 12 16:02:14 2013 +0400 +++ b/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Tue Feb 12 09:25:43 2013 -0800 @@ -81,11 +81,12 @@ 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.Chrono; import java.time.temporal.ChronoField; -import java.time.temporal.ISOChrono; -import java.time.temporal.ISOFields; +import java.time.temporal.IsoFields; import java.time.temporal.Queries; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; @@ -99,6 +100,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -289,10 +291,10 @@ * Appends the value of a date-time field to the formatter using a normal * output style. *
- * The value of the field will be output during a print. + * The value of the field will be output during a format. * If the value cannot be obtained then an exception will be thrown. *
- * The value will be printed as per the normal print of an integer value. + * The value will be printed as per the normal format of an integer value. * Only negative numbers will be signed. No padding will be added. *
* The parser for a variable width value such as this normally behaves greedily, @@ -313,12 +315,12 @@ * Appends the value of a date-time field to the formatter using a fixed * width, zero-padded approach. *
- * The value of the field will be output during a print. + * The value of the field will be output during a format. * If the value cannot be obtained then an exception will be thrown. *
* The value will be zero-padded on the left. If the size of the value * means that it cannot be printed within the width then an exception is thrown. - * If the value of the field is negative then an exception is thrown during printing. + * If the value of the field is negative then an exception is thrown during formatting. *
* This method supports a special technique of parsing known as 'adjacent value parsing'. * This technique solves the problem where a variable length value is followed by one or more @@ -368,9 +370,9 @@ /** * Appends the value of a date-time field to the formatter providing full - * control over printing. + * control over formatting. *
- * The value of the field will be output during a print. + * The value of the field will be output during a format. * If the value cannot be obtained then an exception will be thrown. *
* This method provides full control of the numeric formatting, including @@ -386,7 +388,7 @@ *
* If this method is invoked with equal minimum and maximum widths and a sign style of * {@code NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}. - * In this scenario, the printing and parsing behavior described there occur. + * In this scenario, the formatting and parsing behavior described there occur. * * @param field the field to append, not null * @param minWidth the minimum field width of the printed field, from 1 to 19 @@ -425,11 +427,11 @@ /** * Appends the reduced value of a date-time field to the formatter. *
- * This is typically used for printing and parsing a two digit year. + * This is typically used for formatting and parsing a two digit year. * The {@code width} is the printed and parsed width. * The {@code baseValue} is used during parsing to determine the valid range. *
- * For printing, the width is used to determine the number of characters to print. + * For formatting, the width is used to determine the number of characters to format. * The rightmost characters are output to match the width, left padding with zero. *
* For parsing, exactly the number of characters specified by the width are parsed. @@ -525,12 +527,12 @@ * Appends the text of a date-time field to the formatter using the full * text style. *
- * The text of the field will be output during a print. + * The text of the field will be output during a format. * The value must be within the valid range of the field. * If the value cannot be obtained then an exception will be thrown. * If the field has no textual representation, then the numeric value will be used. *
- * The value will be printed as per the normal print of an integer value. + * The value will be printed as per the normal format of an integer value. * Only negative numbers will be signed. No padding will be added. * * @param field the field to append, not null @@ -543,12 +545,12 @@ /** * Appends the text of a date-time field to the formatter. *
- * The text of the field will be output during a print. + * The text of the field will be output during a format. * The value must be within the valid range of the field. * If the value cannot be obtained then an exception will be thrown. * If the field has no textual representation, then the numeric value will be used. *
- * The value will be printed as per the normal print of an integer value. + * The value will be printed as per the normal format of an integer value. * Only negative numbers will be signed. No padding will be added. * * @param field the field to append, not null @@ -568,10 +570,10 @@ *
* The standard text outputting methods use the localized text in the JDK. * This method allows that text to be specified directly. - * The supplied map is not validated by the builder to ensure that printing or + * The supplied map is not validated by the builder to ensure that formatting or * parsing is possible, thus an invalid map may throw an error during later use. *
- * Supplying the map of text provides considerable flexibility in printing and parsing. + * Supplying the map of text provides considerable flexibility in formatting and parsing. * For example, a legacy application might require or supply the months of the * year as "JNY", "FBY", "MCH" etc. These do not match the standard set of text * for localized month names. Using this method, a map can be created which @@ -588,7 +590,7 @@ * Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd", * or as Roman numerals "I", "II", "III", "IV". *
- * During printing, the value is obtained and checked that it is in the valid range. + * During formatting, the value is obtained and checked that it is in the valid range. * If text is not available for the value then it is output as a number. * During parsing, the parser will match against the map of text and numeric values. * @@ -624,7 +626,7 @@ * They are converted to a date-time with a zone-offset of UTC and printed * using the standard ISO-8601 format. *
- * An alternative to this method is to print/parse the instant as a single + * An alternative to this method is to format/parse the instant as a single * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}. * * @return this, for chaining, not null @@ -637,22 +639,22 @@ /** * Appends the zone offset, such as '+01:00', to the formatter. *
- * This appends an instruction to print/parse the offset ID to the builder. + * This appends an instruction to format/parse the offset ID to the builder. * This is equivalent to calling {@code appendOffset("HH:MM:ss", "Z")}. * * @return this, for chaining, not null */ public DateTimeFormatterBuilder appendOffsetId() { - appendInternal(OffsetIdPrinterParser.INSTANCE_ID); + appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z); return this; } /** * Appends the zone offset, such as '+01:00', to the formatter. *
- * This appends an instruction to print/parse the offset ID to the builder. + * This appends an instruction to format/parse the offset ID to the builder. *
- * During printing, the offset is obtained using a mechanism equivalent + * 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 @@ -665,15 +667,18 @@ * The format of the offset is controlled by a pattern which must be one * of the following: *
- * The "no offset" text controls what text is printed when the offset is zero. + * The "no offset" text controls what text is printed when the total amount of + * the offset fields to be output is zero. * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'. * Three formats are accepted for parsing UTC - the "no offset" text, and the * plus and minus versions of zero defined by the pattern. @@ -683,7 +688,7 @@ * @return this, for chaining, not null */ public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) { - appendInternal(new OffsetIdPrinterParser(noOffsetText, pattern)); + appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText)); return this; } @@ -691,20 +696,48 @@ /** * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter. *
- * This appends an instruction to print/parse the zone ID to the builder. + * 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()}. *
- * During printing, the zone is obtained using a mechanism equivalent + * 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. *
- * During parsing, the zone is parsed and must match a known zone or offset. - * If the zone cannot be parsed then an exception is thrown unless the - * section of the formatter is optional. + * During parsing, the text must match a known zone or offset. + * There are two types of zone ID, offset-based, such as '+01:30' and + * region-based, such as 'Europe/London'. These are parsed differently. + * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser + * expects an offset-based zone and will not match region-based zones. + * The offset ID, such as '+02:30', may be at the start of the parse, + * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is + * equivalent to using {@link #appendOffset(String, String)} using the + * arguments 'HH:MM:ss' and the no offset string '0'. + * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot + * match a following offset ID, then {@link ZoneOffset#UTC} is selected. + * In all other cases, the list of known region-based zones is used to + * find the longest available match. If no match is found, and the parse + * starts with 'Z', then {@code ZoneOffset.UTC} is selected. + * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting. + *
+ * For example, the following will parse: + *
+ * "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") + ** * @return this, for chaining, not null * @see #appendZoneRegionId() @@ -718,21 +751,52 @@ * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter, * rejecting the zone ID if it is a {@code ZoneOffset}. *
- * This appends an instruction to print/parse the zone ID to the builder + * This appends an instruction to format/parse the zone ID to the builder * only if it is a region-based ID. *
- * During printing, the zone is obtained using a mechanism equivalent + * 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()}. *
- * During parsing, the zone is parsed and must match a known zone or offset. - * If the zone cannot be parsed then an exception is thrown unless the - * section of the formatter is optional. - * Note that parsing accepts offsets, whereas printing will never produce - * one, thus parsing is equivalent to {@code appendZoneId}. + * During parsing, the text must match a known zone or offset. + * There are two types of zone ID, offset-based, such as '+01:30' and + * region-based, such as 'Europe/London'. These are parsed differently. + * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser + * expects an offset-based zone and will not match region-based zones. + * The offset ID, such as '+02:30', may be at the start of the parse, + * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is + * equivalent to using {@link #appendOffset(String, String)} using the + * arguments 'HH:MM:ss' and the no offset string '0'. + * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot + * match a following offset ID, then {@link ZoneOffset#UTC} is selected. + * In all other cases, the list of known region-based zones is used to + * find the longest available match. If no match is found, and the parse + * starts with 'Z', then {@code ZoneOffset.UTC} is selected. + * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting. + *
+ * For example, the following will parse: + *
+ * "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") + *+ *
+ * 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 + * produce one. * * @return this, for chaining, not null * @see #appendZoneId() @@ -746,24 +810,52 @@ * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to * the formatter, using the best available zone ID. *
- * This appends an instruction to print/parse the best available + * This appends an instruction to format/parse the best available * zone or offset ID to the builder. * 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}. *
- * During printing, the zone is obtained using a mechanism equivalent + * 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. *
- * During parsing, the zone is parsed and must match a known zone or offset. - * If the zone cannot be parsed then an exception is thrown unless the - * section of the formatter is optional. + * During parsing, the text must match a known zone or offset. + * There are two types of zone ID, offset-based, such as '+01:30' and + * region-based, such as 'Europe/London'. These are parsed differently. + * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser + * expects an offset-based zone and will not match region-based zones. + * The offset ID, such as '+02:30', may be at the start of the parse, + * or prefixed by 'UT', 'UTC' or 'GMT'. The offset ID parsing is + * equivalent to using {@link #appendOffset(String, String)} using the + * arguments 'HH:MM:ss' and the no offset string '0'. + * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot + * match a following offset ID, then {@link ZoneOffset#UTC} is selected. + * In all other cases, the list of known region-based zones is used to + * find the longest available match. If no match is found, and the parse + * starts with 'Z', then {@code ZoneOffset.UTC} is selected. + * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting. *
- * This method is is identical to {@code appendZoneId()} except in the - * mechanism used to obtain the zone. + * For example, the following will parse: + *
+ * "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") + *+ *
+ * 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() @@ -776,9 +868,46 @@ /** * Appends the time-zone name, such as 'British Summer Time', to the formatter. *
- * This appends an instruction to print the textual name of the zone to the builder. + * This appends an instruction to format/parse the textual name of the zone to + * the builder. + *
+ * 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 + * will be the summer or winter time text as appropriate. + * If the lookup for text does not find any suitable reuslt, then the + * {@link ZoneId#getId() ID} will be printed instead. + * If the zone cannot be obtained then an exception is thrown unless the + * section of the formatter is optional. *
- * During printing, the zone is obtained using a mechanism equivalent + * During parsing, either the textual zone name, the zone ID or the offset + * is accepted. Many textual zone names are not unique, such as CST can be + * for both "Central Standard Time" and "China Standard Time". In this + * situation, the zone id will be determined by the region information from + * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard + * zone id for that area, for example, America/New_York for the America Eastern + * zone. The {@link #appendZoneText(TextStyle, Set)} may be used + * to specify a set of preferred {@link ZoneId} in this situation. + * + * @param textStyle the text style to use, not null + * @return this, for chaining, not null + */ + public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) { + appendInternal(new ZoneTextPrinterParser(textStyle, null)); + return this; + } + + /** + * Appends the time-zone name, such as 'British Summer Time', to the formatter. + *
+ * This appends an instruction to format/parse the textual name of the zone to + * the builder. + *
+ * 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()}. @@ -791,31 +920,51 @@ * If the zone cannot be obtained then an exception is thrown unless the * section of the formatter is optional. *
- * Parsing is not currently supported.
+ * During parsing, either the textual zone name, the zone ID or the offset
+ * is accepted. Many textual zone names are not unique, such as CST can be
+ * for both "Central Standard Time" and "China Standard Time". In this
+ * situation, the zone id will be determined by the region information from
+ * formatter's {@link DateTimeFormatter#getLocale() locale} and the standard
+ * zone id for that area, for example, America/New_York for the America Eastern
+ * zone. This method also allows a set of preferred {@link ZoneId} to be
+ * specified for parsing. The matched preferred zone id will be used if the
+ * textural zone name being parsed is not unique.
+ *
+ * If the zone cannot be parsed then an exception is thrown unless the
+ * section of the formatter is optional.
*
* @param textStyle the text style to use, not null
+ * @param preferredZones the set of preferred zone ids, not null
* @return this, for chaining, not null
*/
- public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) {
- // TODO: parsing of zone text?
-// * During parsing, either the textual zone name, the zone ID or the offset
-// * is accepted.
-// * If the zone cannot be parsed then an exception is thrown unless the
-// * section of the formatter is optional.
- appendInternal(new ZoneTextPrinterParser(textStyle));
+ public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle,
+ Set
+ * This appends an instruction to format/parse the chronology ID to the builder.
*
- * The chronology ID will be output during a print.
- * If the chronology cannot be obtained then an exception will be thrown.
+ * 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.
+ *
+ * During parsing, the chronology is parsed and must match one of the chronologies
+ * in {@link Chronology#getAvailableChronologies()}.
+ * If the chronology cannot be parsed then an exception is thrown unless the
+ * section of the formatter is optional.
+ * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
*
* @return this, for chaining, not null
*/
- public DateTimeFormatterBuilder appendChronoId() {
+ public DateTimeFormatterBuilder appendChronologyId() {
appendInternal(new ChronoPrinterParser(null));
return this;
}
@@ -823,14 +972,14 @@
/**
* Appends the chronology name to the formatter.
*
- * The calendar system name will be output during a print.
+ * The calendar system name will be output during a format.
* If the chronology cannot be obtained then an exception will be thrown.
* The calendar system name is obtained from the formatting symbols.
*
* @param textStyle the text style to use, not null
* @return this, for chaining, not null
*/
- public DateTimeFormatterBuilder appendChronoText(TextStyle textStyle) {
+ public DateTimeFormatterBuilder appendChronologyText(TextStyle textStyle) {
Objects.requireNonNull(textStyle, "textStyle");
appendInternal(new ChronoPrinterParser(textStyle));
return this;
@@ -840,39 +989,36 @@
/**
* Appends a localized date-time pattern to the formatter.
*
- * The pattern is resolved lazily using the locale being used during the print/parse
- * (stored in {@link DateTimeFormatter}.
+ * This appends a localized section to the builder, suitable for outputting
+ * a date, time or date-time combination. The format of the localized
+ * section is lazily looked up based on four items:
+ *
+ * During formatting, the chronology is obtained from the temporal object
+ * being formatted, which may have been overridden by
+ * {@link DateTimeFormatter#withChronology(Chronology)}.
*
- * The pattern can vary by chronology, although typically it doesn't.
- * This method uses the standard ISO chronology patterns.
+ * During parsing, if a chronology has already been parsed, then it is used.
+ * Otherwise the default from {@code DateTimeFormatter.withChronology(Chronology)}
+ * is used, with {@code IsoChronology} as the fallback.
+ *
+ * Note that this method provides similar functionality to methods on
+ * {@code DateFormat} such as {@link DateFormat#getDateTimeInstance(int, int)}.
*
* @param dateStyle the date style to use, null means no date required
* @param timeStyle the time style to use, null means no time required
* @return this, for chaining, not null
+ * @throws IllegalArgumentException if both the date and time styles are null
*/
public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle) {
- return appendLocalized(dateStyle, timeStyle, ISOChrono.INSTANCE);
- }
-
- /**
- * Appends a localized date-time pattern to the formatter.
- *
- * The pattern is resolved lazily using the locale being used during the print/parse,
- * stored in {@link DateTimeFormatter}.
- *
- * The pattern can vary by chronology, although typically it doesn't.
- * This method allows the chronology to be specified.
- *
- * @param dateStyle the date style to use, null means no date required
- * @param timeStyle the time style to use, null means no time required
- * @param chrono the chronology to use, not null
- * @return this, for chaining, not null
- */
- public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle, Chrono> chrono) {
- Objects.requireNonNull(chrono, "chrono");
- if (dateStyle != null || timeStyle != null) {
- appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle, chrono));
+ if (dateStyle == null && timeStyle == null) {
+ throw new IllegalArgumentException("Either the date or time style must be non-null");
}
+ appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle));
return this;
}
@@ -880,7 +1026,7 @@
/**
* Appends a character literal to the formatter.
*
- * This character will be output during a print.
+ * This character will be output during a format.
*
* @param literal the literal to append, not null
* @return this, for chaining, not null
@@ -893,7 +1039,7 @@
/**
* Appends a string literal to the formatter.
*
- * This string will be output during a print.
+ * This string will be output during a format.
*
* If the literal is empty, nothing is added to the formatter.
*
@@ -929,13 +1075,13 @@
}
/**
- * Appends a formatter to the builder which will optionally print/parse.
+ * Appends a formatter to the builder which will optionally format/parse.
*
* This method has the same effect as appending each of the constituent
* parts directly to this builder surrounded by an {@link #optionalStart()} and
* {@link #optionalEnd()}.
*
- * The formatter will print if data is available for all the fields contained within it.
+ * The formatter will format if data is available for all the fields contained within it.
* The formatter will parse if the string matches, otherwise no error is returned.
*
* @param formatter the formatter to add, not null
@@ -958,7 +1104,7 @@
*
* 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 #appendValueReduced reduced} two digit form is used.
- * For printing, this outputs the rightmost two digits. For parsing, this will parse using the
+ * 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}
*
- * ZoneId: 'I' outputs the zone ID, such as 'Europe/Paris'.
+ * ZoneId: This outputs the time-zone ID, such as 'Europe/Paris'.
+ * If the count of letters is two, then the time-zone ID is output.
+ * Any other count of letters throws {@code IllegalArgumentException}.
+ *
- * Offset X: This formats the offset using 'Z' when the offset is zero.
- * One letter outputs just the hour', such as '+01'
+ * Zone names: This outputs the display name of the time-zone ID.
+ * If the count of letters is one, two or three, then the short name is output.
+ * If the count of letters is four, then the full name is output.
+ * Five or more letters throws {@code IllegalArgumentException}.
+ *
+ * Offset X and x: This formats the offset based on the number of pattern letters.
+ * One letter outputs just the hour', such as '+01', unless the minute is non-zero
+ * in which case the minute is also output, such as '+0130'.
* Two letters outputs the hour and minute, without a colon, such as '+0130'.
* Three letters outputs the hour and minute, with a colon, such as '+01:30'.
* Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'.
* Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'.
+ * Six or more letters throws {@code IllegalArgumentException}.
+ * Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero,
+ * whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'.
+ *
- * Offset Z: This formats the offset using '+0000' or '+00:00' when the offset is zero.
- * One or 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'.
- *
- * Zone names: Time zone names ('z') cannot be parsed.
+ * 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 {@code IllegalArgumentException}.
+ * The output will be '+0000' when the offset is zero.
+ *
* Optional section: The optional section markers work exactly like calling {@link #optionalStart()}
* and {@link #optionalEnd()}.
@@ -1058,14 +1245,15 @@
* 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.
*
- * The pattern string is similar, but not identical, to {@link java.text.SimpleDateFormat SimpleDateFormat}.
+ * 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 'Z' and 'X' are extended.
+ * 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.
- * The pattern string is also similar, but not identical, to that defined by the
- * Unicode Common Locale Data Repository (CLDR).
*
* @param pattern the pattern to add, not null
* @return this, for chaining, not null
@@ -1107,27 +1295,34 @@
if (field != null) {
parseField(cur, count, field);
} else if (cur == 'z') {
- if (count < 4) {
- appendZoneText(TextStyle.SHORT);
+ if (count > 4) {
+ throw new IllegalArgumentException("Too many pattern letters: " + cur);
+ } else if (count == 4) {
+ appendZoneText(TextStyle.FULL);
} else {
- appendZoneText(TextStyle.FULL);
+ appendZoneText(TextStyle.SHORT);
}
- } else if (cur == 'I') {
+ } else if (cur == 'V') {
+ 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);
}
- if (count < 3) {
- appendOffset("+HHMM", "+0000");
- } else {
- appendOffset("+HH:MM", "+00:00");
- }
+ appendOffset("+HHMM", "+0000");
} else if (cur == 'X') {
if (count > 5) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
- appendOffset(OffsetIdPrinterParser.PATTERNS[count - 1], "Z");
+ appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z");
+ } else if (cur == 'x') {
+ 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) {
@@ -1196,7 +1391,6 @@
appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
}
break;
- case 'G':
case 'M':
case 'Q':
case 'E':
@@ -1220,6 +1414,7 @@
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
break;
+ case 'G':
case 'a':
switch (count) {
case 1:
@@ -1253,44 +1448,44 @@
/** Map of letters to fields. */
private static final Map
* This padding will pad to a fixed width using spaces.
*
- * An exception will be thrown during printing if the pad width
- * is exceeded.
+ * During formatting, the decorated element will be output and then padded
+ * to the specified width. An exception will be thrown during formatting if
+ * the pad width is exceeded.
+ *
+ * During parsing, the padding and decorated element are parsed.
+ * If parsing is lenient, then the pad width is treated as a maximum.
+ * If parsing is case insensitive, then the pad character is matched ignoring case.
+ * The padding is parsed greedily. Thus, if the decorated element starts with
+ * the pad character, it will not be parsed.
*
* @param padWidth the pad width, 1 or greater
* @return this, for chaining, not null
@@ -1316,8 +1518,15 @@
* This padding is intended for padding other than zero-padding.
* Zero-padding should be achieved using the appendValue methods.
*
- * An exception will be thrown during printing if the pad width
- * is exceeded.
+ * During formatting, the decorated element will be output and then padded
+ * to the specified width. An exception will be thrown during formatting if
+ * the pad width is exceeded.
+ *
+ * During parsing, the padding and decorated element are parsed.
+ * If parsing is lenient, then the pad width is treated as a maximum.
+ * If parsing is case insensitive, then the pad character is matched ignoring case.
+ * The padding is parsed greedily. Thus, if the decorated element starts with
+ * the pad character, it will not be parsed.
*
* @param padWidth the pad width, 1 or greater
* @param padChar the pad character
@@ -1338,19 +1547,19 @@
/**
* Mark the start of an optional section.
*
- * The output of printing can include optional sections, which may be nested.
+ * The output of formatting can include optional sections, which may be nested.
* An optional section is started by calling this method and ended by calling
* {@link #optionalEnd()} or by ending the build process.
*
* All elements in the optional section are treated as optional.
- * During printing, the section is only output if data is available in the
+ * During formatting, the section is only output if data is available in the
* {@code TemporalAccessor} for all the elements in the section.
* During parsing, the whole section may be missing from the parsed string.
*
* For example, consider a builder setup as
* {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2)}.
* The optional section ends automatically at the end of the builder.
- * During printing, the minute will only be output if its value can be obtained from the date-time.
+ * During formatting, the minute will only be output if its value can be obtained from the date-time.
* During parsing, the input will be successfully parsed whether the minute is present or not.
*
* @return this, for chaining, not null
@@ -1364,7 +1573,7 @@
/**
* Ends an optional section.
*
- * The output of printing can include optional sections, which may be nested.
+ * The output of formatting can include optional sections, which may be nested.
* An optional section is started by calling {@link #optionalStart()} and ended
* using this method (or at the end of the builder).
*
@@ -1374,13 +1583,13 @@
* on the formatter other than ending the (empty) optional section.
*
* All elements in the optional section are treated as optional.
- * During printing, the section is only output if data is available in the
+ * During formatting, the section is only output if data is available in the
* {@code TemporalAccessor} for all the elements in the section.
* During parsing, the whole section may be missing from the parsed string.
*
* For example, consider a builder setup as
* {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}.
- * During printing, the minute will only be output if its value can be obtained from the date-time.
+ * During formatting, the minute will only be output if its value can be obtained from the date-time.
* During parsing, the input will be successfully parsed whether the minute is present or not.
*
* @return this, for chaining, not null
@@ -1466,18 +1675,17 @@
//-----------------------------------------------------------------------
/**
- * Strategy for printing/parsing date-time information.
+ * Strategy for formatting/parsing date-time information.
*
- * The printer may print any part, or the whole, of the input date-time object.
- * Typically, a complete print is constructed from a number of smaller
+ * The printer may format any part, or the whole, of the input date-time object.
+ * Typically, a complete format is constructed from a number of smaller
* units, each outputting a single field.
*
* The parser may parse any piece of text from the input, storing the result
* in the context. Typically, each individual parser will just parse one
* field, such as the day-of-month, storing the value in the context.
- * Once the parse is complete, the caller will then convert the context
- * to a {@link DateTimeBuilder} to merge the parsed values to create the
- * desired object, such as a {@code LocalDate}.
+ * Once the parse is complete, the caller will then resolve the parsed values
+ * to create the desired object, such as a {@code LocalDate}.
*
* The parse position will be updated during the parse. Parsing will start at
* the specified index and the return value specifies the new parse position
@@ -1489,7 +1697,7 @@
* All implementations that can be instantiated must be final, immutable and thread-safe.
*
* The context is not a thread-safe object and a new instance will be created
- * for each print that occurs. The context must not be stored in an instance
+ * for each format that occurs. The context must not be stored in an instance
* variable or shared with any other threads.
*/
interface DateTimePrinterParser {
@@ -1497,17 +1705,17 @@
/**
* Prints the date-time object to the buffer.
*
- * The context holds information to use during the print.
+ * The context holds information to use during the format.
* It also contains the date-time information to be printed.
*
* The buffer must not be mutated beyond the content controlled by the implementation.
*
- * @param context the context to print using, not null
+ * @param context the context to format using, not null
* @param buf the buffer to append to, not null
* @return false if unable to query the value from the date-time, true otherwise
* @throws DateTimeException if the date-time cannot be printed successfully
*/
- boolean print(DateTimePrintContext context, StringBuilder buf);
+ boolean format(DateTimePrintContext context, StringBuilder buf);
/**
* Parses text into date-time information.
@@ -1557,14 +1765,14 @@
}
@Override
- public boolean print(DateTimePrintContext context, StringBuilder buf) {
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
int length = buf.length();
if (optional) {
context.startOptional();
}
try {
for (DateTimePrinterParser pp : printerParsers) {
- if (pp.print(context, buf) == false) {
+ if (pp.format(context, buf) == false) {
buf.setLength(length); // reset buffer
return true;
}
@@ -1640,14 +1848,14 @@
}
@Override
- public boolean print(DateTimePrintContext context, StringBuilder buf) {
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
int preLen = buf.length();
- if (printerParser.print(context, buf) == false) {
+ if (printerParser.format(context, buf) == false) {
return false;
}
int len = buf.length() - preLen;
if (len > padWidth) {
- throw new DateTimePrintException(
+ throw new DateTimeException(
"Cannot print as output of " + len + " characters exceeds pad width of " + padWidth);
}
for (int i = 0; i < padWidth - len; i++) {
@@ -1658,37 +1866,32 @@
@Override
public int parse(DateTimeParseContext context, CharSequence text, int position) {
+ // cache context before changed by decorated parser
+ final boolean strict = context.isStrict();
+ // parse
if (position > text.length()) {
throw new IndexOutOfBoundsException();
}
+ if (position == text.length()) {
+ return ~position; // no more characters in the string
+ }
int endPos = position + padWidth;
if (endPos > text.length()) {
- return ~position; // not enough characters in the string to meet the parse width
+ if (strict) {
+ return ~position; // not enough characters in the string to meet the parse width
+ }
+ endPos = text.length();
}
int pos = position;
- while (pos < endPos && text.charAt(pos) == padChar) {
+ while (pos < endPos && context.charEquals(text.charAt(pos), padChar)) {
pos++;
}
text = text.subSequence(0, endPos);
- int firstError = 0;
- while (pos >= position) {
- int resultPos = printerParser.parse(context, text, pos);
- if (resultPos < 0) {
- // parse of decorated field had an error
- if (firstError == 0) {
- firstError = resultPos;
- }
- // loop around in case the decorated parser can handle the padChar at the start
- pos--;
- continue;
- }
- if (resultPos != endPos) {
- return ~position; // parse of decorated field didn't parse to the end
- }
- return resultPos;
+ int resultPos = printerParser.parse(context, text, pos);
+ if (resultPos != endPos && strict) {
+ return ~(position + pos); // parse of decorated field didn't parse to the end
}
- // loop runs at least once, so firstError must be set by the time we get here
- return firstError; // return error from first parse of decorated field
+ return resultPos;
}
@Override
@@ -1708,7 +1911,7 @@
LENIENT;
@Override
- public boolean print(DateTimePrintContext context, StringBuilder buf) {
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
return true; // nothing to do here
}
@@ -1749,7 +1952,7 @@
}
@Override
- public boolean print(DateTimePrintContext context, StringBuilder buf) {
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
buf.append(literal);
return true;
}
@@ -1792,7 +1995,7 @@
}
@Override
- public boolean print(DateTimePrintContext context, StringBuilder buf) {
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
buf.append(literal);
return true;
}
@@ -1847,7 +2050,7 @@
/**
* Constructor.
*
- * @param field the field to print, not null
+ * @param field the field to format, not null
* @param minWidth the minimum field width, from 1 to 19
* @param maxWidth the maximum field width, from minWidth to 19
* @param signStyle the positive/negative sign style, not null
@@ -1864,7 +2067,7 @@
/**
* Constructor.
*
- * @param field the field to print, not null
+ * @param field the field to format, not null
* @param minWidth the minimum field width, from 1 to 19
* @param maxWidth the maximum field width, from minWidth to 19
* @param signStyle the positive/negative sign style, not null
@@ -1900,8 +2103,14 @@
}
@Override
- public boolean print(DateTimePrintContext context, StringBuilder buf) {
- Long valueLong = context.getValue(field);
+ 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;
}
@@ -1909,7 +2118,7 @@
DateTimeFormatSymbols symbols = context.getSymbols();
String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value)));
if (str.length() > maxWidth) {
- throw new DateTimePrintException("Field " + field.getName() +
+ throw new DateTimeException("Field " + field.getName() +
" cannot be printed as the value " + value +
" exceeds the maximum print width of " + maxWidth);
}
@@ -1934,7 +2143,7 @@
buf.append(symbols.getNegativeSign());
break;
case NOT_NEGATIVE:
- throw new DateTimePrintException("Field " + field.getName() +
+ throw new DateTimeException("Field " + field.getName() +
" cannot be printed as the value " + value +
" cannot be negative according to the SignStyle");
}
@@ -2057,11 +2266,9 @@
totalBig = totalBig.divide(BigInteger.TEN);
pos--;
}
- setValue(context, totalBig.longValue());
- } else {
- setValue(context, total);
+ return setValue(context, totalBig.longValue(), position, pos);
}
- return pos;
+ return setValue(context, total, position, pos);
}
/**
@@ -2069,9 +2276,19 @@
*
* @param context the context to store into, not null
* @param value the value
+ * @param errorPos the position of the field being parsed
+ * @param successPos the position after the field being parsed
+ * @return the new position
*/
- void setValue(DateTimeParseContext context, long value) {
- context.setParsedField(field, value);
+ 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
@@ -2097,7 +2314,7 @@
/**
* Constructor.
*
- * @param field the field to print, validated not null
+ * @param field the field to format, validated not null
* @param width the field width, from 1 to 18
* @param baseValue the base value
*/
@@ -2122,7 +2339,7 @@
}
@Override
- void setValue(DateTimeParseContext context, long value) {
+ int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
int lastPart = baseValue % range;
if (baseValue > 0) {
value = baseValue - lastPart + value;
@@ -2132,7 +2349,7 @@
if (value < baseValue) {
value += range;
}
- context.setParsedField(field, value);
+ return context.setParsedField(field, value, errorPos, successPos);
}
@Override
@@ -2191,7 +2408,7 @@
}
@Override
- public boolean print(DateTimePrintContext context, StringBuilder buf) {
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
Long value = context.getValue(field);
if (value == null) {
return false;
@@ -2257,8 +2474,7 @@
}
BigDecimal fraction = new BigDecimal(total).movePointLeft(pos - position);
long value = convertFromFraction(fraction);
- context.setParsedField(field, value);
- return pos;
+ return context.setParsedField(field, value, position, pos);
}
/**
@@ -2348,23 +2564,20 @@
}
@Override
- public boolean print(DateTimePrintContext context, StringBuilder buf) {
+ public boolean format(DateTimePrintContext context, StringBuilder buf) {
Long value = context.getValue(field);
if (value == null) {
return false;
}
- String text = null;
- if (field == ChronoField.ERA) {
- Chrono chrono = context.getTemporal().query(Queries.chrono());
- if (chrono == null) {
- chrono = ISOChrono.INSTANCE;
- }
- text = provider.getEraText(chrono, value, textStyle, context.getLocale());
+ 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(field, value, textStyle, context.getLocale());
+ text = provider.getText(chrono, field, value, textStyle, context.getLocale());
}
if (text == null) {
- return numberPrinterParser().print(context, buf);
+ return numberPrinterParser().format(context, buf);
}
buf.append(text);
return true;
@@ -2377,14 +2590,19 @@
throw new IndexOutOfBoundsException();
}
TextStyle style = (context.isStrict() ? textStyle : null);
- Iterator
+ *
* Symbol Meaning Presentation Examples
* ------ ------- ------------ -------
- * G era number/text 1; 01; AD; Anno Domini
+ * 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
@@ -985,10 +1131,11 @@
* n nano-of-second number 987654321
* N nano-of-day number 1234000000
*
- * I time-zone ID zoneId America/Los_Angeles
- * z time-zone name text Pacific Standard Time; PST
+ * 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;
- * X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15;
*
* p pad next pad modifier 1
*
@@ -1023,26 +1170,66 @@
*
+ * Pattern Equivalent builder methods
+ * VV appendZoneId()
+ *
*
+ * Pattern Equivalent builder methods
+ * z appendZoneText(TextStyle.SHORT)
+ * zz appendZoneText(TextStyle.SHORT)
+ * zzz appendZoneText(TextStyle.SHORT)
+ * zzzz appendZoneText(TextStyle.FULL)
+ *
+ *
+ * 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")
+ *
*
+ * Pattern Equivalent builder methods
+ * Z appendOffset("+HHMM","+0000")
+ * ZZ appendOffset("+HHMM","+0000")
+ * ZZZ appendOffset("+HHMM","+0000")
+ *
*