在 Postgres 中时区之间转换

Converting Between Timezones in Postgres

我想了解 Postgre 中的时间戳和时区。我想我明白了,直到我红了 this 文章。
关注 "Converting Between Timezones" 部分。它有两个例子。

(考虑默认时区配置为 UTC。)

示例 1

db=# SELECT timezone('US/Pacific', '2016-01-01 00:00'); outputs 2015-12-31 16:00:00

根据文章和我的理解,因为 timezone 函数的 '2016-01-01 00:00' 部分只是一个字符串,所以它被默默地转换为默认的 UTC。因此,根据 timezone 函数的要求,它会从 '2016-01-01 00:00' UTC 转换为 US/Pacific,即 2015-12-31 16:00:00

示例 2

db=# SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp); outputs 2016-01-01 08:00:00+00

对不起,我不明白为什么,那里的解释也没有帮助。好的,timezone 函数的 '2016-01-01 00:00'::timestamp 部分不再是字符串,而是实际的时间戳。在什么时区?如果是UTC,输出就得和例1一样。所以自动转换成US/Pacific?那么输出是UTC吗?但为什么?我在我的 timezone 中要求 US/Pacific 而不是 UTC。

请解释 timezone 在获取时间戳并被要求转换时的行为。谢谢。

这是我的理解。请告诉我。
我在 postgresql.conf 中定义的默认时区是 UTC。检查此代码

SELECT ts FROM  (VALUES
(timestamptz '2012-03-05 17:00:00+0') -- outputs 2012-03-05 17:00:00+00 --1
,(timestamptz '2012-03-05 18:00:00+1') -- outputs 2012-03-05 17:00:00+00 --2
,(timestamp   '2012-03-05 18:00:00+1') -- outputs 2012-03-05 18:00:00+00 --3
,(timestamp   '2012-03-05 11:00:00'  AT TIME ZONE '+6') -- outputs 2012-03-05 17:00:00+00 --4
,(timestamp   '2012-03-05 17:00:00'  AT TIME ZONE 'UTC') -- outputs 2012-03-05 17:00:00+00 --5
,(timestamp   '2012-03-05 17:00:00'::timestamp) -- outputs 2012-03-05 17:00:00+00 --6
,(timestamp   '2012-03-05 17:00:00'::timestamptz) -- outputs 2012-03-05 17:00:00+00 --7
    ) t(ts);

现在,假装这是 Postgre 说话:
有为输出定义的特殊时区。所以我将以默认的 UTC 输出所有内容。我们走吧。

1 (timestamptz '2012-03-05 17:00:00+0')
这是时间感知数据,偏移量为0,所以是UTC。默认值也是 UTC。我将按原样保存(无需转换)并输出 2012-03-05 17:00:00+00 因为 UTC 输入到 UTC 保存到 UTC 输出。

2 (timestamptz '2012-03-05 18:00:00+1')
也是时间感知数据,偏移量是+1,所以不是UTC。偏移负 1 以将其转换为 UTC,因此我可以将其保存为 UTC,这是默认设置。输出 2012-03-05 17:00:00+00 因为非 UTC 输入到 UTC 保存到 UTC 输出。

3 (timestamp '2012-03-05 18:00:00+1')
时间无关数据。忽略偏移量,假设这是默认的 UTC 并按原样保存。输出 2012-03-05 18:00:00+00 因为,我不知道我不关心我会假装这是我的默认 UTC 输入到 UTC 保存到 UTC 输出。

