I have a MySQL database which is storing a datetime value, let’s say 2020-10-11 12:00:00. (yyyy-mm-dd hh:mm:ss format)
The type of this date (in mysql) is DATETIME
When I retrieve this data in my controller, it has the java 7 type “Date”. But it adds a timezone CEST due to my locale I suspect. Here I already find confusing that when displaying this date which is not supposed to have a timezone attached it actually has… and the debugger says it is “2020-10-11 12:00:00 CEST”.
My problem is that date was not stored with the CEST timezone. It was stored with the America/New_York one, for example. EDIT: What I mean with this line, is that the date was stored from new york using the timezone of new york. So, it was really 12:00:00 AM there, but here in Madrid it was 18:00:00 PM. I need that 18:00:00.
So in New York, someone did an insert at that time. Which means that the time in Europe was different. I need to calculate which time was in Europe when in America was 12AM. But my computer keeps setting that date to CEST when I retrieve it so all my parsing attempts are failing… This was my idea:
Date testingDate // This date is initialized fetching the "2020-10-11 12:00:00" from mySql Calendar calendar = new GregorianCalendar() calendar.setTime(testingDate) calendar.setTimeZone(TimeZone.getTimeZone("America/New_York") SimpleDateFormat localDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") TimeZone localTimeZone = TimeZone.getTimeZone("Europe/Madrid") localDateFormatter.setTimeZone(localTimeZone) String localStringDate = localDateFormatter.format(calendar.getTime()) Date newDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(localStringDate)
Here my idea is that: I create a brand new calendar, put on it the time that I had on America and I also say hey this calendar should have the America Timezone. So when I get the time of it using a formatter from Europe it should add the corresponding hours to it. It makes a lot of sense in my head but it is just not working in the code D: And I really don’t want to calculate the time difference by myself and adding or substracting the hours because that would look extremely hardcoded in my opinion.
Can any one give me some ideas of what I’m interpreting wrong or how should I tackle this problem in a better way?
Important: I’m using java 7 and grails 2.3.6.
Advertisement
Answer
My problem is that date was not stored with the CEST timezone. It was stored with the America/New_York one, for example.
From what I know of MySQL, this is impossible.
Calendar calendar = new GregorianCalendar()
No, don’t. The calendar API is a disaster. Use java.time
, the only time API in java that actually works and isn’t completely broken / very badly designed. If you can’t (java 7 is extremely out of date and insecure, you must upgrade!), there’s the jsr310 backport. Add that dependency and use it.
Let me try to explain how to understand time first, because otherwise, any answer to this question cannot be properly understood:
About time!
There are 3 completely different concepts and they are all often simplified to mean ‘time’, but you shouldn’t simplify them – these 3 different ideas are not really related and if you ever confuse one for another, problems always occur. You cannot convert between these 3 concepts unless you do so deliberately!
- “solarflares time”: These describe the moment in time as a universal global concept that something occurred or will occur. “That solar flare was observed at X” is a ‘solarflares’ time. Best way to store this is millis since epoch.
- “appointment time”: These describe a specific moment in time as it was or will be in some localized place, but stated in a globally understandable way. “We have a zoom meeting next tuesday at 5” is one of these. It’s not actually constant, because locales can decide to adopt new timezones or e.g. move the ‘switch date’ for daylight savings. For example, if you have an appointment at your dentist on ‘november 5th, at 17:00, 2021’, and you want to know how many hours are left until your appointment starts, then the value should not change just because you flew to another timezone and are looking at this number from there. However, it should change if the country where you made the appointment in decided to abolish daylight savings time. That’s the difference between this one and the ‘solarflares’ one. This one can still change due to political decisions.
- “wake-up-alarm time”: These describe a more mutable concept: Some way humans refer to time which doesn’t refer to any specific instant or is even trying to. Think “I like to wake up at 8”, and thus the amount of time until your alarm will go off next is continually in flux if you are travelling across timezones.
Now, on to your question:
I have a MySQL database which is storing a datetime value, let’s say 2020-10-11 12:00:00. (yyyy-mm-dd hh:mm:ss format)
Not so fast. What exact type does that column have? What is in your CREATE TABLE
statement? The key thing to figure out here is what is actually stored on disk? Is it solarflare, appointment, or wakeup-alarm? There’s DATE
, DATETIME
and TIMESTAMP
, and over the years, mysql has significantly changed how these things are stored.
I believe that, assuming you are using the modern takes on storage (So, newish mysql and no settings to explicitly emulate old behaviour), e.g. a DATETIME
stores sign, year, day, hour, minute, and second under the hood, which means it is wakeup alarm style: There is no timezone info in this, therefore, the actual moment in time is not set at all and depends on who is asking.
Contrast to TIMEZONE which is stored as UTC epoch seconds, so it’s solarflares time, and it doesn’t include any timezone at all. You’d have to store that separately. As far as I know, the most useful of the 3 time representations (appointment time) is not a thing in mysql. That’s very annoying; mysql tends to be, so perhaps par for the course.
In java, all 3 concepts exist:
solarflares time is
java.time.Instant
.java.util.Date
,java.sql.Timestamp
,System.currentTimeMillis()
are also solarflares time. That ‘Date’ is solarflares timestamp is insane, but then there is a reason that API was replaced.appointment time is
java.time.ZonedDateTime
wakeup-alarm time is
java.time.LocalDateTime
.
When I retrieve this data in my controller, it has the java 7 type “Date”.
Right. So, solarflares time.
Here’s the crucial thing:
If the type of time stored in MySQL does not match the type of time on the java side, pain happens.
It sure sounds like you have wakeup-alarm time on disk, and it ends up on java side as solarflares time. That means somebody involved a timezone conversion. Could have happened internally in mysql, could have happened in flight between mysql and the jdbc driver (mysql puts it ‘on the wire’ converted), or the jdbc driver did it to match java.sql.Timestamp.
The best solution is not to convert at all, and the only real way to do that is to either change your mysql table def to match java’s, so, make that CREATE TABLE (foo TIMESTAMP)
, as TIMESTAMP is also solarflares time, or, to use, at the JDBC level, not:
someResultSet.getTimestamp(col);
as that returns solarflares time, but:
someResultSet.getObject(col, LocalDateTime.class);
The problem is: Your JDBC driver may not support this. If it doesn’t, it’s a crappy JDBC driver, but that happens sometimes.
This is still the superior plan – plan A. So do not proceed to the crappy plan B alternative unless there is no other way.
Plan B:
Acknowledge that conversion happens and that this is extremely annoying and errorprone. So, make sure you manage it, carefully and explicitly: Make sure the proper SET call is set up so that mysql’s sense of which timezone we are at matched. Consider adding storing the timezone as a column in your table if you really need appointment time. etcetera.