Правильно обрабатывать ВРЕМЯ С ВРЕМЕННОЙ ЗОНОЙ в PostgreSQL - PullRequest
0 голосов
/ 10 мая 2018

У нас есть таблица, заполненная данными из устаревшего отчета другой системы. Столбцы этой таблицы отражают ту же структуру отчета.

Вот сокращенная структура таблицы:

CREATE TABLE IF NOT EXISTS LEGACY_TABLE (
  REPORT_DATE DATE NOT NULL,
  EVENT_ID BIGINT PRIMARY KEY NOT NULL,
  START_HOUR TIMESTAMP WITHOUT TIME ZONE,
  END_HOUR TIME WITHOUT TIME ZONE,
  EXPECTED_HOUR TIME WITHOUT TIME ZONE
);

Мы проводим рефакторинг этой таблицы для работы с разными часовыми поясами разных клиентов. Новая структура будет выглядеть примерно так:

CREATE TABLE IF NOT EXISTS LEGACY_TABLE (
  REPORT_DATE DATE NOT NULL,
  EVENT_ID BIGINT PRIMARY KEY NOT NULL,
  START_HOUR TIMESTAMP WITH TIME ZONE,
  END_HOUR TIME WITH TIME ZONE,
  EXPECTED_HOUR TIME WITH TIME ZONE
);

Эти часовые поля представляют определенный момент времени в течение дня, представленный столбцом REPORT_DATE. Под этим я подразумеваю, что каждый столбец TIME представляет момент в течение дня, указанного в REPORT_DATE.

Некоторые другие моменты для рассмотрения:

  • Мы не знаем, почему START_HOUR имеет формат TIMESTAMP в отчете, который мы получаем из прежней системы. Но мы импортируем данные так, как они приходят к нам.
  • Поля в отчете отформатированы в соответствии с часовым поясом клиента, поэтому для рефакторинга этой таблицы нам нужно объединить часовой пояс клиента (у нас есть эта информация), чтобы правильно вставить временные метки / времена в UTC.

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

START_HOUR - END_HOUR (the result of this operation is currently being casted to TIME WITHOUT TIME ZONE)
START_HOUR < END_HOUR
START_HOUR + EXPECTED_HOUR
EXPECTED_HOUR - END_HOUR
EXPECTED_HOUR < '05:00' 

После некоторых исследований я обнаружил, что не рекомендуется использовать тип TIME WITH TIME ZONE ( Время Postgres с равенством часовых поясов ), и теперь я немного озадачен тем, каков наилучший способ рефакторинга этого таблица для работы с разными часовыми поясами и обработки различных операций с колонками, которые нам нужны.

Кроме того, я уже знаю, что безопасно вычесть два столбца типа TIMESTAMP WITH TIME ZONE. Эта операция вычитания учитывает изменения летнего времени ( Вычитание двух столбцов типа timestamp с часовым поясом ), но как насчет других? И тот, который вычитает ВРЕМЯ из TIMESTAMP?.

А что касается рефакторинга таблицы, должны ли мы в любом случае использовать TIME WITH TIME ZONE? Должны ли мы продолжать использовать TIME WITHOUT TIME ZONE? Или лучше вообще забыть тип TIME и объединить ДАТУ с ВРЕМЯ и изменить столбцы на TIMESTAMP WITH TIME ZONE?

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

1 Ответ

0 голосов
/ 11 мая 2018

Вы утверждали, что:

каждый столбец TIME представляет момент в течение дня, указанного в REPORT_DATE.

Таким образом, вы никогда не пересекаете линию даты в пределах одной строки. Я предлагаю сохранить 1x date 3x time и часовой пояс (как столбец text или FK):

CREATE TABLE legacy_table (
   event_id      bigint PRIMARY KEY NOT NULL
 , report_date   date NOT NULL
 , start_hour    time
 , end_hour      time
 , expected_hour time
 , tz            text  -- time zone
);

Как вы уже нашли, timetz (time with time zone) обычно следует избегать . Он не может работать с правилами DST ( d aylight s aving t ime).

Итак, в основном то, что вы уже имели до . Просто отбросьте компонент даты из start_hour, это пустой груз Просто приведите метку времени ко времени, чтобы отрезать дату. Нравится: (timestamp '2018-03-25 1:00')::time

tz может быть любой строкой, принятой конструкцией AT TIME ZONE, но для надежной работы с различными часовыми поясами лучше использовать исключительно имена часовых поясов. Любой name вы найдете в системном каталоге pg_timezone_names.

Для оптимизации хранилища вы можете собрать разрешенные имена часовых поясов в небольшой справочной таблице и заменить tz text на tz_id int REFERENCES my_tz_table.

Два примера строк с DST и без него:

INSERT INTO legacy_table VALUES
   (1, '2018-03-25', '1:00', '3:00', '2:00', 'Europe/Vienna')  -- sadly, with DST
 , (2, '2018-03-25', '1:00', '3:00', '2:00', 'Europe/Moscow'); -- Russians got rid of DST

Для целей представления или расчетов вы можете сделать что-то вроде:

SELECT (report_date + start_hour)    AT TIME ZONE tz AT TIME ZONE 'UTC' AS start_utc
     , (report_date + end_hour)      AT TIME ZONE tz AT TIME ZONE 'UTC' AS end_utc
     , (report_date + expected_hour) AT TIME ZONE tz AT TIME ZONE 'UTC' AS expected_utc
-- START_HOUR - END_HOUR
     , (report_date + start_hour) AT TIME ZONE tz
     - (report_date + end_hour)   AT TIME ZONE tz AS start_minus_end
FROM   legacy_table;

Вы можете создать один или несколько просмотров для быстрого отображения строк при необходимости. Таблица для хранения необходимой вам информации .

Обратите внимание на круглые скобки! В противном случае оператор + будет связываться до AT TIME ZONE из-за приоритета оператора .

И вот результаты:

дБ <> скрипка здесь

Поскольку в Вене манипулируют временем (и везде, где применяются глупые правила перехода на летнее время), вы получаете «удивительные» результаты.

Связанный:

...