Получить «время с часовым поясом» из «время без часового пояса» и название часового пояса - PullRequest
25 голосов
/ 23 ноября 2011

Во-первых, я понимаю, time with time zone не рекомендуется. Я собираюсь использовать его, потому что я сравниваю несколько time with time zone значений с моим текущим системным временем независимо от дня. То есть пользователь говорит, что начинать каждый день в 08:00 и заканчивать в 12:00 с ИХ часовым поясом, а не с системным часовым поясом. Итак, у меня есть столбец time without time zone в одной таблице, назовем его SCHEDULES.time, и у меня есть столбец Имя часового пояса UNIX в другой таблице, назовем его USERS.tz.

Мой системный часовой пояс 'America/Regina', который не использует DST, поэтому смещение всегда равно -06.

Учитывая время '12: 00: 00' и tz 'Америка / Ванкувер', я хотел бы выделить данные в столбец типа time with time zone, но я НЕ хочу преобразовывать время в мое время. зона, потому что пользователь фактически сказал, начинайте в 12:00 в Ванкувере, а не в Регине.

Таким образом, делаем:

SELECT SCHEDULES.time AT TIME ZONE USERS.tz
FROM SCHEDULES JOIN USERS on USERS.ID=SCHEDULES.USERID;

Результаты (на данный момент) в:

'10:00:00-08'

но я очень хочу:

'12:00:00-08'

Я не могу найти никаких документов, касающихся применения часового пояса ко времени, кроме AT TIME ZONE. Есть ли способ сделать это без манипуляций персонажем или других хаков?

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

select ('12:00:00'::text || utc_offset::text)::timetz
from pg_timezone_names
where name = 'America/Vancouver';

Однако это довольно медленно. Должен быть лучший способ, нет?

ОБНОВЛЕНИЕ 2: Я прошу прощения за путаницу. Таблица SCHEDULES НЕ использует time with time zone, я пытаюсь выбрать time with time zone путем объединения значений из time without time zone и text имени часового пояса.

ОБНОВЛЕНИЕ 3: Спасибо всем, кто участвовал в их (горячем) обсуждении. :) Я был убежден отказаться от моего плана использовать time with time zone для моего вывода и вместо этого использовать timestamp with time zone, поскольку он работает хорошо, более читабелен и решает другую проблему, с которой я собирался столкнуться, часовые пояса, которые катиться в новые даты. IE. «2011-11-21 23:59» в «Америка / Ванкувер» означает «2011-11-22» в «Америка / Регина».

ОБНОВЛЕНИЕ 4: Как я уже говорил в своем последнем обновлении, я выбрал ответ, который @ MichaelKrelin-hacker первым предложил, а @JonSkeet окончательно определил. То есть timestamp with time zone как мой окончательный вывод - лучшее решение. Я закончил с помощью запроса, как:

SELECT timezone(USERS.tz, now()::date + SCHEDULES.time)
FROM SCHEDULES
JOIN USERS ON USERS.ID = SCHEDULES.USERID;

Формат timezone() был переписан Postgres после того, как я ввел (current_date + SCHEDULES.time) AT TIME ZONE USERS.tz в свое представление.

Ответы [ 2 ]

12 голосов
/ 23 ноября 2011

ВНИМАНИЕ: Новичок в PostgreSQL (см. Комментарии к вопросу!).Я немного знаю о часовых поясах, поэтому знаю, что имеет смысл смысл спрашивать.

Мне кажется, что это в основном неподдерживаемая ситуация (к сожалению), когда дело доходит до AT TIME ZONE.Глядя на документацию AT TIME ZONE , она дает таблицу, в которой указаны только типы значений «input»:

  • метка времени без часового пояса
  • метка времени с часовым поясом
  • время с часовым поясом

Нам не хватает того, что вам нужно: время без часового пояса.То, что вы спрашиваете, несколько логично, хотя это зависит от даты ... поскольку разные часовые пояса могут иметь разные смещения в зависимости от даты.Например, 12:00:00 Европа / Лондон может означает 12:00:00 UTC или 11:00:00 UTC, в зависимости от того, зима это или лето.

В моей системе, установив системный часовой пояс на Америку / Регину, запрос

SELECT ('2011-11-22T12:00:00'::TIMESTAMP WITHOUT TIME ZONE) 
                               AT TIME ZONE 'America/Vancouver'

дает мне 2011-11-22 14:00:00-06 в результате.Это не идеал , но, по крайней мере, он дает момент времени (я думаю).Я считаю, что если вы загрузите это с клиентской библиотекой - или сравните ее с другой TIMESTAMP WITH TIME ZONE - вы получите правильный результат.Это просто преобразование текста, которое затем использует системный часовой пояс для вывода.

Это будет достаточно для вас?Можно ли изменить поле SCHEDULES.time на поле TIMESTAMP WITHOUT TIME ZONE или (во время запроса) объединить время из поля с датой, чтобы создать метку времени без часового пояса?

