PostgreSQL / JDBC和TIMESTAMP与TIMESTAMPTZ

时间:2014-04-07 20:14:20

标签: postgresql jpa jdbc

我最近在使用JPA时遇到了很多关于Timestamps的痛苦。我发现我的很多问题已经通过TIMESTAMPTZ用于我的字段而不是TIMESTAMP来清除。我的服务器是UTC,而我的JVM是PST。使用TIMESTAMP WITHOUT TIMEZONE时,JPA似乎几乎不可能对数据库中的UTC值进行标准化。

对我而言,我将这些字段用于"用户何时创建","他们何时上次使用他们的设备","他们最后一次是什么时候得到警报"等等。这些通常是事件,因此它们是实例的时间类型的值。因为他们现在将通过TIMESTAMPTZ,如果我不想要它们,我总是可以查询它们的特定区域。

所以我的问题是,对于Java / JPA / PostgreSQL服务器,什么时候我想在TIMESTAMPTZ上使用TIMESTAMP?有什么用例呢?现在我很难看到为什么我想要使用TIMESTAMP,因此我担心我没有抓住它的价值。

4 个答案:

答案 0 :(得分:23)

一般使用TIMESTAMPZ

Postgres专家David E. Wheeler在一篇博客文章中给出了建议,其标题说明了这一点:
Always Use TIMESTAMP WITH TIME ZONE (TIMESTAMPZ)

如果您要跟踪实际时刻,时间线上的特定点,请使用TIMESTAMP WITH TIME ZONE

一个例外:分区

Wheeler唯一的例外是在时间戳上进行分区时,由于技术限制。我们大多数人的罕见异常

有关分区的信息,请参阅doc并查看Wiki

误称

数据类型名称timestamp with time zonetimestamp without time zone是用词不当。在两个情况下,日期时间值以UTC(无时区偏移)存储。再读一遍前一句。 UTC,始终。“带时区”短语的意思是“注意时区”,而不是“将时区与此值一起存储”。类型之间的区别在于是否应在存储(INSERT或UPDATE)或检索(SELECT查询)期间应用任何时区。 (这种行为是针对Postgres描述的 - 其他数据库在这方面广泛地广泛。)

更确切地说,应该说TIMESTAMP WITHOUT TIME ZONE存储没有时区的日期时间值。但是没有任何时间框架参考,任何查看该数据的人都必须假设(希望,祈祷?)这些值是UTC。但同样,因为你几乎不应该使用这种类型而没有实际意义。

仔细阅读the doc,并进行一些实验以澄清您的理解。

不分区

如果您想存储可能时间而非特定时刻的一般概念,请使用其他类型TIMESTAMP WITHOUT TIME ZONE

例如,今年圣诞节将于2017年12月25日的第一时间开始。那将是2017-12-25T 00:00:00,没有时区指示,也没有从UTC偏移。这个值只是对可能时刻的模糊概念。在我们应用时区(或偏移)之前,它没有任何意义。因此,我们使用TIMESTAMP WITHOUT TIME ZONE存储此内容。

圣诞老人的特殊事件后勤部门的精灵将时区应用为他们的计划过程的一部分。最早的时区目前为Pacific/Kiribati,比UTC早14个小时。精灵计划圣诞老人首次到达那里。精灵安排一个飞行计划,将驯鹿带到其他时区,午夜即将到来,如Pacific/Auckland。随着每个区域的午夜到来,它们继续向西移动。几小时后Asia/Kolkata,稍晚些Europe/Paris,更晚几小时后才会America/Montreal,依此类推。

精灵使用WITH TIME ZONE记录每个特定的传递时刻,而圣诞节的一般概念将存储为WITHOUT TIME ZONE

WITHOUT TIME ZONE商业应用中的另一个用途是安排超过几周的约会。世界各地的政治家们对于弄乱时钟和重新定义时区规则有着莫名的偏爱。他们加入夏令时(DST),离开夏令时,在另一个日期开始夏令时,或在另一个日期结束夏令时,或将他们的时钟移动15分钟或半小时。所有这些都是在过去几年中由土耳其,美国,俄罗斯,委内瑞拉和其他国家完成的。

政客们经常在没有预先警告的情况下做出这些改变。因此,如果您计划在13:00进行为期六个月的牙科预约,则应将其存储为TIMESTAMP WITHOUT TIME ZONE,否则政治家可能会有效地将您的预约更改为中午,下午2点或13:30。

答案 1 :(得分:12)

您可以使用它来表示Joda-Time和新的Java 8时间API调用LocalDateTimeLocalDateTime不代表时间轴上的精确点。它只是一组字段,从一年到几纳秒。它是“日期的描述,用于生日,结合在挂钟上看到的当地时间”。

您可以用它来表示,例如,您的确切出生日期是1975-07-19下午6点。或者说,在全世界范围内,2015-01-01 00:00庆祝下一个新的一年。

为了表现精确的时刻,就像阿姆斯特朗在月球上行走的那一刻,带有时区的时间戳确实更合适。无论JVM的时区和数据库的时区如何,它都应该返回正确的时刻。

答案 2 :(得分:1)

更新以上答案:由于修剪,分区不再是PG11中的例外情况。

https://www.postgresql.org/docs/11/ddl-partitioning.html#DDL-PARTITION-PRUNING

个人成功地针对PG11 AWS RDS测试了查询。官方PG维基也声明使用timestamp without timezone是一个坏主意:

https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp_.28without_time_zone.29_to_store_UTC_times

答案 3 :(得分:0)

使用 Java 8 日期和时间 API,我不会盲目地加入 timestamptz 阵营。

如果您映射 timestamp <=> LocalDateTime,无论默认 Java 应用程序时区如何,您始终会获得相同的值。无论您在 TimeZone.setDefault(TimeZone.getTimeZone("TZ"))/SELECT 之间放入多少个混合不同 TZ 的调用 INSERT,您都将在 Java 中随时获得相同的 LocalDateTime,日期/时间组件将是与 Postgresql TO_CHAR(ts, 'YYYY-MM-DD HH24:MI:SS') 相同。

如果映射 timestamptz <=> LocalDateTime Postgresql JDBC 驱动程序(支持 JDBC 4.2 规范)在将值保存到 DB 时使用默认 Java 时区将 LocalDateTime 转换为 UTC。如果您将其保存在一个默认 TZ 中并在另一个 TZ 中读取,您会得到不同的“本地”结果。

飞机起飞时间以机场当地为准。如果您不需要比较不同城市之间的出发时间 timestamptz & UTC 没有意义,您只需在机票中打印确切的城市当地时间。使用 timestamp 可以按原样保留日期/时间,避免由于 Java 应用默认 TZ + 城市特定 TZ(业务逻辑)而导致的双重 TZ 校正。

timestamptz 在 SQL 中大量转换 TZ 时很有用。你只用 timestamp 写:

date_trunc('day', x.datecol AT TIME ZONE 'UTC' AT TIME ZONE x.timezone)
  AT TIME ZONE x.timezone AT TIME ZONE 'UTC'

虽然 timestamptz 没有必要提到时间是 UTC(如果你遵循这样的约定,你可能应该 xD):

date_trunc('day', x.datecol AT TIME ZONE x.timezone)
  AT TIME ZONE x.timezone

运算符 AT TIME ZONE 过载:

  • timestamp AT TIME ZONE 'X' => timestamptz
  • timestamptz AT TIME ZONE 'X' => timestamp

Postgresql JDBC + Java 8 date&time API spec