question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

LocalDate binding doesn't work when MySQL server has a different timezone to the client

See original GitHub issue

I have a MySql server that is configured with a timezone of UTC. My Java app has a timezone of “Australia/Perth”. When I save down LocalDate fields, they are stored in the DB as the previous day. Eg if I bind 2018-01-10 when I query the DB I see 2018-01-09, and when I retrieve it using JDBI I get 2018-01-09.

Here is a simplified example:

public interface ReportDao {

    @SqlUpdate("insert into report (report_date, title) values (:date, :title")
    void insert(@Bind("date") LocalDate date, @Bind("title") String title);

    @SqlQuery("select report_date from report")
    List<LocalDate> getAllDates();
}

And this is used as follows:

    // ...
    final ReportDao verdictDao = jdbi.onDemand(ReportDao.class);
    LocalDate d = LocalDate.of(2018, Month.JANUARY, 10);
    reportDao.insert(d, "bar");
    reportDao.getAllDates().forEach(System.out::println);

2018-01-09 is printed, when I expect it to print out 2018-01-10.

Client set-up:

  • Timezone Australia/Perth
  • jdbi v3.6.0
  • mysql-connector-java v8.0.11 (using driver: com.mysql.cj.jdbc.Driver)

Server set-up:

  • Timezone UTC
  • MySQL 5.7.25-0ubuntu0.18.04.2
  • Column type either DATE, or DATETIME

The problem occurs because the MySQL driver will convert the java.sql.Date from the client timezone to the server timezone. This seems like the correct thing to do, generally. Here is where JDBI interacts with the MySQL driver, in JavaTimeArgumentFactory line 29:

register(LocalDate.class, Types.DATE, (p, i, v) -> p.setDate(i, java.sql.Date.valueOf(v)));

And in the MySQL driver, in ClientPreparedQueryBindings (called from ClientPreparedStatement), we see this:

    // JDBI calls the 2-arg setDate()
    @Override
    public void setDate(int parameterIndex, Date x) {
        setDate(parameterIndex, x, this.session.getServerSession().getDefaultTimeZone());
    }

    @Override
    public void setDate(int parameterIndex, Date x, TimeZone tz) {
        if (x == null) {
            setNull(parameterIndex);
        } else {
            if (this.ddf == null) {
                this.ddf = new SimpleDateFormat("''yyyy-MM-dd''", Locale.US);
            }
            this.ddf.setTimeZone(tz);
            setValue(parameterIndex, this.ddf.format(x)); // TODO set MysqlType?
        }
    }

I’ve tested the above scenario with PostgreSQL and it doesn’t suffer from the same issue.

I can think of 2 possible fixes for this in the JDBI source:

1. Pin the time-zone by supplying a calendar to the setDate() call, in JavaTimeArgumentFactory

// Calendar.getInstance() pins the client-side timezone thereby avoiding conversion
register(LocalDate.class, Types.DATE, (p, i, v) -> p.setDate(i, java.sql.Date.valueOf(v), Calendar.getInstance()));

2. Supply an ISO date string instead of the date, in JavaTimeArgumentFactory

// Send the String to the DB, and allow the DB to coerce it into a date
register(LocalDate.class, Types.DATE, (p, i, v) -> p.setDate(i, v.toString()));

I’ve tested 1. on both MySQL and Postgresql and it works. It is a bit horrible to create a calendar though.

I haven’t tested 2. but it should work. Especially, given that both MySQL and Postgresql drivers format the date as a string before sending to the servers. 2. is flaky though, as I’m pretty sure Oracle allows a date format to be set per session. So there’s a good chance that this will not work in systems not using ISO formats.

Please let me know if you’d like more info!

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:20 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
jcoleman-scottcommented, Jul 29, 2020

I can confirm this solved the issue for me.

2reactions
stevenschlanskercommented, Jul 26, 2020

Praise be to the Flying Spaghetti Monster! Would a MySQL / Jdbi user suffering LocalDate binding issues please confirm that time binding works well with the new driver? Thank you!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Spring data query for localdate returns wrong entries
LocalDate, LocalDateTime, and LocalTime values set through Connector/J were altered when there was a timezone difference between the server ...
Read more >
93444: LocalDateTime parameter values altered when client ...
Description: java.time.LocalDateTime parameter values are being altered when the client timezone and server timezone are different.
Read more >
LocalDate MySQL issue - wrong day persisted - Jmix Forum
Depending on the set time zone in the application the persisted date is wrong. LocalDate AFAIK should not be affected by time zones...
Read more >
Management & Monitoring - Micronaut Documentation
Random Port Binding. The way the server binds to random ports has improved and should result in fewer port binding exceptions in tests....
Read more >
PostgreSQL Timestamps and Timezones: What You Need to ...
If your aim is to write a brand new database application, then you ... The convener had to rely on their own calculations...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found