应用于时间戳时,具有相同属性的时区名称会产生不同的结果

时间:2012-08-20 17:50:33

标签: postgresql datetime timezone

我只是花了一个小时的绝望,这两个表达的结果有差异:


db=# SELECT '2012-01-18 1:0 CET'::timestamptz AT TIME ZONE 'UTC'
           ,'2012-01-18 1:0 Europe/Vienna'::timestamptz AT TIME ZONE 'UTC';
      timezone       |      timezone
---------------------+---------------------
 2012-08-18 00:00:00 | 2012-08-17 23:00:00

显然,第二个表达式根据DST规则扣除两个小时,其中第一个表达式仅使用标准偏移量。

我检查了这两个时区名称的目录。他们都在那里,看起来一样:

db=# SELECT * FROM pg_timezone_names WHERE name IN ('CET', 'Europe/Vienna');
     name      | abbrev | utc_offset | is_dst
---------------+--------+------------+--------
 Europe/Vienna | CEST   | 02:00:00   | t
 CET           | CEST   | 02:00:00   | t

我咨询了PostgreSQL manual about time zones

  

PostgreSQL允许您以三种不同的形式指定时区:

     

全时区名称,例如America / New_York。已识别的时区名称列在pg_timezone_names视图中   (见45.67节)。 PostgreSQL使用广泛使用的zoneinfo时间   为此目的的区域数据,因此也可以识别相同的名称   很多其他软件。

     

时区缩写,例如PST。 与全时相比,此类规范仅定义了与UTC的特定偏移量   区域名称可能意味着一组夏令时过渡日期   规则也是如此。公认的缩写列于   pg_timezone_abbrevs视图(参见第45.66节)。你不能设置   配置参数timezone或log_timezone到时区   缩写,但您可以在日期/时间输入值中使用缩写   以及AT TIME ZONE操作员。

大胆强调我的。

那为什么会有区别?

我的设置(添加了更多详细信息)

SELECT version();
                                                version
-------------------------------------------------------------------------------------------------------
 PostgreSQL 9.1.4 on x86_64-unknown-linux-gnu, compiled by gcc-4.4.real (Debian 4.4.5-8) 4.4.5, 64-bit

SHOW timezone_abbreviations;

 timezone_abbreviations
------------------------
 Default

..我(假设)加载此文件中的缩写:     /usr/share/postgresql/9.1/timezonesets/Default

时区名CET来自我,我感到很茫然。但显然它在我的装置中存在。 quick test on sqlfiddle显示相同的结果。

我在具有类似设置的两台不同服务器上进行了测试。还有PostgreSQL 8.4。在所有这些pg_timezone_names中找到'CET'作为时区名称

2 个答案:

答案 0 :(得分:4)

在我发布这个之后,我运行了另一个查询来检查怀疑:

SELECT * FROM pg_timezone_abbrevs
WHERE  abbrev IN ('CEST', 'CET');

 abbrev | utc_offset | is_dst
--------+------------+--------
 CEST   | 02:00:00   | t
 CET    | 01:00:00   | f

事实证明,时区缩写名为CET(这是有道理的,“CET”是缩写)。似乎PostgreSQL选择了全名的缩写。因此,即使我在时区名称中找到CET,表达式'2012-01-18 1:0 CET':: timestamptz也会根据时间上略有不同的时间规则进行解释zone 缩写

SELECT '2012-01-18 1:0 CEST'::timestamptz(0)
      ,'2012-01-18 1:0 CET'::timestamptz(0)
      ,'2012-01-18 1:0 Europe/Vienna'::timestamptz(0);

      timestamptz       |      timestamptz       |      timestamptz
------------------------+------------------------+------------------------
 2012-01-18 00:00:00+01 | 2012-01-18 01:00:00+01 | 2012-01-18 01:00:00+01


SELECT '2012-08-18 1:0 CEST'::timestamptz(0)
      ,'2012-08-18 1:0 CET'::timestamptz(0)
      ,'2012-08-18 1:0 Europe/Vienna'::timestamptz(0);

      timestamptz       |      timestamptz       |      timestamptz
------------------------+------------------------+------------------------
 2012-08-18 01:00:00+02 | 2012-08-18 02:00:00+02 | 2012-08-18 01:00:00+02

