19

When Hibernate writes a Java Calendar object to an SQL TIMESTAMP column, to which time zone does it adjust the date, that of the computer or that specified in the calendar object (or some other)?

When Hibernate reads the TIMESTAMP into the calendar object, to which time zone does it translate the date?

Derek Mahar
  • 27,608
  • 43
  • 124
  • 174

4 Answers4

18

When Hibernate writes a Java Calendar object to an SQL TIMESTAMP column, to which time zone does it adjust the date, that of the computer or that specified in the calendar object (or some other)?

Hiberante 3.x uses the following in the CalendarType (see HB-1006):

public void set(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
    final Calendar cal = (Calendar) value;
    //st.setTimestamp( index,  new Timestamp( cal.getTimeInMillis() ), cal ); //JDK 1.5 only
    st.setTimestamp( index,  new Timestamp( cal.getTime().getTime() ), cal );
}

So Hibernate uses PreparedStatement#setTimestamp(int, Timestamp, Calendar) which uses the time zone of the calendar.

When Hibernate reads the TIMESTAMP into the calendar object, to which time zone does it translate the date?

Well, again, let's look at the CalendarType class:

public Object get(ResultSet rs, String name) throws HibernateException, SQLException {

    Timestamp ts = rs.getTimestamp(name);
    if (ts!=null) {
        Calendar cal = new GregorianCalendar();
        if ( Environment.jvmHasTimestampBug() ) {
            cal.setTime( new Date( ts.getTime() + ts.getNanos() / 1000000 ) );
        }
        else {
            cal.setTime(ts);
        }
        return cal;
    }
    else {
        return null;
    }

}

So Hibernate constructs a default GregorianCalendar using the current time in the default time zone with the default locale.


As a side note, I highly suggest to read the following question:

Community
  • 1
  • 1
Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
  • Nice answer! Straight from the "source"! Out of curiosity, what is JVM timestamp bug to which the code refers? – Derek Mahar Nov 07 '10 at 20:25
  • @Derek The javadoc says *Does this JVM have the IBM JDK 1.3.1. The bug is `new Timestamp(x).getTime()!=x`*. – Pascal Thivent Nov 08 '10 at 04:23
  • 1
    It's interesting to note that the current version of the Hibernate type classes are far more generic and less direct (and thus, harder to follow) than the version from which you cite. Nevertheless, the essential, lowest-level code is mostly the same, except now Hibernate no longer passes the calendar to `setTimestamp()`. Though the documentation at http://download.oracle.com/javase/6/docs/api/java/sql/PreparedStatement.html#setTimestamp%28int,%20java.sql.Timestamp%29 does not say, my guess is that this method still uses the default time zone. – Derek Mahar Nov 08 '10 at 10:53
  • @Derek What "current" version are you referring to, 3.6.0.Final? – Pascal Thivent Nov 08 '10 at 11:07
  • By the way, that reference to the question about daylight saving time and time zone best practices is excellent! – Derek Mahar Nov 09 '10 at 10:06
  • @Derek I think so too :) Oh, and I didn't forget you, I still need to answer the (scary!) comments from yesterday. Not sure how this will fit in a comment box but I'll try. – Pascal Thivent Nov 09 '10 at 10:44
  • @Pascal, no hurry! You've already answered so many of my questions, the least I can do is be patient! ;) If your comment doesn't fit in the comment box, I could always create a new question for them instead. Or, you could provide a link to an external page such as a post to a blog or http://pastebin.com/. – Derek Mahar Nov 09 '10 at 17:37
  • This is true and it appears this JPA implementation is violating reflexive and transitive properties. That is, if a Calendar object holds a TimeZone property and is stored with a TimeZone component, it should be returned with the same TimeZone and hence value - not the default TimeZone of the local environment. Ugh. – Darrell Teague Feb 22 '13 at 21:12
  • 1
    It is just creating a `Calendar` and the default when you do that is to use the default time zone. All you have to do is change the timezone on that `Calendar` object to whatever timezone you prefer. The internal storage is just a counter of milliseconds. Changing the time zone just causes the `get` methods on the calendar to convert the milliseconds differently. – Lee Meador Oct 15 '13 at 23:24
6

I just spent 6 hours on a similar issue and thought I would document it here. Hibernate indeed does use the JVM timezone but it can be changed by extending the CalendarType like this:

public class UTCCalendarType extends CalendarType {

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

    /**
     * This is the original code from the class, with two changes. First we pull
     * it out of the result set with an example Calendar. Second, we set the new
     * calendar up in UTC.
     */
    @Override
    public Object get(ResultSet rs, String name) throws SQLException {
        Timestamp ts = rs.getTimestamp(name, new GregorianCalendar(UTC));
        if (ts != null) {
            Calendar cal = new GregorianCalendar(UTC);
            cal.setTime(ts);
            return cal;
        } else {
            return null;
        }
    }

    @Override
    public void set(PreparedStatement st, Object value, int index) throws SQLException {
        final Calendar cal = (Calendar) value;
        cal.setTimeZone(UTC);
        st.setTimestamp(index, new Timestamp(cal.getTime().getTime()), cal);
    }
}

the secret sauce here is :

  rs.getTimestamp(name, new GregorianCalendar(UTC));

This converts the timezone from the result set to whatever timezone you want. So what I did was use this type with any UTC calendars and the standard Hibernate type for the local time. Works slick as a whistle...

markthegrea
  • 3,731
  • 7
  • 55
  • 78
2

By default, it's up to the JDBC Driver to decide what timezone to use. Typically, the JVM time zone is used unless you configure the JDBC Driver to use a custom time zone.

If you want to control what time zone is used, you can set the time zone at the JVM level. If you want the JVM time zone to differ from the one used by the database, then you need to use the hibernate.jdbc.time_zone Hibernate 5.2 configuration property:

<property name="hibernate.jdbc.time_zone" value="US/Eastern"/>
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
0

If you don't want to write the code yourself, you can just use the open source library DbAssist. After applying this fix, the dates in the database will be treated by JDBC and then Hibernate as UTC, so you do not even have to change your entitiy classes.

For example, if you are using JPA Annotations with Hibernate 4.3.11, add the following Maven dependency:

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

Then you just apply the fix:

For Hibernate + Spring Boot setup, add the @EnableAutoConfiguration annotation before the application class.

For HBM files, you have to change the entity mapping files to map Date types to the custom one:

<property name="createdAt" type="com.montrosesoftware.dbassist.types.UtcDateType" column="created_at"/>

If you want to learn more about how to apply the fix for different Hibernate versions (or HBM files), refer to the project's github. You can also read more about the time zone shift issue in this article.

orim
  • 143
  • 3
  • 18