ZonedDateTime обрабатывает только одну сторону летнего времени при расчете часовой разницы

Я пытаюсь найти разницу между двумя датами и временем, которые находятся в часовом поясе Аделаиды. Разница в часах возвращается правильно в случае начала летнего времени, но неверно для окончания летнего времени.

Согласно приведенному ниже выводу, в случае перехода на летнее время на 1 октября 2023 года разница в часах между 01:30 и 03:30 составляет 1, что, как и ожидалось, с учетом перехода на летнее время (время 02:30 не существует).

Но то же самое не работает в случае окончания летнего времени. На 2 апреля 2023 года разница в часах между 02:30 и 03:30 также должна быть равна 1, но результат равен 2 (120 минут).

Ниже тот же код и соответствующий вывод. Не могли бы вы сообщить мне, почему ZonedDateTime может обрабатывать только одну сторону изменения летнего времени, а не другую. Примечание. Использование 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

Для сценария окончания летнего времени, поскольку он возвращается как 2 часа, в моем списке временных меток есть 2 записи со значениями 02:30 и 02:30, и в качестве обходного пути я использую Set здесь, чтобы удалить дубликаты, но в идеале это не должно иметь было дело.

Ashik K 09.12.2022 00:28

Ваша проблема в том, что 2:30 2 апреля неоднозначно. Через час после 2:30 по летнему времени снова 2:30, на этот раз стандартное время. Используйте lastSchedulerRun.withEarlierOffsetAtOverlap() и lastSchedulerRun.withLaterOffsetAtOverlap(), чтобы выбрать, какой из них вы получите (2023-04-02T02:30+10:30[Australia/Adelaide] или 2023-04-02T02:30+09:30[Australia/Adelaide]).

Ole V.V. 10.12.2022 12:30
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
164
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Когда часовой пояс установлен на java LocalDateTime, значение даты считается уже введенным/преобразованным в указанный TZ. Если в ТЗ есть настройки перехода на летнее время, он также считается уже добавленным/удаленным.

Для вашего случая: Летнее время в Австралии/Аделаиде начинается 1 октября в 02:00 и заканчивается 2 апреля в то же время.

Теперь я собираюсь объяснить, почему вывод правильный:

--------------- Начало летнего времени--------------------------------

currentSchedulerRun 2023-10-01T03:30+10:30[Австралия/Аделаида]

lastSchedulerRun 2023-10-01T01:30+09:30[Австралия/Аделаида]

deltaBetweenDatesInHours 1

Считается, что значение даты уже находится в TZ Аделаиды и к нему добавлено летнее время, поэтому исходное время без летнего времени составляет 02:30, что составляет ровно один час от 01:30.

----------------------------Конец летнего времени---------------------------------

currentSchedulerRun 2023-04-02T03:30+09:30[Австралия/Аделаида]

lastSchedulerRun 2023-04-02T02:30+10:30[Австралия/Аделаида]

deltaBetweenDatesInHours 2

Как и в приведенном выше случае, считается, что значение даты уже находится в TZ Аделаиды и летнее время удалено, поэтому исходное время с летним временем составляет 04:30, что составляет ровно два часа от 02:30.

Надеюсь, это поможет понять больше информации о часовых поясах Java.

Посмотрите, как часы будут переводиться вперед и назад в указанные даты:

Источник: timeanddate.com

02 апреля 2023 года: часы будут переведены на 02:00. В тот момент, когда время впервые достигнет 2:59:59, часы будут переведены обратно на 2:00:00 по стандартному времени, и они начнут отсчитывать 3 часа ночи во второй раз. Когда повторный час закончится, местное время изменится с 2:59:59 до 3:00:00, как и в любой другой день. Этот день длится 25 часов.

01 октября 2023 г.: час с 2:00:00 до 2:59:59 не будет существовать в ночь переключения. Часы будут переведены вперед с 1:59:59 по стандартному времени до 3:00:00 по летнему времени. Этот день длится 23 часа.

Учитывая это объяснение, следующий код ведет себя так, как ожидалось:

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));
    }
}

Вывод:

2
1

Не используйте 0 перед десятичным целым числом

Вы использовали 01, 02, 03 и т. д., которые не вызвали проблем, просто случайно, потому что они меньше 8. Java рассматривает целое число, начинающееся с 0, как целое число с основанием 8, т. е. 08 и 09 не существуют ( диапазон восьмеричных цифр от 0 до 7). Если бы вы использовали что-то вроде LocalDate.of(2022, 08, 09), компилятор бы пожаловался.

Спасибо Арвинд за ответ. Таким образом, вы хотите сказать, что код возвращает 2, поскольку вывод в случае сценария окончания летнего времени является подтверждением/пониманием того, что время с 2:00 до 2:59 повторялось, чтобы указать повторяющийся час.

Ashik K 10.12.2022 13:53

Другие вопросы по теме