【问题标题】:Why is my timestamp shifted in timezone?为什么我的时间戳在时区中移动?
【发布时间】:2012-11-06 00:27:31
【问题描述】:

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

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 所做的那样),我不太关心时区,但我显然不希望它们破坏我的数据。

您可能已经推断,连接到数据库的客户端应用程序是 UTC+1(荷兰)。

此外,列类型timestamp without time zone 的选择是由 Hibernate 在自动生成架构时做出的。那应该是timestamp with time zone 吗?

【问题讨论】:

    标签: java hibernate postgresql jpa timestamp


    【解决方案1】:

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

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

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

    一些驱动程序还提供了一个连接属性,允许您指定在与数据库之间进行转换时使用的时区。

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 我认为这是将timestamp without time zone 读取为本地时区的Java/JDBC 事物。
      • 这是根据当前时区解释timestamp [without time zone]的普遍正确方法!没有什么特别的。 This related answer 可能有助于理解。
      • @ErwinBrandstetter 不,带有out 时区的时间戳是UTC,因为数据库没有“当前”时区,因为它是数据存储,而不是瞬态事情。
      【解决方案3】:

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

      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>
      

      在您的程序中,您只有 get/setMyTimeWithTzConversion() 用于通过休眠访问成员变量 Timestamp myTime,并且在此 getter/setter 中您进行时区转换。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-16
        • 1970-01-01
        • 2011-05-08
        • 1970-01-01
        • 2011-05-26
        • 1970-01-01
        • 2016-07-19
        • 2015-03-03
        相关资源
        最近更新 更多