我已经习惯将过去的日期作为 UTC 存储在数据库中,因为这实际上是事件发生的时间。对于未来的日期,我会将其存储在特定的时区中,以避免诸如闰秒或时区规则更改之类的更改。
Postgres 有timestamp with timezone,但在幕后,它将它存储为 UTC,推断指定的时区是 UTC 的偏移量。如果时区规则发生变化,那将不会反映在列中。
在这种情况下推荐什么?
我已经习惯将过去的日期作为 UTC 存储在数据库中,因为这实际上是事件发生的时间。对于未来的日期,我会将其存储在特定的时区中,以避免诸如闰秒或时区规则更改之类的更改。
Postgres 有timestamp with timezone,但在幕后,它将它存储为 UTC,推断指定的时区是 UTC 的偏移量。如果时区规则发生变化,那将不会反映在列中。
在这种情况下推荐什么?
把它想象成一个日历事件。UTC 对此没有意义
听起来您想存储相对于某个时区的本地时间。在这种情况下,将 a timestamp(无时区)和 the存储timezone在单独的列中。
例如,假设您要录制将于 2030 年 2 月 26 日上午 10 点在芝加哥发生的事件,并且无论该日期生效的时区规则如何,它都必须在当地时间上午 10 点。
如果数据库存储没有时区的时间戳:
unutbu=# select '2030-02-26 10:00:00'::timestamp as localtime, 'America/Chicago' AS tzone;
+---------------------+-----------------+
| localtime | tzone |
+---------------------+-----------------+
| 2030-02-26 10:00:00 | America/Chicago |
+---------------------+-----------------+
稍后,您可以使用以下方法找到事件的 UTC 日期时间
unutbu=# select '2030-02-26 10:00:00'::timestamp AT TIME ZONE 'America/Chicago' AT TIME ZONE 'UTC';
+---------------------+
| timezone |
+---------------------+
| 2030-02-26 16:00:00 |
+---------------------+
该查询返回 UTC 日期时间2030-02-26 16:00:00,它对应2030-02-26 10:00:00于芝加哥的本地时间。
使用AT TIME ZONE 将时区规则的应用延迟到进行查询而不是timestamptz插入时。
使用AT TIME ZONEon atimestamp将日期时间本地化到给定的时区,但在用户的 timezone中报告日期时间。使用on a将日期时间转换为给定的时区,然后删除偏移量,从而返回 a 。上面,使用了两次:第一次本地化 a ,然后将返回的时区转换为新的时区 (UTC)。结果是UTC 格式。AT TIME ZONEtimestamptztimestampAT TIME ZONEtimestamptimestamptztimestamp
这是一个示例,展示了AT TIME ZONEs 在 s 上的行为timestamp:
unutbu=# SET timezone = 'America/Chicago';
unutbu=# SELECT '2030-02-26 10:00:00'::timestamp AT TIME ZONE 'America/Chicago';
+------------------------+
| timezone |
+------------------------+
| 2030-02-26 10:00:00-06 |
+------------------------+
unutbu=# SET timezone = 'America/Los_Angeles';
unutbu=# SELECT '2030-02-26 10:00:00'::timestamp AT TIME ZONE 'America/Chicago';
+------------------------+
| timezone |
+------------------------+
| 2030-02-26 08:00:00-08 |
+------------------------+
2030-02-26 10:00:00-06并且2030-02-26 08:00:00-08是相同的日期时间,但在不同的用户时区报告。这表明芝加哥的上午 10 点是洛杉矶的上午 8 点(使用当前时区定义):
unutbu=# SELECT '2030-02-26 10:00:00-06'::timestamptz AT TIME ZONE 'America/Los_Angeles';
+---------------------+
| timezone |
+---------------------+
| 2030-02-26 08:00:00 |
+---------------------+
使用AT TIME ZONE两次的替代方法是将用户时区设置为UTC. 然后你可以使用
select localtime AT TIME ZONE tzone
请注意,以这种方式完成时,将timestamptz返回 a 而不是 a timestamp。
请注意,存储本地时间可能会出现问题,因为可能存在不存在的时间和不明确的时间。例如,2018-03-11 02:30:00是 中不存在的本地时间America/Chicago。Postgresql 通过假设它指的是夏令时 (DST) 开始后的相应时间来规范化不存在的本地时间(好像有人忘记将时钟调快):
unutbu=# select '2018-03-11 02:30:00'::timestamp AT TIME ZONE 'America/Chicago' AT TIME ZONE 'UTC';
+---------------------+
| timezone |
+---------------------+
| 2018-03-11 08:30:00 |
+---------------------+
(1 row)
unutbu=# select '2018-03-11 03:30:00'::timestamp AT TIME ZONE 'America/Chicago' AT TIME ZONE 'UTC';
+---------------------+
| timezone |
+---------------------+
| 2018-03-11 08:30:00 |
+---------------------+
(1 row)
本地时间不明确的一个例子是2018-11-04 01:00:00在America/Chicago. 由于 DST,它发生了两次。Postgresql 通过在 DST 结束后选择较晚的时间来解决这种歧义:
unutbu=# select '2018-11-04 01:00:00'::timestamp AT TIME ZONE 'America/Chicago' AT TIME ZONE 'UTC';
+---------------------+
| timezone |
+---------------------+
| 2018-11-04 07:00:00 |
+---------------------+
请注意,这意味着无法2018-11-04 06:00:00 UTC通过在时区中存储本地时间来引用America/Chicago:
unutbu=# select '2018-11-04 00:59:59'::timestamp AT TIME ZONE 'America/Chicago' AT TIME ZONE 'UTC';
+---------------------+
| timezone |
+---------------------+
| 2018-11-04 05:59:59 |
+---------------------+
特别是如果您想保护自己免受未来 zune 更改的影响,您应该使用timestamp with time zone.
PostgreSQL 内部存储自 2000-01-01 00:00:00 以来的微秒数,这不受时区更改的影响。如果您不断更新您的 PostgreSQL,它将始终为您的会话时区正确显示该绝对值。
PostgreSQL 中没有规定闰秒。
对于未来的日期,我会将其存储在特定的时区中,以避免诸如闰秒或时区规则更改之类的更改。
这似乎落后了。与其他时区相比,UTC的主要优势在于它不太容易出现意外的未来变化:UTC 仅在日历年的已知有限时间点引入闰秒;并且没有频繁的政治授权的抵消变化。
在某些本地管理的时区中存储值会使这些值更容易(与 UTC 相比)在含义上发生任意、不可预测的未来变化。
因此,一般建议是:将所有时间值(无论是日期,还是日期+时间)在数据库中存储为 UTC,在内部将它们作为 UTC 值处理;并仅在外部接口与本地时区进行转换。
对于 PostgreSQL,这意味着更喜欢TIMESTAMP WITH TIME ZONE.