Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8272473

Parsing epoch seconds at a DST transition with a non-UTC parser is wrong

    XMLWordPrintable

Details

    • b13
    • Verified

    Backports

      Description

        ADDITIONAL SYSTEM INFORMATION :
        java 16.0.2 2021-07-20
        Java(TM) SE Runtime Environment (build 16.0.2+7-67)
        Java HotSpot(TM) 64-Bit Server VM (build 16.0.2+7-67, mixed mode, sharing)

        I had a few older versions of java installed as well, and the same thing happened on Java 11 and Java 8

        A DESCRIPTION OF THE PROBLEM :
        If you build a DateTimeFormatter that accepts INSTANT_SECONDS only, and give it a time zone that experiences daylight savings transitions (I used America/New_York in my reproduction case, but saw it fail on some others), then use that formatter to parse an epoch date string, the resulting date is an hour earlier than it should be. This happens when transitioning from daylight savings time to standard time. Parsing the same timestamp with a different format got the correct result.

        I did some tracing, and it looked to me like the problem happens in java.time.format.Parsed#resolve(), which seemed to convert the unambiguous INSTANT_SECONDS value into an ambiguous date + time + timezone value, then generated a fresh INSTANT_SECONDS value, and incorrectly chose the earlier of the two possible values.

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        Construct a DateTimeFormatter that read INSTANT_SECONDS in a time zone with daylight saving time. Try to parse a daylight savings time transition moment with that formatter. Observe the resulting INSTANT_SECONDS is not the value we read in.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        parsing epoch seconds should get the correct value
        ACTUAL -
        The parsed value was an hour earlier than expected.

        ---------- BEGIN SOURCE ----------
        import java.time.ZoneId;
        import java.time.ZoneOffset;
        import java.time.ZonedDateTime;
        import java.time.format.DateTimeFormatter;
        import java.time.format.DateTimeFormatterBuilder;
        import java.time.format.SignStyle;
        import java.time.temporal.ChronoField;
        import java.time.temporal.TemporalAccessor;
        import java.util.Locale;

        public class DateParseBug {
            public static void main(String[] args) {
                final DateTimeFormatter epochSecondFormatter = new DateTimeFormatterBuilder()
                        .appendValue(ChronoField.INSTANT_SECONDS, 1, 19, SignStyle.NORMAL)
                        .toFormatter(Locale.ROOT)
                        .withZone(ZoneId.of("America/New_York"));
                // This is the transition point from DST to Standard time for that year in America/New_York tz
                ZonedDateTime sixAm = ZonedDateTime.of(2020, 11, 1, 6, 0, 0, 0, ZoneOffset.UTC);
                TemporalAccessor actual = epochSecondFormatter.parse(epochSecondFormatter.format(sixAm));
                if (actual.getLong(ChronoField.INSTANT_SECONDS) != sixAm.getLong(ChronoField.INSTANT_SECONDS)) {
                    System.out.println("Parsed back a wrong value. Expected: [" + sixAm.getLong(ChronoField.INSTANT_SECONDS) +
                            "] Actual: [" + actual.getLong(ChronoField.INSTANT_SECONDS) + "]");
                } else {
                    System.out.println("Parsed Correctly");
                }
            }
        }

        ---------- END SOURCE ----------

        FREQUENCY : always


        Attachments

          1. DateParseBug.java
            1 kB
            Andrew Wang
          2. InstantSecondParsingTest.java
            1 kB
            Andrew Wang

          Issue Links

            Activity

              People

                naoto Naoto Sato
                webbuggrp Webbug Group
                Votes:
                0 Vote for this issue
                Watchers:
                10 Start watching this issue

                Dates

                  Created:
                  Updated:
                  Resolved: