6

I have timezone related problem.

I have a program in JAVA and MySql database. I do not use any ORM, but I have custom jdbc library which uses mysql connector java v 6.0.4

I send settings as part of the connection string as:

 serverTimezone - America/Chicago
 useTimezone - true
 useJDBCCompliantTimezoneShift - false
 useLegacyDatetimeCode - false

I try to select a date from db e.g 2019-02-13.

Database is located at time zone US/Eastern and server running java program is located at time zone America/Chicago.

There is no way to change the location / time zone of any of the servers.

In Java, I get a date one day backwards (2019-02-12). Problem is caused because of timestamp -

1550034000 is 2019-02-13 00:00:00 in US/Eastern

but

1550034000 is 2019-02-12 23:00:00 in America/Chicago

So as the result I have java.sql.Date object with date 2019-02-12. There is no help to add timezone offset because time information is cut off from date.

Can you propose some solution on how to get a proper date without timezone shift?

Edit: I am using serverTimezone setting but I am not sure if value should be timezone that database is using or it is just overriding timezone of JVM / server running the application.

piet.t
  • 11,718
  • 21
  • 43
  • 52
peter.cambal
  • 522
  • 6
  • 16
  • Show the code (inserts and selects)? Show the table structure, specificity the types of the date objects. – danblack Feb 13 '19 at 09:25
  • 1
    [Check this out](https://stackoverflow.com/a/43270268/2185630) as it could give you some clues. – dbl Feb 13 '19 at 09:26
  • It may require modifying your custom JDBC library, but if the column datatype is `date`, retrieve into a `LocalDate`, not a `java.sql.Date`. The latter has design problems (as you have encountered, but not limited to that) and is long outdated. `LocalDate` is from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). The modern API is so much nicer to work with. – Ole V.V. Feb 13 '19 at 09:35
  • @danblack I was able to simulate the problem with simple select - SELECT CURDATE() as 'date'; – peter.cambal Feb 13 '19 at 09:35
  • The _1550034000 is 2019-02-13 00:00:00 in US/Eastern ... in America/Chicago_ part is correct behavior. What exactly do you want? Show the times relative to Eastern time to people in Chicago? – Salman A Feb 13 '19 at 10:40
  • @SalmanA I wanted to show dates to people in Chicago. I do not care about the hours and minutes. And the dates are for both zones same except for 2 hours a day. – peter.cambal Feb 13 '19 at 12:24
  • 1
    Are you able to modify your custom JDBC library to return `java.time.LocalDate` instead of (or in addition to) `java.sql.Timestamp` ? – Gord Thompson Feb 13 '19 at 13:16

2 Answers2

5

First, I have read that you can’t in your case, but for other readers I would like to state that the general recommendation is to run everything in UTC, at least when you span more than one time zone. So this would have been the best solution to your issue.

Second, as both I and Gord Thompson have mentioned in comments, the second best solution is to handle dates as LocalDate, not java.sql.Date. While the latter only pretends not to have time of day, it really has design problems that make it hard to solve your issue. A LocalDate really is a date without time of day and without time zone, so should be a bet that is a lot safer (except that database drivers that convert incorrectly to and from LocalDate have been heard of; I keep my fingers crossed; again running everything in UTC would eliminate those bugs too). Edit: Assuming you can modify your custom JDBC library, here’s how to get a LocalDate from a ResultSet:

    LocalDate correctDateDirectlyFromDatabase
            = yourResultSet.getObject("yourDateColumn", LocalDate.class);

It requires at least JDBC 4.2, you probably have that.

If none of the above is available to you, here comes the way to mend the incorrect Date you got from the database. It’s a bit of a hack, but will work.

import java.sql.Date;

// …

    // Modern ID of the time zone previously known as US/Eastern
    ZoneId datebaseTimeZone = ZoneId.of("America/New_York");

    Date dateFromDatabase = new Date(TimeUnit.SECONDS.toMillis(1_550_034_000));
    System.out.println("Date as retrieved from database (or pretending): " + dateFromDatabase);

    long epochMillis = dateFromDatabase.getTime();
    ZonedDateTime dateTime = Instant.ofEpochMilli(epochMillis)
            .atZone(datebaseTimeZone);
    LocalDate realDate = dateTime.toLocalDate();
    // Sanity check
    if (! realDate.atStartOfDay(datebaseTimeZone).equals(dateTime)) {
        throw new IllegalStateException("Failed to convert date correctly from " + datebaseTimeZone + " time zone");
    }

    System.out.println("Date is " + realDate);

When I ran this in America/Chicago time zone, it printed:

Date as retrieved from database (or pretending): 2019-02-12
Date is 2019-02-13

I have tried running it in other time zones. In some time zones the first line prints 2019-02-12, in others 2019-02-13. The last line prints 2019-02-13 in all time zones I have tried.

Now I have given you a LocalDate. That’s good, this is what you should want to use in your further processing. In case you need a java.sql.Date for another legacy API that you don’t want to change just yet, convert back to a correct java.sql.Date this way:

    Date oldfashionedJavaSqlDate = Date.valueOf(realDate);
    System.out.println("Date converted back to " + oldfashionedJavaSqlDate);

Date converted back to 2019-02-13

And when I say correct, it requires that no one is tampering with the default time zone of your JVM, which is easy for any program running in the JVM to do.

Link: Oracle tutorial: Date Time explaining how to use java.time.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Another approach that occurred to me would be to retrieve `SELECT CAST('1970-01-01 00:00:00' AS DATETIME)` as a `Timestamp` and then use `getTime()` to determine the (milli)seconds to use for a `ZoneOffset`. It wouldn't require prior knowledge of the server configuration, although DST/Summer_Time might throw a wrench into things. – Gord Thompson Feb 13 '19 at 15:51
  • Yes, I’m afraid that summer time/DST renders this approach too risky. Upside is of course that it would be more flexible in case the database time zone is not known or can change. – Ole V.V. Feb 13 '19 at 15:54
  • I created function that converts java.sql.date from db to LocalDate as you suggested and later I am using java.time API. Or is there some way to tell driver to convert date result using java.time automatically? Later I created function to compare offsets of these two timezones and added the offset in seconds between them to get the desired result, but decided to use your solution to convert because it is more elegant :) – peter.cambal Feb 15 '19 at 10:54
  • Yes, @peter.cambal, if your JDBC driver complies with JDBC 4.2 or later, it will give you a `LocalDate` directly. See my little edit. – Ole V.V. Feb 15 '19 at 11:23
  • 1
    @peter.cambal - You may need to upgrade your MySQL Connector/J to something newer than 6.0.4 in order to have `.getObject("yourDateColumn", LocalDate.class)` work reliably. MySQL Connector/J had (and [still has](https://bugs.mysql.com/?id=93444)) some quirks regarding `java.time` objects. – Gord Thompson Feb 15 '19 at 15:12
2

I am using serverTimezone setting but I am not sure if value should be timezone that database is using or it is just overriding timezone of JVM / server running the application.

serverTimezone=America/Chicago means "interpret the results from the server as if the server was using the America/Chicago time zone regardless of the default time zone that the server is configured to use". So if you used that setting in your connection string you would get Timestamp values converted to America/Chicago even though the default time zone for the server is apparently America/New_York.

However, before committing to that approach you'd want to confirm that the server really is using America/New_York (which would presumably switch back and forth between Eastern Standard Time and Eastern Daylight Time) and not a fixed offset like UTC-5 (which would always stay on "Eastern Standard Time").

Gord Thompson
  • 116,920
  • 32
  • 215
  • 418