What time zone does Hibernate use when it reads and writes a Java Calendar object to an SQL TIMESTAMP?
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:
- Daylight saving time and Timezone best practices
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...