javaoraclehibernatespring-data-jpatimezone

Hibernate LocalDate conversion


Both my application and the database are on America/Sao_Paulo timezone.

The database is Oracle 19c and the application is using:

My problem is saving LocalDate or LocalDateTime to Oracle using JPA entities.

The table columns are all using the DATE type and the problem happens if I map them to LocalDate or LocalDateTime properties. The problem only happens on days where Daylight Time Savings starts.

Let's say I have 2 properties:

@Column(name = "holiday1")
private LocalDate holiday1;

@Column(name = "holiday2")
private LocalDateTime holiday2;
...
entity.setHoliday1(LocalDate.of(2004, 11, 2));
entity.setHoliday2(LocalDateTime.of(2004, 11, 2, 0, 0, 0));

In this example, 2004-11-02 was the start of DTS in Brazil, on this day there was no midnight, so Hibernate is changing the 00:00 time part to 01:00 whenever it does the INSERT command and I end up with 2004-11-02 01:00:00 on the database.

I tried adding the property spring.jpa.properties.hibernate.jdbc.time_zone=UTC and it made no difference.

If I change the properties' type to OffsetDateTime and do:

entity.setHoliday1(LocalDateTime.of(2004, 11, 2, 0, 0, 0).atOffset(ZoneOffset.UTC));

I end up with 2004-11-02 21:00:00 on the database. However if use both spring.jpa.properties.hibernate.jdbc.time_zone=UTC and OffsetDateTime Hibernate finally inserts 2004-11-02 00:00 on the database.

Is there a way to make Hibernate ignore the local timezone settings and treat LocalDate correctly? I can't globally set the application timezone to UTC or change a huge amount of legacy code to OffsetDateTime.


Solution

  • Even though JPA 2.2 doesn't support ZonedDateTime, Hibernate does. I ended changing the properties to ZonedDateTime and added spring.jpa.properties.hibernate.jdbc.time_zone=UTC.

    This was the only way I found to force Hibernate to behave in the way I expected.

    Internally, Hibernate uses TimeStamp to handle OffsetDateTime and Date to handle LocalDate. No matter what I tried, it always uses the default timezone to make the conversion before sending the data to the database.

    I also tried setting spring.datasource.hikari.data-source-properties.oracle.jdbc.timezoneAsRegion but it made no difference.

    Working with Hibernate is quite frustrating, this is not the first time I have to handle some buggy behavior because they re-implemented poorly something already offered by the JDBC driver.

    I didn't had the chance to try Hibernate 6, maybe I'll update this answer in the future.

    Update for Hibernate 6.2

    Well, it got even worse: LocalDate is still converted using the default timezone, no matter what I tried.

    In addition to the previous property, now I also had to add spring.jpa.properties.hibernate.timezone.default_storage=NORMALIZE.

    Despite what the documentation says, after I tried to use NORMALIZE_UTC, Oracle started throwing ORA-01878 errors.