РЕДАКТИРОВАТЬ: Если выВы довольны «текущей датой»: она выглядит так, как будто вы можете просто изменить свой запрос на:

SELECT (current_date + SCHEDULES.time) AT TIME ZONE USERS.tz
from SCHEDULES JOIN USERS on USERS.ID=SCHEDULES.USERID

Конечно, текущая системная дата не можетсовпадать с текущей датой в местном часовом поясе думаю это исправит эту часть ...

SELECT ((current_timestamp AT TIME ZONE USERS.tz)::DATE + schedules.time)
       AT TIME ZONE USERS.tz
from SCHEDULES JOIN USERS on USERS.ID=SCHEDULES.USERID

Другими словами:

  • Возьми текущий момент
  • Тренируйсяместная дата / время в часовом поясе пользователя
  • Возьмите дату этого
  • Добавьте время расписания к этой дате, чтобы получить TIMESTAMP WITHOUT TIME ZONE
  • Использование AT TIME ZONEприменить часовой пояс к этой местной дате / времени

Я уверен, что есть лучший способ, но я думаю это имеет смысл.

Вы должныИмейте в виду, что в некоторых случаях это может не сработать:

  • Каким вы хотите, чтобы результат был на время 01:30 в день, когда часы пропускают с 01:00 до 02:00так что 01:30 не происходит вообще?
  • Каким вы хотите, чтобы результат был для времени 01:30 в день, когда часы возвращаются с 02:00 до 01:00,так что 01:30 происходит дважды?
3 голосов
/ 23 ноября 2011

Вот демонстрация того, как рассчитать время без приведения к тексту:

CREATE TEMP TABLE schedule(t time, tz text);
INSERT INTO schedule values
 ('12:00:00', 'America/Vancouver')
,('12:00:00', 'US/Mountain')
,('12:00:00', 'America/Regina');

SELECT s.t AT TIME ZONE s.tz
        - p.utc_offset
        + EXTRACT (timezone from now()) * interval '1s'
FROM   schedule s
JOIN   pg_timezone_names p ON s.tz = p.name;

В основном вам нужно вычесть смещение UTC и добавить смещение вашего местного часового пояса, чтобы прибыть в данный часовой пояс.

Вы можете ускорить вычисления, жестко задав местное смещение.В вашем случае (Америка / Регина) это должно быть:

SELECT s.t AT TIME ZONE s.tz
        - p.utc_offset
        - interval '6h'
FROM   schedule s
JOIN   pg_timezone_names p ON s.tz = p.name;

Поскольку pg_timezone_names является представлением, а не системной таблицей, оно довольно медленное - как продемонстрированный вариант с приведением к текстовому представлению.и обратно.

Я бы сохранял сокращения часовых поясов и принимал двойное приведение через text без объединения в pg_timezone_names для оптимальной производительности.


БЫСТРОЕ решение

Виновник , который замедляет вас pg_timezone_names.После некоторых испытаний я обнаружил, что pg_timezone_abbrevs намного лучше.Конечно, вы должны сохранить правильные сокращения часовых поясов вместо имен часовых поясов, чтобы добиться этого.Часовой пояс Имена автоматически принимают во внимание летнее время, часовой пояс Сокращения в основном просто коды для смещения времени. Документация:

Сокращение часового пояса, например PST.Такая спецификация просто определяет конкретное смещение от UTC, в отличие от полных имен часовых поясов, которые также могут подразумевать набор правил перехода на летнее время.

Посмотрите на эти результаты испытаний илиПопробуйте сами:

SELECT * FROM  pg_timezone_names;

Общее время выполнения: 541.007 мс

SELECT * FROM pg_timezone_abbrevs;

Общее время выполнения: 0.523 мс

Фактор 1000 .Идете ли вы с вашей идеей на приведение к text и обратно к timetz или с моим методом для вычисления времени, не важно.Оба метода очень быстрые.Только не используйте pg_timezone_names.

На самом деле, как только вы сохраните сокращения часовых поясов, вы можете выбрать маршрут кастинга без любых дополнительных объединений.Используйте аббревиатуру вместо utc_offset.Результаты точны согласно вашему определению.

CREATE TEMP TABLE schedule(t time, abbrev text);
INSERT INTO schedule values
 ('12:00:00', 'PST')  -- 'America/Vancouver'
,('12:00:00', 'MST')  -- 'US/Mountain'
,('12:00:00', 'CST'); -- 'America/Regina'

-- calculating
SELECT s.t AT TIME ZONE s.abbrev
     - a.utc_offset
     + EXTRACT (timezone from now()) * interval '1s'
FROM   schedule s
JOIN   pg_timezone_abbrevs a USING (abbrev);

-- casting (even faster!)
SELECT (t::text || abbrev)::timetz
FROM   schedule s;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...