Java 8 java.time: добавление TemporalUnit в Instant vs LocalDateTime
Я играю с новым пакетом 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
? Должен ли я ожидать часовой пояс от ввода пользователя?
Ответы
Ответ 1
Я продолжу и отправлю ответ на основе моего окончательного решения и своего рода резюме очень длинной цепи комментариев.
Чтобы начать, вся цепочка преобразования:
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);
Теперь нам просто нужно выводить как человеческую читаемую строку в формате ISO_LOCAL_DATE_TIME
.
Timestamp timestamp = ....
LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0));
String formatted = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(time);