Bug #98436 java.sql.PreparedStatement.setDate() and setObject(LocalDate) use wrong TimeZone
Submitted: 30 Jan 2020 6:26 Modified: 3 Feb 2020 11:56
Reporter: Ruslan Stelmachenko Email Updates:
Status: Duplicate Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version:Connector/J v8.0.19 OS:Debian (MySQL v5.7.28)
Assigned to: CPU Architecture:x86
Tags: date timezone

[30 Jan 2020 6:26] Ruslan Stelmachenko
Description:
Javadoc of java.sql.PreparedStatement.setDate() method says:

Sets the designated parameter to the given java.sql.Date value using the **default time zone of the virtual machine** that is running the application.

But, the implementation currently uses timezone of the MySQL Server instead of timezone of Java VM.

See:
* com.mysql.cj.ClientPreparedQueryBindings#setDate(int, java.sql.Date, java.util.Calendar)
* com.mysql.cj.AbstractQueryBindings#setObject(int, java.lang.Object) - which calls setDate with calender=null under the hood

This leads to wrong value of SQL DATE when MySQL server timezone have less offset than Java VM timezone (e.g. MySQL server: UTC, JVM: Europe/Kiev).

For example, with this query

UPDATE t SET date = ?

you do:

statement.setObject(1, LocalDate.of(1990, 10, 20));

or

statement.setDate(position, java.sql.Date.valueOf(LocalDate.of(1990, 10, 20)));

Both variants will actually save "1990-10-19" into DB.

How to repeat:
1. Set timezone of MySQL server instance to UTC.
2. Set timezone of JVM to some timezone east from UTC. E.g. Europe/Kiev.
3. Create a table with `DATE` column.
4. Create PreparedStatement with a query, that updates `DATE` field value and uses argument binding for the new field value.
5. Bind using PreparedStatement.setDate without Calendar argument or .setObject(LocalDate). Use "1990-10-20" as a date value.
6. Execute PreparedStatement.
7. Check the table state with any MySQL client. You will see that value of `DATE` column is "1990-10-19".

Suggested fix:
Change default timezone used by setDate method (if no Calendar instance explicitly given - 3rd argument) to current JVM timezone, as stated in PreparedStatement interface documentation.

See com.mysql.cj.ClientPreparedQueryBindings#setDate(int, java.sql.Date, java.util.Calendar).

Current workaround:

Use setDate variant with Calendar argument explicity passed with Calendar instance containing current JVM timezone. Like:

statement.setDate(1, java.sql.Date.valueOf(LocalDate.of(1990, 10, 20)), Calendar.getInstance());

Never use statement.setObject(1, LocalDate); variant until this bug is fixed or always use MySQL server instance with timezone equal to JVM timezone. But this is not always possible.
[3 Feb 2020 11:56] Alexander Soklakov
Hi Ruslan,

This report is a duplicate of Bug#91112.