4

This seems like a stupid question, but I am not able to understand this creepy behavior. I am completely aware of the fact that java Date class does not store any TimeZone information in it. It just stores the number of milliseconds since January 1, 1970, 00:00:00 GMT

Thing is that, I am using MySql which is residing on a server with UTC timezone and I am also storing DateTime in UTC only. If I make a select query then I get this date 2014-01-17 16:15:49

By using http://www.epochconverter.com/ I get this:

Epoch timestamp: 1389975349
Timestamp in milliseconds: 1389975349000
Human time (GMT): Fri, 17 Jan 2014 16:15:49 GMT
Human time (your time zone): Friday, January 17, 2014 9:45:49 PM

Now comes the part of Hibernate. I am running my Java web app on a machine having IST as system timezone. I made a simple object fetch using Id and fetched createdDate property which is a Date object. I have wrote a simple code to understand its output, here is the code:

Date dt = c.getCreatedDate();
System.out.println(dt.getTime());
System.out.println(dt);
DateFormat df = new SimpleDateFormat("dd/MM/yyyy  hh:mm a z");
df.setTimeZone(TimeZone.getTimeZone("IST"));
System.out.println(df.format(dt));
df.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(df.format(dt));

And following is the output for this:

1389955549000
2014-01-17 16:15:49.0
17/01/2014  04:15 PM IST
17/01/2014  10:45 AM UTC

If you put this 1389955549000 in http://www.epochconverter.com/ then you get following output:

GMT: Fri, 17 Jan 2014 10:45:49 GMT
Your time zone: Friday, January 17, 2014 4:15:49 PM GMT+5.5

This is not the expected output, right. It is giving me time in millis which is -5:30 from the UTC time, so If I try to get time in IST timezone then it actually gives me time which is in UTC

Does anyone got idea where I am doing wrong?

--------------------------How I fixed it----------------------------

Based on suggestions from - Ako and Basil Bourque

Hibernate takes system timezone into consideration while fetching date/time fields from database. If you have stored DateTime in UTC in database but your system time or for least your java app timezone is in other timezone(e.g, IST - Indian Standard Time) then hibernate thinks that DateTime stored in database is also in IST and this is what causes whole problem.

For this as Basil suggested, use same timezones accross different servers. UTC should be preferred. Fix that I applied is that I added following code:

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

in ServletContextListener which made my app timezone in UTC and now hibernate is fetching and storing dates as expected.

Community
  • 1
  • 1