4 (timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6')
再次是时间不敏感的数据。忽略偏移量(如果有)。然后将其转换为给定的 AT TIME ZONE '+6' 偏移量,这样我就可以将其视为完整的时间感知数据。所以我最后的数据是2012-03-05 17:00:00+00。但这仍然不是时间感知数据。因此,我假设这是我的默认 UTC 并按原样保存。输出 2012-03-05 17:00:00+00 因为,我不知道我不关心我会假装这是我的默认 UTC 输入到 UTC 保存到 UTC 输出。

5 (timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC')
和前面的数据一样,时间不敏感的数据。如果有的话,我会忽略偏移量。然后我将它转换为给定的 AT TIME ZONE 'UTC',所以没有实际转换,因为没有实际偏移量(UTC 偏移量为 0)。所以我的最终数据是2012-03-05 17:00:00。但这仍然不是时间感知数据。因此,我假设这是我的默认 UTC 并按原样保存。输出 2012-03-05 17:00:00 因为,我不知道我不关心我会假装这是我的默认 UTC 输入到 UTC 保存到 UTC 输出

6 (timestamp '2012-03-05 17:00:00'::timestamp)
这是时间无关数据,再次转换为时间无关数据。因此,与 4 一样,我将忽略任何偏移量(如果有)。也没有 AT TIME ZONE,所以没有转换。我最后的时间无关数据是 '2012-03-05 17:00:00'。我假设这是我的默认 UTC 并按原样保存。输出 2012-03-05 17:00:00+00 因为,我不知道我不关心我会假装这是我的默认 UTC 输入到 UTC 保存到 UTC 输出

7 (timestamp '2012-03-05 17:00:00'::timestamptz)
这是时间不敏感的数据,转换为时间敏感的数据。但是没有抵消,转换,什么都没有。所以,这是 UTC。所以,我会按原样保存它。输出 2012-03-05 17:00:00+00 因为 UTC 输入到 UTC 保存到 UTC 输出。

(总的来说希望以上内容对大家有所帮助)

现在!关于文章
示例 1
SELECT timezone('US/Pacific', '2016-01-01 00:00');
时间无关数据,但我可以将其转换为时间感知数据。根据article,由于没有时区信息,所以可以在默认的UTC时区解析。 因此,时间感知的 UTC 数据,按原样保存,但在输出之前将其转换为 US/Pacific。这就是为什么 article 说 "We get the wall time in California for 2016-01-01 00:00 UTC. " 输出是 2015-12-31 16:00:00,对于 '2016-01-01 00:00' UTC 输入,这是加利福尼亚的挂钟时间。

Article 也表示 "Note that we passed the timestamp as a string, which was implicitly cast to a timestamptz"。
这可以写成SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamptz);并且仍然输出2015-12-31 16:00:00。时间感知数据,没有偏移量,所以它的偏移量是 0,所以它是 UTC。 UTC 也是默认设置,因此只需按原样保存即可。在输出之前将其转换为 US/Pacific。这就是它再次输出 2015-12-31 16:00:00 的原因。

因为“timezone(zone, timestamp) 等价于SQL-符合构造 timestamp AT TIME ZONE zone”,根据 article,那么

SELECT timezone('US/Pacific', '2016-01-01 00:00');
SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamptz);
timestamptz '2016-01-01 00:00' at time zone 'US/Pacific'
timestamptz '2016-01-01 00:00+00' at time zone 'US/Pacific'

都一样
Time-aware data (or make it be time-aware),无偏移,保存为UTC,转换后输出,如US/Pacific.


示例 2
SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp);
时间无关数据。我能否将其转换为 UTC 默认值,如示例 1 所示?不,因为转换为时间无关(::timestamp 部分)。我无能为力。它是时间感知数据。

如果有偏移量,我会忽略。与上面4不同的是,没有定义偏移量,没有AT TIME ZONE '+ or -X'。因此,为了获得 UTC,我将根据 US/Pacific'2016-01-01 00:00' 转换回 UTC。从 Pacific 到 UTC 添加 8 小时。我的 UTC 现在是 2016-01-01 08:00:00+00。按原样保存。输出 2016-01-01 08:00:00+00 因为,我不知道我不关心我会假装这是我的默认 UTC 输入到 UTC 保存到 UTC 输出

同样,根据 articletimezone(zone, timestamp) 等同于符合 SQL 的构造 timestamp AT TIME ZONE zone”,所以

SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp);
timestamp '2016-01-01 00:00' at time zone 'US/Pacific'
timestamp '2016-01-01 00:00+00' at time zone 'US/Pacific'

都一样

时间无关数据,忽略偏移量,转换回UTC,这是UTC,另存为UTC输出为UTC。

谢谢

让我解释一下这两个例子:

我们都假定时区为 UTC(即 SET timezone TO UTC)。

db=# SELECT timezone('US/Pacific', '2016-01-01 00:00');
      timezone
---------------------
 2015-12-31 16:00:00
(1 row)

这相当于 SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamptz),即 Postgres 将字符串隐式转换为 timestamptz

我们知道timezone函数在timestamptimestamptz之间来回转换:

因为我们给它一个 timestamptz 作为输入,它会输出一个 timestamp。换句话说,它将绝对时间点 2016-01-01 00:00Z 转换为 US/Pacific 中的挂钟时间,即洛杉矶时钟在该绝对时间点显示的时间。

在示例 2 中,我们做相反的事情,即将 timestamp 转换为 timestamptz。换句话说,我们在问:洛杉矶的时钟显示2016-01-01 00:00时的绝对时间点是什么?

你提到:

Ok, the '2016-01-01 00:00'::timestamp part of the timezone function is no longer a string, but an actual timestamp. In what timezone?

'2016-01-01 00:00'::timestamp 是一个 timestamp,即墙上时间。它没有时区的概念。

我想你可能还没有完全理解 timestamptimestamptz 之间的区别,这是这里的关键。只需将它们视为 wall time,即挂在墙上的时钟上显示的世界某个地方的时间,以及 absolute time,即我们宇宙中的绝对时间。

