J Steven Perry J Steven Perry - 27 days ago 15
Java Question

Unwanted Automatic Time Zone Conversion Using Hibernate/JPA and JDK Date

I am using Hibernate (4.2) as my persistence provider, and I have a JPA entity that contains a Date field:

@Entity
@Table(name = "MY_TABLE")
public class MyTable implements Serializable {
. . .
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "START_DATE")
private Date startDate;
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
. . .
}


The column corresponding to START_DATE is defined as
START_DATE TIMESTAMP
(no time zone).

I am using Joda-Time (2.3) internally to my application to deal with the date (always in UTC), and just prior to persisting the Entity, I use the
toDate()
method of Joda's
DateTime
class to get a JDK
Date
object in order to obey the mapping:

public void myMethod(DateTime startDateUTC) {
. . .
MyTable table = /* obtain somehow */
table.setStartDate(startDateUTC.toDate());
. . .
}


When I look in the DB at the value that is stored, I notice that somewhere (JDK? Hibernate?) converts the Date value using the default Time Zone of the JVM where the code runs. In my case that is "America/Chicago".

The problem really manifests itself near Daylight Savings Time (DST). For example, if the time internally is

2014-03-09T02:55:00Z


it gets stored as

09-Mar-14 03:55:00


What I would like, is for it to be stored as

09-Mar-14 02:55:00


However, in CDT, 2:55AM on March 9 does not exist ("Spring forward"). So something (JDK? Hibernate?) is rolling the date forward.

I would like for the instant that gets stored in the DB to be in UTC. After all, that's how I am dealing with it internally to my application, but as soon as I hand it off to be persisted, it gets converted to my default time zone.

Note: I am unable to set the default TimeZone using

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


because the JVM on which I'm running is shared across multiple applications.

How do I store the date in UTC without setting the JVM default Time Zone to UTC?

Answer

I ran into this myself. What I saw is that even though you've specified UTC as the time zone in your Date (and can see this by printing it out and seeing the 'Z' at the end), for some reason, the JVM wants to take over and convert the date for you using the JVM's default time zone.

Anyway, what you need is a custom mapping to work around this. Try using Jadira:

@Entity
@Table(name = "MY_TABLE")
public class MyTable implements Serializable {
  . . .
  @Column(name = "START_DATE")
  @Type(type="org.jadira.usertype.dateandtime.legacyjdk.PersistentDate")
  private Date startDate;
  public Date getStartDate() {
    return startDate;
  }
  public void setStartDate(Date startDate) {
    this.startDate = startDate;
  }
  . . .
}

By default Jadira's PersistentDate class uses UTC as the time zone when it converts the date to the millisecond value that gets stored in the DB. You can specify other time zones, but it sounds like UTC is what you want to store.

As the comment to your post suggests, sometimes the tool you use to query the DB is doing the mindlessly stupid automatic-what's-my-JDK-default-TZ based conversion for you, leading you to believe the value is still incorrect.

You may try also to store the raw value (as an INTEGER) just to convince yourself that the correct millisecond value is being stored.

HTH,

Mose

Comments