Какой тип метки времени я должен выбрать в базе данных PostgreSQL?

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

Я могу

  • выберите TIMESTAMP WITHOUT TIME ZONE и помните, какой часовой пояс использовался во время ввода для этого поля.
  • выберите TIMESTAMP WITHOUT TIME ZONE и добавьте другое поле, которое будет содержать имя часового пояса, которое было использовано во время вставки
  • выберите TIMESTAMP WITH TIME ZONE и установите соответствующие временные метки.

У меня есть небольшое предпочтение для варианта 3 (временная отметка с часовым поясом), но хотелось бы получить образованное мнение по этому вопросу.

Ответ 1

Во-первых, обработка времени и арифметика PostgreSQL - это фантастика, а вариант 3 - в общем случае. Это, однако, неполное представление о времени и времени и может быть дополнено:

  • Сохранять имя часового пояса пользователя в качестве пользовательского предпочтения (например, America/Los_Angeles, а не -0700).
  • Имейте пользовательские события/данные времени, отправленные локально в свою систему координат (скорее всего, это смещение от UTC, например -0700).
  • В приложении преобразуйте время в UTC и сохраните его с помощью столбца TIMESTAMP WITH TIME ZONE.
  • Время возврата запрашивает локальный часовой пояс пользователя (т.е. преобразует от UTC в America/Los_Angeles).
  • Установите для своей базы данных timezone значение UTC.

Этот параметр не всегда работает, потому что трудно получить часовую зону пользователя и, следовательно, совет хеджирования использовать TIMESTAMP WITH TIME ZONE для облегченных приложений. Тем не менее, позвольте мне более подробно объяснить некоторые второстепенные аспекты этого варианта 4.

Как и вариант 3, причина для WITH TIME ZONE заключается в том, что время, в которое произошло что-то, - это момент абсолютный. WITHOUT TIME ZONE дает относительный часовой пояс. Никогда, никогда, никогда не смешивайте абсолютные и относительные TIMESTAMPs.

С точки зрения программирования и согласованности, все расчеты выполняются с использованием UTC в качестве часового пояса. Это не требование PostgreSQL, но оно помогает при интеграции с другими языками программирования или средами. Установка CHECK в столбце, чтобы убедиться, что запись в столбце отметки времени имеет смещение часового пояса 0, является защитной позицией, которая предотвращает несколько классов ошибок (например, script записывает данные в файл и что-то другое сортирует данные времени, используя лексический сорт). Опять же, PostgreSQL не нуждается в этом, чтобы правильно выполнять вычисления даты или конвертировать между часовыми поясами (т.е. PostgreSQL очень умело конвертирует время между любыми двумя произвольными часовыми поясами). Чтобы данные, поступающие в базу данных, сохранялись со смещением нуля:

