Closed jodastephen closed 11 years ago
Clarify spec and add new builder method appendInstant(int) to provide control.
# HG changeset patch
# User scolebourne
# Date 1365983509 -3600
# Node ID f6a018f6b0ce6c3cb5b199a83e9b028382ad7d0b
# Parent 96815bfd0d13e81efae0ce234ab6a36acaf78fb6
Enhance formatting of instants
Add option to control fractional digits
See #301
diff -r 96815bfd0d13 -r f6a018f6b0ce src/share/classes/java/time/format/DateTimeFormatter.java
--- a/src/share/classes/java/time/format/DateTimeFormatter.java Fri Apr 12 12:24:00 2013 -0700
+++ b/src/share/classes/java/time/format/DateTimeFormatter.java Mon Apr 15 00:51:49 2013 +0100
@@ -1040,6 +1040,11 @@
* <p>
* This returns an immutable formatter capable of formatting and parsing
* the ISO-8601 instant format.
+ * When formatting, the second-of-minute is always output.
+ * The nano-of-second outputs zero, three, six or nine digits digits as necessary.
+ * When parsing, time to at least the seconds field is required.
+ * Fractional seconds from zero to nine are parsed.
+ * The localized decimal style is not used.
* <p>
* This is a special case formatter intended to allow a human readable form
* of an {@link java.time.Instant}. The {@code Instant} class is designed to
diff -r 96815bfd0d13 -r f6a018f6b0ce src/share/classes/java/time/format/DateTimeFormatterBuilder.java
--- a/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Fri Apr 12 12:24:00 2013 -0700
+++ b/src/share/classes/java/time/format/DateTimeFormatterBuilder.java Mon Apr 15 00:51:49 2013 +0100
@@ -657,11 +657,19 @@
//-----------------------------------------------------------------------
/**
- * 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
+ * Instants have a fixed output format
+ * 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>
* 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 +677,50 @@
* @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>
+ * 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.
@@ -2883,43 +2930,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 +2984,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;
}
diff -r 96815bfd0d13 -r f6a018f6b0ce src/share/classes/java/time/format/DateTimeParseContext.java
--- a/src/share/classes/java/time/format/DateTimeParseContext.java Fri Apr 12 12:24:00 2013 -0700
+++ b/src/share/classes/java/time/format/DateTimeParseContext.java Mon Apr 15 00:51:49 2013 +0100
@@ -118,9 +118,13 @@
/**
* Creates a copy of this context.
+ * This retains the case sensitive and strict flags.
*/
DateTimeParseContext copy() {
- return new DateTimeParseContext(formatter);
+ DateTimeParseContext newContext = new DateTimeParseContext(formatter);
+ newContext.caseSensitive = caseSensitive;
+ newContext.strict = strict;
+ return newContext;
}
//-----------------------------------------------------------------------
diff -r 96815bfd0d13 -r f6a018f6b0ce test/java/time/tck/java/time/TCKInstant.java
--- a/test/java/time/tck/java/time/TCKInstant.java Fri Apr 12 12:24:00 2013 -0700
+++ b/test/java/time/tck/java/time/TCKInstant.java Mon Apr 15 00:51:49 2013 +0100
@@ -370,6 +370,8 @@
{"Z"},
{"1970-01-01T00:00:00"},
{"1970-01-01T00:00:0Z"},
+ {"1970-01-01T00:0:00Z"},
+ {"1970-01-01T0:00:00Z"},
{"1970-01-01T00:00:00.0000000000Z"},
};
}
@@ -2045,60 +2047,64 @@
Object[][] data_toString() {
return new Object[][] {
{Instant.ofEpochSecond(65L, 567), "1970-01-01T00:01:05.000000567Z"},
+ {Instant.ofEpochSecond(65L, 560), "1970-01-01T00:01:05.000000560Z"},
+ {Instant.ofEpochSecond(65L, 560000), "1970-01-01T00:01:05.000560Z"},
+ {Instant.ofEpochSecond(65L, 560000000), "1970-01-01T00:01:05.560Z"},
+
{Instant.ofEpochSecond(1, 0), "1970-01-01T00:00:01Z"},
- {Instant.ofEpochSecond(60, 0), "1970-01-01T00:01Z"},
- {Instant.ofEpochSecond(3600, 0), "1970-01-01T01:00Z"},
+ {Instant.ofEpochSecond(60, 0), "1970-01-01T00:01:00Z"},
+ {Instant.ofEpochSecond(3600, 0), "1970-01-01T01:00:00Z"},
{Instant.ofEpochSecond(-1, 0), "1969-12-31T23:59:59Z"},
- {LocalDateTime.of(0, 1, 2, 0, 0).toInstant(ZoneOffset.UTC), "0000-01-02T00:00Z"},
- {LocalDateTime.of(0, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "0000-01-01T12:30Z"},
+ {LocalDateTime.of(0, 1, 2, 0, 0).toInstant(ZoneOffset.UTC), "0000-01-02T00:00:00Z"},
+ {LocalDateTime.of(0, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "0000-01-01T12:30:00Z"},
{LocalDateTime.of(0, 1, 1, 0, 0, 0, 1).toInstant(ZoneOffset.UTC), "0000-01-01T00:00:00.000000001Z"},
- {LocalDateTime.of(0, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "0000-01-01T00:00Z"},
+ {LocalDateTime.of(0, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "0000-01-01T00:00:00Z"},
{LocalDateTime.of(-1, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "-0001-12-31T23:59:59.999999999Z"},
- {LocalDateTime.of(-1, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-0001-12-31T12:30Z"},
- {LocalDateTime.of(-1, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-0001-12-30T12:30Z"},
+ {LocalDateTime.of(-1, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-0001-12-31T12:30:00Z"},
+ {LocalDateTime.of(-1, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-0001-12-30T12:30:00Z"},
- {LocalDateTime.of(-9999, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "-9999-01-02T12:30Z"},
- {LocalDateTime.of(-9999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "-9999-01-01T12:30Z"},
- {LocalDateTime.of(-9999, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "-9999-01-01T00:00Z"},
+ {LocalDateTime.of(-9999, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "-9999-01-02T12:30:00Z"},
+ {LocalDateTime.of(-9999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "-9999-01-01T12:30:00Z"},
+ {LocalDateTime.of(-9999, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "-9999-01-01T00:00:00Z"},
{LocalDateTime.of(-10000, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "-10000-12-31T23:59:59.999999999Z"},
- {LocalDateTime.of(-10000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-10000-12-31T12:30Z"},
- {LocalDateTime.of(-10000, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-10000-12-30T12:30Z"},
- {LocalDateTime.of(-15000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-15000-12-31T12:30Z"},
+ {LocalDateTime.of(-10000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-10000-12-31T12:30:00Z"},
+ {LocalDateTime.of(-10000, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-10000-12-30T12:30:00Z"},
+ {LocalDateTime.of(-15000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-15000-12-31T12:30:00Z"},
- {LocalDateTime.of(-19999, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "-19999-01-02T12:30Z"},
- {LocalDateTime.of(-19999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "-19999-01-01T12:30Z"},
- {LocalDateTime.of(-19999, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "-19999-01-01T00:00Z"},
+ {LocalDateTime.of(-19999, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "-19999-01-02T12:30:00Z"},
+ {LocalDateTime.of(-19999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "-19999-01-01T12:30:00Z"},
+ {LocalDateTime.of(-19999, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "-19999-01-01T00:00:00Z"},
{LocalDateTime.of(-20000, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "-20000-12-31T23:59:59.999999999Z"},
- {LocalDateTime.of(-20000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-20000-12-31T12:30Z"},
- {LocalDateTime.of(-20000, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-20000-12-30T12:30Z"},
- {LocalDateTime.of(-25000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-25000-12-31T12:30Z"},
+ {LocalDateTime.of(-20000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-20000-12-31T12:30:00Z"},
+ {LocalDateTime.of(-20000, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "-20000-12-30T12:30:00Z"},
+ {LocalDateTime.of(-25000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "-25000-12-31T12:30:00Z"},
- {LocalDateTime.of(9999, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "9999-12-30T12:30Z"},
- {LocalDateTime.of(9999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "9999-12-31T12:30Z"},
+ {LocalDateTime.of(9999, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "9999-12-30T12:30:00Z"},
+ {LocalDateTime.of(9999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "9999-12-31T12:30:00Z"},
{LocalDateTime.of(9999, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "9999-12-31T23:59:59.999999999Z"},
- {LocalDateTime.of(10000, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "+10000-01-01T00:00Z"},
- {LocalDateTime.of(10000, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "+10000-01-01T12:30Z"},
- {LocalDateTime.of(10000, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "+10000-01-02T12:30Z"},
- {LocalDateTime.of(15000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+15000-12-31T12:30Z"},
+ {LocalDateTime.of(10000, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "+10000-01-01T00:00:00Z"},
+ {LocalDateTime.of(10000, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "+10000-01-01T12:30:00Z"},
+ {LocalDateTime.of(10000, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "+10000-01-02T12:30:00Z"},
+ {LocalDateTime.of(15000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+15000-12-31T12:30:00Z"},
- {LocalDateTime.of(19999, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "+19999-12-30T12:30Z"},
- {LocalDateTime.of(19999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+19999-12-31T12:30Z"},
+ {LocalDateTime.of(19999, 12, 30, 12, 30).toInstant(ZoneOffset.UTC), "+19999-12-30T12:30:00Z"},
+ {LocalDateTime.of(19999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+19999-12-31T12:30:00Z"},
{LocalDateTime.of(19999, 12, 31, 23, 59, 59, 999_999_999).toInstant(ZoneOffset.UTC), "+19999-12-31T23:59:59.999999999Z"},
- {LocalDateTime.of(20000, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "+20000-01-01T00:00Z"},
- {LocalDateTime.of(20000, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "+20000-01-01T12:30Z"},
- {LocalDateTime.of(20000, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "+20000-01-02T12:30Z"},
- {LocalDateTime.of(25000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+25000-12-31T12:30Z"},
+ {LocalDateTime.of(20000, 1, 1, 0, 0).toInstant(ZoneOffset.UTC), "+20000-01-01T00:00:00Z"},
+ {LocalDateTime.of(20000, 1, 1, 12, 30).toInstant(ZoneOffset.UTC), "+20000-01-01T12:30:00Z"},
+ {LocalDateTime.of(20000, 1, 2, 12, 30).toInstant(ZoneOffset.UTC), "+20000-01-02T12:30:00Z"},
+ {LocalDateTime.of(25000, 12, 31, 12, 30).toInstant(ZoneOffset.UTC), "+25000-12-31T12:30:00Z"},
- {LocalDateTime.of(-999_999_999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC).minus(1, DAYS), "-1000000000-12-31T12:30Z"},
- {LocalDateTime.of(999_999_999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC).plus(1, DAYS), "+1000000000-01-01T12:30Z"},
+ {LocalDateTime.of(-999_999_999, 1, 1, 12, 30).toInstant(ZoneOffset.UTC).minus(1, DAYS), "-1000000000-12-31T12:30:00Z"},
+ {LocalDateTime.of(999_999_999, 12, 31, 12, 30).toInstant(ZoneOffset.UTC).plus(1, DAYS), "+1000000000-01-01T12:30:00Z"},
- {Instant.MIN, "-1000000000-01-01T00:00Z"},
+ {Instant.MIN, "-1000000000-01-01T00:00:00Z"},
{Instant.MAX, "+1000000000-12-31T23:59:59.999999999Z"},
};
}
diff -r 96815bfd0d13 -r f6a018f6b0ce test/java/time/tck/java/time/format/TCKDateTimeFormatters.java
--- a/test/java/time/tck/java/time/format/TCKDateTimeFormatters.java Fri Apr 12 12:24:00 2013 -0700
+++ b/test/java/time/tck/java/time/format/TCKDateTimeFormatters.java Mon Apr 15 00:51:49 2013 +0100
@@ -63,6 +63,7 @@
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
import static java.time.temporal.ChronoField.DAY_OF_YEAR;
import static java.time.temporal.ChronoField.HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.INSTANT_SECONDS;
import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
import static java.time.temporal.ChronoField.NANO_OF_SECOND;
@@ -1180,6 +1181,52 @@
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
+ @DataProvider(name="sample_isoInstant")
+ Object[][] provider_sample_isoInstant() {
+ return new Object[][]{
+ {0, 0, "1970-01-01T00:00:00Z", null},
+ {0, null, "1970-01-01T00:00:00Z", null},
+ {0, -1, null, DateTimeException.class},
+
+ {-1, 0, "1969-12-31T23:59:59Z", null},
+ {1, 0, "1970-01-01T00:00:01Z", null},
+ {60, 0, "1970-01-01T00:01:00Z", null},
+ {3600, 0, "1970-01-01T01:00:00Z", null},
+ {86400, 0, "1970-01-02T00:00:00Z", null},
+
+ {0, 1, "1970-01-01T00:00:00.000000001Z", null},
+ {0, 2, "1970-01-01T00:00:00.000000002Z", null},
+ {0, 10, "1970-01-01T00:00:00.000000010Z", null},
+ {0, 100, "1970-01-01T00:00:00.000000100Z", null},
+ };
+ }
+
+ @Test(dataProvider="sample_isoInstant")
+ public void test_print_isoInstant(
+ long instantSecs, Integer nano, String expected, Class<?> expectedEx) {
+ TemporalAccessor test = buildAccessorInstant(instantSecs, nano);
+ if (expectedEx == null) {
+ assertEquals(DateTimeFormatter.ISO_INSTANT.format(test), expected);
+ } else {
+ try {
+ DateTimeFormatter.ISO_INSTANT.format(test);
+ fail();
+ } catch (Exception ex) {
+ assertTrue(expectedEx.isInstance(ex));
+ }
+ }
+ }
+
+ @Test(dataProvider="sample_isoInstant")
+ public void test_parse_isoInstant(
+ long instantSecs, Integer nano, String input, Class<?> invalid) {
+ if (input != null) {
+ TemporalAccessor parsed = DateTimeFormatter.ISO_INSTANT.parseUnresolved(input, new ParsePosition(0));
+ assertEquals(parsed.getLong(INSTANT_SECONDS), instantSecs);
+ assertEquals(parsed.getLong(NANO_OF_SECOND), (nano == null ? 0 : nano));
+ }
+ }
+
@Test
public void test_isoInstant_basics() {
assertEquals(DateTimeFormatter.ISO_INSTANT.getChronology(), null);
@@ -1334,6 +1381,15 @@
return mock;
}
+ private TemporalAccessor buildAccessorInstant(long instantSecs, Integer nano) {
+ MockAccessor mock = new MockAccessor();
+ mock.fields.put(INSTANT_SECONDS, instantSecs);
+ if (nano != null) {
+ mock.fields.put(NANO_OF_SECOND, (long) nano);
+ }
+ return mock;
+ }
+
private void buildCalendrical(Expected expected, String offsetId, String zoneId) {
if (offsetId != null) {
expected.add(ZoneOffset.of(offsetId));
diff -r 96815bfd0d13 -r f6a018f6b0ce test/java/time/tck/java/time/format/TCKInstantPrinterParser.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/time/tck/java/time/format/TCKInstantPrinterParser.java Mon Apr 15 00:51:49 2013 +0100
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * This file is available under and governed by the GNU General Public
+ * License version 2 only, as published by the Free Software Foundation.
+ * However, the following notice accompanied the original version of this
+ * file:
+ *
+ * Copyright (c) 2010-2013, Stephen Colebourne & Michael Nascimento Santos
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of JSR-310 nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package tck.java.time.format;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Test DateTimeFormatterBuilder.appendInstant().
+ */
+@Test
+public class TCKInstantPrinterParser {
+
+ @DataProvider(name="printGrouped")
+ Object[][] data_printGrouped() {
+ return new Object[][] {
+ {0, 0, "1970-01-01T00:00:00Z"},
+
+ {-1, 0, "1969-12-31T23:59:59Z"},
+ {1, 0, "1970-01-01T00:00:01Z"},
+ {60, 0, "1970-01-01T00:01:00Z"},
+ {3600, 0, "1970-01-01T01:00:00Z"},
+ {86400, 0, "1970-01-02T00:00:00Z"},
+
+ {182, 2, "1970-01-01T00:03:02.000000002Z"},
+ {182, 20, "1970-01-01T00:03:02.000000020Z"},
+ {182, 200, "1970-01-01T00:03:02.000000200Z"},
+ {182, 2000, "1970-01-01T00:03:02.000002Z"},
+ {182, 20000, "1970-01-01T00:03:02.000020Z"},
+ {182, 200000, "1970-01-01T00:03:02.000200Z"},
+ {182, 2000000, "1970-01-01T00:03:02.002Z"},
+ {182, 20000000, "1970-01-01T00:03:02.020Z"},
+ {182, 200000000, "1970-01-01T00:03:02.200Z"},
+
+ {Instant.MAX.getEpochSecond(), 999999999, "+1000000000-12-31T23:59:59.999999999Z"},
+ {Instant.MIN.getEpochSecond(), 0, "-1000000000-01-01T00:00:00Z"},
+ };
+ }
+
+ @Test(dataProvider="printGrouped")
+ public void test_print_grouped(long instantSecs, int nano, String expected) {
+ Instant instant = Instant.ofEpochSecond(instantSecs, nano);
+ DateTimeFormatter f = new DateTimeFormatterBuilder().appendInstant().toFormatter();
+ assertEquals(f.format(instant), expected);
+ }
+
+ //-----------------------------------------------------------------------
+ @DataProvider(name="printDigits")
+ Object[][] data_printDigits() {
+ return new Object[][] {
+ {-1, 0, 0, "1970-01-01T00:00:00Z"},
+ {0, 0, 0, "1970-01-01T00:00:00Z"},
+ {1, 0, 0, "1970-01-01T00:00:00.0Z"},
+ {3, 0, 0, "1970-01-01T00:00:00.000Z"},
+ {9, 0, 0, "1970-01-01T00:00:00.000000000Z"},
+
+ {-1, -1, 0, "1969-12-31T23:59:59Z"},
+ {-1, 1, 0, "1970-01-01T00:00:01Z"},
+ {-1, 60, 0, "1970-01-01T00:01:00Z"},
+ {-1, 3600, 0, "1970-01-01T01:00:00Z"},
+ {-1, 86400, 0, "1970-01-02T00:00:00Z"},
+
+ {-1, 182, 2, "1970-01-01T00:03:02.000000002Z"},
+ {-1, 182, 20, "1970-01-01T00:03:02.00000002Z"},
+ {-1, 182, 200, "1970-01-01T00:03:02.0000002Z"},
+ {-1, 182, 2000, "1970-01-01T00:03:02.000002Z"},
+ {-1, 182, 20000, "1970-01-01T00:03:02.00002Z"},
+ {-1, 182, 200000, "1970-01-01T00:03:02.0002Z"},
+ {-1, 182, 2000000, "1970-01-01T00:03:02.002Z"},
+ {-1, 182, 20000000, "1970-01-01T00:03:02.02Z"},
+ {-1, 182, 200000000, "1970-01-01T00:03:02.2Z"},
+
+ {0, 182, 2, "1970-01-01T00:03:02Z"},
+ {0, 182, 20, "1970-01-01T00:03:02Z"},
+ {0, 182, 200, "1970-01-01T00:03:02Z"},
+ {0, 182, 2000, "1970-01-01T00:03:02Z"},
+ {0, 182, 20000, "1970-01-01T00:03:02Z"},
+ {0, 182, 200000, "1970-01-01T00:03:02Z"},
+ {0, 182, 2000000, "1970-01-01T00:03:02Z"},
+ {0, 182, 20000000, "1970-01-01T00:03:02Z"},
+ {0, 182, 200000000, "1970-01-01T00:03:02Z"},
+
+ {1, 182, 2, "1970-01-01T00:03:02.0Z"},
+ {1, 182, 20, "1970-01-01T00:03:02.0Z"},
+ {1, 182, 200, "1970-01-01T00:03:02.0Z"},
+ {1, 182, 2000, "1970-01-01T00:03:02.0Z"},
+ {1, 182, 20000, "1970-01-01T00:03:02.0Z"},
+ {1, 182, 200000, "1970-01-01T00:03:02.0Z"},
+ {1, 182, 2000000, "1970-01-01T00:03:02.0Z"},
+ {1, 182, 20000000, "1970-01-01T00:03:02.0Z"},
+ {1, 182, 200000000, "1970-01-01T00:03:02.2Z"},
+
+ {3, 182, 2, "1970-01-01T00:03:02.000Z"},
+ {3, 182, 20, "1970-01-01T00:03:02.000Z"},
+ {3, 182, 200, "1970-01-01T00:03:02.000Z"},
+ {3, 182, 2000, "1970-01-01T00:03:02.000Z"},
+ {3, 182, 20000, "1970-01-01T00:03:02.000Z"},
+ {3, 182, 200000, "1970-01-01T00:03:02.000Z"},
+ {3, 182, 2000000, "1970-01-01T00:03:02.002Z"},
+ {3, 182, 20000000, "1970-01-01T00:03:02.020Z"},
+ {3, 182, 200000000, "1970-01-01T00:03:02.200Z"},
+
+ {9, 182, 2, "1970-01-01T00:03:02.000000002Z"},
+ {9, 182, 20, "1970-01-01T00:03:02.000000020Z"},
+ {9, 182, 200, "1970-01-01T00:03:02.000000200Z"},
+ {9, 182, 2000, "1970-01-01T00:03:02.000002000Z"},
+ {9, 182, 20000, "1970-01-01T00:03:02.000020000Z"},
+ {9, 182, 200000, "1970-01-01T00:03:02.000200000Z"},
+ {9, 182, 2000000, "1970-01-01T00:03:02.002000000Z"},
+ {9, 182, 20000000, "1970-01-01T00:03:02.020000000Z"},
+ {9, 182, 200000000, "1970-01-01T00:03:02.200000000Z"},
+
+ {9, Instant.MAX.getEpochSecond(), 999999999, "+1000000000-12-31T23:59:59.999999999Z"},
+ {9, Instant.MIN.getEpochSecond(), 0, "-1000000000-01-01T00:00:00.000000000Z"},
+ };
+ }
+
+ @Test(dataProvider="printDigits")
+ public void test_print_digits(int fractionalDigits, long instantSecs, int nano, String expected) {
+ Instant instant = Instant.ofEpochSecond(instantSecs, nano);
+ DateTimeFormatter f = new DateTimeFormatterBuilder().appendInstant(fractionalDigits).toFormatter();
+ assertEquals(f.format(instant), expected);
+ }
+
+ //-----------------------------------------------------------------------
+ @DataProvider(name="parseDigits")
+ Object[][] data_parse_digits() {
+ return new Object[][] {
+ {0, 0, "1970-01-01T00:00:00Z"},
+ {0, 0, "1970-01-01T00:00:00Z"},
+ {0, 0, "1970-01-01T00:00:00.0Z"},
+ {0, 0, "1970-01-01T00:00:00.000Z"},
+ {0, 0, "1970-01-01T00:00:00.000000000Z"},
+
+ {-1, 0, "1969-12-31T23:59:59Z"},
+ {1, 0, "1970-01-01T00:00:01Z"},
+ {60, 0, "1970-01-01T00:01:00Z"},
+ {3600, 0, "1970-01-01T01:00:00Z"},
+ {86400, 0, "1970-01-02T00:00:00Z"},
+
+ {182, 234000000, "1970-01-01T00:03:02.234Z"},
+ {182, 234000000, "1970-01-01T00:03:02.2340Z"},
+ {182, 234000000, "1970-01-01T00:03:02.23400Z"},
+ {182, 234000000, "1970-01-01T00:03:02.234000Z"},
+ {182, 234000000, "1970-01-01T00:03:02.234000000Z"},
+
+ {Instant.MAX.getEpochSecond(), 999999999, "+1000000000-12-31T23:59:59.999999999Z"},
+ {Instant.MIN.getEpochSecond(), 0, "-1000000000-01-01T00:00:00.000000000Z"},
+ };
+ }
+
+ @Test(dataProvider="parseDigits")
+ public void test_parse_digitsMinusOne(long instantSecs, int nano, String input) {
+ Instant expected = Instant.ofEpochSecond(instantSecs, nano);
+ DateTimeFormatter f = new DateTimeFormatterBuilder().appendInstant(-1).toFormatter();
+ assertEquals(f.parse(input, Instant::from), expected);
+ }
+
+ @Test(dataProvider="parseDigits")
+ public void test_parse_digitsNine(long instantSecs, int nano, String input) {
+ DateTimeFormatter f = new DateTimeFormatterBuilder().appendInstant(9).toFormatter();
+ if (input.charAt(input.length() - 11) == '.') {
+ Instant expected = Instant.ofEpochSecond(instantSecs, nano);
+ assertEquals(f.parse(input, Instant::from), expected);
+ } else {
+ try {
+ f.parse(input, Instant::from);
+ fail();
+ } catch (DateTimeException ex) {
+ // expected
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ @Test(expectedExceptions=IllegalArgumentException.class)
+ public void test_appendInstant_tooSmall() {
+ new DateTimeFormatterBuilder().appendInstant(-2);
+ }
+
+ @Test(expectedExceptions=IllegalArgumentException.class)
+ public void test_appendInstant_tooBig() {
+ new DateTimeFormatterBuilder().appendInstant(10);
+ }
+
+}
Looks ok.
Missing '.' at end of sentence of javadoc for appendInstant().
The
appendInstant()
method allows anything containing INSTANT_SECONDS to be formatted. However, there is no control over how the fraction of second is formatted.Add a new method
appendInstant(int fractionalDigits)
where 0 means no fraction, 1 would be one dp up to 9 for 9dp. -1 would be used for "as many as necessary", as per the currentappendInstant()
method.