2016-08-09 5 views
0

Каков наилучший способ добавить указанный интервал к временной отметке с часовым поясом, если я не хочу выполнять вычисления в часовом поясе сервера , Это особенно важно для перехода на летнее время.PostgresSQL: добавление интервала к отметке времени в другом часовом поясе

например. рассмотрим вечер, когда мы «поднимемся вперед». (Здесь, в Торонто, я думаю, что это был 2016-03-13 в 2 часа ночи).
Если взять штамп времени: 2016-03-13 00:00:00-05 и добавить '1 day' к нему, в Канаде/Восточной, я ожидаю получить 2016-03-14 00:00:00-04 -> 1 день позже, но на самом деле только 23 часа Но если добавить 1 день к нему в Saskatchewan (место, которое не использует DST), я бы хотел, чтобы он добавил 24 часа, так что я бы получил 2016-03-13 01:00:00-04.

Если у меня есть столбцы/переменные

t1 timestamp with time zone; 
t2 timestamp with time zone; 
step interval; 
zoneid text; --represents the time zone 

я принципиально хочу сказать

t2 = t1 + step; --in a time zone of my choosing 

Postgres документации, кажется, указывает, что метки времени с часовым поясом внутренне хранятся во времени UTC, который, кажется, указывают на что в столбце timestamptz нет расчёта часового пояса в нем. Стандарт SQL указывает, что Операция datetime + interval должна поддерживать часовой пояс первого операнда.

t2 = (t1 AT TIME ZONE zoneid + step) AT TIME ZONE zoneid; 

, кажется, не работает, потому что первая литая превращает t1 в часовой пояс менее метки времени и, таким образом, не может считаться DST переходы

t2 = t1 + step; 

, кажется, не работает, как это делает операции в часовом поясе моего SQL-сервера

установить временную зону postgres перед операцией и изменить ее после?

Лучшая иллюстрация:

CREATE TABLE timestamps (t1 timestamp with time zone, timelocation text); 
SET Timezone 'America/Toronto'; 
INSERT INTO timestamps(t1, timelocation) VALUES('2016-03-13 00:00:00 America/Toronto', 'America/Toronto'); 
INSERT INTO timestamps(t1, timelocation) VALUES('2016-03-13 00:00:00 America/Regina', 'America/Regina'); 

SELECT t1, timelocation FROM timestamps; -- shows times formatted in Toronto time. OK 
"2016-03-13 00:00:00-05";"America/Toronto" 
"2016-03-13 01:00:00-05";"America/Regina" 

SELECT t1 + '1 day', timelocation FROM timestamps; -- Toronto timestamp has advanced by 23 hours. OK. Regina time stamp has also advanced by 23 hours. NOT OK. 
"2016-03-14 00:00:00-04";"America/Toronto" 
"2016-03-14 01:00:00-04";"America/Regina" 

Как это обойти?

a) Настроить timestamptz на временную метку tz в соответствующем часовом поясе?

SELECT t1 AT TIME ZONE timelocation + '1 day', timelocation FROM timestamps; --OK. Though my results are timestamps without time zone now. 
"2016-03-14 00:00:00";"America/Toronto" 
"2016-03-14 00:00:00";"America/Regina" 

SELECT t1 AT TIME ZONE timelocation + '4 hours', timelocation FROM timestamps; -- NOT OK. I want the Toronto time to be 5am 
"2016-03-13 04:00:00";"America/Toronto" 
"2016-03-13 04:00:00";"America/Regina" 

b) Изменить часовой пояс postgres и продолжить.

SET TIMEZONE = 'America/Regina'; 
SELECT t1 + '1 day', timelocation FROM timestamps; -- Now the Regina time stamp is correct, but toronto time stamp is incorrect (should be 22:00-06) 
"2016-03-13 23:00:00-06";"America/Toronto" 
"2016-03-14 00:00:00-06";"America/Regina" 

