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