Маппинг даты и времени в Java

Сначала определяют семантику поля, затем тип SQL и Java. «Дата» и «момент» — разные доменные значения.

Java 8+

СмыслJavaSQL-идея
календарная датаLocalDateDATE
время сутокLocalTimeTIME
локальные дата-время без зоныLocalDateTimeTIMESTAMP WITHOUT TIME ZONE
моментInstantтип/настройка для момента
момент со смещениемOffsetDateTimeзависит от диалекта
правила географической зоныZonedDateTimeчасто момент + отдельный zone id

Поддержка ORM и JDBC зависит от версии драйвера, JPA provider и СУБД. Маппинг проверяют интеграционным тестом, а не только типом поля.

@Entity
class PurchaseOrder {
    @Id
    private Long id;
 
    private Instant createdAt;
    private LocalDate deliveryDate;
}

Instant подходит для факта создания. LocalDate — для календарного дня доставки, если время не нужно.

До Java 8

  • java.sql.Date — SQL DATE;
  • java.sql.Time — SQL TIME;
  • java.sql.Timestamp — дата-время;
  • java.util.Date — момент, несмотря на название;
  • Calendar добавляет работу с зоной, но API mutable и неудобен.

В старом JPA для java.util.Date/Calendar применялся @Temporal:

@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;

С типами java.time @Temporal не используется.

Часовые пояса

Нужно согласовать:

  • JVM default zone;
  • JDBC driver/session zone;
  • тип столбца и семантику СУБД;
  • формат API;
  • правила отображения пользователю.

Настройка Hibernate hibernate.jdbc.time_zone может нормализовать JDBC-обмен для поддерживаемых типов, но не исправляет неверно выбранную доменную семантику.

Проверки

Интеграционные тесты должны включать:

  • не-UTC зону;
  • границу суток/месяца;
  • дробные секунды и их точность;
  • переход DST для зон, где он существует;
  • запись и чтение разными сессиями;
  • миграцию старых данных.

Связано: TIMESTAMP, JPA Hibernate ORM, Маппинг Hibernate.

Вопросы

Чем Instant отличается от LocalDateTime? Instant — единственный момент; LocalDateTime не содержит зоны или смещения.

Нужен ли @Temporal для LocalDate? Нет, он предназначался для старых Date/Calendar.

Достаточно ли хранить OffsetDateTime для будущего расписания? Не всегда: смещение не содержит правил географической зоны.

Зачем интеграционный тест с реальной БД? Драйвер и СУБД могут преобразовывать зону и точность иначе, чем ожидает unit-тест.