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

ZonedDateTime.parse() returns wrong ZoneOffset around DST fall transition

    Details

    • Type: Bug
    • Status: Resolved
    • Priority: P3
    • Resolution: Fixed
    • Affects Version/s: 8u20
    • Fix Version/s: 9
    • Component/s: core-libs
    • Subcomponent:
    • Resolved In Build:
      b100
    • CPU:
      x86_64
    • OS:
      windows_7

      Backports

        Description

        FULL PRODUCT VERSION :
        java version "1.8.0_20"
        Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
        Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

        ADDITIONAL OS VERSION INFORMATION :
        Microsoft Windows [Version 6.1.7601]

        A DESCRIPTION OF THE PROBLEM :
        Parsing a string with ZonedDateTime.parse() that contains zone offset and zone ID "Europe/Berlin" returns a wrong ZonedDateAndTime (different offset). This error starts exactly at the transition time (included) and ends one hour later (excluded).


        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        1. create a ZonedDateTime with date/time of the DST transition in fall and a ZoneId different from UTC (e.g. "Europe/Berlin")
        2. apply ZonedDateTime.format() with ISO_ZONED_DATE_TIME formatter
        3. apply ZonedDateTime.parse() to the formatted string with same formatter
        4. compare the result to the starting value

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        The parsed string should represent the same instant on the time line.
        ACTUAL -
        Starting at the fall DST transition time (included) and ending one hour later (excluded) the offset is one hour too high. Lies 2..5 show wrong result, 1st and last line are correct.

        2012-10-28T02:45:00+02:00[Europe/Berlin] -> 2012-10-28T02:45+02:00[Europe/Berlin]
        2012-10-28T02:00:00+01:00[Europe/Berlin] -> 2012-10-28T02:00+02:00[Europe/Berlin]
        2012-10-28T02:15:00+01:00[Europe/Berlin] -> 2012-10-28T02:15+02:00[Europe/Berlin]
        2012-10-28T02:30:00+01:00[Europe/Berlin] -> 2012-10-28T02:30+02:00[Europe/Berlin]
        2012-10-28T02:45:00+01:00[Europe/Berlin] -> 2012-10-28T02:45+02:00[Europe/Berlin]
        2012-10-28T03:00:00+01:00[Europe/Berlin] -> 2012-10-28T03:00+01:00[Europe/Berlin]


        REPRODUCIBILITY :
        This bug can be reproduced always.

        ---------- BEGIN SOURCE ----------
        import java.time.ZoneId;
        import java.time.ZonedDateTime;
        import java.time.format.DateTimeFormatter;
        import java.time.temporal.ChronoUnit;

        public class LocalDateTimeIssue {
          public static void main(String[] args) {
            ZonedDateTime zdt = ZonedDateTime.of(2012, 10, 28, 2, 45, 0, 0, ZoneId.of("Europe/Berlin"));
            DateTimeFormatter fmt = DateTimeFormatter.ISO_ZONED_DATE_TIME;
            for (int i = 0; i < 6; i++) {
              String s = zdt.format(fmt);
              System.out.println(s + " -> " + ZonedDateTime.parse(s, fmt));
              zdt = zdt.plus(15, ChronoUnit.MINUTES);
            }
          }
        }

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

          Issue Links

            Activity

            Hide
            ntv Nadeesh Tv (Inactive) added a comment - - edited
            @Stephen - After incorporating your suggested fix , is the following line about " previous offset" still valid ?

            "For Overlaps, the general strategy is that if the local date-time falls in the middle of an Overlap, then the previous offset will be retained"
            Show
            ntv Nadeesh Tv (Inactive) added a comment - - edited @Stephen - After incorporating your suggested fix , is the following line about " previous offset" still valid ? "For Overlaps, the general strategy is that if the local date-time falls in the middle of an Overlap, then the previous offset will be retained"
            Hide
            scolebourne Stephen Colebourne added a comment -
            That comment refers to the various cases where things are ambiguous. The whole part (including bits not quoted above) indicate that if there is a "previous offset" (such as when adding a Period to a ZonedDateTime) then it should be used. However, if there is no "previous offset" than summer time should be used. Note that the whole section refers to the ambiguous cases.In the bug report, the input is not ambiguous, so that documentation section does not apply.

            The documentation section that should apply to this bug is the ordered list in the "Resolving" section of `DateTimeFormatter`. Unfortunately, the ordered list is missing steps 9 and 10 that should define the last two steps of resolving. Step 9 should be a description of the `Parsed.resolveFractional()` method. Step 10 should be a description of the `Parsed.resolveInstant()` method. It would make sense to add this documentation with this change (although doing so makes the bug fix harder to backport). Proposed docs:

            9. If a second-based field is present, but `LocalTime` was not parsed, then the resolver ensures that milli, micro and nano second values are available to meet the contract of `ChronoField`. These will be set to zero if missing.

            10. If both a date and time were parsed and either an offset or zone is present, the field `INSTANT_SECONDS` is created. If an offset was parsed then the offset will be combined with the local date-time to form the instant, with any zone ignored. If a `ZoneId` was parsed without an offset then the zone will be combined with the local date-time to form the instant using the rules of `ChronoZonedDateTime.atZone()`.



            Show
            scolebourne Stephen Colebourne added a comment - That comment refers to the various cases where things are ambiguous. The whole part (including bits not quoted above) indicate that if there is a "previous offset" (such as when adding a Period to a ZonedDateTime) then it should be used. However, if there is no "previous offset" than summer time should be used. Note that the whole section refers to the ambiguous cases.In the bug report, the input is not ambiguous, so that documentation section does not apply. The documentation section that should apply to this bug is the ordered list in the "Resolving" section of `DateTimeFormatter`. Unfortunately, the ordered list is missing steps 9 and 10 that should define the last two steps of resolving. Step 9 should be a description of the `Parsed.resolveFractional()` method. Step 10 should be a description of the `Parsed.resolveInstant()` method. It would make sense to add this documentation with this change (although doing so makes the bug fix harder to backport). Proposed docs: 9. If a second-based field is present, but `LocalTime` was not parsed, then the resolver ensures that milli, micro and nano second values are available to meet the contract of `ChronoField`. These will be set to zero if missing. 10. If both a date and time were parsed and either an offset or zone is present, the field `INSTANT_SECONDS` is created. If an offset was parsed then the offset will be combined with the local date-time to form the instant, with any zone ignored. If a `ZoneId` was parsed without an offset then the zone will be combined with the local date-time to form the instant using the rules of `ChronoZonedDateTime.atZone()`.
            Hide
            rpatil Ramanand Patil added a comment -
            [~scolebourne] Thank you for your clarifications on why offset should be given priority over zone. I am providing the mail extract here for reference:

            The code deals correctly with the "good" case where the offset is valid for the zone.
            The question is what should the code do if the offset is not valid for the zone, eg.
              2012-10-28T02:45+03:00[Europe/Berlin]

            There are three options:
            1) parse error
            2) prioritise the zone
            3) prioritise the offset

            The key thing to factor into the decision is that time-zones change.
            Thus, the parser needs to allow for the fact that when the date-time was written to a string, +03:00 actually was a valid offset. (Imagine the situation where Germany changes from +01:00 in winter and +02:00 in summer to +02:00 in winter and +03:00 in summer). This kind of change to time-zones happens all the time.

            Given the fact that time-zone rules change, option 1 is too harsh.
            There is no way to know if the input actually is invalid. It might have come from another system with a more accurate time-zone database, or an older one.

            Options 2 and 3 both handle the case where the time-zone database has changed, option 2 changes to offset to make the result valid, option 3 changes the local time to make the result valid.

            As a general rule, when transmitting dates and times across a network boundary, the time-zone is not sent at all. As such, the receiving party necessarily places all their trust in the offset. The same should apply here, placing trust in the offset, not the zone (as the offset is a fixed known element, but the zone varies over time).

            Making the offset always take priority is a change to the existing results for invalid offsets, but it will be the right choice.
            Show
            rpatil Ramanand Patil added a comment - [~scolebourne] Thank you for your clarifications on why offset should be given priority over zone. I am providing the mail extract here for reference: The code deals correctly with the "good" case where the offset is valid for the zone. The question is what should the code do if the offset is not valid for the zone, eg.   2012-10-28T02:45+03:00[Europe/Berlin] There are three options: 1) parse error 2) prioritise the zone 3) prioritise the offset The key thing to factor into the decision is that time-zones change. Thus, the parser needs to allow for the fact that when the date-time was written to a string, +03:00 actually was a valid offset. (Imagine the situation where Germany changes from +01:00 in winter and +02:00 in summer to +02:00 in winter and +03:00 in summer). This kind of change to time-zones happens all the time. Given the fact that time-zone rules change, option 1 is too harsh. There is no way to know if the input actually is invalid. It might have come from another system with a more accurate time-zone database, or an older one. Options 2 and 3 both handle the case where the time-zone database has changed, option 2 changes to offset to make the result valid, option 3 changes the local time to make the result valid. As a general rule, when transmitting dates and times across a network boundary, the time-zone is not sent at all. As such, the receiving party necessarily places all their trust in the offset. The same should apply here, placing trust in the offset, not the zone (as the offset is a fixed known element, but the zone varies over time). Making the offset always take priority is a change to the existing results for invalid offsets, but it will be the right choice.
            Hide
            hgupdate HG Updates added a comment -
            URL: http://hg.openjdk.java.net/jdk9/dev/jdk/rev/f371bdfb7875
            User: igerasim
            Date: 2015-12-25 13:44:59 +0000
            Show
            hgupdate HG Updates added a comment - URL: http://hg.openjdk.java.net/jdk9/dev/jdk/rev/f371bdfb7875 User: igerasim Date: 2015-12-25 13:44:59 +0000
            Hide
            hgupdate HG Updates added a comment -
            URL: http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/f371bdfb7875
            User: lana
            Date: 2016-01-06 20:14:31 +0000
            Show
            hgupdate HG Updates added a comment - URL: http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/f371bdfb7875 User: lana Date: 2016-01-06 20:14:31 +0000

              People

              • Assignee:
                rpatil Ramanand Patil
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                8 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: