--- a/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Wed May 15 15:01:59 2013 +0100
+++ b/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Wed May 15 07:48:57 2013 -0700
@@ -77,6 +77,7 @@
import java.math.RoundingMode;
import java.text.ParsePosition;
import java.time.DateTimeException;
+import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -142,7 +143,7 @@
* can be used, see {@link #appendPattern(String)}.
* In practice, this simply parses the pattern and calls other methods on the builder.
*
- * <h3>Specification for implementors</h3>
+ * @implSpec
* This class is a mutable builder intended for use from a single thread.
*
* @since 1.8
@@ -187,6 +188,44 @@
private int valueParserIndex = -1;
/**
+ * Gets the formatting pattern for date and time styles for a locale and chronology.
+ * The locale and chronology are used to lookup the locale specific format
+ * for the requested dateStyle and/or timeStyle.
+ *
+ * @param dateStyle the FormatStyle for the date
+ * @param timeStyle the FormatStyle for the time
+ * @param chrono the Chronology, non-null
+ * @param locale the locale, non-null
+ * @return the locale and Chronology specific formatting pattern
+ * @throws IllegalArgumentException if both dateStyle and timeStyle are null
+ */
+ public static String getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle,
+ Chronology chrono, Locale locale) {
+ Objects.requireNonNull(locale, "locale");
+ Objects.requireNonNull(chrono, "chrono");
+ if (dateStyle == null && timeStyle == null) {
+ throw new IllegalArgumentException("Either dateStyle or timeStyle must be non-null");
+ }
+ LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale);
+ String pattern = lr.getJavaTimeDateTimePattern(
+ convertStyle(timeStyle), convertStyle(dateStyle), chrono.getCalendarType());
+ return pattern;
+ }
+
+ /**
+ * 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 un-required
+ */
+ private static int convertStyle(FormatStyle style) {
+ if (style == null) {
+ return -1;
+ }
+ return style.ordinal(); // indices happen to align
+ }
+
+ /**
* Constructs a new instance of the builder.
*/
public DateTimeFormatterBuilder() {
@@ -344,7 +383,7 @@
*/
public DateTimeFormatterBuilder appendValue(TemporalField field) {
Objects.requireNonNull(field, "field");
- active.valueParserIndex = appendInternal(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL));
+ appendValue(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL));
return this;
}
@@ -360,15 +399,15 @@
* If the value of the field is negative then an exception is thrown during formatting.
* <p>
* 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
+ * This technique solves the problem where a value, variable or fixed width, is followed by one or more
* fixed length values. The standard parser is greedy, and thus it would normally
* steal the digits that are needed by the fixed width value parsers that follow the
* variable width one.
* <p>
* No action is required to initiate 'adjacent value parsing'.
- * When a call to {@code appendValue} with a variable width is made, the builder
+ * When a call to {@code appendValue} is made, the builder
* enters adjacent value parsing setup mode. If the immediately subsequent method
- * call or calls on the same builder are to this method, then the parser will reserve
+ * call or calls on the same builder are for a fixed width value, then the parser will reserve
* space so that the fixed width values can be parsed.
* <p>
* For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);}
@@ -381,7 +420,7 @@
* nothing for the month.
* <p>
* Adjacent value parsing applies to each set of fixed width not-negative values in the parser
- * that immediately follow any kind of variable width value.
+ * that immediately follow any kind of value, variable or fixed width.
* Calling any other append method will end the setup of adjacent value parsing.
* Thus, in the unlikely event that you need to avoid adjacent value parsing behavior,
* simply add the {@code appendValue} to another {@code DateTimeFormatterBuilder}
@@ -402,7 +441,8 @@
throw new IllegalArgumentException("The width must be from 1 to 19 inclusive but was " + width);
}
NumberPrinterParser pp = new NumberPrinterParser(field, width, width, SignStyle.NOT_NEGATIVE);
- return appendFixedWidth(width, pp);
+ appendValue(pp);
+ return this;
}
/**
@@ -420,8 +460,10 @@
* This behavior can be affected by 'adjacent value parsing'.
* See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details.
* <p>
- * In strict parsing mode, the minimum number of parsed digits is {@code minWidth}.
- * In lenient parsing mode, the minimum number of parsed digits is one.
+ * In strict parsing mode, the minimum number of parsed digits is {@code minWidth}
+ * and the maximum is {@code maxWidth}.
+ * In lenient parsing mode, the minimum number of parsed digits is one
+ * and the maximum is 19 (except as limited by adjacent value parsing).
* <p>
* 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)}.
@@ -452,17 +494,13 @@
maxWidth + " < " + minWidth);
}
NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle);
- if (minWidth == maxWidth) {
- appendInternal(pp);
- } else {
- active.valueParserIndex = appendInternal(pp);
- }
+ appendValue(pp);
return this;
}
//-----------------------------------------------------------------------
/**
- * Appends the reduced value of a date-time field to the formatter.
+ * Appends the reduced value of a date-time field with fixed width to the formatter.
* <p>
* This is typically used for formatting and parsing a two digit year.
* The {@code width} is the printed and parsed width.
@@ -471,51 +509,116 @@
* 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.
* <p>
- * For parsing, exactly the number of characters specified by the width are parsed.
- * This is incomplete information however, so the base value is used to complete the parse.
- * The base value is the first valid value in a range of ten to the power of width.
+ * For strict parsing, the number of characters allowed by the width are parsed.
+ * For lenient parsing, the number of characters must be at least 1 and less than 10.
+ * If the number of digits parsed is equal to {@code width} and the value is positive,
+ * the value of the field is computed to be the first number greater than
+ * or equal to the {@code baseValue} with the same least significant characters,
+ * otherwise the value parsed is the field value.
+ * This allows a reduced value to be entered for values in range of the baseValue
+ * and width and absolute values can be entered for values outside the range.
* <p>
* For example, a base value of {@code 1980} and a width of {@code 2} will have
* valid values from {@code 1980} to {@code 2079}.
* During parsing, the text {@code "12"} will result in the value {@code 2012} as that
- * is the value within the range where the last two digits are "12".
- * <p>
- * This is a fixed width parser operating using 'adjacent value parsing'.
- * See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details.
+ * is the value within the range where the last two characters are "12".
+ * Compare with lenient parsing the text {@code "1915"} that will result in the
+ * value {@code 1915}.
*
* @param field the field to append, not null
- * @param width the width of the printed and parsed field, from 1 to 18
+ * @param width the field width of the printed and parsed field, from 1 to 10
+ * @param baseValue the base value of the range of valid values
+ * @return this, for chaining, not null
+ * @throws IllegalArgumentException if the width or base value is invalid
+ * @see #appendValueReduced(java.time.temporal.TemporalField, int, int, int)
+ */
+ public DateTimeFormatterBuilder appendValueReduced(TemporalField field,
+ int width, int baseValue) {
+ return appendValueReduced(field, width, width, baseValue);
+ }
+
+ /**
+ * Appends the reduced value of a date-time field with a flexible width to the formatter.
+ * <p>
+ * This is typically used for formatting and parsing a two digit year
+ * but allowing for the year value to be up to maxWidth.
+ * <p>
+ * For formatting, the {@code width} and {@code maxWidth} are used to
+ * determine the number of characters to format.
+ * If the value of the field is within the range of the {@code baseValue} using
+ * {@code width} characters then the reduced value is formatted otherwise the value is
+ * truncated to fit {@code maxWidth}.
+ * The rightmost characters are output to match the width, left padding with zero.
+ * <p>
+ * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed.
+ * For lenient parsing, the number of characters must be at least 1 and less than 10.
+ * If the number of digits parsed is equal to {@code width} and the value is positive,
+ * the value of the field is computed to be the first number greater than
+ * or equal to the {@code baseValue} with the same least significant characters,
+ * otherwise the value parsed is the field value.
+ * This allows a reduced value to be entered for values in range of the baseValue
+ * and width and absolute values can be entered for values outside the range.
+ * <p>
+ * For example, a base value of {@code 1980} and a width of {@code 2} will have
+ * valid values from {@code 1980} to {@code 2079}.
+ * During parsing, the text {@code "12"} will result in the value {@code 2012} as that
+ * is the value within the range where the last two characters are "12".
+ * Compare with parsing the text {@code "1915"} that will result in the
+ * value {@code 1915}.
+ *
+ * @param field the field to append, not null
+ * @param width the field width of the printed and parsed field, from 1 to 10
+ * @param maxWidth the maximum field width of the printed field, from 1 to 10
* @param baseValue the base value of the range of valid values
* @return this, for chaining, not null
* @throws IllegalArgumentException if the width or base value is invalid
*/
- public DateTimeFormatterBuilder appendValueReduced(
- TemporalField field, int width, int baseValue) {
+ public DateTimeFormatterBuilder appendValueReduced(TemporalField field,
+ int width, int maxWidth, int baseValue) {
Objects.requireNonNull(field, "field");
- ReducedPrinterParser pp = new ReducedPrinterParser(field, width, baseValue);
- appendFixedWidth(width, pp);
+ ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, baseValue);
+ appendValue(pp);
return this;
}
/**
- * Appends a fixed width printer-parser.
+ * Appends a fixed or variable width printer-parser handling adjacent value mode.
+ * If a PrinterParser is not active then the new PrinterParser becomes
+ * the active PrinterParser.
+ * Otherwise, the active PrinterParser is modified depending on the new PrinterParser.
+ * If the new PrinterParser is fixed width and has sign style {@code NOT_NEGATIVE}
+ * then its width is added to the active PP and
+ * the new PrinterParser is forced to be fixed width.
+ * If the new PrinterParser is variable width, the active PrinterParser is changed
+ * to be fixed width and the new PrinterParser becomes the active PP.
*
- * @param width the width
* @param pp the printer-parser, not null
* @return this, for chaining, not null
*/
- private DateTimeFormatterBuilder appendFixedWidth(int width, NumberPrinterParser pp) {
+ private DateTimeFormatterBuilder appendValue(NumberPrinterParser pp) {
if (active.valueParserIndex >= 0) {
+ final int activeValueParser = active.valueParserIndex;
+
// adjacent parsing mode, update setting in previous parsers
- NumberPrinterParser basePP = (NumberPrinterParser) active.printerParsers.get(active.valueParserIndex);
- basePP = basePP.withSubsequentWidth(width);
- int activeValueParser = active.valueParserIndex;
- active.printerParsers.set(active.valueParserIndex, basePP);
- appendInternal(pp.withFixedWidth());
- active.valueParserIndex = activeValueParser;
+ NumberPrinterParser basePP = (NumberPrinterParser) active.printerParsers.get(activeValueParser);
+ if (pp.minWidth == pp.maxWidth && pp.signStyle == SignStyle.NOT_NEGATIVE) {
+ // Append the width to the subsequentWidth of the active parser
+ basePP = basePP.withSubsequentWidth(pp.maxWidth);
+ // Append the new parser as a fixed width
+ appendInternal(pp.withFixedWidth());
+ // Retain the previous active parser
+ active.valueParserIndex = activeValueParser;
+ } else {
+ // Modify the active parser to be fixed width
+ basePP = basePP.withFixedWidth();
+ // The new parser becomes the mew active parser
+ active.valueParserIndex = appendInternal(pp);
+ }
+ // Replace the modified parser with the updated one
+ active.printerParsers.set(activeValueParser, basePP);
} else {
- // not adjacent parsing
- appendInternal(pp);
+ // The new Parser becomes the active parser
+ active.valueParserIndex = appendInternal(pp);
}
return this;
}
@@ -657,11 +760,24 @@
//-----------------------------------------------------------------------
/**
- * Appends an instant using ISO-8601 to the formatter.
+ * Appends an instant using ISO-8601 to the formatter, formatting fractional
+ * digits in groups of three.
* <p>
* Instants have a fixed output format.
- * They are converted to a date-time with a zone-offset of UTC and printed
+ * They are converted to a date-time with a zone-offset of UTC and formatted
* using the standard ISO-8601 format.
+ * With this method, formatting nano-of-second outputs zero, three, six
+ * or nine digits digits as necessary.
+ * The localized decimal style is not used.
+ * <p>
+ * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS}
+ * and optionally (@code NANO_OF_SECOND). The value of {@code INSTANT_SECONDS}
+ * may be outside the maximum range of {@code LocalDateTime}.
+ * <p>
+ * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing.
+ * The end-of-day time of '24:00' is handled as midnight at the start of the following day.
+ * The leap-second time of '23:59:59' is handled to some degree, see
+ * {@link DateTimeFormatter#parsedLeapSecond()} for full details.
* <p>
* 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)}.
@@ -669,11 +785,55 @@
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder appendInstant() {
- appendInternal(new InstantPrinterParser());
+ appendInternal(new InstantPrinterParser(-2));
return this;
}
/**
+ * Appends an instant using ISO-8601 to the formatter with control over
+ * the number of fractional digits.
+ * <p>
+ * Instants have a fixed output format, although this method provides some
+ * control over the fractional digits. They are converted to a date-time
+ * with a zone-offset of UTC and printed using the standard ISO-8601 format.
+ * The localized decimal style is not used.
+ * <p>
+ * The {@code fractionalDigits} parameter allows the output of the fractional
+ * second to be controlled. Specifying zero will cause no fractional digits
+ * to be output. From 1 to 9 will output an increasing number of digits, using
+ * zero right-padding if necessary. The special value -1 is used to output as
+ * many digits as necessary to avoid any trailing zeroes.
+ * <p>
+ * When parsing in strict mode, the number of parsed digits must match the
+ * fractional digits. When parsing in lenient mode, any number of fractional
+ * digits from zero to nine are accepted.
+ * <p>
+ * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS}
+ * and optionally (@code NANO_OF_SECOND). The value of {@code INSTANT_SECONDS}
+ * may be outside the maximum range of {@code LocalDateTime}.
+ * <p>
+ * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing.
+ * The end-of-day time of '24:00' is handled as midnight at the start of the following day.
+ * The leap-second time of '23:59:59' is handled to some degree, see
+ * {@link DateTimeFormatter#parsedLeapSecond()} for full details.
+ * <p>
+ * 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)}.
+ *
+ * @param fractionalDigits the number of fractional second digits to format with,
+ * from 0 to 9, or -1 to use as many digits as necessary
+ * @return this, for chaining, not null
+ */
+ public DateTimeFormatterBuilder appendInstant(int fractionalDigits) {
+ if (fractionalDigits < -1 || fractionalDigits > 9) {
+ throw new IllegalArgumentException("The fractional digits must be from -1 to 9 inclusive but was " + fractionalDigits);
+ }
+ appendInternal(new InstantPrinterParser(fractionalDigits));
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
* 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.
@@ -1049,7 +1209,7 @@
* <p>
* 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.
+ * The calendar system name is obtained from the Chronology.
*
* @param textStyle the text style to use, not null
* @return this, for chaining, not null
@@ -1838,7 +1998,7 @@
* 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.
+ * Numbers will be printed and parsed using the standard DecimalStyle.
* The resolver style will be {@link ResolverStyle#SMART SMART}.
* <p>
* Calling this method will end any open optional sections by repeatedly
@@ -1858,7 +2018,7 @@
* 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.
+ * Numbers will be printed and parsed using the standard DecimalStyle.
* The resolver style will be {@link ResolverStyle#SMART SMART}.
* <p>
* Calling this method will end any open optional sections by repeatedly
@@ -1898,7 +2058,7 @@
optionalEnd();
}
CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
- return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD,
+ return new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD,
resolverStyle, null, chrono, null);
}
@@ -1921,7 +2081,7 @@
* for the next parser. If an error occurs, the returned index will be negative
* and will have the error position encoded using the complement operator.
*
- * <h3>Specification for implementors</h3>
+ * @implSpec
* This interface must be implemented with care to ensure other classes operate correctly.
* All implementations that can be instantiated must be final, immutable and thread-safe.
* <p>
@@ -2282,24 +2442,25 @@
/**
* Array of 10 to the power of n.
*/
- static final int[] EXCEED_POINTS = new int[] {
- 0,
- 10,
- 100,
- 1000,
- 10000,
- 100000,
- 1000000,
- 10000000,
- 100000000,
- 1000000000,
+ static final long[] EXCEED_POINTS = new long[] {
+ 0L,
+ 10L,
+ 100L,
+ 1000L,
+ 10000L,
+ 100000L,
+ 1000000L,
+ 10000000L,
+ 100000000L,
+ 1000000000L,
+ 10000000000L,
};
final TemporalField field;
final int minWidth;
- private final int maxWidth;
+ final int maxWidth;
private final SignStyle signStyle;
- private final int subsequentWidth;
+ final int subsequentWidth;
/**
* Constructor.
@@ -2328,7 +2489,7 @@
* @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater,
* -1 if fixed width due to active adjacent parsing
*/
- private NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle, int subsequentWidth) {
+ protected NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle, int subsequentWidth) {
// validated by caller
this.field = field;
this.minWidth = minWidth;
@@ -2343,6 +2504,9 @@
* @return a new updated printer-parser, not null
*/
NumberPrinterParser withFixedWidth() {
+ if (subsequentWidth == -1) {
+ return this;
+ }
return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, -1);
}
@@ -2363,24 +2527,24 @@
return false;
}
long value = getValue(valueLong);
- DateTimeFormatSymbols symbols = context.getSymbols();
+ DecimalStyle decimalStyle = context.getDecimalStyle();
String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value)));
if (str.length() > maxWidth) {
throw new DateTimeException("Field " + field.getName() +
" cannot be printed as the value " + value +
" exceeds the maximum print width of " + maxWidth);
}
- str = symbols.convertNumberToI18N(str);
+ str = decimalStyle.convertNumberToI18N(str);
if (value >= 0) {
switch (signStyle) {
case EXCEEDS_PAD:
if (minWidth < 19 && value >= EXCEED_POINTS[minWidth]) {
- buf.append(symbols.getPositiveSign());
+ buf.append(decimalStyle.getPositiveSign());
}
break;
case ALWAYS:
- buf.append(symbols.getPositiveSign());
+ buf.append(decimalStyle.getPositiveSign());
break;
}
} else {
@@ -2388,7 +2552,7 @@
case NORMAL:
case EXCEEDS_PAD:
case ALWAYS:
- buf.append(symbols.getNegativeSign());
+ buf.append(decimalStyle.getNegativeSign());
break;
case NOT_NEGATIVE:
throw new DateTimeException("Field " + field.getName() +
@@ -2397,7 +2561,7 @@
}
}
for (int i = 0; i < minWidth - str.length(); i++) {
- buf.append(symbols.getZeroDigit());
+ buf.append(decimalStyle.getZeroDigit());
}
buf.append(str);
return true;
@@ -2426,13 +2590,13 @@
char sign = text.charAt(position); // IOOBE if invalid position
boolean negative = false;
boolean positive = false;
- if (sign == context.getSymbols().getPositiveSign()) {
+ if (sign == context.getDecimalStyle().getPositiveSign()) {
if (signStyle.parse(true, context.isStrict(), minWidth == maxWidth) == false) {
return ~position;
}
positive = true;
position++;
- } else if (sign == context.getSymbols().getNegativeSign()) {
+ } else if (sign == context.getDecimalStyle().getNegativeSign()) {
if (signStyle.parse(false, context.isStrict(), minWidth == maxWidth) == false) {
return ~position;
}
@@ -2448,7 +2612,7 @@
if (minEndPos > length) {
return ~position;
}
- int effMaxWidth = maxWidth + Math.max(subsequentWidth, 0);
+ int effMaxWidth = (context.isStrict() || isFixedWidth() ? maxWidth : 9) + Math.max(subsequentWidth, 0);
long total = 0;
BigInteger totalBig = null;
int pos = position;
@@ -2456,7 +2620,7 @@
int maxEndPos = Math.min(pos + effMaxWidth, length);
while (pos < maxEndPos) {
char ch = text.charAt(pos++);
- int digit = context.getSymbols().convertToDigit(ch);
+ int digit = context.getDecimalStyle().convertToDigit(ch);
if (digit < 0) {
pos--;
if (pos < minEndPos) {
@@ -2550,62 +2714,110 @@
*/
static final class ReducedPrinterParser extends NumberPrinterParser {
private final int baseValue;
- private final int range;
/**
* Constructor.
*
* @param field the field to format, validated not null
- * @param width the field width, from 1 to 18
+ * @param minWidth the minimum field width, from 1 to 10
+ * @param maxWidth the maximum field width, from 1 to 10
* @param baseValue the base value
*/
- ReducedPrinterParser(TemporalField field, int width, int baseValue) {
- super(field, width, width, SignStyle.NOT_NEGATIVE);
- if (width < 1 || width > 18) {
- throw new IllegalArgumentException("The width must be from 1 to 18 inclusive but was " + width);
+ ReducedPrinterParser(TemporalField field, int minWidth, int maxWidth,
+ int baseValue) {
+ this(field, minWidth, maxWidth, baseValue, 0);
+ if (minWidth < 1 || minWidth > 10) {
+ throw new IllegalArgumentException("The minWidth must be from 1 to 10 inclusive but was " + minWidth);
+ }
+ if (maxWidth < 1 || maxWidth > 10) {
+ throw new IllegalArgumentException("The maxWidth must be from 1 to 10 inclusive but was " + minWidth);
+ }
+ if (maxWidth < minWidth) {
+ throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " +
+ maxWidth + " < " + minWidth);
}
if (field.range().isValidValue(baseValue) == false) {
throw new IllegalArgumentException("The base value must be within the range of the field");
}
- this.baseValue = baseValue;
- this.range = EXCEED_POINTS[width];
- if ((((long) baseValue) + range) > Integer.MAX_VALUE) {
+ if ((((long) baseValue) + EXCEED_POINTS[maxWidth]) > Integer.MAX_VALUE) {
throw new DateTimeException("Unable to add printer-parser as the range exceeds the capacity of an int");
}
}
+ /**
+ * Constructor.
+ * The arguments have already been checked.
+ *
+ * @param field the field to format, validated not null
+ * @param minWidth the minimum field width, from 1 to 10
+ * @param maxWidth the maximum field width, from 1 to 10
+ * @param baseValue the base value
+ * @param subsequentWidth the subsequentWidth for this instance
+ */
+ private ReducedPrinterParser(TemporalField field, int minWidth, int maxWidth,
+ int baseValue, int subsequentWidth) {
+ super(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
+ this.baseValue = baseValue;
+ }
+
@Override
long getValue(long value) {
- return Math.abs(value % range);
+ long absValue = Math.abs(value);
+ if (value >= baseValue && value < baseValue + EXCEED_POINTS[minWidth]) {
+ // Use the reduced value if it fits in minWidth
+ return absValue % EXCEED_POINTS[minWidth];
+ }
+ // Otherwise truncate to fit in maxWidth
+ return absValue % EXCEED_POINTS[maxWidth];
}
@Override
int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
- int lastPart = baseValue % range;
- if (baseValue > 0) {
- value = baseValue - lastPart + value;
- } else {
- value = baseValue - lastPart - value;
- }
- if (value < baseValue) {
- value += range;
+ int parseLen = successPos - errorPos;
+ if (parseLen == minWidth && value >= 0) {
+ long range = EXCEED_POINTS[minWidth];
+ long lastPart = baseValue % range;
+ long basePart = baseValue - lastPart;
+ if (baseValue > 0) {
+ value = basePart + value;
+ } else {
+ value = basePart - value;
+ }
+ if (basePart != 0 && value < baseValue) {
+ value += range;
+ }
}
return context.setParsedField(field, value, errorPos, successPos);
}
+ /**
+ * Returns a new instance with fixed width flag set.
+ *
+ * @return a new updated printer-parser, not null
+ */
@Override
- NumberPrinterParser withFixedWidth() {
- return this;
+ ReducedPrinterParser withFixedWidth() {
+ if (subsequentWidth == -1) {
+ return this;
+ }
+ return new ReducedPrinterParser(field, minWidth, maxWidth, baseValue, -1);
}
+ /**
+ * Returns a new instance with an updated subsequent width.
+ *
+ * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater
+ * @return a new updated printer-parser, not null
+ */
@Override
- boolean isFixedWidth() {
- return true;
+ ReducedPrinterParser withSubsequentWidth(int subsequentWidth) {
+ return new ReducedPrinterParser(field, minWidth, maxWidth, baseValue,
+ this.subsequentWidth + subsequentWidth);
}
@Override
public String toString() {
- return "ReducedValue(" + field.getName() + "," + minWidth + "," + baseValue + ")";
+ return "ReducedValue(" + field.getName() + "," + minWidth + "," + maxWidth + "," + baseValue + ")";
}
}
@@ -2654,24 +2866,24 @@
if (value == null) {
return false;
}
- DateTimeFormatSymbols symbols = context.getSymbols();
+ DecimalStyle decimalStyle = context.getDecimalStyle();
BigDecimal fraction = convertToFraction(value);
if (fraction.scale() == 0) { // scale is zero if value is zero
if (minWidth > 0) {
if (decimalPoint) {
- buf.append(symbols.getDecimalSeparator());
+ buf.append(decimalStyle.getDecimalSeparator());
}
for (int i = 0; i < minWidth; i++) {
- buf.append(symbols.getZeroDigit());
+ buf.append(decimalStyle.getZeroDigit());
}
}
} else {
int outputScale = Math.min(Math.max(fraction.scale(), minWidth), maxWidth);
fraction = fraction.setScale(outputScale, RoundingMode.FLOOR);
String str = fraction.toPlainString().substring(2);
- str = symbols.convertNumberToI18N(str);
+ str = decimalStyle.convertNumberToI18N(str);
if (decimalPoint) {
- buf.append(symbols.getDecimalSeparator());
+ buf.append(decimalStyle.getDecimalSeparator());
}
buf.append(str);
}
@@ -2688,7 +2900,7 @@
return (effectiveMin > 0 ? ~position : position);
}
if (decimalPoint) {
- if (text.charAt(position) != context.getSymbols().getDecimalSeparator()) {
+ if (text.charAt(position) != context.getDecimalStyle().getDecimalSeparator()) {
// valid if whole field is optional, invalid if minimum width
return (effectiveMin > 0 ? ~position : position);
}
@@ -2703,7 +2915,7 @@
int pos = position;
while (pos < maxEndPos) {
char ch = text.charAt(pos++);
- int digit = context.getSymbols().convertToDigit(ch);
+ int digit = context.getDecimalStyle().convertToDigit(ch);
if (digit < 0) {
if (pos < minEndPos) {
return ~position; // need at least min width digits
@@ -2883,43 +3095,50 @@
// seconds per day = 86400
private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L;
private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L;
- private static final CompositePrinterParser PARSER = new DateTimeFormatterBuilder()
- .parseCaseInsensitive()
- .append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral('T')
- .append(DateTimeFormatter.ISO_LOCAL_TIME).appendLiteral('Z')
- .toFormatter().toPrinterParser(false);
+ private final int fractionalDigits;
- InstantPrinterParser() {
+ InstantPrinterParser(int fractionalDigits) {
+ this.fractionalDigits = fractionalDigits;
}
@Override
public boolean format(DateTimePrintContext context, StringBuilder buf) {
// use INSTANT_SECONDS, thus this code is not bound by Instant.MAX
Long inSecs = context.getValue(INSTANT_SECONDS);
- Long inNanos = context.getValue(NANO_OF_SECOND);
- if (inSecs == null || inNanos == null) {
+ Long inNanos = null;
+ if (context.getTemporal().isSupported(NANO_OF_SECOND)) {
+ inNanos = context.getTemporal().getLong(NANO_OF_SECOND);
+ }
+ if (inSecs == null) {
return false;
}
long inSec = inSecs;
- int inNano = NANO_OF_SECOND.checkValidIntValue(inNanos);
+ int inNano = NANO_OF_SECOND.checkValidIntValue(inNanos != null ? inNanos : 0);
+ // format mostly using LocalDateTime.toString
if (inSec >= -SECONDS_0000_TO_1970) {
// current era
long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970;
long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1;
long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS);
- LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, inNano, ZoneOffset.UTC);
+ LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC);
if (hi > 0) {
buf.append('+').append(hi);
}
- buf.append(ldt).append('Z');
+ buf.append(ldt);
+ if (ldt.getSecond() == 0) {
+ buf.append(":00");
+ }
} else {
// before current era
long zeroSecs = inSec + SECONDS_0000_TO_1970;
long hi = zeroSecs / SECONDS_PER_10000_YEARS;
long lo = zeroSecs % SECONDS_PER_10000_YEARS;
- LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, inNano, ZoneOffset.UTC);
+ LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC);
int pos = buf.length();
- buf.append(ldt).append('Z');
+ buf.append(ldt);
+ if (ldt.getSecond() == 0) {
+ buf.append(":00");
+ }
if (hi < 0) {
if (ldt.getYear() == -10_000) {
buf.replace(pos, pos + 2, Long.toString(hi - 1));
@@ -2930,14 +3149,38 @@
}
}
}
+ // add fraction
+ if ((fractionalDigits < 0 && inNano > 0) || fractionalDigits > 0) {
+ buf.append('.');
+ int div = 100_000_000;
+ for (int i = 0; ((fractionalDigits == -1 && inNano > 0) ||
+ (fractionalDigits == -2 && (inNano > 0 || (i % 3) != 0)) ||
+ i < fractionalDigits); i++) {
+ int digit = inNano / div;
+ buf.append((char) (digit + '0'));
+ inNano = inNano - (digit * div);
+ div = div / 10;
+ }
+ }
+ buf.append('Z');
return true;
}
@Override
public int parse(DateTimeParseContext context, CharSequence text, int position) {
// new context to avoid overwriting fields like year/month/day
+ int minDigits = (fractionalDigits < 0 ? 0 : fractionalDigits);
+ int maxDigits = (fractionalDigits < 0 ? 9 : fractionalDigits);
+ CompositePrinterParser parser = new DateTimeFormatterBuilder()
+ .append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral('T')
+ .appendValue(HOUR_OF_DAY, 2).appendLiteral(':')
+ .appendValue(MINUTE_OF_HOUR, 2).appendLiteral(':')
+ .appendValue(SECOND_OF_MINUTE, 2)
+ .appendFraction(NANO_OF_SECOND, minDigits, maxDigits, true)
+ .appendLiteral('Z')
+ .toFormatter().toPrinterParser(false);
DateTimeParseContext newContext = context.copy();
- int pos = PARSER.parse(newContext, text, position);
+ int pos = parser.parse(newContext, text, position);
if (pos < 0) {
return pos;
}
@@ -2952,10 +3195,18 @@
Long nanoVal = newContext.getParsed(NANO_OF_SECOND);
int sec = (secVal != null ? secVal.intValue() : 0);
int nano = (nanoVal != null ? nanoVal.intValue() : 0);
+ int days = 0;
+ if (hour == 24 && min == 0 && sec == 0 && nano == 0) {
+ hour = 0;
+ days = 1;
+ } else if (hour == 23 && min == 59 && sec == 60) {
+ context.setParsedLeapSecond();
+ sec = 59;
+ }
int year = (int) yearParsed % 10_000;
long instantSecs;
try {
- LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0);
+ LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0).plusDays(days);
instantSecs = ldt.toEpochSecond(ZoneOffset.UTC);
instantSecs += Math.multiplyExact(yearParsed / 10_000L, SECONDS_PER_10000_YEARS);
} catch (RuntimeException ex) {
@@ -4017,9 +4268,7 @@
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());
+ String pattern = getLocalizedDateTimePattern(dateStyle, timeStyle, chrono, locale);
formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);
DateTimeFormatter old = FORMATTER_CACHE.putIfAbsent(key, formatter);
if (old != null) {
@@ -4029,19 +4278,6 @@
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 : "") + "," +
@@ -4096,7 +4332,7 @@
case 'Y':
field = weekDef.weekBasedYear();
if (count == 2) {
- return new ReducedPrinterParser(field, 2, 2000);
+ return new ReducedPrinterParser(field, 2, 2, 2000, 0);
} else {
return new NumberPrinterParser(field, count, 19,
(count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1);