ThreeTen / threeten

This project was the home of code used to develop a modern date and time library for JDK8. Development has moved to OpenJDK and a separate backport project, threetenbp.
http://threeten.github.io/
191 stars 37 forks source link

Instant formatter fractional second control #301

Closed jodastephen closed 11 years ago

jodastephen commented 11 years ago

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 current appendInstant() method.

jodastephen commented 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);
+    }
+
+}
RogerRiggs commented 11 years ago

Looks ok.

Missing '.' at end of sentence of javadoc for appendInstant().

jodastephen commented 11 years ago

Resolved in http://hg.openjdk.java.net/threeten/threeten/jdk/rev/835edcf9578a