TIMESTAMP WITH TIME ZONE
означает не то, что вы думаете. К сожалению, это не означает «взять эту метку времени и сохранить ее вместе с соответствующим часовым поясом как два отдельных значения в поле». Вместо этого PostgreSQL обрабатывает это как «возьмите эту метку времени, которая, как вы должны предположить, соответствует местному времени, если только у нее нет спецификатора часового пояса, и преобразуйте ее в UTC, затем сохраните ее как UTC. Преобразуйте ее обратно в местное время для отображения».
Информация о часовом поясе фактически используется, а затем отбрасывается при импорте, что делает TIMESTAMP WITH TIME ZONE
ужасным неправильным названием.
Проблема, с которой вы столкнулись, заключается в том, что эпоха для timestamp with time zone
- это эпоха UTC, а не местное время. Поскольку предполагается, что временная метка соответствует местному времени, если вы не укажете спецификатор часового пояса, это означает, что TimeZone влияет на интерпретацию входных данных.
Подробности
Когда вы пишете:
cast('2015/01/07 14:00:00' as timestamp)
или эквивалент для литерала:
TIMESTAMP '2015/01/07 14:00:00'
вы говорите «отметка времени« 2015/01/07 14:00:00 »как точка времени настенных часов без определенного часового пояса». Локальный часовой пояс на это не влияет. Предполагается, что эпоха находится в том же часовом поясе, что и метка времени, какой бы она ни была. Вот почему настройка TimeZone не влияет на него:
regress=# SET TimeZone = 'Australia/Perth';
SET
regress=# SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp));
date_part
------------
1420639200
(1 row)
regress=# SET TimeZone = UTC;
SET
regress=# SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp));
date_part
------------
1420639200
(1 row)
Теперь, когда вы вместо этого используете timestamp with time zone
, вы говорите, что отметка времени указана по местному времени, если не указано иное. Он будет импортирован и преобразован в формат UTC для внутреннего хранения. Затем оно преобразуется обратно в местное время, как определено TimeZone, для отображения/вывода.
Эпоха UTC, а не местное время.
Вот почему это происходит:
regress=# SET TimeZone = 'Australia/Perth';
SET
regress=# SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp with time zone));
date_part
------------
1420610400
(1 row)
regress=# SET TimeZone = UTC;
SET
regress=# SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp with time zone));
date_part
------------
1420639200
(1 row)
Причина, по которой результат extract
отличается, заключается в том, что значение входной метки времени отличается. Это то же значение, но TimeZone учитывается при чтении и загрузке значения. Это имеет больше смысла, если вы посмотрите на это в таблице:
CREATE TABLE myts (ts timestamp without time zone, tstz timestamp with time zone);
SET TimeZone = UTC;
INSERT INTO myts(ts,tstz) VALUES ('2015/01/07 14:00:00','2015/01/07 14:00:00');
SET TimeZone = 'Australia/Perth';
INSERT INTO myts(ts,tstz) VALUES ('2015/01/07 14:00:00','2015/01/07 14:00:00');
теперь посмотрим на содержимое:
regress=# Set TimeZone = UTC;
SET
regress=# SELECT * FROM myts;
ts | tstz
---------------------+------------------------
2015-01-07 14:00:00 | 2015-01-07 14:00:00+00
2015-01-07 14:00:00 | 2015-01-07 06:00:00+00
(2 rows)
и эпохи:
regress=# SELECT extract(epoch from ts) as ets, extract(epoch from tstz) as etstz FROM myts;
ets | etstz
------------+------------
1420639200 | 1420639200
1420639200 | 1420610400
(2 rows)
Как видите, на вещи влияет ввод, а не вывод.
А как насчет явных часовых поясов?
А что, если мы установим явный часовой пояс во входных данных?
SET TimeZone = UTC;
INSERT INTO myts(ts,tstz) VALUES ('2015/01/07 14:00:00 +8','2015/01/07 14:00:00 +8');
Вы увидите, что эффект такой же, как если бы TimeZone был установлен на Australia/Perth
, т. е. локальный параметр TimeZone игнорируется для ввода, поскольку временная метка содержит явный часовой пояс.
Это по-прежнему дает разные эпохи для временной метки с часовым поясом и без него. Спецификатор часового пояса удаляется из поля timestamp
, поскольку он используется для преобразования поля timstamptz
.
(Да, тот факт, что часовые пояса на timestamp
отбрасываются, ужасен. Есть много ужасных вещей о времени SQL.)
Так как же добиться желаемого результата?
Используйте timestamp
, если вам нужно местное время, а не точки всемирного времени.
В качестве альтернативы сообщите PostgreSQL, что вам нужна эпоха timestamptz без преобразования обратно в местное время, то есть в UTC, переинтерпретировав ее как временную метку в UTC с помощью оператора AT TIME ZONE
:
SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp) AT TIME ZONE 'UTC');
Или просто запустите свой сервер с часовым поясом, установленным на UTC
. Честно говоря, это то, что делает большинство людей, поскольку семантика TimeZone
и timestamp
против timestamptz
в большинстве случаев не очень полезна.
почему 8.3 отличается?
Понятия не имею, мне придется копаться в гораздо большем количестве заметок о выпуске и журналах коммитов, чем у меня есть время. Похоже, ввод timstamptz
был изменен, чтобы уважать TimeZone
, но я не знаю, когда и каково было точное обоснование в то время.
person
Craig Ringer
schedule
08.01.2015