8166138: DateTimeFormatter.ISO_INSTANT should handle offsets
authornishjain
Thu, 04 Oct 2018 17:25:51 +0530
changeset 52012 6f58ecdb060a
parent 52011 8705c6d536c5
child 52013 92383597fa21
8166138: DateTimeFormatter.ISO_INSTANT should handle offsets Reviewed-by: rriggs, scolebourne, naoto Contributed-by: pallavi.sonal@oracle.com
src/java.base/share/classes/java/time/format/DateTimeFormatter.java
src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java
test/jdk/java/time/tck/java/time/format/TCKInstantPrinterParser.java
--- a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java	Thu Oct 04 10:35:59 2018 +0200
+++ b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java	Thu Oct 04 17:25:51 2018 +0530
@@ -1138,9 +1138,12 @@
      * <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.
+     * When formatting, the instant will always be suffixed by 'Z' to indicate UTC.
+     * The second-of-minute is always output.
      * The nano-of-second outputs zero, three, six or nine digits as necessary.
-     * When parsing, time to at least the seconds field is required.
+     * When parsing, the behaviour of {@link DateTimeFormatterBuilder#appendOffsetId()}
+     * will be used to parse the offset, converting the instant to UTC as necessary.
+     * The 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>
--- a/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java	Thu Oct 04 10:35:59 2018 +0200
+++ b/src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java	Thu Oct 04 17:25:51 2018 +0530
@@ -837,6 +837,10 @@
      * The leap-second time of '23:59:59' is handled to some degree, see
      * {@link DateTimeFormatter#parsedLeapSecond()} for full details.
      * <p>
+     * When formatting, the instant will always be suffixed by 'Z' to indicate UTC.
+     * When parsing, the behaviour of {@link DateTimeFormatterBuilder#appendOffsetId()}
+     * will be used to parse the offset, converting the instant to UTC as necessary.
+     * <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)}.
      *
@@ -3468,7 +3472,7 @@
                     .appendValue(MINUTE_OF_HOUR, 2).appendLiteral(':')
                     .appendValue(SECOND_OF_MINUTE, 2)
                     .appendFraction(NANO_OF_SECOND, minDigits, maxDigits, true)
-                    .appendLiteral('Z')
+                    .appendOffsetId()
                     .toFormatter().toPrinterParser(false);
             DateTimeParseContext newContext = context.copy();
             int pos = parser.parse(newContext, text, position);
@@ -3486,6 +3490,7 @@
             Long nanoVal = newContext.getParsed(NANO_OF_SECOND);
             int sec = (secVal != null ? secVal.intValue() : 0);
             int nano = (nanoVal != null ? nanoVal.intValue() : 0);
+            int offset = newContext.getParsed(OFFSET_SECONDS).intValue();
             int days = 0;
             if (hour == 24 && min == 0 && sec == 0 && nano == 0) {
                 hour = 0;
@@ -3498,7 +3503,7 @@
             long instantSecs;
             try {
                 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0).plusDays(days);
-                instantSecs = ldt.toEpochSecond(ZoneOffset.UTC);
+                instantSecs = ldt.toEpochSecond(ZoneOffset.ofTotalSeconds(offset));
                 instantSecs += Math.multiplyExact(yearParsed / 10_000L, SECONDS_PER_10000_YEARS);
             } catch (RuntimeException ex) {
                 return ~position;
--- a/test/jdk/java/time/tck/java/time/format/TCKInstantPrinterParser.java	Thu Oct 04 10:35:59 2018 +0200
+++ b/test/jdk/java/time/tck/java/time/format/TCKInstantPrinterParser.java	Thu Oct 04 17:25:51 2018 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2018, 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
@@ -69,12 +69,18 @@
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
 import java.time.format.ResolverStyle;
 import java.time.temporal.TemporalAccessor;
 
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+/*
+ * @test
+ * @bug 8166138
+ */
+
 /**
  * Test DateTimeFormatterBuilder.appendInstant().
  */
@@ -200,7 +206,10 @@
                 {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"},
+
+                {0, 0, "1970-01-01T00:00:00+00:00"},
+                {0, 0, "1970-01-01T05:30:00+05:30"},
+                {0, 0, "1970-01-01T01:00:00.0+01:00"},
 
                 {-1, 0, "1969-12-31T23:59:59Z"},
                 {1, 0, "1970-01-01T00:00:01Z"},
@@ -208,16 +217,22 @@
                 {3600, 0, "1970-01-01T01:00:00Z"},
                 {86400, 0, "1970-01-02T00:00:00Z"},
 
+                {-1, 0, "1969-12-31T23:59:59+00:00"},
+                {1, 0, "1970-01-01T05:30:01+05:30"},
+                {60, 0, "1969-12-31T19:01:00-05:00"},
+                {3600, 0, "1970-01-01T06:30:00+05:30"},
+                {86400, 0, "1970-01-01T19:00:00-05:00"},
+
                 {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"},
 
-                {((23 * 60) + 59) * 60 + 59, 123456789, "1970-01-01T23:59:59.123456789Z"},
+                {182, 234000000, "1970-01-01T00:03:02.234+00:00"},
+                {182, 234000000, "1970-01-01T05:33:02.2340+05:30"},
+                {182, 234000000, "1969-12-31T19:03:02.23400-05:00"},
+                {182, 234000000, "1970-01-01T00:03:02.234000+00:00"},
 
-                {Instant.MAX.getEpochSecond(), 999999999, "+1000000000-12-31T23:59:59.999999999Z"},
-                {Instant.MIN.getEpochSecond(), 0, "-1000000000-01-01T00:00:00.000000000Z"},
         };
     }
 
@@ -230,22 +245,46 @@
         assertEquals(f.parse(input).query(DateTimeFormatter.parsedLeapSecond()), Boolean.FALSE);
     }
 
-    @Test(dataProvider="parseDigits")
+    @DataProvider(name="parseNineDigits")
+    Object[][] data_parse_ninedigits() {
+        return new Object[][] {
+                {0, 0, "1970-01-01T00:00:00.000000000Z"},
+                {0, 0, "1970-01-01T05:30:00.000000000+05:30"},
+
+                {182, 234000000, "1970-01-01T00:03:02.234000000Z"},
+                {182, 234000000, "1970-01-01T01:03:02.234000000+01:00"},
+
+                {((23 * 60) + 59) * 60 + 59, 123456789, "1970-01-01T23:59:59.123456789Z"},
+                {((23 * 60) + 59) * 60 + 59, 123456789, "1970-01-02T05:29:59.123456789+05:30"},
+
+                {Instant.MAX.getEpochSecond(), 999999999, "+1000000000-12-31T23:59:59.999999999Z"},
+                {Instant.MIN.getEpochSecond(), 0, "-1000000000-01-01T00:00:00.000000000Z"},
+                {Instant.MAX.getEpochSecond(), 999999999, "+1000000000-12-31T23:59:59.999999999+00:00"},
+                {Instant.MIN.getEpochSecond(), 0, "-1000000000-01-01T00:00:00.000000000+00:00"},
+        };
+    }
+
+    @Test(dataProvider="parseNineDigits")
     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);
