Названия часовых поясов с одинаковыми свойствами дают разные результаты при применении к метке времени

Я только что провел час в отчаянии с несоответствием в этих результатах этих двух выражений:


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). Вы не можете установить параметры временной зоны или log_timezone в часовом поясе аббревиатура, но вы можете использовать сокращения в значениях ввода даты/времени и с оператором AT TIME ZONE.

Жирный Акцент Мой.

Так почему разница?

Моя настройка (добавлено больше деталей)

  • PostgreSQL 9.1.4 на Debian Squeeze (стандартные сжимание-backports из http://backports.debian.org/debian-backports)

  • Локальный timezone параметр по умолчанию относится к языку системы de_AT.UTF-8, но для примера не имеет значения.

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. Но, очевидно, это есть в моих установках. A быстрый тест на sqlfiddle показывает тот же результат.

Я тестировал на двух разных серверах с аналогичной настройкой. Также с PostgreSQL 8.4. Нашел "CET" как имя часового пояса в pg_timezone_names во всех них.

Ответ 1

Сразу после того, как я разместил это, я запустил еще один запрос, чтобы проверить подозрение:

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...

Ответ 2

Причина, по которой сокращения в часовом поясе не включают переходные правила перехода на летнее время (DST), заключается в том, что они имеют тенденцию подразумевать состояние. Здесь, на Среднем Западе США, мы находимся в КНТ (Центральное стандартное время) в зимние месяцы и на CDT (Central Daylight Time) до конца года. Есть аномальные области, которые не используют DST, поэтому он становится сложным.

PostgreSQL не поддерживает свои собственные данные часового пояса, хотя он упаковывает последние данные часового пояса Олсона в каждом выпуске для тех операционных систем, которые его не предоставляют. Как правило, 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)