CREATE TABLE my_tbl (
  my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR:  new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1

Он не идеален на 100%, но он обеспечивает достаточно сильную меру предотвращения взлома, которая гарантирует, что данные уже конвертированы в UTC. Есть много мнений о том, как это сделать, но это кажется лучшим на практике из моего опыта.

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

-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 15:47:58.138995-07
(1 row)

test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:02.235541
(1 row)

Обратите внимание, что AT TIME ZONE 'UTC' разделяет информацию о часовом поясе и создает относительный TIMESTAMP WITHOUT TIME ZONE с использованием целевой системы целей (UTC).

При преобразовании из неполного TIMESTAMP WITHOUT TIME ZONE в TIMESTAMP WITH TIME ZONE отсутствующий часовой пояс наследуется от вашего соединения:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
        -7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
        -7
(1 row)

-- Now change to UTC    
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 22:48:40.540119+00
(1 row)

-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:49.444446
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
         0
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
         0
(1 row)

Нижняя строка:

  • хранить часовой пояс пользователя как именованный ярлык (например, America/Los_Angeles), а не смещение от UTC (например, -0700)
  • используйте UTC для всех, если нет веской причины хранить ненулевое смещение
  • обрабатывать все ненулевые UTC-времена как ошибку ввода
  • никогда не смешивать и сопоставлять относительные и абсолютные отметки времени
  • также используйте UTC как timezone в базе данных, если возможно

Замечание по программному языку Примечание: тип данных Python datetime очень хорош в поддержании различия между абсолютными и относительными временами (хотя сначала разочаровывает, пока вы не дополните его библиотека, например PyTZ).


ИЗМЕНИТЬ

Позвольте мне объяснить разницу между относительным vs абсолютным немного больше.

Абсолютное время используется для записи события. Примеры: "Пользователь 123 вошел в систему" ​​или "церемония окончания обучения начинается в 2011-05-28 2 вечера PST". Независимо от вашего локального часового пояса, если вы можете телепортироваться туда, где произошло событие, вы можете стать свидетелем происходящего. Большинство данных времени в базе данных являются абсолютными (и поэтому должны быть TIMESTAMP WITH TIME ZONE, в идеале с смещением +0 и текстовой меткой, представляющей правила, определяющие конкретный часовой пояс, а не смещение).

Относительным событием было бы записывать или планировать время чего-то с точки зрения еще не определенного часового пояса. Примеры: "наши деловые двери открываются в 8 утра и закрываются в 9 вечера", "пусть каждый понедельник в 7 утра едут на еженедельный завтрак" или "каждый Хэллоуин в 8 вечера". В общем, относительное время используется в шаблоне или factory для событий, а абсолютное время используется практически для всего остального. Существует одно редкое исключение, которое стоит указать, что должно иллюстрировать значение относительных времен. Для будущих событий, которые достаточно далеко в будущем, где может быть неопределенность в отношении абсолютного времени, в котором что-то может произойти, используйте относительную метку времени. Вот пример реального мира:

Предположим, что он 2004 год, и вам нужно запланировать доставку 31 октября 2008 года в 13:00 на западном побережье США (т.е. America/Los_Angeles/PST8PDT). Если вы сохранили это с использованием абсолютного времени с помощью ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE, доставка показала бы в 2 часа дня, потому что правительство США приняло Закон о энергетической политике 2005 года, который изменил правила, регулирующие летнее время. В 2004 году, когда была запланирована поставка, дата 10-31-2008 была бы Стандартным временем Тихого океана (+8000), но начиная с 2005 года в базах данных с часовым поясом было признано, что 10-31-2008 было бы тихоокеанским летним временем (+0700), Сохранение относительной метки времени с часовым поясом привело бы к правильному графику доставки, поскольку относительная временная метка невосприимчива к плохому информированному конгрессу Конгресса. Если обрезание между использованием относительных vs абсолютных времен для планирования вещей - это нечеткая линия, но мое эмпирическое правило заключается в том, что планирование для чего-либо в будущем более чем 3-6mo должно использовать относительные отметки времени (по расписанию = абсолютное vs запланированное = относительный).

Другим/последним типом относительного времени является INTERVAL. Пример: "сеанс истечет через 20 минут после входа пользователя". INTERVAL может использоваться правильно с абсолютными отметками времени (TIMESTAMP WITH TIME ZONE) или относительными отметками времени (TIMESTAMP WITHOUT TIME ZONE). В равной степени правильно сказать: "пользовательский сеанс истекает через 20 минут после успешного входа в систему (login_utc + session_duration)" или "наша утренняя встреча на завтрак может длиться только 60 минут (recurring_start_time + meeting_length)".

Последние бит путаницы: DATE, TIME, TIME WITHOUT TIME ZONE и TIME WITH TIME ZONE - все относительные типы данных. Например: '2011-05-28'::DATE представляет собой относительную дату, поскольку у вас нет информации о часовом поясе, которая может использоваться для идентификации полуночи. Аналогично, '23:23:59'::TIME является относительным, потому что вы не знаете ни часового пояса, ни DATE, представленного временем. Даже с '23:59:59-07'::TIME WITH TIME ZONE вы не знаете, что такое DATE. И, наконец, DATE с часовым поясом на самом деле не является DATE, это TIMESTAMP WITH TIME ZONE:

test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 07:00:00
(1 row)

test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 00:00:00
(1 row)

Ввод даты и часовых поясов в базах данных - это хорошо, но легко получить тонко неправильные результаты. Минимальные дополнительные усилия необходимы для хранения информации о времени правильно и полностью, однако это не означает, что требуется дополнительное усилие.

Ответ 2

Ответ Шона слишком сложный и вводящий в заблуждение.

Дело в том, что как "WITH TIME ZONE", так и "БЕЗ ВРЕМЕННОЙ ЗОНЫ" сохраняют значение как unix-подобную отметку времени UTC. Разница заключается в том, как отображается метка времени. Когда "С часовым поясом", тогда отображаемое значение представляет собой сохраненное значение UTC, переведенное в пользовательскую зону. Когда "БЕЗ ЧАСТОТНОГО ЗОНА" значение сохраненного UTC закручено, чтобы показать ту же самую циферблату независимо от того, в какой зоне пользователь установил ".

Единственная ситуация, когда "БЕЗОПАСНЫЙ часовой пояс" используется, - это когда номинальная стоимость часов применяется независимо от реальной зоны. Например, когда отметка времени указывает, что кабины для голосования могут закрыться (т.е. Закрываются в 20:00 независимо от часового пояса человека).

Использовать выбор 3. Всегда использовать "С часовым поясом", если только не существует особой причины.

Ответ 3

Мое предпочтение относится к варианту 3, так как Postgres может затем выполнять некоторые из этих действий, вычисляя временные метки по отношению к часовому поясу для вас, тогда как с двумя другими вам придется это делать самостоятельно. Дополнительные накладные расходы на хранение временной метки с часовым поясом действительно незначительны, если вы не говорите миллионы записей, и в этом случае у вас, вероятно, уже есть довольно мясистые требования к хранилищу.