LocalDateTime values are wrong around 1900 (caused by JDK-8061577)

Description

I have a problem with a timestamp field which is mapped to a LocalDateTime. We don't use UTC times in the database. Therefor, I expect the timestamp in the database to be equal to the value in the LocalDateTime.

The problem is that the conversion from a Timestamp to a LocalDateTime in LocalDateTimeJavaDescriptor uses the default timezone. For some reasons, this makes the two timestamps differ for timestamps before January 2, 1900.

I attached a testcase which shows the issue.

Environment

None

Activity

Show:
Yoann Rodière
February 27, 2019, 11:40 AM

> I do not think the problem is in the JDK. Apparently, we had some weird timezones in the Netherlands before World War 2.

You definitely had But the problem is not limited to Netherlands, it also shows up with Europe/Paris and Europe/Oslo, though with a different breaking point (around 1895 for Oslo).

Note that I'm not saying there's a problem because the result of the conversion by the JDK is different from what I expect.
I'm saying there is a problem because:

  1. The result of the conversion when using java.time is different from the result of the conversion when using java.util (Calendar, ...).

  2. Even if java.time was wrong, there's still a problem in java.util because a round-trip conversion (to milliseconds since the epoch and back) sometimes does not produce the original value.

Anyway, I'm not saying that to avoid changing the code in Hibernate ORM. Changing the way we do conversion seems to work around the bug, which is why I submitted a PR.

SH
February 27, 2019, 12:43 PM

Thank you for the explanation.

I took a look at your PR. It will work for converting a timestamp to a LocalDateTime, but I believe the other types should be handled another way as well (i.e. Date.class). And this problem is not limited to the LocalDateTimeJavaDescriptor, but appears also in LocalDateJavaDescriptor.

Furthermore, looking at the original LocalDateTimeJavaDescriptor, there are probably some more bugs in the methods wrap and unwrap. If the input value for the method wrap is a java.util.Date (I do not know when this will be the case, but it is probably implemented with a reason), this value is cast to a Timestamp. This will throw a ClassCastException.

Another example: unwrapping a value to a java.sql.Date returns a java.util.Date. This can also lead to a class cast exception (a java.sql.Date is a java.util.Date, but not the other way around).

I will attach 2 unit tests for testing the wrap and unwrap-methods of LocalDateTimeJavaDescriptor and LocalDateJavaDescriptor.

Yoann Rodière
March 1, 2019, 12:54 PM

I did some research, and it turns out there is actually a problem in the JDK, but it will never get fixed: https://bugs.openjdk.java.net/browse/JDK-8061577
The problem is a fundamental difference in the internal calendar used by GregorianCalendar and java.time. In short, they are both right, but they make different assumptions, and are thus not always compatible.

I updated my PR to test for this specific problem and to implement a workaround for LocalDateTime, Instant, ZonedDateTime and OffsetDateTime. LocalDate doesn't seem affected, but I added a test to demonstrate that.
The resulting behavior probably won't be perfect, because there's a fundamental mismatch between java.sql.Timestamp (used by the JDBC drivers) and the java.time types, but it should be a bit better. For a perfect match between the database and your application, I can't think of any other solution than using java.sql.Timestamp in your application, unfortunately.

Regarding the other types that unwrap/wrap are supposed to handle: the root cause is not the same as the problem you detected in LocalDateTime, so it would deserve a separate ticket. I tried to expand a bit on your tests and started to experiment with some fixes, but there are lots of unexpected results. Some are obviously wrong (like returning an Instant when a Long is requested), while others are subjective (different people will want to convert OffsetDateTime to java.sql.Date differently, for example). I doubt everything can be fixed.

However, as far as I can see, these other conversions are not used in Hibernate ORM unless you define your own custom types that map OffsetDateTime (for example) to something other than java.sql.Timestamp.

In case it may be of use, here's the branch where I experimented: https://github.com/yrodiere/hibernate-orm/tree/java-type-descriptor-bugs

SH
March 6, 2019, 9:27 AM

I did some more testing with the LocalDate. Here the problem is not present, but only if the incoming type is java.sql.Date. If for some reason the database would returns Timestamp or the wrap method is called with a java.util.Date, wrong values are returned.

I do not know if this could happen.

Yoann Rodière
March 14, 2019, 7:55 AM

A fix was merged in https://github.com/hibernate/hibernate-orm/pull/2800 ; it will be released in 5.4.2 . Thanks for the detailed reports!

Fixed

Assignee

Yoann Rodière

Reporter

SH

Fix versions

Labels

None

backPortable

Backport?

Suitable for new contributors

None

Requires Release Note

None

backportDecision

None

Components

Affects versions

Priority

Major
Configure