scalajava-timezoneddatetimedatetimeformatterjava.time.instant

Different ZonedDateTime of GMT and BST become same Instant after toInstant conversion in Scala


to summarize my question, I have 2 different ZonedDateTime objects with GMT and BST but same local time. They become the same Instant values after toInstant conversion. I am expecting after conversion, they should be different values because although they are both 'Tue Feb 23 18:00:46 2021', the strings are representing time for different time zones. I am not sure whether this is a bug of library itself or I am doing something wrong.

I have 2 different ZonedDateTime generated from this piece of Scala code:

import ...
import java.util.{Set => JavaSet}
val string2 = "Tue Feb 23 18:00:46 BST 2021" 
val string1 = "Tue Feb 23 18:00:46 GMT 2021" 
val timestampFormat: DateTimeFormatter = new DateTimeFormatterBuilder()
    .appendOptional(DateTimeFormatter.ISO_DATE_TIME)
    .appendOptional(DateTimeFormatter.ISO_INSTANT)
    .appendOptional(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
    .appendOptional(DateTimeFormatter.ISO_ZONED_DATE_TIME)
    .appendOptional(DateTimeFormatter.RFC_1123_DATE_TIME)
    .appendPattern("EEE MMM ppd HH:mm:ss ")
    .appendZoneText(TextStyle.SHORT, JavaSet.of(ZoneId.of("Europe/London")))
    .appendPattern(" yyyy")
    .toFormatter(Locale.US)

val parsedZdt2: ZonedDateTime = ZonedDateTime.parse(string2, timestampFormat)
val parsedZdt1: ZonedDateTime = ZonedDateTime.parse(string1, timestampFormat)
println(parsedZdt1 == parsedZdt2)
println("parsedZdt1.toString: " + parsedZdt1.toString)
println("parsedZdt2.toString: " + parsedZdt2.toString)

This prints out:

false
parsedZdt1.toString: 2021-02-23T18:00:46Z[GMT]
parsedZdt2.toString: 2021-02-23T18:00:46Z[Europe/London]

However when I convert them to instant:

println(parsedZdt1.toInstant == parsedZdt2.toInstant)
println("parsedZdt1.toInstant.toString: " + parsedZdt1.toInstant.toString)
println("parsedZdt2.toInstant.toString: " + parsedZdt2.toInstant.toString)

I got this printed out:

true
parsedZdt1.toInstant.toString: 2021-02-23T18:00:46Z
parsedZdt2.toInstant.toString: 2021-02-23T18:00:46Z

My questions are

  1. Why do the 2 values become the same after toInstant conversion?
  2. Are the 2 values representing same time even before toInstant, i.e. , are parsedZdt1 and parsedZdt2 representing the same moment?

Thank you!


Solution

  • tl;dr

    You said:

    the strings are representing time for different time zones

    Different time zones, yes, but the time zones happen to share the same offset on that particular date. Many time zones share the same offset, coincidentally, on particular dates.

    London time is GMT/UTC time, sometimes, when not observing Daylight Saving Time (DST).

    BST = GMT (sometimes)

    If by BST, you mean the time zone Europe/London, that zone had an offset of zero hours from UTC/GMT on that date of 2021-02-23.

    👉 So 6 PM on February 23rd in London time was also 6 PM in GMT.

    Later in the year, from March 28, 2021 to October 31, 2021, the UK observes DST. During those months their offset is one hour ahead of UTC.

    See Time Change 2021 in England, United Kingdom on TimeAndDate.com.

    Example code

    Let’s compare two moments using Instant#equals.

    LocalDate ld = LocalDate.of( 2021 , Month.FEBRUARY , 23 ) ;
    LocalTime lt = LocalTime.of( 18 , 0 , 0 ) ;
    boolean sameMoment = 
        ZonedDateTime.of( ld , lt , ZoneId.of( "Europe/London" ) ).toInstant()
        .equals(
            OffsetDateTime.of( ld , lt , ZoneOffset.UTC ).toInstant()
        ) ;
    

    See this code run live at Ideone.com.

    true

    Change the month to June, during Daylight Saving Time observance.

    LocalDate ld = LocalDate.of( 2021 , Month.JUNE , 23 ) ;
    LocalTime lt = LocalTime.of( 18 , 0 , 0 ) ;
    boolean sameMoment = 
        ZonedDateTime.of( ld , lt , ZoneId.of( "Europe/London" ) ).toInstant()
        .equals(
            OffsetDateTime.of( ld , lt , ZoneOffset.UTC ).toInstant()
        ) ;
    

    false

    ZoneOffset

    Also, you can directly interrogate for the offset of a moment, in both ZonedDateTime and OffsetDateTime objects. Call getOffset to get a ZoneOffset object.

    LocalDate ld = LocalDate.of( 2021 , Month.FEBRUARY , 23 );
    LocalTime lt = LocalTime.of( 18 , 0 , 0 );
    
    ZonedDateTime zdtLondon = ZonedDateTime.of( ld , lt , ZoneId.of( "Europe/London" ) );
    OffsetDateTime odtUtc = OffsetDateTime.of( ld , lt , ZoneOffset.UTC );
    
    ZoneOffset offsetLondon = zdtLondon.getOffset();
    ZoneOffset offsetUtc = odtUtc.getOffset();
    
    System.out.println( zdtLondon + " has an offset of: " + offsetLondon );
    System.out.println( odtUtc + " has an offset of: " + offsetUtc );
    

    We see both have an offset of zero hours-minutes-seconds from UTC, indicated by a Z, pronounced “Zulu”.

    2021-02-23T18:00Z[Europe/London] has an offset of: Z

    2021-02-23T18:00Z has an offset of: Z

    Change the month to June, and we get different offsets. London time shows an hour ahead of UTC.

    2021-06-23T18:00+01:00[Europe/London] has an offset of: +01:00

    2021-06-23T18:00Z has an offset of: Z