SET TIMEZONE = 'America/Toronto'; 
SELECT t1 + '1 day', timelocation FROM timestamps; -- toronto is correct, regina is not, as before 
"2016-03-14 00:00:00-04";"America/Toronto" 
"2016-03-14 01:00:00-04";"America/Regina" 

Это решение будет работать только в том случае, если я постоянно переключаю часовой пояс postgres перед каждым операционным интервалом.

+0

Редактирование: переменные t1 и t2 должны быть записаны как «метка времени с часовым поясом». Извините, я новичок здесь и не могу найти кнопку редактирования. – djmorris

ответ

2

Это сочетание двух свойств, что вызывает проблему:

  1. timestamp with time zone хранится в UTC и не содержит какой-либо информации о часовом поясе. Лучшее имя для него было бы “ UTC timestamp ”.

  2. Добавление timestamp with time zone и interval всегда выполняется в текущем часовом поясе, то есть один набор с параметром конфигурации TimeZone.

Так что вам действительно нужно хранить отметку время и часовой пояс, в котором он действует, вы должны хранить комбинацию timestamp without time zone и text, представляющие часовой пояс.

Как вы правильно заметили, вам нужно было бы переключить текущий часовой пояс, чтобы выполнить добавление интервалов по правилу перехода на летнее время (иначе PostgreSQL не знает, как долго 1 day есть). Но вы не должны делать это вручную, вы можете использовать PL/PgSQL функции, чтобы сделать это для вас:

CREATE OR REPLACE FUNCTION add_in_timezone(
     ts timestamp without time zone, 
     tz text, 
     delta interval 
    ) RETURNS timestamp without time zone 
    LANGUAGE plpgsql IMMUTABLE AS 
$$DECLARE 
    result timestamp without time zone; 
    oldtz text := current_setting('TimeZone'); 
BEGIN 
    PERFORM set_config('TimeZone', tz, true); 
    result := (ts AT TIME ZONE tz) + delta; 
    PERFORM set_config('TimeZone', oldtz, true); 
    RETURN result; 
END;$$; 

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

test=> SELECT add_in_timezone('2016-03-13 00:00:00', 'America/Toronto', '1 day'); 
    add_in_timezone 
--------------------- 
2016-03-14 00:00:00 
(1 row) 

test=> SELECT add_in_timezone('2016-03-13 00:00:00', 'America/Regina', '1 day'); 
    add_in_timezone 
--------------------- 
2016-03-14 00:00:00 
(1 row) 

test=> SELECT add_in_timezone('2016-03-13 00:00:00', 'America/Toronto', '4 hours'); 
    add_in_timezone 
--------------------- 
2016-03-13 05:00:00 
(1 row) 

test=> SELECT add_in_timezone('2016-03-13 00:00:00', 'America/Regina', '4 hours'); 
    add_in_timezone 
--------------------- 
2016-03-13 04:00:00 
(1 row) 

Вы могли бы рассмотреть вопрос о создании комбинированного типа

CREATE TYPE timestampattz AS (
    ts timestamp without time zone, 
    zone text 
); 

и определения операторов и проливает на нее, но это, наверное, главный проект, который превышает то, что вы хотите для этого.

Существует даже расширение PostgreSQL timestampandtz, что делает именно это; возможно, это именно то, что вам нужно (я не смотрел, что такое семантика для добавления).

+0

Спасибо Laurenz. К сожалению, я задал свой вопрос. Эти две переменные - это временная метка с часовым поясом, как вы предложили. Но проблема все еще существует. – djmorris

+0

Докажите, пожалуйста. Как ваш вариант использования отличается от примеров, которые я дал? –

+0

Вместо TIMESTAMPTZ '2016-03-13 00:00:00 CST' вставьте это значение вместе с TIMESTAMPTZ '2016-03-13 00:00:00 America/Toronto' в таблицу со столбцом timestamptz. Затем мне нужен метод, чтобы добавить «1 день» к любому из этих значений либо в «CST», либо в временном интервале «Торонто». – djmorris

Смежные вопросы