1

I am trying to find the difference between 2 datetime which are in Adelaide timezone. The difference in hours is returned correctly in case of DST start but wrong for DST end.

As per the output below, in case of DST start, for 1st Oct 2023, the difference in hours between 01:30 and 03:30 AM is 1 which is as expected considering DST switch(02:30 AM time does not exist).

But the same is not working in case of DST end. For 2nd April 2023, the difference in hours between 02:30 and 03:30 AM also should be 1 but the output is 2 (120 minutes).

Below the same code and it's respective output. Can you please let me know why the ZonedDateTime is able to handle only one side of the DST change and not the other. Note: Using Java 8.

public class DateHourDifference {
    public static void main(String[] args) {
        dstStart();
        dstEnd();
    }

    private static void dstEnd() {
        // When local daylight time is about to reach
        // Sunday, 2 April 2023, 3:00:00 am clocks are turned backward 1 hour to
        // Sunday, 2 April 2023, 2:00:00 am local standard time instead.
        System.out.println("-------------DST End---------------------------------");
        LocalDateTime schedulerRunTime = LocalDateTime.of(2023, 04, 02, 3, 30);
        LocalDateTime lastStatusLogRecord = LocalDateTime.of(2023, 04, 02, 2, 30);
        ZonedDateTime currentSchedulerRun = schedulerRunTime.atZone(ZoneId.of("Australia/Adelaide"));
        ZonedDateTime lastSchedulerRun = lastStatusLogRecord.atZone(ZoneId.of("Australia/Adelaide"));
        System.out.println("currentSchedulerRun " + currentSchedulerRun.toString());
        System.out.println("lastSchedulerRun " + lastSchedulerRun.toString());
        long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES.between(lastSchedulerRun, currentSchedulerRun);
        System.out.println("deltaBetweenDatesInMinutes " + deltaBetweenDatesInMinutes);
        long deltaBetweenDatesInHours = ChronoUnit.HOURS.between(lastSchedulerRun, currentSchedulerRun);
        System.out.println("deltaBetweenDatesInHours " + deltaBetweenDatesInHours);
    }

    private static void dstStart() {
        // When local standard time is about to reach
        // Sunday, 1 October 2023, 2:00:00 am clocks are turned forward 1 hour to
        // Sunday, 1 October 2023, 3:00:00 am local daylight time instead.
        System.out.println("---------------DST Start-------------------------------");
        LocalDateTime schedulerRunTime = LocalDateTime.of(2023, 10, 01, 3, 30);
        LocalDateTime lastStatusLogRecord = LocalDateTime.of(2023, 10, 01, 1, 30);
        ZonedDateTime currentSchedulerRun = schedulerRunTime.atZone(ZoneId.of("Australia/Adelaide"));
        ZonedDateTime lastSchedulerRun = lastStatusLogRecord.atZone(ZoneId.of("Australia/Adelaide"));
        System.out.println("currentSchedulerRun " + currentSchedulerRun.toString());
        System.out.println("lastSchedulerRun " + lastSchedulerRun.toString());
        long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES.between(lastSchedulerRun, currentSchedulerRun);
        System.out.println("deltaBetweenDatesInMinutes " + deltaBetweenDatesInMinutes);
        long deltaBetweenDatesInHours = ChronoUnit.HOURS.between(lastSchedulerRun, currentSchedulerRun);
        System.out.println("deltaBetweenDatesInHours " + deltaBetweenDatesInHours);
    }
}
---------------DST Start-------------------------------
currentSchedulerRun 2023-10-01T03:30+10:30[Australia/Adelaide]
lastSchedulerRun 2023-10-01T01:30+09:30[Australia/Adelaide]
deltaBetweenDatesInMinutes 60
deltaBetweenDatesInHours 1
-------------DST End---------------------------------
currentSchedulerRun 2023-04-02T03:30+09:30[Australia/Adelaide]
lastSchedulerRun 2023-04-02T02:30+10:30[Australia/Adelaide]
deltaBetweenDatesInMinutes 120
deltaBetweenDatesInHours 2
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Ashik K
  • 13
  • 5
  • For the DST end scenario, since it returns as 2 hours, my list of timestamps has 2 records with values 02:30 and 02:30 and as a workaround, I use a Set here to remove the duplicates, but ideally this should not have been the case. – Ashik K Dec 08 '22 at 23:28
  • 1
    Your issue is that 2:30 on 2 April is ambiguous. One hour after 2:30 summer time it is again 2:30, this time standard time. Use `lastSchedulerRun.withEarlierOffsetAtOverlap()` and `lastSchedulerRun.withLaterOffsetAtOverlap()` to control which one you get (`2023-04-02T02:30+10:30[Australia/Adelaide]` or `2023-04-02T02:30+09:30[Australia/Adelaide]`). – Ole V.V. Dec 10 '22 at 11:30