Abhinav
  • 3,322
  • 9
  • 47
  • 63
  • Why is it not correct? "17/01/2014 10:45" in UTC is the same as "17/01/2014 16:15" in IST. Looks correct as for me. – ako Jan 17 '14 at 20:58
  • @ako it is incorrect because look at top, mysql output is 16:15:49 GMT and java output for same is 04:15 PM IST or you can say it 16:15 IST while UTC or GMT according to java is 10:45 AM. And 16:15 in your comment should not be IST, it should be UTC according to entry in mysql – Abhinav Jan 17 '14 at 21:06
  • 2
    You can run JVM with option -Duser.timezone=UTC. It should help. See [link](http://stackoverflow.com/questions/2627992/force-java-timezone-as-gmt-utc) for more information. – ako Jan 17 '14 at 21:23
  • but why is it happening like this? It should fetch correct time in millis. As `Date` is unaware of Timezones. Any convenient way of achieving solution mentioned in link you have given in hibernate? – Abhinav Jan 17 '14 at 21:37
  • 2
    From [link](http://docs.oracle.com/javase/7/docs/api/java/sql/PreparedStatement.html), method `setDate(int, java.sql.Date, java.util.Calendar)`: The driver uses the Calendar object to construct an SQL DATE value, which the driver then sends to the database. With a Calendar object, the driver can calculate the date taking into account a custom timezone. If no Calendar object is specified, the driver uses the default timezone, which is that of the virtual machine running the application. – ako Jan 17 '14 at 21:52
  • @ako thanks for your help, it helped a lot. I got it resolved. Made an edit in question itself describing the fix. – Abhinav Jan 18 '14 at 09:04
  • @Abhi I never meant for you to set the JVM’s current default time zone. I added a section at end of my Answer to explain. You should rework all your date-time code to **pass the expected/desired time zone** as an argument to the method calls. Calling `TimeZone.setDefault` should only be done as a last resort in the most desperate of situations. – Basil Bourque Oct 01 '15 at 15:50

2 Answers2

6

Confusing Question

Your question could use some rewriting.

If I make a select query – You should explain this and give the exact query statement.

Red Herring

Since dealing with two separate servers (database server, web app server), each with a different time zone setting, you should separate the question more cleanly. Indeed, the MySQL server seems to be just a red herring, a distraction.

Instead of talking about the irrelevant MySQL server, you should have tested and reported the actual time. Easy to do… Just google "current time in utc".

Thus the old sailors' adage: Use one compass or three, but never two.

Time Zones

Three-letter time zone codes are outmoded, being neither standardized nor unique. "IST" means "India Standard Time" and "Irish Standard Time", for example.

Use time zone names, as seen in this slightly outdated list. In your case, +05:30, you could use "Asia/Kolkata", also known as "Asia/Calcutta" in the older tables. That is 5.5 hours ahead of UTC/GMT.

Joda-Time

The java.util.Date & Calendar classes are notoriously bad and confusing, as you've come to see. Avoid them. Use either the open-source third-party Joda-Time or, in Java 8, the new java.time.* classes (inspired by Joda-Time).

The code below uses Joda-Time 2.3 and Java 8 on a Mac with US west coast time zone.

Baseline

Let's establish that 1389975349000L ≈ 16:15 UTC ≈ 21:45 India.

This agrees with EpochConverter.com, as the question stated.

// Specify a time zone rather than depend on defaults.
DateTimeZone timeZoneKolkata = DateTimeZone.forID( "Asia/Kolkata" );

long millis = 1389975349000L;
DateTime dateTimeUtc = new DateTime( millis, DateTimeZone.UTC );
DateTime dateTimeKolkata = dateTimeUtc.toDateTime( timeZoneKolkata );

Dump to console…

System.out.println( "millis: " + millis );
System.out.println( "dateTimeUtc: " + dateTimeUtc );
System.out.println( "dateTimeKolkata: " + dateTimeKolkata );

When run…

millis: 1389975349000
dateTimeUtc: 2014-01-17T16:15:49.000Z
dateTimeKolkata: 2014-01-17T21:45:49.000+05:30

Mystery Number

The question mentions a second number: 1389955549000L.

That number turns out to be the same date as the first number, with different time.

Let's establish that 1389955549000L ≈ 10:45 UTC ≈ 16:15 India.

long mysteryMillis = 1389955549000L;
DateTime mysteryUtc = new DateTime( mysteryMillis, DateTimeZone.UTC );
DateTime mysteryKolkata = mysteryUtc.toDateTime( timeZoneKolkata );

Dump to console…

System.out.println( "mysteryMillis: " + mysteryMillis );
System.out.println( "mysteryUtc: " + mysteryUtc );
System.out.println( "mysteryKolkata: " + mysteryKolkata );

When run…

mysteryMillis: 1389955549000
mysteryUtc: 2014-01-17T10:45:49.000Z
mysteryKolkata: 2014-01-17T16:15:49.000+05:30

Conclusion

I'm not 100% sure, but…

→ Your web app server machine seems to have its clock set improperly, set to UTC time rather than India time.

The web app server is apparently let to 16:15 time in the India time zone, but apparently at that moment the true time in India was 21:45. In other words, the time did not match the time zone.

Mixing UTC time with non-UTC time zone = WRONG.

If you set an Indian time zone, then set an Indian time to match.

Details

Note that we have "16:15" in common in both sets of numbers.

The java.util.Date class has a very bad design where it has no time zone information itself BUT applies the Java Virtual Machine's default time zone in its implementation of toString. This is one of many reasons to avoid this class, but may be key to your problem.

Lessons Learned

Avoid java.util.Date, java.util.Calendar, and java.text.SimpleDateFormat classes. Use only as required for exchanging values as needed with other classes.

Use Joda-Time or the new java.time.* classes (JSR 310).

Specify a time zone, never rely on default time zone.

Match the time to time zone on each server. Verify by googling "current time in utc".

Set host server’s operating system’ time zone to UTC or GMT where possible. No such choice on some OSes, so choose "Atlantic/Reykjavik" as a workaround, because Iceland stays on UTC/GMT year-round without any Daylight Savings Time nonsense.

Do not set the JVM’s default time zone with a call to TimeZone.setDefault (except as a last resort in the worst situations). Setting the default is rude, as it immediately affects all the code in all the threads in all the apps running in that JVM. And it is unreliable as any of that other code can change it on your app, during runtime. Instead, specify a time zone in all your date-time code. Never rely implicitly on the JVM’s current default. Both Joda-Time and java.time have methods that take an argument of time zone. So, while it is a good practice to set all your server computers’ host OS’ time zone to UTC, you should never depend on that in your Java code.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Excellent suggestions, I myself have faced tens of time problem with java date and calendar classes and all issues are timezone based. Jodatime is definitely a much better library for handling time but Hibernate does not have native support for it. That is why I am sticking around with `Date` class only. And this is such a problem that once you store it in a `Date` object and then you transfer it into Joda then issue remains the same. Because date calculated wrong timezone date. – Abhinav Jan 18 '14 at 08:53
  • I got this problem resolved. I have made an edit in question describing solution based on your suggestions. Thanks! – Abhinav Jan 18 '14 at 09:03
  • 1
    I don't think simply avoid java.util.Date or Calendar will solve this issue. And it is more involved combined problem among, Java, Database/Driver and Hibernate. – zd333 Oct 01 '15 at 14:54
  • @Basil excellent suggestions again. One question, why do you suggest setting Operating System time zone to UTC and not the JVM time zone, because the Operating System timezone is more restrictive and encompasses everything in it. Thx. – HopeKing Sep 01 '17 at 13:02
  • 1
    @HopeKing Generally, the best practice for a server is to set its default time zone to UTC. Logging, error reports, and other business should be done in UTC. Sysadmins and programmers should learn to think of UTC as the one true time, the one reliable time-tracking that is free of anomalies such as Daylight Saving Time (DST) and bored politicians. All other zones are mere variations. Keep a second clock on your desk set to UTC. The JVM should also be set to default to UTC. There may be exceptions due to odd technical issue (I have had one such exception). – Basil Bourque Sep 01 '17 at 15:24
1

There is another option of solving the time zone shift issue; in this case you don't have to put your entire JVM in the UTC time zone (it is not a good idea anyway; for example, you might want to share JVM across multiple Java applications and such a hacky solution can cause problems in the future).

So, there is a small open source project DbAssist, which provides a clean and elegant solution to this problem. Internally, it maps the java.util.Date fields in your entities to a custom UtcDateType. The custom type forces JDBC (and later Hibernate) to treat the dates in the database as UTC. There are different versions of this fix for different versions of Hibernate (its API was changed a couple of times between versions), so you have to pick a correct one according to the table here.

On the same page, you can also find the detailed instructions how to install and apply the fix. Generally, if you are using for example Hibernate 5.2.2, just add the following lines to your POM file:

<dependency>
    <groupId>com.montrosesoftware</groupId>
    <artifactId>DbAssist-5.2.2</artifactId>
    <version>1.0-RELEASE</version>
</dependency>

Then, the application of the fix differs between JPA Annotations and HBM files setup, so I will present only example for one possible setup; for JPA Annotations case (without Spring Boot), just add this line to the persistence.xml file between <persistence-unit> tag:

<class>com.montrosesoftware.dbassist.types</class>

Now the dates in your entities are treated as UTC, when read/saved to the DB. If you want to learn more about the very essence of the date and timezone problem in Java/Hibernate, you can read this article explaining it with more details.

Smont
  • 61
  • 1
  • 1