Получение неправильного результата после вычитания дня из даты с использованием LocalDateTIme и ZonedDateTime

Я тестирую с "2016-03-28T02:00:00+0200" (1459123200 в UTC Sec.)

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

"2016-03-27T03:00:00+0200"

Но я получаю это:

2016-03-26T01:00+01:00[Европа/Стокгольм]

КОД:

public class DateFormatSampleCode {
    public static void main(String[] args) 
    {
        LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(1459123200, 0, ZoneOffset.UTC);

        System.out.println(localDateTime);
        localDateTime = localDateTime.minusDays(1);
        System.out.println(localDateTime);

        ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of("Europe/Stockholm"));

        System.out.println(zonedDateTime);
    }
}

Пожалуйста, проверьте и укажите, где я ошибаюсь.


person Dushyant Sapra    schedule 22.02.2017    source источник
comment
Вы не можете вычесть день из местной даты/времени, которые не имеют понятия о переходе на летнее время, и ожидать, что будут применяться правила перехода на летнее время.   -  person RealSkeptic    schedule 22.02.2017
comment
@RealSkeptic Не могли бы вы подсказать, как это сделать.   -  person Dushyant Sapra    schedule 22.02.2017
comment
Если вам нужны зонированные дата/время, используйте ZonedDateTime. Не используйте LocalDateTime.   -  person RealSkeptic    schedule 22.02.2017
comment
Я думаю, что я уже использовал его выше. Дело в том, что я получаю время в сек. в формате UTC от эпохи. Мне нужно сохранить его и вернуть с зонным смещением.   -  person Dushyant Sapra    schedule 22.02.2017


Ответы (2)


Хорошо, что вы нашли решение, я просто хотел бы добавить некоторые идеи и предложить небольшое улучшение к вашему ответу.

Установка часового пояса JVM по умолчанию с использованием TimeZone.setDefault — не лучший способ добиться этого. Хотя это может работать в большинстве случаев, это немного рискованно и подвержено ошибкам, если вы считаете, что этот код работает в более сложной среде.

Это связано с тем, что TimeZone.setDefault изменяет часовой пояс по умолчанию для всего JVM. Это повлияет на любое другое приложение, работающее в той же JVM. Другие части того же приложения также будут затронуты, и даже этот же код, работающий в нескольких потоках, может дать вам неправильные результаты (и условия гонки сложно отлаживать).

Я заметил, что вы используете TimeZone.setDefault(TimeZone.getTimeZone(timezone));. Это означает, что вы уже работаете с определенным часовым поясом, поэтому нет необходимости полагаться на значение по умолчанию JVM. Если у вас есть конкретное имя часового пояса, просто используйте его вместо имени по умолчанию. Поэтому я предлагаю вам, чтобы метод addDays был таким:

public ZonedDateTime addDays(long myUTCTimeInSeconds, int days, String timezone) {
    // get the instant from the UTC seconds
    Instant instant = Instant.ofEpochSecond(myUTCTimeInSeconds);
    // get the instant at the specified timezone
    ZonedDateTime z = instant.atZone(ZoneId.of(timezone));

    // add days
    return z.plusDays(days);
}

Сделаны улучшения:

  • plusDays уже вычитает 1 день, если передать ему -1. Нет необходимости проверять значение и использовать метод abs.
  • не используйте часовой пояс JVM по умолчанию: вместо ZoneId.systemDefault() используйте timezone, который у вас уже есть (тот, который вы использовали в методе setDefault)
  • instant.atZone эквивалентно ZonedDateTime.ofInstant. ИМО, atZone более "читабелен", но в данном случае это вопрос выбора и стиля кода. На окончательный результат это не влияет.

При этом вы можете сделать:

// call directly, no need to change the default timezone
System.out.println(addDays(1459123200, -1, "Europe/Stockholm"));

Это напечатает:

2016-03-27T03:00+02:00[Европа/Стокгольм]

person Community    schedule 11.09.2017

Думаю, я могу ответить на свой вопрос выше.

Вот код.

public ZonedDateTime addDays(long myUTCTimeInSeconds, int days) {
    Instant instant = Instant.ofEpochSecond(myUTCTimeInSeconds);
    ZonedDateTime dateTimeWithOffSet = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
    if (localDays >= 0) {
        dateTimeWithOffSet = dateTimeWithOffSet.plusDays(localDays);
    } else {
        dateTimeWithOffSet = dateTimeWithOffSet.minusDays(abs(localDays));
    }
    return dateTimeWithOffSet;
}

Если часовой пояс отличается от часового пояса системы, мы можем установить часовой пояс по умолчанию и после вызова вышеуказанного метода сбросить часовой пояс следующим образом:

TimeZone systemDefaultTimeZone = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone(timezone));

addDays(1459123200, 1);
TimeZone.setDefault(systemDefaultTimeZone);
person Dushyant Sapra    schedule 07.03.2017