我在时区名称中找到10个时区缩写,并且无法理解为什么那些。目的是什么?

其中,由于DST设置,时间偏移(utc_offset)在四种情况下不一致:

SELECT n.*, a.*
FROM   pg_timezone_names n 
JOIN   pg_timezone_abbrevs a ON  a.abbrev = n.name
WHERE  n.utc_offset <> a.utc_offset;

 name | abbrev | utc_offset | is_dst | abbrev | utc_offset | is_dst
------+--------+------------+--------+--------+------------+--------
 CET  | CEST   | 02:00:00   | t      | CET    | 01:00:00   | f
 EET  | EEST   | 03:00:00   | t      | EET    | 02:00:00   | f
 MET  | MEST   | 02:00:00   | t      | MET    | 01:00:00   | f
 WET  | WEST   | 01:00:00   | t      | WET    | 00:00:00   | f

在这些情况下,人们可能会被愚弄(就像我一样),查找tz 名称并找到实际未应用的时间偏移。这是一个不幸的设计 - 如果不是错误,至少是文档错误

我在手册中找不到有关如何解决时区名称缩写之间的歧义的问题。显然缩写优先。

Appendix B.1. Date/Time Input Interpretation提到查找时区缩写,但仍然不清楚如何识别时区名称以及哪些优先级如果是暧昧的令牌。

  

如果令牌是文本字符串,请与可能的字符串匹配:

     

将令牌的二进制搜索表查找作为时区缩写。

嗯,这句话中有一点点暗示,缩写首先出现,但没有确定的。此外,两个表格中都有一列abbrevpg_timezone_namespg_timezone_abbrevs ......

答案 1 :(得分:2)

时区缩写不包括夏令时(DST)转换规则的原因是它们倾向于暗示状态。在美国中西部地区,我们在冬季的CST(中央标准时间)以及今年剩余时间的CDT(中部夏令时)。有些异常区域没有使用DST,所以它变得复杂。

PostgreSQL没有维护自己的时区数据,尽管它为那些没有提供它的操作系统在每个版本中打包最新的Olson时区数据。通常,PostgreSQL将使用来自操作系统的时区信息,因此如果您遇到问题,请确保您拥有最新版本。

作为参考,今天在我的系统上,我得到了这些结果:

test=# SELECT '2012-01-18 1:0 CET'::timestamptz AT TIME ZONE 'UTC'
test-#       ,'2012-01-18 1:0 Europe/Vienna'::timestamptz AT TIME ZONE 'UTC';
      timezone       |      timezone       
---------------------+---------------------
 2012-01-18 00:00:00 | 2012-01-18 00:00:00
(1 row)

test=# SELECT * FROM pg_timezone_names WHERE name IN ('CET', 'Europe/Vienna');
     name      | abbrev | utc_offset | is_dst 
---------------+--------+------------+--------
 CET           | CEST   | 02:00:00   | t
 Europe/Vienna | CEST   | 02:00:00   | t
(2 rows)
test=# SELECT * FROM pg_timezone_abbrevs
test-# WHERE  abbrev IN ('CEST', 'CET');
 abbrev | utc_offset | is_dst 
--------+------------+--------
 CEST   | 02:00:00   | t
 CET    | 01:00:00   | f
(2 rows)

test=# SELECT '2012-01-18 1:0 CEST'::timestamptz(0)
test-#       ,'2012-01-18 1:0 CET'::timestamptz(0)
test-#       ,'2012-01-18 1:0 Europe/Vienna'::timestamptz(0);
      timestamptz       |      timestamptz       |      timestamptz       
------------------------+------------------------+------------------------
 2012-01-17 17:00:00-06 | 2012-01-17 18:00:00-06 | 2012-01-17 18:00:00-06
(1 row)

test=# SELECT '2012-08-18 1:0 CEST'::timestamptz(0)
test-#       ,'2012-08-18 1:0 CET'::timestamptz(0)
test-#       ,'2012-08-18 1:0 Europe/Vienna'::timestamptz(0);
      timestamptz       |      timestamptz       |      timestamptz       
------------------------+------------------------+------------------------
 2012-08-17 18:00:00-05 | 2012-08-17 19:00:00-05 | 2012-08-17 18:00:00-05
(1 row)