Как выполнить преобразование TZ на уровне строк в UTC с помощью Postgres? - PullRequest
0 голосов
/ 18 июня 2019

Во-первых, это , а не дубликат вопроса о преобразовании UTC в TZ .Это не тот ответ, который я ищу.Я также не ищу ответы о времени смещения .

  • TZ до UTC
  • НЕ UTCв TZ
  • NOT смещения

Вариант использования: международный "Cron Jobs"

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

По сути, у меня есть такая таблица:

 name |      timezone       | usertime
------+---------------------+----------
 Joe  | America/New_York    | 02:00:00
 Jane | America/Chicago     | 02:00:00
 John | America/Denver      | 02:00:00
 Jess | America/Phoenix     | 02:00:00
 Jack | America/Los_Angeles | 02:00:00
 Ping | Asia/Shanghai       | 02:00:00

И я хочу задать вопрос::

Если я посчитаю с какого-либо дня UTC now(), сколько времени было (или будет) usertime в UTC?

0.WITH TIME ZONE => НЕПРАВИЛЬНО!

Тип time бесполезен.Документация postgres даже предупреждает об этом, обвиняя стандарт SQL, но он реализован для полноты.

timestamp лучше, но ...

  • WITHOUT TIME ZONE на самом деле применяет применение локальной системы. tz
  • WITH TIME ZONE требует часовой пояс, но сохраняет только смещение

Кажется, что лучше всего использовать текстовые типы.

1.AT TIME ZONE => НЕПРАВИЛЬНО!

Это преобразование полностью в обратном направлении.Прежде всего, он отображает все относительно местного системного времени, независимо от установленного часового пояса.Он также считает в обратном направлении (по местному времени), а не вперед (в установленный часовой пояс).

SELECT
  name,
  timezone,
  usertime,
  usertime::time AT TIME ZONE timezone AS time
FROM demo;
 name |      timezone       | usertime |    time
------+---------------------+----------+-------------
 Joe  | America/New_York    | 02:00:00 | 04:00:00-04
 Jane | America/Chicago     | 02:00:00 | 03:00:00-05
 John | America/Denver      | 02:00:00 | 02:00:00-06
 Jess | America/Phoenix     | 02:00:00 | 01:00:00-07
 Jack | America/Los_Angeles | 02:00:00 | 01:00:00-07
 Ping | Asia/Shanghai       | 02:00:00 | 16:00:00+08
(5 rows)

2.Time + Timezone => НЕПРАВИЛЬНО!

Как вы можете видеть здесь, тип time фактически вообще не допускает часовых поясов, только время смещения :

SELECT
  name,
  timezone,
  (usertime || ' ' || timezone)::time AT TIME ZONE 'UTC'
    AS realtime
FROM demo;
ERROR:  invalid input syntax for type time: "02:00:00 America/New_York"

3.timestamp + timezone => WRONG!

Вы бы подумали, что если бы вы явно сконструировали строку с часовым поясом, таким как 1985-10-26 09:00:00 America/Los_Angeles, вы получите правильное время.Нету.Это полностью игнорирует это.

SELECT
  name,
  timezone,
  (current_date || ' ' || usertime || ' ' || timezone)::timestamp,
  usertime
FROM demo;
 name |      timezone       |      timestamp      | usertime
------+---------------------+---------------------+----------
 Joe  | America/New_York    | 2019-06-17 02:00:00 | 02:00:00
 Jane | America/Chicago     | 2019-06-17 02:00:00 | 02:00:00
 John | America/Denver      | 2019-06-17 02:00:00 | 02:00:00
 Jess | America/Phoenix     | 2019-06-17 02:00:00 | 02:00:00
 Jack | America/Los_Angeles | 2019-06-17 02:00:00 | 02:00:00
 Ping | Asia/Shanghai       | 2019-06-17 02:00:00 | 02:00:00
(5 rows)

1 Ответ

0 голосов
/ 18 июня 2019

Уродливое решение: Double Cast

Это неэффективное полное сканирование таблицы , но я думаю, работает правильно.

Вы должны

  1. приведение текущей временной метки к целевому часовому поясу
  2. добавить это к целевому времени ( должно быть в подвыражении)
  3. приведение полного результата в целевой часовой пояс
  4. затем приведите снова к UTC

Это чрезвычайно деликатно . Малейшее изменение (дата / отметка времени, скобки, упорядочение), скорее всего, приведет к тому, что что-то не будет работать.

SELECT
  name,
  timezone,
  usertime,
  ((current_timestamp AT TIME ZONE timezone)::date + usertime)
    AT TIME ZONE timezone
    AT TIME ZONE 'UTC'
    AS realtime
FROM demo;
 name |      timezone       | usertime |      realtime
------+---------------------+----------+---------------------
 Ping | Asia/Shanghai       | 02:00:00 | 2019-06-17 18:00:00
 Joe  | America/New_York    | 02:00:00 | 2019-06-18 06:00:00
 Jane | America/Chicago     | 02:00:00 | 2019-06-18 07:00:00
 John | America/Denver      | 02:00:00 | 2019-06-18 08:00:00
 Jess | America/Phoenix     | 02:00:00 | 2019-06-17 09:00:00
 Jack | America/Los_Angeles | 02:00:00 | 2019-06-17 09:00:00

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

 Jack | America/Los_Angeles | 02:00:00 | 2019-06-17 09:00:00
 Ping | Asia/Shanghai       | 02:00:00 | 2019-06-16 18:00:00

Как исправить переполнение времени

Обновлено : Я не уверен, что 100% часовых поясов фактически покрыты в предыдущем примере. Кажется, что «откат назад» происходит независимо от того, как вы это делаете, просто в разное время для разных зон.

Другой подход - сбросить информацию о дате и перевести текущее время, чтобы получить правильное окно для запроса событий:

SELECT *
FROM (
  SELECT
    name,
    timezone,
    usertime,
    (((current_timestamp AT TIME ZONE timezone)::date + usertime)
      AT TIME ZONE timezone
      AT TIME ZONE 'UTC')::time
      AS utc_time,
    (((current_timestamp AT TIME ZONE timezone)::date + usertime)
      AT TIME ZONE timezone
      AT TIME ZONE 'Indian/Chagos')::time
      AS ctu_time
  FROM demo
  ) as d
WHERE utc_time
  BETWEEN (current_timestamp AT TIME ZONE 'UTC')::time
    AND (current_timestamp AT TIME ZONE 'UTC')::time + INTERVAL '15 MINUTES'
  OR ctu_time
  BETWEEN (current_timestamp AT TIME ZONE 'Indian/Chagos')::time
    AND (current_timestamp AT TIME ZONE 'Indian/Chagos')::time + INTERVAL '15 MINUTES'
;

Проблема в том, что между 23:58 и 00:13 нет чисел, поэтому вам нужно выбрать два часовых пояса для работы.

Более точные, элегантные и / или эффективные решения?

Я бы хотел увидеть решение, которое всегда показывало бы будущую дату / время события, а не прошедшие.

Мне также было бы интересно узнать, есть ли способ запросить что-то вроде WHERE realtime >= now() AND realtime < near_future, не вызывая полное сканирование таблицы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...