Java : Mysql timestamp daylight saving bug - java

I have a particular table in mysql with a field refresh_time Type as timestamp.
In my code, I update the refresh_time field to a future date of next month. For that, I calculate the milliseconds for next month date using following logic :
periodRefresh = currentTime + MyServiceUtils.calculateNoOfDaysInMonth(currentTime)*86400000L;
And then the value periodRefresh (milliseconds) I convert to java.sql.Timestamp and pass it to db side to udpate the field :
new Timestamp(periodRefresh);
But what has happened was that the current date in db was 2021-04-03 15:57:13 and when the above logic was run to update the time to next month, same time, it updated the time as 2021-05-03 13:57:13 which is 2 hours less than expected.
Our server follows UTC timezone, but I see that the user was from Australia, and in that day (4th April 2021) there was a daylight savings time in AEST. But even then it doesn't add to above as if I convert 2021-04-03 15:57:13 UTC to AEST, it comes as 2021-04-03 01:57:13 and the daylight savings there is at 3 am.
All the above has confused me a bit with following questions:
If I am following UTC timezones, then also does sql.Timestamp and mysql can get affected by daylight saving time?
How is AEST daylight saving time affecting my Australia based user when I follow UTC timezone?
Even if DST was affecting, so it should have increased/decreased time by 1 hour, but how in my case a difference of 2 hours happened?

