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

Time zone names with identical properties yield different result when applied to timestamp

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

1
2
3
4
5
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规则扣除两个小时,其中第一个表达式仅使用标准偏移量。

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

1
2
3
4
5
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手册:

PostgreSQL allows you to specify time zones in three different forms:

A full time zone name, for example America/New_York. The recognized time zone names are listed in the pg_timezone_names view
(see Section 45.67). PostgreSQL uses the widely-used zoneinfo time
zone data for this purpose, so the same names are also recognized by
much other software.

A time zone abbreviation, for example PST. Such a specification merely defines a particular offset from UTC, in contrast to full time
zone names which can imply a set of daylight savings transition-date
rules as well. The recognized abbreviations are listed in the
pg_timezone_abbrevs view (see Section 45.66). You cannot set the
configuration parameters timezone or log_timezone to a time zone
abbreviation, but you can use abbreviations in date/time input values
and with the AT TIME ZONE operator.

大胆的重点我的。

为什么差异呢?

我的设置(添加了更多细节)

  • 关于Debian Squeeze的PostgreSQL 9.1.4(来自http://backports.debian.org/debian-backports的标准挤压后退)

  • 本地timezone设置默认为系统区域设置de_AT.UTF-8,但应与示例无关。

1
2
3
4
5
6
7
8
9
10
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来自我,我感到很茫然。但显然它在我的装置中存在。对sqlfiddle的快速测试显示了相同的结果。

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


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

1
2
3
4
5
6
7
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也会根据时区缩写的细微不同规则进行解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)在四种情况下不一致:

1
2
3
4
5
6
7
8
9
10
11
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名称并找到实际未应用的时间偏移。这是一个不幸的设计 - 如果不是一个bug,至少是一个文档错误。

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

附录B.1。日期/时间输入解释提到了时区缩写的查找,但仍然不清楚如何识别时区名称以及在模糊标记的情况下哪些具有优先级。

If the token is a text string, match up with possible strings:

Do a binary-search table lookup for the token as a time zone abbreviation.

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


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)