您在自己的回答中举的例子不太准确。

SELECT ts FROM  (VALUES
(timestamptz '2012-03-05 17:00:00+0') -- outputs 2012-03-05 17:00:00+00 --1
,(timestamptz '2012-03-05 18:00:00+1') -- outputs 2012-03-05 17:00:00+00 --2
,(timestamp   '2012-03-05 18:00:00+1') -- outputs 2012-03-05 18:00:00+00 --3
,(timestamp   '2012-03-05 11:00:00'  AT TIME ZONE '+6') -- outputs 2012-03-05 17:00:00+00 --4
,(timestamp   '2012-03-05 17:00:00'  AT TIME ZONE 'UTC') -- outputs 2012-03-05 17:00:00+00 --5
,(timestamp   '2012-03-05 17:00:00'::timestamp) -- outputs 2012-03-05 17:00:00+00 --6
,(timestamp   '2012-03-05 17:00:00'::timestamptz) -- outputs 2012-03-05 17:00:00+00 --7
    ) t(ts);

您的示例存在的问题是您构建的数据集只有一列。由于一列只能有一种类型,因此每一行(或本例中的单个值)都被转换为相同的类型,即 timestamptz,即使某些值被计算为 timestamp(例如值 3 ).因此,您在这里有一个额外的隐式转换。

让我们将示例拆分为单独的查询,看看发生了什么:

示例 1

db=# SELECT timestamptz '2012-03-05 17:00:00+0';
      timestamptz
------------------------
 2012-03-05 17:00:00+00

您可能已经知道,timestamptz '2012-03-05 17:00:00+0''2012-03-05 17:00:00+0'::timestamptz 是等价的(我更喜欢后者)。因此,为了使用与文章中相同的语法,我将重写:

db=# SELECT '2012-03-05 17:00:00+0'::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00

现在,这里发生了什么?嗯,比你原来的解释少。该字符串被简单地解析为 timestamptz。打印结果时,它使用当前设置的 timezone 配置将其转换回底层数据结构的人类可读表示,即 2012-03-05 17:00:00+00.

让我们更改 timezone 配置,看看会发生什么:

db=# SET timezone TO 'Europe/Berlin';
SET
db=# SELECT '2012-03-05 17:00:00+0'::timestamptz;
      timestamptz
------------------------
 2012-03-05 18:00:00+01

唯一改变的是如何在屏幕上打印timestamptz,即使用Europe/Berlin 时区。

示例 2

db=# SELECT timestamptz '2012-03-05 18:00:00+1';
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

同样,只是解析日期。

示例 3

db=# SELECT timestamp '2012-03-05 18:00:00+1';
      timestamp
---------------------
 2012-03-05 18:00:00
(1 row)

这与'2012-03-05 18:00:00+1'::timestamp相同。这里发生的是时区偏移量被简单地忽略了,因为你要求 timestamp.

示例 4

db=# SELECT timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6';
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

让我们重写得更简单:

db=# SELECT timezone('+6', '2012-03-05 11:00:00'::timestamp);
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

这是在问:偏移+6小时的时区墙上的时钟显示2012-03-05 11:00:00时的绝对时间是多少?

示例 5

db=# SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC';
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

让我们重写:

db=# SELECT timezone('UTC', '2012-03-05 17:00:00'::timestamp);
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

这是在问:UTC 时区墙上的时钟显示 2012-03-05 17:00:00 时的绝对时间是多少?

示例 6

db=# SELECT timestamp '2012-03-05 17:00:00'::timestamp;
      timestamp
---------------------
 2012-03-05 17:00:00
(1 row)

此处您向 timestamp 投射两次,这没有区别。让我们简化一下:

db=# SELECT '2012-03-05 17:00:00'::timestamp;
      timestamp
---------------------
 2012-03-05 17:00:00
(1 row)

我想这很清楚。

示例 7

db=# SELECT timestamp '2012-03-05 17:00:00'::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

让我们重写:

db=# SELECT ('2012-03-05 17:00:00'::timestamp)::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

您首先将字符串解析为 timestamp,然后使用当前集合 timezone 将其转换为 timestamptz。如果我们更改 timezone,我们会得到其他东西,因为 Postgres 在将 timestamp(或缺少时区信息的字符串)转换为 timestamptz:

时假定时区
db=# SET timezone TO 'Europe/Berlin';
SET
db=# SELECT ('2012-03-05 17:00:00'::timestamp)::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+01
(1 row)

这个以 UTC 表示的绝对时间是 2012-03-05 16:00:00+00,因此与原始示例不同。


我希望这能澄清事情。同样,了解 timestamptimestamptz 之间的区别是关键。想想挂钟时间与绝对时间。