-            assertEquals(f.parse(input).query(DateTimeFormatter.parsedExcessDays()), Period.ZERO);
-            assertEquals(f.parse(input).query(DateTimeFormatter.parsedLeapSecond()), Boolean.FALSE);
-        } else {
-            try {
-                f.parse(input, Instant::from);
-                fail();
-            } catch (DateTimeException ex) {
-                // expected
-            }
-        }
+        Instant expected = Instant.ofEpochSecond(instantSecs, nano);
+        assertEquals(f.parse(input, Instant::from), expected);
+        assertEquals(f.parse(input).query(DateTimeFormatter.parsedExcessDays()), Period.ZERO);
+        assertEquals(f.parse(input).query(DateTimeFormatter.parsedLeapSecond()), Boolean.FALSE);
+    }
+
+    @DataProvider(name="parseMaxMinInstant")
+    Object[][] data_parse_MaxMinInstant() {
+        return new Object[][] {
+                {"+1000000000-12-31T23:59:59.999999999-01:00"},
+                {"-1000000000-01-01T00:00:00.000000000+01:00"}
+        };
+    }
+
+    @Test(dataProvider="parseMaxMinInstant", expectedExceptions=DateTimeParseException.class)
+    public void test_invalid_Instant(String input) {
+        DateTimeFormatter f = new DateTimeFormatterBuilder().appendInstant(-1).toFormatter();
+        f.parse(input, Instant::from);
     }
 
     @Test
@@ -283,4 +322,12 @@
         new DateTimeFormatterBuilder().appendInstant(10);
     }
 
+    //------------------------------------------------------------------------
+    @Test
+    public void test_equality() {
+        Instant instant1 = Instant.parse("2018-09-12T22:15:51+05:30");
+        Instant instant2 = Instant.parse("2018-09-12T16:45:51Z");
+        assertEquals(instant2, instant1);
+    }
+
 }