让我解释一下这两个例子:
在这两个例子中,我们假设一个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
函数在
timestamp
和
timestamptz
之间进行转换:
![enter image description here](https://istack.dev59.com/AzRQu.webp)
由于我们将其作为输入给出了一个timestamptz
,它将输出一个timestamp
。换句话说,它将绝对时间点2016-01-01 00: 00Z
转换为US/Pacific
墙上时间,即洛杉矶时钟在该绝对时间点所显示的时间。
在示例2中,我们正在执行相反的操作,即将timestamp
转换为timestamptz
。换句话说,我们正在问:当洛杉矶时钟显示2016-01-01 00:00
时,绝对时间是什么?
您提到:
好的,时区函数的'2016-01-01 00:00':: timestamp
部分不再是字符串,而是实际的时间戳。在哪个时区?
'2016-01-01 00:00':: timestamp
是一个timestamp
,即墙上时间。它没有时区概念。
我认为您可能没有完全理解timestamp
和timestamptz
之间的区别,这是关键。只需将它们视为墙上时间,即世界上某个地方显示的时间,和绝对时间,即我们宇宙中的绝对时间。
您自己回答中的例子并不完全准确。
SELECT ts FROM (VALUES
(timestamptz '2012-03-05 17:00:00+0')
,(timestamptz '2012-03-05 18:00:00+1')
,(timestamp '2012-03-05 18:00:00+1')
,(timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6')
,(timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC')
,(timestamp '2012-03-05 17:00:00'::timestamp)
,(timestamp '2012-03-05 17:00:00'::timestamptz)
) 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
现在,这里发生了什么?比你原来的解释少得多。该字符串仅被解析为。当结果被打印时,它使用当前设置的配置将其转换回底层数据结构的可读表示,即2012-03-05 17:00:00 +00。让我们更改配置并查看会发生什么:
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
是一样的。这里发生的是,时区偏移被忽略了,因为你要求一个时间戳。
示例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=
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
,因此与原始示例不同。
我希望这能澄清事情。再次强调,理解timestamp
和timestamptz
之间的区别非常重要。可以将其视为墙上时间与绝对时间的区别。
SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp at time zone 'UTC');
- bma