Подтвердить что ты не робот

Получите "время с часовым поясом" с "время без часового пояса" и имя часового пояса

Во-первых, я понимаю, что time with time zone не рекомендуется. Я собираюсь использовать его, потому что сравниваю несколько значений time with time zone с текущим системным временем, независимо от дня. То есть пользователь начинает каждый день в 08:00 и заканчивается в 12:00 с часовым поясом THEIR, а не системным часовым поясом. Таким образом, у меня есть столбец time without time zone в одной таблице, позвоните ему SCHEDULES.time, и у меня есть столбец UNIX time zone в другой таблице, позвоните ему 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, я пытаюсь выполнить SELECT a time with time zone, объединяя значения из имени часового пояса time without time zone и text.

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

ОБНОВЛЕНИЕ 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 в мое представление.

4b9b3361

Ответ 1

ПРЕДУПРЕЖДЕНИЕ: Новичок PostgreSQL (см. комментарии к вопросу!). Я немного знаю о часовых поясах, поэтому я знаю, что имеет смысл спросить.

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

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

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

В моей системе, установив часовой пояс системы в America/Regina, запрос

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, либо (во время запроса) объединить время из поля с датой, чтобы создать временную метку без часового пояса?

EDIT: Если вы довольны "текущей датой", похоже, вы можете просто изменить свой запрос на:

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 встречается дважды?

Ответ 2

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

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 для оптимальной производительности.


Решение FAST

виновник, который замедляет вас, pg_timezone_names. После некоторого тестирования я обнаружил, что pg_timezone_abbrevs намного превосходит. Разумеется, для достижения этой цели вам нужно сохранить правильные сокращения сокращений часовых поясов вместо имен часовых поясов. Названия часовых поясов учитывают DST автоматически, аббревиатуры часовых поясов - это в основном просто коды для временного смещения. Документация:

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

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

SELECT * FROM  pg_timezone_names;

Общее время выполнения: 541,007 ms

SELECT * FROM pg_timezone_abbrevs;

Общая продолжительность выполнения: 0,523 ms

Фактор 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;