6

我刚刚花了一个小时对这两个表达式的这些结果的差异感到绝望:


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 手册

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的来源。但显然它存在于我的装置中。对 sqlfiddle的快速测试显示了相同的结果。

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

4

2 回答 2

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

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 个时区缩写,但不明白为什么会有这些。目的是什么?

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

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名称并找到实际未应用的时间偏移量。这是一个不幸的设计——如果不是错误,至少是文档错误

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

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

如果标记是文本字符串,则匹配可能的字符串:

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

嗯,这句话有一个轻微的暗示,缩写是第一位的,但没有确定的。abbrev此外,两个表中都有一列,pg_timezone_names并且pg_timezone_abbrevs...

于 2012-08-20T18:09:47.473 回答
2

时区缩写不包括夏令时 (DST) 转换规则的原因是它们往往暗示一种状态。在美国中西部,我们在冬季使用 CST(中部标准时间),其余时间使用 CDT(中部夏令时间)。有不使用夏令时的异常区域,因此变得复杂。

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';
      时区 | 时区       
---------------------+---------------------
 2012-01-18 00:00:00 | 2012-01-18 00:00:00
(1 行)

test=# SELECT * FROM pg_timezone_names WHERE name IN ('CET', 'Europe/Vienna');
     姓名 | 缩写 | utc_offset | is_dst
---------------+--------+------------+--------
 中考 | CEST | 02:00:00 | 吨
 欧洲/维也纳 | CEST | 02:00:00 | 吨
(2 行)
test=# SELECT * FROM pg_timezone_abbrevs
test-# WHERE abbrev IN ('CEST', 'CET');
 缩写 | utc_offset | is_dst
--------+------------+--------
 CEST | 02:00:00 | 吨
 中考 | 01:00:00 | F
(2 行)

测试=#选择'2012-01-18 1:0 CEST'::timestamptz(0)
测试-#,'2012-01-18 1:0 CET'::timestamptz(0)
test-# ,'2012-01-18 1:0 Europe/Vienna'::timestamptz(0);
      时间戳 | 时间戳 | 时间戳记       
------------------------------------+------------+ ----------------------
 2012-01-17 17:00:00-06 | 2012-01-17 18:00:00-06 | 2012-01-17 18:00:00-06
(1 行)

测试=#选择'2012-08-18 1:0 CEST'::timestamptz(0)
测试-#,'2012-08-18 1:0 CET'::timestamptz(0)
test-# ,'2012-08-18 1:0 Europe/Vienna'::timestamptz(0);
      时间戳 | 时间戳 | 时间戳记       
------------------------------------+------------+ ----------------------
 2012-08-17 18:00:00-05 | 2012-08-17 19:00:00-05 | 2012-08-17 18:00:00-05
(1 行)
于 2012-08-21T12:27:42.890 回答