【问题标题】:JPA, Postgresql and SQL INSERT statement: UTC timestamp not working properlyJPA、Postgresql 和 SQL INSERT 语句:UTC 时间戳无法正常工作
【发布时间】:2020-11-12 17:26:36
【问题描述】:

我写了一个带有两个 Instant 字段的简单 Java 实体:

@Entity
@Table(name = "tb_foo")
public class Foo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE")
    private Instant date1;
    
    @Column(columnDefinition = "TIMESTAMP WITHOUT TIME ZONE")
    private Instant date2;
    ...

然后我在项目的资源根文件夹中写了一个简单的 import.sql 来测试 UTC/Local 是否工作:

INSERT INTO tb_foo (date1, date2) VALUES ('2020-07-22T20:50:01.12345Z', NOW());

由于我的本地时区是 GMT-3,因此上面的 20:50:01 应该在客户端显示为 17:50:01,并且 NOW() 应该显示为与我的本地时间相同(大约 0:25 在我测试的那一刻)。我在 H2 数据库中对其进行了测试,它运行良好:

这是由 JPA 自动生成到 H2 的 DDL 和 INSERT:

create table tb_foo (
    id bigint generated by default as identity,
    date1 TIMESTAMP WITHOUT TIME ZONE,
    date2 TIMESTAMP WITHOUT TIME ZONE,
    primary key (id)
)
INSERT INTO tb_foo (date1, date2) VALUES ('2020-07-22T20:50:01.12345Z', NOW()) 

但是,当我在 Postgres (12.x) 上测试相同的 Java 和 SQL 代码时,它不起作用(20h UTC 显示为 20 而不是 17):

这是 JPA 自动生成到 Postgresql 的 DDL 和 INSERT:

create table tb_foo (
    id int8 generated by default as identity,
    date1 TIMESTAMP WITHOUT TIME ZONE,
    date2 TIMESTAMP WITHOUT TIME ZONE,
    primary key (id)
)
INSERT INTO tb_foo (date1, date2) VALUES ('2020-07-22T20:50:01.12345Z', NOW())

我错过了什么?

更新:

我又做了一个测试:我通过 Java 写了另一个数据库插入(不是 SQL INSERT):

Foo foo = new Foo(null, Instant.parse("2020-07-23T20:50:01Z"), Instant.now());
fooRepository.save(foo);

这次 UTC 时间戳 20:50 被正确插入到 Postgresql 中!它在 pgadmin 客户端上显示为 17:50!所以也许正确的问题是:

如何在 SQL INSERT 调用中为 Postgresql 正确指定文字 UTC 时间戳以将其存储在没有时区的时间戳中?

我尝试了两种方式:

INSERT INTO tb_foo (date1, date2) VALUES ('2020-07-22T20:50:01.12345Z', NOW());

INSERT INTO tb_foo (date1, date2) VALUES ('2020-07-22 20:50:01 +00', NOW());

两者都不起作用(在 GMT-03 系统的 pgadmin 客户端上,它们都显示 20:50 而不是 17:50)。

【问题讨论】:

  • 分享 H2 & Postgres 中使用的 DDL
  • @PrasadU 你去。我已经编辑了问题。
  • 您将2020-07-22T20:50:01.12345Z 存储到定义为timestamp WITHOUT time zone 的列中,因此该值“按原样”获取并“按原样”返回。听起来好像你真的想要 Postgres 中的 timestamp WITH time zone
  • @a_horse_with_no_name 我认为这不是重点。为了澄清我已经测试了 NOW() 时间戳。由于 pgadmin 客户端将 NOW() 时间戳显示为我的本地时间 (GTM-3),因此 UTC 文字 20:50 应该显示为 17:50,就像 H2 客户端一样。 NOW() 时间戳是一种测试数据库客户端是否向用户显示没有时区的时间戳作为本地或 UTC(在这两种情况下都是本地的)的方法。
  • 没有什么真正迫使客户端(在这种情况下我假设 pgadmin)将存储的时间调整到您的区域。我认为 postgres 客户端通过不做任何事情来向您展示正确的价值。在向最终用户提供数据时,由您的后端或 Web 客户端进行最终的显示调整。

标签: java postgresql jpa


【解决方案1】:

可能是因为:如果在输入中为没有时区的时间指定了时区,它会被静默忽略。它会忽略您的“Z”并插入显示的日期时间。

https://www.postgresql.org/docs/12/datatype-datetime.html

【讨论】:

    【解决方案2】:

    知道了。要在 SQL INSERT 中指定 UTC 时间戳文字以处理没有时区的 Postgresql 时间戳字段,您必须首先声明 TIMESTAMP WITH TIME ZONE

    INSERT INTO tb_foo (date1, date2) VALUES (TIMESTAMP WITH TIME ZONE '2020-07-23 20:50:01.12345+00', NOW());
    

    INSERT INTO tb_foo (date1, date2) VALUES (TIMESTAMP WITH TIME ZONE '2020-07-23T20:50:01.12345Z', NOW());
    

    这也适用于 H2 数据库。所以这是一种提供适用于两个数据库的 SQL 脚本的方法。

    【讨论】:

    • 您可以在后端应用程序中使用没有 TIMEZONE 的时间戳并处理 UTC-everywhere 模式。或使用 WITH TIMEZONE 但确保您的服务器和会话正确设置 UTC 区域。见dba.stackexchange.com/questions/59006/…(记得阅读Shay Rojansky的回答,除了最高投票)
    猜你喜欢
    • 2012-07-07
    • 1970-01-01
    • 1970-01-01
    • 2016-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 2019-07-23
    相关资源
    最近更新 更多