2 Answers2

2

See how clocks will be turned forward and backward on your given dates:

enter image description here

Source: timeanddate.com

On 02-Apr-2023: the clocks will be set back to 02:00. The moment when the time first reaches 2:59:59, clocks will be moved back to 2:00:00 standard time and they will begin ticking towards 3 am for a second time. When the repeated hour is over, local time will go from 2:59:59 to 3:00:00, just like on any other day. This day is 25 hours long.

On 01-Oct-2023: The hour from 2:00:00 to 2:59:59 won't exist on the night of the switch. Clocks will be turned forward from 1:59:59 standard time to 3:00:00 Daylight Saving Time. This day is 23 hours long.

Given this explanation, the following code behaves as expected:

public class Main {
    public static void main(String args[]) {
        ZoneId zoneId = ZoneId.of("Australia/Adelaide");

        ZonedDateTime schedulerRunTime = ZonedDateTime.of(LocalDateTime.of(2023, 4, 2, 3, 30), zoneId);
        ZonedDateTime lastStatusLogRecord = ZonedDateTime.of(LocalDateTime.of(2023, 4, 2, 2, 30), zoneId);
        System.out.println(ChronoUnit.HOURS.between(lastStatusLogRecord, schedulerRunTime));

        schedulerRunTime = ZonedDateTime.of(LocalDateTime.of(2023, 10, 1, 3, 30), zoneId);
        lastStatusLogRecord = ZonedDateTime.of(LocalDateTime.of(2023, 10, 1, 1, 30), zoneId);
        System.out.println(ChronoUnit.HOURS.between(lastStatusLogRecord, schedulerRunTime));
    }
}

Output:

2
1

Do not use 0 before a decimal integer

You have used 01, 02, 03 etc. which has not caused a problem, just by chance because they are less than 8. Java considers an integer starting with 0 as an integer with a base of 8 i.e. 08 and 09 do not exist (the range of octal digits is 0 to 7). Had you used something like, LocalDate.of(2022, 08, 09), the compiler would have complained.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • Thank you Arvind for the answer. So you mean to say that the code is returning 2 as the output in case of the DST end scenario is an acknowledgment/ understanding that the time between 2:00 to 2:59 was repeated, to indicate the repeated hour. – Ashik K Dec 10 '22 at 12:53
1

When a time zone is set to a java LocalDateTime, the date value is considered to be already in/converted to the specified TZ. If the TZ has DST settings, it's also considered to be already added/removed.

For your case : Australia/Adelaide DST starts 1st of Octobre at 02:00 and ends 2nd of April at the same time.

I'm now going to explain why the output is correct :

---------------DST Start-------------------------------

currentSchedulerRun 2023-10-01T03:30+10:30[Australia/Adelaide]

lastSchedulerRun 2023-10-01T01:30+09:30[Australia/Adelaide]

deltaBetweenDatesInHours 1

The date value is considered to be already in the Adelaide TZ and has the DST added, so the original time without DST is 02:30 which is exacly one hour from 01:30

-------------DST End---------------------------------

currentSchedulerRun 2023-04-02T03:30+09:30[Australia/Adelaide]

lastSchedulerRun 2023-04-02T02:30+10:30[Australia/Adelaide]

deltaBetweenDatesInHours 2

Like the above case, the date value is considered to be already in the Adelaide TZ and has the DST removed, so the original time with DST is 04:30 which is exacly two hours from 02:30

Hope this helps understanding more stuff about java timezones.

Habib Z.
  • 66
  • 4