Java MySQL Timestamp time zone problems - java

I have a java.util.Date object, and I need to insert it into a datetime field in MySQL in UTC format.
java.util.Date date = myDateFromSomewhereElse;
PreparedStatement prep = con.prepareStatement(
"INSERT INTO table (t1, t2) VALUES (?,?)");
java.sql.Timestamp t = new Timestamp(date.getTime());
prep.setTimestamp(1, t, Calendar.getInstance(TimeZone.getTimeZone("PST"));
prep.setTimestamp(2, t, Calendar.getInstance(TimeZone.getTimeZone("UTC"));
System.out.println(prep.toString());
Which gives me the prepared SQL statement string:
INSERT INTO table (t1, t2) VALUES ('2012-05-09 11:37:08','2012-05-09 11:37:08');
The timestamp returned is the same timestamp regardless of the timezone I specify. It's ignoring the Calendar object with timezone that I specify. What is going on and what am I doing wrong?

Jordan, actually you had the right idea. The problem is there's a bug in MySQL JDBC driver and the Calendar argument is completely ignored by default. Look at the source code for PreparedStatement to really see what's going on.
Notice it format's the Timestamp using the JVM's time zone. This will only work if your JVM is using UTC time zone. The Calendar object is completely ignored.
this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss''", Locale.US);
timestampString = this.tsdf.format(x);
In order for MySQL to use the Calendar argument, you have to disable the legacy date/time code with the following connection option:
useLegacyDatetimeCode=false
So you might use it when connecting to the database like this:
String url = "jdbc:mysql://localhost/tz?useLegacyDatetimeCode=false"
If you disable the legacy datetime code using the above line, then it WILL render your Timestamp in the target Calendar's time zone:
if (targetCalendar != null) {
targetCalendar.setTime(x);
this.tsdf.setTimeZone(targetCalendar.getTimeZone());
timestampString = this.tsdf.format(x);
} else {
this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ());
timestampString = this.tsdf.format(x);
}
It's pretty easy to see what's going on here. If you pass in a Calendar object, it will use this when formatting the data. Otherwise, it will use the database's time zone to format the data. Strangely, if you pass in a Calendar, it will also set the time to the given Timestamp value (which seems to be pointless).

Check this link for explanation for MySQL (and you shouldn't try to apply advices about Oracle to MySQL).
The TIMESTAMP data type is used for values that contain both date and time parts. TIMESTAMP has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.
MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as DATETIME.) By default, the current time zone for each connection is the server's time.

TimeZones are just different ways to view a date (which is a fixed point in time). I wrote a little example here (pay close attention to the assert):
// timezone independent date (usually interpreted by the timezone of
// the default locale of the user machine)
Date now = new Date();
// now lets get explicit with how we wish to interpret the date
Calendar london = Calendar.getInstance(TimeZone.getTimeZone("Europe/London"));
Calendar paris = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris"));
// now set the same date on two different calendar instance
london.setTime(now);
paris.setTime(now);
// the time is the same
assert london.getTimeInMillis() == paris.getTimeInMillis();
// London is interpreted one hour earlier than Paris (as of post date of 9th May 2012)
String londonTime = london.get(Calendar.HOUR) + ":" + london.get(Calendar.MINUTE);
String londonTZ = london.getTimeZone().getDisplayName(london.getTimeZone().inDaylightTime(london.getTime()), TimeZone.SHORT);
System.out.println(londonTime + " " + londonTZ);
// Paris is interpreted one hour later than Paris (as of post date of 9th May 2012)
String parisTime = paris.get(Calendar.HOUR) + ":" + paris.get(Calendar.MINUTE);
String parisTZ = paris.getTimeZone().getDisplayName(paris.getTimeZone().inDaylightTime(paris.getTime()), TimeZone.SHORT);
System.out.println(parisTime + " " + parisTZ);
The output to this snippet is (the result will be different depending on execution date/time):
8:18 BST
9:18 CEST
Your snippet in the question is simply not doing anything with regard to the date being stored. Usually databases are configured for a native TimeZone. I advise storing an extra field representing the TimeZone to be used when interpreting the date.
It is not (generally) a good idea to modify dates (which are essentially just milliseconds before/after a fixed point in time) as this would be a lossy modification that would be interpreted differently at different points in the year (due to daylight savings time).
Or this : http://puretech.paawak.com/2010/11/02/how-to-handle-oracle-timestamp-with-timezone-from-java/

Related

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

Converting LocalDateTime to Timestamp format

Im converting date time string format to Timestamp (java.sql) from different format. I have converted it to LocalDateTime based on different format. Is there anyway to convert LocalDateTime object to Timestamp?
It’s more delicate than you think, so good you asked.
Since you can use java.time, the modern Java date and time API, you should not wish to apply the old-fashioned and poorly designed Timestamp class too.
I am assuming that you were asking for a Timestamp because you need something that you can store into an SQL database column of type timestamp (without time zone). Allow me to mention that for most purposes you don’t want such a column. The standard SQL datatype timestamp doesn’t live up its name. While the idea of a timestamp is that it should identify an unambiguous point in time when something happened, an SQL timestamp does not define such a point in time. It defines a date and time of day, and anyone is free to interpret it in any time zone, allowing for a variation of some 26 hours. Instead for a point in time you should use timestamp with timezone. It doesn’t live up to its name either in that it doesn’t let you choose the time zone. It stores times in UTC, so does identify a unique point in time.
On the Java side use an OffsetDateTime for storing into a timestamp with timezone column. Since you’ve got a LocalDateTime, you need to convert. And for the conversion you need to know the time zone intended behind your LocalDateTime. For example:
final ZoneId zone = ZoneId.of("Asia/Tehran");
LocalDateTime ldt = LocalDateTime.of(2019, 2, 25, 23, 45);
OffsetDateTime odt = ldt.atZone(zone).toOffsetDateTime();
System.out.println("As OffsetDateTime: " + odt);
As OffsetDateTime: 2019-02-25T23:45+03:30
Store into your database:
PreparedStatement ps = yourDatabaseConnection.prepareStatement(
"insert into your_table(your_ts_with_timezone) values (?)");
ps.setObject(1, odt);
ps.executeUpdate();
If either for some reason you do need a timestamp without time zone in your database or you cannot change it, you don’t need any conversion at all. Just store the LocalDateTime you had:
PreparedStatement ps = yourDatabaseConnection.prepareStatement(
"insert into your_table(your_ts_without_timezone) values (?)");
ps.setObject(1, ldt);
If you are programming to a legacy API that requires an old-fashioned java.sql.Timestampobject, things are getting even delicater. A Timestamp does define a point in time. So you will now need to convert from a LocalDateTime that does not define a point in time, to a Timestamp that does define one, then pass the Timestamp to an API that may store it in a database column that again does not define an unambiguous point in time. You will need to be sure which time zones are used for the conversions, and still there may be corner cases that fail or (worse) give incorrect results. However, as the_storyteller mentioned in a comment, the conversion is straightforward enough:
Timestamp ts = Timestamp.valueOf(ldt);
System.out.println("As old-fashioned Timestamp: " + ts);
As old-fashioned Timestamp: 2019-02-25 23:45:00.0
The conversion uses your JVMs default time zone. This setting is also used by TImestamp.toString(), so the output is as expected. This is shaky, though, since any program running in the JVM may change the default time zone setting at any time, so generally you don’t know what you get. To exercise control over the time zone used for conversion:
Instant i = ldt.atZone(zone).toInstant();
Timestamp ts = Timestamp.from(i);
Links
SQL- Difference between TIMESTAMP, DATE AND TIMESTAMP WITH TIMEZONE?
try it out with java 8 built in classes
public class Test {
public static void main(String[] args) {
Timestamp timestamp = Timestamp.valueOf(LocalDateTime.now());
// java timestamp class
System.out.println(timestamp);
// miliseconds
System.out.println(timestamp.getTime());
}
}

How to get time according to timezone

I am storing timezone value in database as +5:30 or +2:00 or -1:00
I am fetching data from database and changing time according to time zone in java.
Here I would like to know how can I get time according to saved timezone value.
Use Timezone and Calendar. And check SO before asking...
Examples here, or here
TimeZone tz = Calendar.getInstance().getTimeZone();
System.out.println(tz.getDisplayName());// (i.e. Moscow Standard Time)
System.out.println(tz.getID());
That should get the time zone that the computer the user is using has been set to.
Unless you want to use a complicated solution by pinging a server and making a server-side check to see where the IP came from, then that is the closest you're going to get.
Use SimpleDateFormat and setTimeZone
String timeZone = "GMT" + offset // offset is the value you store in the db( +5:30, -2:30)
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
format.setTimeZone(TimeZone.getTimeZone(timeZone));
Date date = format.parse("2014-11-19T09:01:02");

Date Comparison of SQL and Java

I want to compare two dates.First date is getting retrieved from system date and other one I'm getting through one query. But here problem is both dates are same as I can see but in If else function I'm getting different result. Below is my code and in output section I have given which output is gonna come with respective function?
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date currdate = new Date();
System.out.println("****"+dateFormat.format(currdate));
Output is : - 2014-01-21
stat = con.prepareStatement("select date from finaltbale where tl_id='"+tempmaxid+"'");
rs = stat.executeQuery();
rs.next();
Date tempdate = rs.getDate(1);
Output is :- 2014-01-21
if(tempdate.equals(currdate)){
System.out.println("Dates are same");
}
else{
System.out.println("Dates are different");
}
Output is : - Dates are different
The system date that you get will also include hours, minutes, seconds and milliseconds. You are only printing out the year, month and day. So they will look the same when you print these but when you compare them with .equals it will return false due to the time portion of the dates being different.
You could format the dates and compare the strings from the format to see if they are the same
if(dateFormat.format(tempdate).equals(dateFormat.format(currdate))){...}
I wouldn't recomend comparing strings, but in this case you may want to change the equals in your if to matches, since equals compares to another object so both dates may have the same value but they are completely different, on the other hand, matches compares to a regular expression.
Another way to do that is through the Calendar methods after, before or compareTo witch is the best way to do it.
The answer by Java Devil is correct.
To elaborate…
"Date" is a tricky word in the worlds of Java, JDBC, and SQL.
You are calling getDate on a ResultSet. That renders a java.sql.Date – note the sql in the middle there! A java.sql.Date is a java.util.Date adjusted to have a zero time (00:00:00.000) in UTC/GMT (no time zone offset). That adjustment means the internal number of milliseconds since epoch changes.
I'm assuming your line Date currdate = new Date(); is using java.util.Date. When mixing this kind of SQL and non-SQL code, you should explicitly specify the package on those classes.
So, you are comparing a pair objects which contain a different number of milliseconds.
Call the getTime() method on both your java.util.Date and your java.sql.Date objects to see their number of milliseconds since epoch.
By the way, this is complicated by the fact that the toString methods on both java.util.Date and java.sql.Date apply your JVM's default time zone when rendering a string. But neither object actually contains a time zone, just the number of milliseconds since 1970 began in UTC/GMT (no time zone offset).
In SQL, to capture both date and time use the standard TIMESTAMP type or a proprietary type provided by your particular database.
Joda-Time
The Joda-Time 2.3 library can be helpful here. Pass either the java.sql.Date or java.util.Date object to a DateTime constructor along with the UTC time zone object to get a clear picture of the value with which you are struggling.
java.util.Date date = new java.util.Date( 1390276603054L );
DateTime dateTimeUtc = new DateTime( date, DateTimeZone.UTC );
System.out.println( "dateTimeUtc: " + dateTimeUtc );
When run…
2014-01-21T03:56:43.054Z
Tip
On a side note…
The comment on the question by a_horse_with_no_name is correct: 'date' is a terrible name for a column in your database. Here's the most valuable tip I've learned about SQL…
The SQL standard promises that a trailing underscore will never be used in any identifier
So if you name every column with a trailing underscore, you never have to worry about conflicts with the many many keywords and other identifiers defined by both the standard and by many proprietary extensions in various databases.
So use date_.

Query a mysql DATETIME with a given timezone in Java

I am trying to query a mysql DATETIME from Java. I know the the time zone of the server, but I cannot pull the datetime out with the time zone as I would expect.
ResultSet rs = st.executeQuery(...);
Date d1=rs.getTime(i, Calendar.getInstance(TimeZone.getTimeZone("UTC")));
Date d2=rs.getTime(i, Calendar.getInstance(TimeZone.getTimeZone("PST")));
System.out.println("d1: "+d1.getTime());
System.out.println("d2: "+d2.getTime());
This leaves me with:
d1: 40258000
d2: 40258000
Am I missing something basic here?
ResultSet.getDate() does take the Calendar into account. But I cant use it because it truncates the time info. It's still strange ResultSet.getTime() wouldn't handle any timezone conversions.
The documentation states:
This method uses the given calendar to construct an appropriate millisecond value for the time if the underlying database does not store timezone information.
So perhaps the database does store time zone information in this case?
What does your value in the database look like, and what is it supposed to represent?
An alternative approach would be letting the MySQL server convert the time zone:
SELECT CONVERT_TZ(timefield,'PST','UTC') AS tf
for example would convert the DATETIME value in the field timefield from PST to UTC time (and then returning it as tf).
getTime on the Date class returns the epoch time according to http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Date.html
so it would adjust out your timezone. I think that page implies you should use the timezone adjusted calendar to get your local time.

Categories

Resources