【问题标题】:why is postgresql fooling me with timestamps?为什么 postgresql 用时间戳愚弄我?
【发布时间】:2018-03-22 05:35:48
【问题描述】:

我正在开发一个系统,我正在尝试最大限度地减少时区可能引入的错误,因此我在 (postgresql) 数据库上使用时间戳字段,但我在创建记录时使用 UNIX 纪元的秒数​​和当我读取记录时(使用EXTRACT(EPOCH)读取,插入时使用TO_TIMESTAMP())。

这样我就可以摆脱时区问题。我还是这样。经过一番挖掘后,我发现从表中读取值时,postgresql 变得有点困惑。考虑这个查询:

select current_timestamp, extract(EPOCH from current_timestamp), id, last_gps_read, 
    extract(EPOCH from current_timestamp) - extract(EPOCH 
from last_gps_read) from sometable where id=1

这给了

              now              |    date_part     | id |      last_gps_read       |    ?column?     
-------------------------------+------------------+----+--------------------------+-----------------
 2018-03-21 23:26:07.263931-06 | 1521696367.26393 |  1 | 2018-03-21 23:26:00.5273 | 21606.736631155

请注意日期之间的距离非常接近(仅相差大约 7 秒?)。

所以当我使用 extract(EPOCH from x) 技巧时,我认为差异会给我大约 7 秒....而不是我得到〜21607(我在 GMT-6 上,这解释了为什么它是 21600秒的差异)。这绝对不是很酷,因为这意味着当报告两个日期的 UNIX 纪元以来的秒数时,它在报告来自表的数据的秒数时以某种方式引入了时区(我刚刚检查了 current_timestamp 的 UNIX 纪元以来的秒数是正确的)。

这样做的原因是什么?因为这对我来说听起来很像一个错误。

PS 我可以考虑将数据库上的字段类型更改为使用整数来保存自 UNIX 纪元以来的实际秒数,因此我肯定会摆脱它,但这听起来有点矫枉过正。

【问题讨论】:

  • 使用timestamp without time zone而不是timestamp with time zone来记录事件几乎总是错误的做法,除非您只处理来自一个时区的数据,否则会给您带来麻烦。
  • 请注意,即使 UTC 也有闰秒,您的 TZ 转换表必须知道这一点。目前 TAI UTC 相差 37 秒。

标签: postgresql timezone timestamp


【解决方案1】:

不同之处在于EXTRACT(EPOCH FROM ...) 始终计算相对于 GMT 时间的时间(众所周知的01.01.1970 00:00:00+00)。如果您您的当前时间转换为格林威治标准时间,您将获得时间戳2018-03-21 17:26:00.5273+00,其相差6小时约7秒,或约6 * 3600 + 7 = 21607 GPS 时间戳。

您可以将 GPS 时间转换为当地时间,或从时间戳差中减去 21600 以获得所需的结果。

【讨论】:

  • 但是为什么一开始从 UNIX 纪元开始计算秒数时有任何 TZ 调整???对于给定的时间点(这是我们持有的时间戳),它应该始终返回自 UNIX 纪元以来的相同秒数,而不管我站在哪里。
  • 计算是相对于纪元开始的,即UTC时间。您的时间戳有一个时区,EXTRACT 在计算时间时必须考虑到该时区。我认为解决您的问题的最干净的方法是将正确时区的 GPS 时间添加到您的数据库中。
  • 看......假设我们正在谈论一个特定的时间事件:我的出生。如果您要求我出生于位于加利福尼亚州旧金山的服务器,它应该告诉您我出生于 1975 年 11 月 25 日大约晚上 10 点......但是,如果您要求该日期到服务器位于委内瑞拉马拉开波(我出生的地方),它将显示 11 月 26 日凌晨 2 点。或类似的东西。当然......如果我要求提取月份的日期,它应该包括 TZ 进行计算,这是有道理的。 但是如果您询问自 UNIX 纪元以来的秒数,则无论 TZ 如何,两台服务器应该说相同的数量。
  • 假设你生日那天有两台 Postgres 服务器。一个在旧金山,另一个在马拉开波。当你看到曙光时,SELECT EXTRACT(EPOCH FROM NOW()) 在两者上都被执行了。两者都返回完全相同的秒数,而两个位置的本地时间戳不同。 EPOCH的操作是正确的。但是,如果您想要不同的行为,则必须对其进行调整。我看不出您为什么不以当地时间导入 GPS 时间。然后一切都应该按照你需要的方式工作。它还可以让您更轻松地使用您的表格。
  • 但是@clemens,这很有趣。如果您检查我使用的查询,则在提供 current_timestamp 时提取 (EPOCH) 采用本地时间,考虑时区并正确提供秒数。当我对表中的数据使用它时,它的行为不同。它的作用类似于:我采用本地时间,然后在 GMT+0 上使用这个时间(换句话说,它不考虑时区),然后我们计算秒数。为什么在使用 current_timestamp 和从表中读取时它的行为不同?无论如何....会彻底阅读答案。
【解决方案2】:

@clemens 对样本的回答加倍...

匹配您的时区:

t=# set timezone to 'GMT+6';
SET

你做了什么:

t=# with c("ct", last_gps_read) as (values('2018-03-21 23:26:07.263931-06'::timestamptz,'2018-03-21 23:26:00.5273'::timestamp))
select ct
, extract(EPOCH from ct)
, last_gps_read
, extract(EPOCH from ct) - extract(EPOCH from last_gps_read)
from c;
              ct               |    date_part     |      last_gps_read       |    ?column?
-------------------------------+------------------+--------------------------+-----------------
 2018-03-21 23:26:07.263931-06 | 1521696367.26393 | 2018-03-21 23:26:00.5273 | 21606.736631155
(1 row)

你应该做什么:

t=# with c("ct", last_gps_read) as (values('2018-03-21 23:26:07.263931-06'::timestamptz,'2018-03-21 23:26:00.5273'::timestamp))
select ct
, extract(EPOCH from ct)
, last_gps_read
, extract(EPOCH from ct - last_gps_read)
from c;
              ct               |    date_part     |      last_gps_read       | date_part
-------------------------------+------------------+--------------------------+-----------
 2018-03-21 23:26:07.263931-06 | 1521696367.26393 | 2018-03-21 23:26:00.5273 |  6.736631
(1 row)

为什么:你将double precisiondouble precision 分开,得到epoch aware of time zone - epoch not aware of timezone。所以你的结果是预期的。和documented。我建议做的是使用interval 来划分时间戳,无论是否知道时区(因为间隔同时操作),然后从interval 中提取纪元。这样您就不需要调整时区或将时间戳与时区统一为AT TIME ZONE 'GTM' 或其他...

【讨论】:

    猜你喜欢
    • 2015-03-03
    • 2016-06-27
    • 1970-01-01
    • 2014-01-22
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    相关资源
    最近更新 更多