In MySQL, TIMESTAMP datatypes are always stored in tables as UTC. When you put them into the database, it first translates them from your connection's time_zone setting to UTC. And when you retrieve them it translates them the other way. This is not true of DATETIME data types.
This TIMESTAMP behavior is handy if you have a global app. You can ask each user for their preferred time zone, and store it as a user-preference setting.
If you take a raw (seconds since the UNIX epoch in UTC) TIMESTAMP value and add a month's worth of seconds to it, the resulting timestamp value may be in a different daylight-time regime. So it will be translated differently.
Either
do your timestamp processing in MySQL, for example with
UPDATE tbl SET periodRefresh = periodRefresh + INTERVAL 1 MONTH;
do it all in your app and use DATETIME, not TIMESTAMP, data types (to avoid the database's time zone translation, or
do it all in your app and use SET time_zone='UTC'; on every connection.

Related

What is logic of manipulation with timezones in Java/Kotlin?

Let's assume that I have client's time saved in my database as 2020-09-22T10:50:37.276240900
I need to present this date in web-service for client app depending on client timezone, for example I need to add 2 hours to saved date if client lives in UTC+2 timezone.
So what am I doing for ?
Getting date from entity and adding timezone to time taken from database (startDate: LocalDateTime)
entity.startDate.atZone(ZoneId.of("Europe/Vienna"))
what gives me the value of ZonedDateTime 2020-09-22T10:50:37.276240900+02:00[Europe/Vienna]
This value is what I'm expecting for, basically "initial time plus 2 hours". After that I would to format this time to have output with this 2 hours of being added, some kind of this
12:50 22.09.2020
but when I do format like this
entity.startDate
.atZone(ZoneId.of("Europe/Vienna"))
.format(DateTimeFormatter.ofPattern(NotificationListener.EUROPEAN_DATE_FORMAT, Locale.ENGLISH))
where const val EUROPEAN_DATE_FORMAT = "HH:mm dd.MM.yyyy"
I get this output 10:50 22.09.2020 which looks like my format is not applied properly, so I cannot see my 2 hours.
So my questions are:
am I correct to adding timezone of client app in described way ?
how to apply timezone in more precise way and format this date to see timezone zone applied ?
LocalDateTime.atZone does not "move" the point in time. In fact it tries to present the point in time where the local time in the given timezone is exactly what the LocalDateTime shows.
In other words: if your LocalDateTime represented 10:00 at some date, then the ZonedDateTime output of atZone will also represent 10:00 local time at the specified time zone (except in cases where that local time doesn't exist due to DST changes).
So if your stored time is actually in UTC, you need to add one more step:
ZonedDateTime utcTime = entity.startDate.atZone(ZoneOffset.UTC);
ZonedDateTime localTime = utcTime.withZoneSameInstant(ZoneId.of("Europe/Vienna"));
Alternatively you can avoid calculating the localTime each time and instead configure the DateTimeFormatter to use a given time zone (which means it'll do the necessary calculations internally) using DateTimeFormatter.withZone. If you do this then you can pass the utcTime to it directly.

How can I take into account the TimeZone of java.sql.Date when I read it from database?

My application reads java.sql.Date from database witch contains these dates in America/New_York time zone (-4 UTC). After a fetching of data Hibernate creates objects java.sql.Date and represents them in my local time zone. So, I need to convert date from database in UTC directly. How can I do that?
I need something like this
Instant.ofEpochMilli(((java.sql.Date) value).getTime()).atOffset(offset);
But offset doesn't do what I want. For example:
time in database: 01-02-2020 22:00 (in America/New_York -> it's UTC-4 and I need to add extra 4 hours)
time in my application: 01-02-2020 22:00 +4 (because my time zone is UTC+4). When I set ZoneOffset.UTC
Instant.ofEpochMilli(((java.sql.Date) value).getTime()).atOffset(ZoneOffset.UTC)
it removes 4 hours ans toString() result = 01-02-2020T16:00Z
How can I add 4 hour to date (java.sql.Date) in database so that it would be 02-02-2020 02:00 UTC ?
For a point in time with a time zone such as 2020-02-01T22:00-04:00[America/New_York], do not use java.sql.Date. For two reasons:
java.sql.Date is a poorly designed class, a true hack, indeed, on top if the already poorly designed java.util.Date class. Fortunately both Date classes are also long outdated.
java.sql.Date was designed for a date without time of day.
Instead:
In your SQL database use timestamp with time zone and store times consistently in UTC. So the time stored in your database should be 2020-02-02T02:00Z (Z for UTC).
In Java retrieve your time into an OffsetDateTime (since JDBC 4.2 we can do that, bypassing java.sql.Date and java.sql.Timestamp completely). Then if needed convert to a ZonedDateTime in your time zone. Use a proper time zone ID in the region/city format (not just what you think the UTC offset is).
For a demonstration:
ZoneId zone = ZoneId.of("Asia/Tbilisi");
OffsetDateTime dateTimeFromDatabase
= OffsetDateTime.of(2020, 2, 2, 2, 0, 0, 0, ZoneOffset.UTC);
ZonedDateTime dateTimeInYourTimeZone
= dateTimeFromDatabase.atZoneSameInstant(zone);
System.out.println(dateTimeInYourTimeZone);
Output:
2020-02-02T06:00+04:00[Asia/Tbilisi]
Edit 1: You said:
I understand that this is bad to use outdated java.sql.Date, but I
have no choice. "java.sql.Date was designed for a date without time of
day." - but I thought I can anyway get time of day by calling
(java.sql.Date) value).getTime() (because it returns timestamp)
From the documentation:
To conform with the definition of SQL DATE, the millisecond values
wrapped by a java.sql.Date instance must be 'normalized' by setting
the hours, minutes, seconds, and milliseconds to zero in the
particular time zone with which the instance is associated.
So it seems to me that you’re breaking the contract. What the consequences are, I don’t know. They probably depend on your JDBC driver. That is, behaviour might change with the next version of that JDBC driver.
Edit 2: I took a closer look at your data. I agree with you that they are wrong; but the problem is not in the code you have presented, it’s in the java.sql.Date object that you seem to have received somehow.
For my investigation I did:
// time in database: 01-02-2020 22:00
// (in America/New_York -> it's UTC-4 and I need to add extra 4 hours)
ZonedDateTime dateTimeInDatebase = ZonedDateTime
.of(2020, 2, 1, 22, 0, 0, 0, ZoneId.of("America/New_York"));
System.out.println("In database: " + dateTimeInDatebase);
long correctEpochMillis = dateTimeInDatebase.toInstant().toEpochMilli();
System.out.println("Correct millis: " + correctEpochMillis);
// toString() result = 01-02-2020T16:00Z
OffsetDateTime observedDateTime
= OffsetDateTime.of(2020, 2, 1, 16, 0, 0, 0, ZoneOffset.UTC);
long observedEpochMilli = observedDateTime.toInstant().toEpochMilli();
System.out.println("Observed millis: " + observedEpochMilli);
Duration error = Duration.between(dateTimeInDatebase, observedDateTime);
System.out.println("Error: " + error);
The output is:
In database: 2020-02-01T22:00-05:00[America/New_York]
Correct millis: 1580612400000
Observed millis: 1580572800000
Error: PT-11H
Observations:
The UTC offset in New York in February is not -04:00 but -05:00 (-04:00 is the correct offset during summer time/DST).
The millisecond value that you have retrieved from your java.sql.Date does not denote the point in time that it should. There is nothing in your code that changes the point in time. So you are not only getting an incorrect type, you are also getting an incorrect value.
Read the error printed in the last output line as a period of time of minus 11 hours. The millisecond value in your java.sql.Date is 11 hours too early.
You have yourself explained some of the discrepancy with the time zone difference, and I believe that this is true. We have not yet verified that this is the whole story. So I also cannot tell you what the solution is. Other than filing a ticket to the provider of your incorrect type and value so you get correct data instead. A possible hack is to add 11 hours, of course, but whether you then should add only 10 hours in the summer time part of the year — I am not the correct person to ask.
Edit 3:
I just came up with an idea to fix twice value of timestamp. Like the
first time - add offset of local zone (fix the influence of jdbc
driver), and the second - handle offset of dates stored in database.
We can do that if we want:
Instant observedResult = Instant.parse("2020-02-01T16:00:00Z");
Object receivedValue = new java.sql.Date(observedResult.toEpochMilli());
long receivedEpochMillis = ((java.sql.Date) receivedValue).getTime();
ZonedDateTime adjustedDateTime = Instant.ofEpochMilli(receivedEpochMillis)
.atZone(ZoneId.systemDefault())
.withZoneSameLocal(ZoneId.of("America/New_York"));
System.out.println(adjustedDateTime);
Output when run in Asia/Tbilisi time zone (so this is what ZoneId.systemDefault() returned; it’s at offset +04:00 all year):
2020-02-01T20:00-05:00[America/New_York]
It brings us closer to what you say was in the database, but it’s still a couple of hours too early. I am sorry.
Links
My answer to a related question: Getting the date from a ResultSet for use with java.time classes
Documentation of java.sql.Date

Convert from joda.Datetime to time.LocalDateTime is the right way?

I need timestamp format for my dates in database.
For now i have joda.Datetime in database , but also in my restApi application.
I tried to create a new column , and converted the existing joda.Datetime in the other column time.LocalDateTime. Also I replaced in all code joda.DateTime with time.LocalDateTime.
It works, but when i make a get call in postman, i received a json like:
{
seconds: x1,
minutes: x2,
hours: x3,
days: x4,
........
}
I think i need a convertor, to show the timestamp as "dd-mm-yy hh-mm-ss"
I want to have timestamp format in database to be able to execute SQL standard operation and named queries on time.
In my database I have bytea type for dates. I use PostgreSQL with DBeaver.
Is this the right way, or you could recommend me another option?
Is this the right way, or you could recommend me another option?
Without experience with PostgreSQL I should say that bytea is the wrong datatype for your dates or timestamps. timestamp with time zone is good and supports SQL operations and queries. It further has the advantage that you can store OffsetDateTime (perhaps even Instant, I am not sure) directly, so you avoid formatting your timestamp for storing it. That’ll be recommended.
For a time stamp to be a time stamp date and time of day is not enough. Date and time of day will be interpreted differently in different time zones (and is even ambiguous in the fall when summer time ends and the clocks are turned backward). As far as I have understood timestamp with time zone will make sure that time stamps are stored in UTC, so will be unambiguous points in time. In Java the Instant class represents a point in time independently of time zone, so is good for timestamps. Some JDBC drivers allow you to store an Instant directly into a timestamp with time zone column, others require you to convert to OffsetDateTime first. In the latter case use
OffsetDateTime dateTimeForDatabase = yourInstant.atOffset(ZoneOffset.UTC);
Edit: Please note that the with time zone bit is a bit of a lie, as #Jon Skeet points out in a comment. The database doesn’t store a time zone or offset, it only makes sure that dates and times are stored in UTC for removing ambiguity about the point in time.
Link: Date/Time Types in the PostgreSQL docs

Time zone conversion to CST based GMT offset

I have date(01-oct-2014), time (00:37:31), GMT difference(-360) now
I want to get the time conveted to CST. The solution can be in javascript
Or oracle databse.
I have read several articles but could'nt get any where..can some one help me out on this...
In Oracle, to convert your local time to time of another timezone, you need to CAST TIMESTAMP WITH TIMEZONE.
For example, I want to convert 'IST' Indian standard time, i.e. my local timezone to 'CST', i.e. Central :
SQL> WITH T AS
2 ( SELECT to_timestamp('10/01/2014 11','mm/dd/yyyy hh24') ist FROM dual
3 )
4 SELECT ist,
5 CAST(CAST(ist AS TIMESTAMP WITH TIME ZONE) at TIME zone 'CST' AS TIMESTAMP) cst
6 FROM t
7 /
IST CST
----------------------------------- ------------------------------
01-OCT-14 11.00.00.000000000 AM 01-OCT-14 12.30.00.000000 AM
Take care of Daylight saving. You might have to take care in understanding CST and CDT.
There are several ways to do this:
SELECT
(TIMESTAMP '2014-10-01 00:37:31') AT TIME ZONE 'CST',
FROM_TZ((TIMESTAMP '2014-10-01 00:37:31'), 'CST'),
CAST((TIMESTAMP '2014-10-01 00:37:31') AT TIME ZONE 'CST' AS TIMESTAMP)
FROM DUAL;
It depends if the result shall include the new time zone or not.

Set Time Without Knowing Date

I want to be able to store time values in a mysql database without actually knowing a specific date they are associated with. I am using the java.sql.time to store the data.
long timeInMillis = 43200000; //This is 12 hours in milliseconds.
Time time = new Time(timeInMillis);
For whatever reason this is giving me a time of 07:00:00 and it should be 12:00:00. I'm assuming this is because when setting a time variable it is based on the amount of time past a specific date. How do I set a time variable without actually using a pre-defined date?
The reason I am doing this is because I want to store a week day and a time range in a database. So Sunday between 12 and 1 would be 0 between 12:00:00 and 13:00:00. I want to be able to compare an actual date value against the database and see if the date falls between the time periods based on the dates day of week regardless of the month or year. Storing full dates in the database for each possible weekday and time would result in thousands of unnecessary entries.
For whatever reason this is giving me a time of 07:00:00 and it should be 12:00:00. I'm assuming this is because when setting a time variable it is based on the amount of time past a specific date
Can happen due to TimeZone difference. Check for timezone information while saving and retrieving values.
This is likely a timezone issue, but I solved the problem by passing the time values into the database as a string. It is probably best to use a physical string in this instance anyways unless timezone properties are important.
String startTime = "12:00:00";
String endTime = 01:00:00;
If timezone is important I assume the best method to use unless you are preparsing your date object would be to set the timezone directly in your mysql statement for the current session. More details can be found here: How do I set the time zone of MySQL?
EDIT: Turns out in the end this wasn't actually a time zone issue (although in other circumstances it could be, so don't discredit adjusting for time zone). I was calculating the time in milliseconds incorrectly.

Categories

Resources