views:

6201

answers:

6

How can I configure JPA/Hibernate to store a date/time in the database as UTC (GMT) time zone? Consider this annotated JPA entity:

public class Event {
    @Id
    public int id;

    @Temporal(TemporalType.TIMESTAMP)
    public java.util.Date date;
}

If the date is 2008-Feb-03 9:30am Pacific Standard Time (PST), then I want the UTC time of 2008-Feb-03 5:30pm stored in the database. Likewise, when the date is retrieved from the database, I want it interpreted as UTC. So in this case 530pm is 530pm UTC. When it's displayed it will be formatted as 9:30am PST.

+6  A: 

To the best of my knowledge, you need to put your entire java app in UTC timezone (so that hibernate will store dates in UTC), and you'll need to convert to whatever timezone desired when you display stuff (at least we do it this way).

At startup, we do:

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

and set the desired timezone to the DateFormat:

fmt.setTimeZone(TimeZone.getTimeZone("Europe/Budapest"))

Hope this helps,
cheers,
mitch

mitchnull
+4  A: 

You would think this common problem would be taken care of by Hibernate. But its not! There are a few "hacks" to get it right.

The one I use is to store the Date as a Long in the database. So I am always working with milliseconds after 1/1/70. I then have getters and setters on my Class that return/accept only Dates. So the API remains the same. The down side is that I have longs in the database. SO with SQL I can pretty much only do <,>,= comparisons -- not fancy date operators.

Another approach is to user a custom mapping type as described here: http://www.hibernate.org/100.html

I think the correct way to deal with this is to use a Calendar instead of a Date though. With the Calendar you can set the TimeZone before persisting.

NOTE: Silly stackoverflow won't let me comment, so here is a response to david a.

If you create this object in Chicago:

new Date(0);

Hibernate persists it as "12/31/1969 18:00:00". Dates should be devoid of timezone, so I'm not sure why the adjustment would be made.

joekutner
Shame on me! You were right and the link from your post explains it well. Now I guess my answer deserves some negative reputation :)
david a.
Not at all. you encouraged me to post a very explicit example of why this is a problem.
joekutner
I was able to persist times correctly using the Calendar object so that they are stored in the DB as UTC as you suggested. However, when reading persisted entities back from the database, Hibernate assumes they're in the local time zone and the Calendar object is incorrect!
John K
A: 

Why should Hibernate take care of this? The Date instance does carry not any timezone information, its state is only represented by epoch time of a certain moment. If the instance of Date you're trying to save is offset by a timezone shift, then it just means it was instantiated without taking the timezone into account (e.g. while parsing a string or another form user's input, user's timezone was not considered). Below the data abstraction layer, some sort of Hibernate filter may for sure implement a timezone correction, but it would be more appropriate to me to do it when the Date is instantiated.

EDIT: Ok, this is false. See joekutner's comments on above post.

david a.
+2  A: 

Date is not in any time zone (it is a millisecond office from a defined moment in time same for everyone), but underlying (R)DBs generally store timestamps in political format (year, month, day, hour, minute, second, ...) that is time-zone sensitive.

To be serious, Hibernate MUST be allow being told within some form of mapping that the DB date is in such-and-such timezone so that when it loads or stores it it does not assume its own...

+1  A: 

Please take a look at my project on Sourceforge which has user types for standard SQL Date and Time types as well as JSR 310 and Joda Time. All of the types try to address the offsetting issue. See http://sourceforge.net/projects/usertype/

Chris Pheby
+2  A: 

Hibernate is ignorant of time zone stuff in Dates (because there isn't any), but it's actually the JDBC layer that's causing problems. ResultSet.getTimestamp and PreparedStatement.setTimestamp both say in their docs that they transform dates to/from the current JVM timezone by default when reading and writing from/to the database.

I came up with a solution to this by subclassing org.hibernate.type.TimestampType that forces these JDBC methods to use UTC instead of the local time zone:

public class UtcTimestampType extends TimestampType {

    private static final long serialVersionUID = 8088663383676984635L;

    private static final TimeZone UTC = TimeZone.getTimeZone("UTC");

    @Override
    public Object get(ResultSet rs, String name) throws SQLException {
        return rs.getTimestamp(name, Calendar.getInstance(UTC));
    }

    @Override
    public void set(PreparedStatement st, Object value, int index) throws SQLException {
        Timestamp ts;
        if(value instanceof Timestamp) {
            ts = (Timestamp) value;
        } else {
            ts = new Timestamp(((java.util.Date) value).getTime());
        }
        st.setTimestamp(index, ts, Calendar.getInstance(UTC));
    }
}

The same thing should be done to fix TimeType and DateType if you use those types. The downside is you'll have to manually specify that these types are to be used instead of the defaults on every Date field in your POJOs (and also breaks pure JPA compatibility), unless someone knows of a more general override method.

divestoclimb