0

I am using Java 17. I am trying to parse different strings as ZonedDateTime but when I try to convert it to an Instant, the output is not as expected. For eg:

        String first = "2020-01-08T21:00:00Z[Europe/Berlin]";
        ZonedDateTime zone1 = ZonedDateTime.parse(first);
        String second = "2020-01-08T20:00:00Z[UTC]";
        ZonedDateTime zone2 = ZonedDateTime.parse(second);
        System.out.println(zone1.toInstant());
        System.out.println(zone2.toInstant());

The output is (this is wrong, both times should be same):

2020-01-08T21:00:00Z
2020-01-08T20:00:00Z

However, when I create a ZonedDateTime object using constructor and ZoneId I get correct output:

        ZonedDateTime z1 = ZonedDateTime.of(2020,1,8,21,0,0,0,ZoneId.of("Europe/Berlin"));
        System.out.println(z1.toInstant());
        
        ZonedDateTime z2 = ZonedDateTime.of(2020,1,8,20,0,0,0,ZoneId.of("UTC"));
        System.out.println(z2.toInstant());

Output:

2020-01-08T20:00:00Z
2020-01-08T20:00:00Z

Can anyone tell me why my parse method is not working as expected?

Note: This issue is NOT related to DST bug for ZonedTimeZone in JDK8:

9
  • 4
    Doesn't "Z" mean "Zulu Time", UTC? Commented May 7, 2024 at 13:04
  • 4
    I'm surprised that the library doesn't throw an exception complaining that "Z" and "Berlin" are conflicting. Commented May 7, 2024 at 13:39
  • @k314159 maybe contra-intuitive, but from the documentation we get "The ISO-like date-time formatter that formats or parses a date-time with offset and zone ... extends the ISO-8601 extended offset date-time format to add the time-zone." (emphasis added) Commented May 7, 2024 at 14:13
  • 2
    In Java 8 the parse method would respect the time zone and ignore the UTC offset ( Z meaning offset 0 in your case). It was considered a bug and was fixed so that today the offset is respected no matter if it disagrees with the time zone. Seems you can’t make everyone happy. Commented May 7, 2024 at 14:49
  • 1
    To ignore the UTC offset from the string: Parse date and time using LocalDateTime ldt = LocalDateTime.parse(first, DateTimeFormatter.ISO_ZONED_DATE_TIME);. Parse the time zone using ZoneId zid = DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(first, ZoneId::from);. Obtain your ZonedDateTime with ldt.atZone(zid). In case of your string first we get 2020-01-08T21:00+01:00[Europe/Berlin], which converts to an Instant of 2020-01-08T20:00:00Z as requested. Beware of unpredictable results in the time overlap at fall back where summer time ends. Commented May 7, 2024 at 16:13

2 Answers 2

3

In your input string "2020-01-08T21:00:00Z[Europe/Berlin]" (and the second one) remove 'Z' as that means that it is a UTC time and subsequent timezone is ignored. So, both of your times are in UTC meaning that the first one is NOT in [Europe/Berlin] time zone. You might want to parse the strings using format mask using DateTimeFormatter class. Or replace 'Z' with '+01:00' and then you don't need to use formatting.

Sign up to request clarification or add additional context in comments.

Comments

0

You need to parse it and provide the correct format. Below code works for me.

var dateUtcPlus1 = "2025-01-10T17:15:00+01:00"

ZonedDateTime date1 = ZonedDateTime.parse(dateUtcPlus1, DateTimeFormatter.ISO_OFFSET_DATE_TIME).truncatedTo(ChronoUnit.SECONDS);

In fact I have a parameterized unit test to explain the same.

@ParameterizedTest(name = "Date {0} is {2} Date {1}")
    @CsvSource({
        "2025-01-10T17:15:00+01:00,2025-01-10T16:15:00+00:00,EqualTo",
        "2025-01-10T15:15:00+05:00,2025-01-10T10:15:00+00:00,EqualTo",
        "2025-01-10T12:15:00+05:00,2025-01-10T08:15:00+00:00,Before",
        "2025-01-10T15:55:00+05:00,2025-01-10T10:45:00+00:00,After",
    })
    void testEqualTimesInDifferentZonesFromStringFormat(String sourceDate, String targetDate, String expectedResult) {
      ZonedDateTime date1 = ZonedDateTime.parse(sourceDate, DateTimeFormatter.ISO_OFFSET_DATE_TIME).truncatedTo(ChronoUnit.SECONDS);
      ZonedDateTime date2 = ZonedDateTime.parse(targetDate, DateTimeFormatter.ISO_OFFSET_DATE_TIME).truncatedTo(ChronoUnit.SECONDS);

      if ("EqualTo".equals(expectedResult)) {
        assertThat(date1).isEqualTo(date2);
      } else if ("Before".equals(expectedResult)) {
        assertThat(date1.isBefore(date2)).isTrue();
      } else if ("After".equals(expectedResult)) {
        assertThat(date1.isAfter(date2)).isTrue();
      }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.