在 PostgreSQL timestamptz 类型中保留时区

Posted

技术标签:

【中文标题】在 PostgreSQL timestamptz 类型中保留时区【英文标题】:Preserve timezone in PostgreSQL timestamptz type 【发布时间】:2013-12-15 00:15:25 【问题描述】:

对于符合 ISO8601 的日期时间

2004-10-19 10:23:54+02

是否可以将具有+02 偏移量的值反映在存储的列值中并在选择时保留?

根据我对appropriate section of the docs Postgres 的阅读,Postgres 的默认行为是转换为 UTC,此时原始偏移量丢失。这当然是我所看到的。

数据是通过无法添加任何特殊 tz 转换的 ORM 访问的,因此我真的需要简单地存储具有原始偏移量的日期时间,并在选择时反映该值。

对于任何急于告诉我这是同一时间实例的人来说,保存这个值对这个数据很重要。

【问题讨论】:

是否可以将偏移量存储在单独的列中,这样您就不会受到 Postgres 的摆布? @tadman Ha。试图把它卷成一列。好像也不是什么不合理的事情。 数据源是什么?字符串文字?或另一列 - 什么类型? @ErwinBrandstetter 一个经过验证的 ISO8601 字符串。 您的格式不符合 ISO 8601。符合标准的格式如下所示:2004-10-19T10:23:54+02:00。 (有关这方面的说明在您的链接文档中。)PostgreSQL 确实以 UTC 存储时间戳。对于时区感知列,它在输入时转换 from 客户端的时区,并在输出时将 to 转换为(可能)不同的客户端时区。应用程序代码不需要进行任何转换,但可能需要设置其时区。 (对于会话,set time zone 'America/Anchorage'; 【参考方案1】:

正如您自己已经弄清楚的那样,时区根本不会使用 Postgres 日期/时间类型保存,即使使用 timestamptz 也不保存。它的角色分别只是输入修饰符或输出修饰符。仅保存值(时间点)。此相关答案中有大量详细信息:

Ignoring timezones altogether in Rails and PostgreSQL

因此,如果你想保留输入字符串的那部分,你必须从字符串中提取它并自己保存。我会使用如下表格:

CREATE TABLE tstz
 ...
 , ts timestamp    -- without time zone
 , tz text
)

tz,即text,可以保存数字偏移量以及时区缩写,或时区名称.

困难在于根据解析器遵循的所有各种规则并且以不易破坏的方式提取时区部分。 让解析器完成工作,而不是编写自己的过程。考虑这个演示:

WITH ts_literals (tstz) AS (
   VALUES ('2013-11-28 23:09:11.761166+03'::text)
         ,('2013-11-28 23:09:11.761166 CET')
         ,('2013-11-28 23:09:11.761166 America/New_York')
   )
SELECT tstz
      ,tstz::timestamp AS ts
      ,right(tstz, -1 * length(tstz::timestamp::text)) AS tz
FROM   ts_literals;

SQL Fiddle.

在日期和时间之间使用或不使用T。关键逻辑在这里:

right(tstz, -1 * length(tstz::timestamp::text)) AS tz

在修剪解析器识别为日期/时间组件的长度后,获取时间戳字符串的剩余部分。正如您所说,这取决于输入:

经过验证的 ISO8601 字符串

【讨论】:

感谢您的详细回复。令人失望的是,这不可能在一列中实现。 @markdsievers 如果你仔细想想,偏移量是多余的信息。 “真实”时间是 UTC/GMT,即自纪元以来的毫秒数。如果在您的应用程序和数据的上下文中您真正关心保留偏移量,那意味着您关心本地时间,这意味着您应该捕获和记录时区。例如:“太平洋/奥克兰”。时区不仅仅是一个偏移量,它包括夏令时 (DST) 和其他异常的规则/历史。如果没有记录时区,则日期时间+偏移量和 UTC 日期时间之间没有有用的区别。 6 岁的我看到了他的方式的错误。叮叮叮,年轻的自己。谢谢欧文和巴兹尔。【参考方案2】:

Java 开发人员可以将Joda TimeJadira UserTypePersistentDateTimeAndZone 结合使用。示例:

@Basic(optional = false)
@Columns(columns =  @Column(name = "modificationtime"),
        @Column(name = "modificationtime_zone") )
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTimeAndZone")
@Index(name = "payment_modificationtime_idx")
private DateTime modificationTime = null;

在本例中,DateTime 信息分为 2 列:

    modificationtime timestamp without time zone 以 UTC 时区存储时间戳 modificationtime_zone varchar(255) 将时区 ID 存储为字符串(例如 America/Caracas

虽然 Joda Time 和 Jadira(以及 Hibernate)是 Java 特有的(并且是事实上的方法),但可以应用上述构造 RDBMS 列以存储时间戳和时区的方法任何编程语言。

【讨论】:

【参考方案3】:

本机 postgres date/time datatypes 不会为您保留输入时区。如果您需要在数据库中将其作为时间戳查询并显示原始信息,则必须以某种方式存储这两条信息。

我打算建议您的 ORM 可以定义自定义充气/放气方法来处理魔术,但显然它不能。你应该指出你正在使用哪个 ORM。

您可以让 ORM 在数据库中存储/检索字符串,并在 Postgres 中使用 trigger 将其转换为存储在另一列中的时间戳,该列在执行数据库端查询时使用。如果你有很多表包含这种类型的数据,那可能有点笨拙。

如果你真的想要数据库中的单个列中的数据,你可以在 Postgres 中定义一个composite type,尽管你的 ORM 可能无法处理它们。

【讨论】:

故意省略了 ORM 细节,因为这是另一个后续问题的主题。正如标题所示,这个问题只是解决了将 TZ 存储在 Postgres timestamptz 类型的一列中的可能性。

以上是关于在 PostgreSQL timestamptz 类型中保留时区的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL中的时区转换不一致[重复]

如何使用 sqitch postgresql 验证更改列数据类型更改?

将 oracle.sql.TIMESTAMPTZ 转换为字符串值的问题

TIMESTAMPTZ 和函数不变性的索引

使用 CASE 和 generate_series() 查询,结果 timestamptz 降序排列

从时间戳中丢弃毫秒部分