在 PostgreSQL 中表示未来时间

Representing a future time in PostgreSQL

我已经习惯于将过去的日期作为 UTC 存储在数据库中,因为那实际上是事件发生的时间。对于未来的日期,我会用特定的时区存储它,以避免闰秒或时区规则更改等变化。

Postgres 有 timestamp with timezone,但在幕后,它将它存储为 UTC,推断指定的时区是 UTC 的偏移量。如果时区规则发生变化,那将不会反映在该列中。

在这种情况下有什么建议?

特别是,如果你想保护自己免受未来时区变化的影响,你应该使用timestamp with time zone

PostgreSQL 内部存储了自 2000 年 1 月 1 日以来的微秒数 00:00:00,这对于时区更改是安全的。如果您不断更新 PostgreSQL,它将始终正确显示您会话时区的绝对值。

PostgreSQL 中没有闰秒规定。

For future dates, I would store it with a specific timezone, to avoid changes such as leap seconds or timezone rule changes.

这似乎落后了。 UTC 相对于其他时区的主要优势 是它较少 容易发生意外的未来变化:UTC 仅在已知的有限点引入闰秒日历年;并且有 none 频繁的政治强制偏移更改。

在某些本地管理的时区中存储值会使这些值更容易(与 UTC 相比)发生任意的、不可预测的未来含义变化。

因此,一般建议是:将所有时间值(无论是日期,还是日期+时间)存储为数据库中的 UTC,并在内部将其处理为 UTC 值;并仅在外部接口转换 to/from 本地时区。

对于 PostgreSQL,这意味着更喜欢 TIMESTAMP WITH TIME ZONE

Think [of it] like a calendar event. UTC doesn’t make sense for that

听起来您想存储一个关于某个时区的 localtime。 在这种情况下,将 timestamp(无时区)和 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 |
+---------------------+

查询 returns UTC 日期时间,2030-02-26 16:00:00,对应芝加哥当地时间 2030-02-26 10:00:00

使用AT TIME ZONE 将时区规则的应用延迟到进行查询的时间,而不是插入timestamptz的时间。


timestamp 上使用 AT TIME ZONE 将日期时间本地化为给定时区,但 报告 用户时区中的日期时间。 在 timestamptz 上使用 AT TIME ZONE 将日期时间转换为给定时区,然后删除偏移量,从而返回 timestamp。 上面,AT TIME ZONE 被使用了两次:第一次本地化 timestamp,然后将返回的 timestamptz 转换为新时区 (UTC)。结果是 UTC 中的 timestamp

这是一个示例,展示了 AT TIME ZONEtimestamp 上的行为:

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-062030-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 两次的替代方法是 set the user timezoneUTC。那么你可以使用

select localtime AT TIME ZONE tzone

请注意,以这种方式完成后,将返回 timestamptz 而不是 timestamp


请注意,存储本地时间可能会出现问题,因为可能存在不存在的时间和模棱两可的时间。 例如,2018-03-11 02:30:00America/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)

本地时间不明确的一个示例是 America/Chicago 中的 2018-11-04 01:00:00。由于夏令时,它发生了两次。 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 |
+---------------------+

请注意,这意味着无法通过将本地时间存储在 America/Chicago 时区来引用 2018-11-04 06:00:00 UTC

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 |
+---------------------+