Java 8 java.time: добавление TemporalUnit в Instant vs LocalDateTime

19

Я играю с новым пакетом java.time в Java 8. У меня есть устаревшая база данных, которая дает мне java.util.Date , которую я конвертирую в Instant .

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

Моя первая мысль была Instant.plus() , но это дает мне UnsupportedTemporalTypeException для значений больше дня. Мгновенно, по-видимому, не поддерживает операции на большие единицы времени. Хорошо, что угодно, LocalDateTime .

Итак, это дает мне этот код:

private Date adjustDate(Date myDate, TemporalUnit unit){
    Instant instant = myDate.toInstant();
    LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    dateTime = dateTime.plus(1, unit);
    Instant updatedInstant = dateTime.atZone(ZoneId.systemDefault()).toInstant();
    return new Date(dueInstant.toEpochMilli());
}

Теперь, это мой первый раз, используя новый API времени, поэтому я, возможно, что-то пропустил. Но мне кажется неуклюжим, что мне нужно идти:

Date --> Instant --> LocalDateTime --> do stuff--> Instant --> Date.

Даже если бы мне не пришлось использовать деталь Date, я бы все же подумал, что это было немного неудобно. Итак, мой вопрос в том, что я делаю это совершенно неправильно и что это лучший способ сделать это?

Изменить . Расширение обсуждения в комментариях.

Думаю, теперь мне лучше понять, как работают LocalDateTime и Instant с java.util.Date и java.sql.Timestamp. Спасибо всем.

Теперь, более практическое рассмотрение. Скажем, пользователь отправляет мне дату, где бы они ни находились в мире, произвольный часовой пояс. Они отправляют мне 2014-04-16T13:00:00 , который я могу проанализировать в LocalDateTime. Затем я конвертирую его непосредственно в java.sql.Timestamp и сохраняю в своей базе данных.

Теперь, не делая ничего другого, я вытаскиваю свой java.sql.timestamp из своей базы данных, конвертируюсь в LocalDateTime , используя timestamp.toLocalDateTime() . Все хорошо. Затем я возвращаю это значение моему пользователю с использованием форматирования ISO_DATE_TIME. Результатом является 2014-04-16T09:00:00 .

Я предполагаю, что это различие связано с некоторым типом неявного преобразования в / из UTC. Я думаю, что мой часовой пояс по умолчанию может применяться к значению (EDT, UTC-4), что объясняет, почему число отключено на 4 часа.

Новый вопрос (ы). Где здесь происходит неявное преобразование из местного времени в UTC? Каков лучший способ сохранить часовые пояса. Должен ли я не перейти непосредственно из локального времени в виде строки (2014-04-16T13: 00: 00) в LocalDateTime ? Должен ли я ожидать часовой пояс от ввода пользователя?

    
задан jacobhyphenated 01.04.2014 в 19:56
источник
  • Какую ценность вы хотели бы представлять здесь? Мгновенный логически не знает о системе календаря - это всего лишь момент времени, поэтому добавление месяца к нему не имеет смысла. Вы также должны тщательно рассмотреть вопрос о том, действительно ли вы хотите использовать системный часовой пояс - хотите ли вы получать разные результаты для одних и тех же значений в зависимости от того, где вы работаете? –  Jon Skeet 01.04.2014 в 20:00
  • Если все хранится в UTC в вашей базе данных, вы можете подумать, что отметка времени является мгновенной в часовой пояс UTC и преобразует ее в LocalDateTime с использованием UTC. Вы можете инкапсулировать это преобразование в свою сущность или использовать jadira, который (AFAIK) позволяет вам напрямую сопоставлять LocalDateTime. –  JB Nizet 01.04.2014 в 20:22
  • Это выглядит правильно для меня, если вы укажете UTC как часовой пояс. Я бы использовал Date.from (Instant) вместо использования миллисекунд. Будущие версии Hibernat, вероятно, будут поддерживать новые типы времени напрямую. –  JB Nizet 01.04.2014 в 20:32
  • JDBC требует, чтобы драйверы интерпретировали время и временные метки без часового пояса как находящиеся в локальном часовом поясе, поэтому, если ваш часовой пояс не является utc, или ваш драйвер не соответствует jdbc, это предположение неверно. –  Mark Rotteveel 01.04.2014 в 23:12
  • @jacobhyphenated. java.sql.Timestamp имеет метод toLocalDateTime () и статическое значениеOf (LocalDateTime). Не нужно использовать Instant в качестве посредника. –  Mark Rotteveel 02.04.2014 в 09:06
Показать остальные комментарии

1 ответ

17

Я продолжу и отправлю ответ на основе моего окончательного решения и своего рода резюме очень длинной цепи комментариев.

Чтобы начать, вся цепочка преобразования:

Date --> Instant --> LocalDateTime --> Do stuff --> Instant --> Date

Необходимо сохранить информацию о часовом поясе и выполнять операции в объекте, подобном дате, который знает календарь и весь контекст в нем. В противном случае мы рискуем неявно переходить в локальный часовой пояс, и если мы попытаемся перевести его в формат, пригодный для чтения человеком, время может измениться из-за этого.

Например, метод toLocalDateTime() класса java.sql.Timestamp неявно преобразуется в часовой пояс по умолчанию. Это было нежелательно для моих целей, но это не обязательно плохое поведение. Важно, однако, знать об этом. Это проблема с преобразованием непосредственно из устаревшего объекта даты Java в объект LocalDateTime . Поскольку унаследованные объекты обычно считаются UTC, преобразование использует локальное смещение часового пояса.

Теперь, скажем, наша программа принимает входные данные 2014-04-16T13:00:00 и сохраняет в базе данных как java.sql.Timestamp .

//Parse string into local date. LocalDateTime has no timezone component
LocalDateTime time = LocalDateTime.parse("2014-04-16T13:00:00");

//Convert to Instant with no time zone offset
Instant instant = time.atZone(ZoneOffset.ofHours(0)).toInstant();

//Easy conversion from Instant to the java.sql.Timestamp object
Timestamp timestamp = Timestamp.from(instant);

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

Timestamp timestamp = ...

//Convert to LocalDateTime. Use no offset for timezone
LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0));

//Add time. In this case, add one day.
time = time.plus(1, ChronoUnit.DAYS);

//Convert back to instant, again, no time zone offset.
Instant output = time.atZone(ZoneOffset.ofHours(0)).toInstant();

Timestamp savedTimestamp = Timestamp.from(output);

Теперь нам просто нужно выводить как удобочитаемую строку String в формате ISO_LOCAL_DATE_TIME .

Timestamp timestamp = ....
LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0));
String formatted = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(time);
    
ответ дан jacobhyphenated 21.04.2014 в 14:45
  • ZoneOffset.ofHours (0) может быть лучше выражен как ZoneOffset.UTC. Вам также может быть проще работать с ZonedDateTime, так как этот класс поддерживает все поведение плюс / минус LocalDateTime, но сохраняет информацию о часовом поясе. –  JodaStephen 27.05.2014 в 11:57
  • Для конкретного случая использования дней вам не нужно (местное / зонирование) время по дате, так как продолжительность дней может быть добавлена ​​непосредственно к моменту: Timestamp.from (timestamp.toInstant (). plus (Продолжительность .ofDays (1))) –  Christopher Currie 10.08.2016 в 20:32
  • Добавление Duration.ofDays к Instant не будет учитывать летнюю экономию или тому подобное, поэтому не рекомендуется. Вместо этого конвертируйте в ZonedDateTime. –  Christoffer Hammarström 03.04.2017 в 19:34