为什么我的时间戳在时区中移动了?

时间:2012-11-17 13:02:04

标签: java hibernate postgresql jpa timestamp

我在timestamp without time zone列的PostgreSQL 9.1数据库中有这个日期:

2012-11-17 13:00:00

它的意思是UTC,我已经通过选择它作为UNIX时间戳(EXTRACT epoch)进行了验证。

int epoch = 1353157200; // from database
Date date = new Date((long)epoch * 1000);
System.out.println(date.toGMTString()); // output 17 Nov 2012 13:00:00 GMT

但是,当我使用JPA / Hibernate读取此日期时,出现问题。这是我的映射:

@Column(nullable=true,updatable=true,name="startDate")
@Temporal(TemporalType.TIMESTAMP)
private Date start;

我得到的Date是:

17 Nov 2012 12:00:00 GMT

为什么会发生这种情况,更重要的是,我该如何阻止它?

请注意,我只是想及时存储点数(如java.util.Date那样),我不关心时区,除了我显然不希望它们破坏我的数据。< / p>

正如您所推断的那样,连接到数据库的客户端应用程序是UTC + 1(荷兰)。

此外,列类型timestamp without time zone的选择是由Hibernate在自动生成模式时做出的。那可能是timestamp with time zone吗?

3 个答案:

答案 0 :(得分:4)

如果数据库没有提供时区信息,那么JDBC驱动程序应将其视为JVM的本地时区(请参阅PreparedStatement.setDate(int, Date)):

  

使用运行应用程序的虚拟机的默认时区将指定参数设置为给定的java.sql.Date值。

Javadoc和JDBC规范没有明确说明有关ResultSet等的任何内容,但为了保持一致,大多数驱动程序也会将该规则应用于从数据库中检索的日期。如果要显式控制所使用的时区,则需要使用在正确的时区内同时接受set/getDate/Time/Timestamp对象的各种Calendar方法。

某些驱动程序还提供了一个连接属性,允许您指定转换为数据库或从数据库转换时使用的时区。

答案 1 :(得分:1)

我发现将列类型更改为timestamp with time zone可以解决问题。我必须将所有其他时间戳列转换为该列。

由此我得出结论,Hibernate不会像UTC那样读取timestamp without time zone列,而是像本地时区一样。如果有办法让它解释为UTC,请告诉我。

答案 2 :(得分:1)

有几种解决方案可以解决这些问题:

1)最简单的方法是在JDBC连接字符串中设置时区 - 只要数据库支持这一点。

对于MySQL,您可以使用useGmtMillisForDatetimes=true强制在数据库中使用UTC。据我所知Postgres不支持这样的选项,但我可能错了,因为我不使用Postgres。

2)使用

在Java客户端程序中设置默认时区
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

缺点:您还可以更改您不想要的时区,例如,如果您的程序有UI部分。

3)仅对Hibernate使用带有特殊getter的映射: 在您的映射文件

<property name="myTimeWithTzConversion" type="timestamp" access="property">
  <column name="..." />
</property>

并且在您的程序中,只有hibernate访问成员变量get/setMyTimeWithTzConversion()才能访问Timestamp myTime,并且在此getter / setter中执行时区转换。

我们最终决定3)(这是一个更多的编程工作),因为我们没有必要更改现有数据库,我们的数据库是UTC + 1(禁止JDBC连接字符串解决方案)和那没有干扰现有的UI。