我有一个没有时区的带有时间戳的表。YYYY-MM-DD HH:MM:SS
和一个字段“时区”,“P”代表太平洋或“M”代表山区。
我需要创建一个“带有时区的时间戳”类型的字段
鉴于我拥有的两个字段,有没有办法正确考虑夏令时?
具体来说:时间戳:2013-11-03 01:00:00 时区:“P”将变为:2013-11-03 01:00:00-07
和时间戳:2013-11-03 03:00:00 时区:“P”将变为:2013-11-03 03:00:00-08
我有一个没有时区的带有时间戳的表。YYYY-MM-DD HH:MM:SS
和一个字段“时区”,“P”代表太平洋或“M”代表山区。
我需要创建一个“带有时区的时间戳”类型的字段
鉴于我拥有的两个字段,有没有办法正确考虑夏令时?
具体来说:时间戳:2013-11-03 01:00:00 时区:“P”将变为:2013-11-03 01:00:00-07
和时间戳:2013-11-03 03:00:00 时区:“P”将变为:2013-11-03 03:00:00-08
首先,当说结果会变成 example2013-11-03 01:00:00-07
时,应该补充一点,这实际上取决于 SQL 客户端的时区设置。例如,欧洲时间的会话永远不会读取2013-11-03 01:00:00-07
为 a 的值,timestamp with time zone
因为没有欧洲国家在GMT-07
。
也就是说,可以通过将AT TIME ZONE构造应用于timestamp without time zone
.
假设我们从US/Pacific
时区运行它:
SET time zone 'US/Pacific';
SELECT t AT TIME ZONE
case z when 'P' then 'US/Pacific' when 'M' then 'US/Mountain' end
from (values
('2013-11-03 01:00:00'::timestamp, 'P'),
('2013-11-03 03:00:00'::timestamp, 'P')
) as v(t,z);
结果是:
时区 ---------------------- 2013-11-03 01:00:00-08 2013-11-03 03:00:00-08
2013-11-03 01:00:00 AT time zone 'US/Pacific'
具有歧义,因为它属于在-07
时区中首先发生的小时跨度,然后-08
在 DST 切换后的时区中第二次发生。postgres的解释是看-08
时区。如果我们考虑前一分钟,它属于-07
时区。
TIMESTAMP WITHOUT TIME ZONE
如果您考虑它们的名称,则和TIMESTAMP WITH TIME ZONE
( )之间的区别TIMESTAMPTZ
可能会非常难以理解。(事实上,规范似乎很混乱,以至于各种 RDBMS 以不同的方式实现它。)
在 PostgreSQL 中,两种类型都不会存储值存储时的时区,而是TIMESTAMPTZ
将值存储为基于 UTC 引用的精确时刻,而TIMESTAMP WITHOUT TIME ZONE
始终是相对的。
TIMESTAMPTZ
将调整 a 以表示与最初存储的时间相同的时刻(在世界的任何地方),就像它在客户端配置的当前时区中的时刻一样。TIMESTAMP WITHOUT TIME ZONE
始终是相同的值,即使您查询它的时区不同:由 表示的时刻2013-11-03 03:00:00
将是不明确的,并且取决于客户端设置。据推测,您使用“时区”列(P
或M
)TIMESTAMP WITHOUT TIME ZONE
来补偿输入值的歧义。
原则上,如果您与存储时间戳的相对时区处于相同的相对时区,则应该返回相同的值,因此,如果您已将客户端设置在US/Pacific
时区并且您已存储2013-11-03 03:00:00
在您的P
时间区,你应该2013-11-03 03:00:00
回来。但是,这仅在相对值没有歧义时才有效。
您的第一个示例中的问题是已经存在一些歧义:
时间戳:2013-11-03 01:00:00 时区:“P”将变为:2013-11-03 01:00:00-07
2013-11-03 01:00:00
可以表示时区中两个不同的US/Pacific
时间点,因此仅使用2013-11-03 01:00:00
和"P"
,您已经丢失了无法恢复的信息。
如果您只是希望它根据当时的 DST 设置在“-08”和“-07”之间切换,这将自动为您完成,但您应该TIMESTAMPTZ
首先使用 a精确到您所代表的时间点。
这是保留初始时区的示例,因此您可以看到“-08”和“-07”之间的变化:
SET time zone 'US/Pacific';
SELECT t AS "Date/Time for US/Pacific",
t AT time zone 'UTC' "Date/Time in UTC"
FROM (VALUES
('2013-11-03 00:00:00-07'::timestamptz),
('2013-11-03 01:00:00-07'::timestamptz),
('2013-11-03 02:00:00-07'::timestamptz),
('2013-11-03 03:00:00-07'::timestamptz)) AS v(t);
结果:
| DATE/TIME FOR US/PACIFIC | DATE/TIME IN UTC |
|--------------------------|---------------------|
| 2013-11-03 00:00:00-07 | 2013-11-03 07:00:00 |
| 2013-11-03 01:00:00-07 | 2013-11-03 08:00:00 |
| 2013-11-03 01:00:00-08 | 2013-11-03 09:00:00 |
| 2013-11-03 02:00:00-08 | 2013-11-03 10:00:00 |
不幸的是,没有办法仅使用您的两个字段来处理 DST 更改。
当然值得阅读PostgreSQL 手册的日期/时间类型部分,并注意AT TIME ZONE
文档中表格的“返回类型”列,以便更好地理解这些问题。
检查这对您是否有意义
set timezone to 'PST8PDT';
select now();
now
-------------------------------
2013-09-28 03:24:20.169189-07
select ts,
ts at time zone 'PST' as "PST",
ts at time zone 'PDT' as "PDT"
from (values
('2013-11-03 01:00:00'::timestamp),
('2013-11-03 02:00:00'),
('2013-11-03 03:00:00')
) s (ts)
;
ts | PST | PDT
---------------------+------------------------+------------------------
2013-11-03 01:00:00 | 2013-11-03 01:00:00-08 | 2013-11-03 01:00:00-07
2013-11-03 02:00:00 | 2013-11-03 02:00:00-08 | 2013-11-03 01:00:00-08
2013-11-03 03:00:00 | 2013-11-03 03:00:00-08 | 2013-11-03 02:00:00-08