Какой тип метки времени выбрать в базе данных PostgreSQL? - PullRequest
116 голосов
/ 27 мая 2011

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

Я могу

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

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

Ответы [ 3 ]

138 голосов
/ 28 мая 2011

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

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

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

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

С точки зрения программирования и согласованности убедитесь, что все расчеты выполнены с использованием UTC в качестве часового пояса. Это не требование PostgreSQL, но оно помогает при интеграции с другими языками программирования или средами. Установка CHECK для столбца, чтобы убедиться, что запись в столбце отметки времени имеет смещение часового пояса 0, является защитной позицией, которая предотвращает несколько классов ошибок (например, скрипт сбрасывает данные в файл и что-то еще сортирует данные времени, используя лексическую сортировку). Опять же, 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 ).


EDIT

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

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

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

Предположим, это 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),Сохранение относительной отметки времени с указанием часового пояса привело бы к правильному графику доставки, поскольку относительная отметка времени защищена от несанкционированного вмешательства Конгресса.Там, где разрыв между использованием относительного и абсолютного времени для планирования событий, является нечеткой линией, но мое эмпирическое правило заключается в том, что при планировании чего-либо в будущем более 3-6 месяцев следует использовать относительные временные метки (запланировано = абсолютное и запланированное =относительный ???).

Другой / последний тип относительного времени - 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)

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

54 голосов
/ 10 июля 2013

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

Дело в том, что «WITH TIME ZONE» и «WITHOUT TIME ZONE» хранят значение как юникс-подобную абсолютную метку времени UTC.Разница заключается в том, как отображается метка времени.Когда «С часовой пояс», то отображаемое значение является сохраненным значением UTC, переведенным в зону пользователя.Когда «БЕЗ часового пояса» сохраненное значение UTC искажается, чтобы показать один и тот же циферблат независимо от того, какую зону пользователь установил ».

Единственная ситуация, когда« БЕЗ часового пояса »можно использовать, это когдазначение циферблата применяется независимо от фактической зоны. Например, когда временная метка указывает, когда кабины для голосования могут закрыться (т. е. они закрываются в 20:00 независимо от часового пояса человека).

Использовать выбор 3. Всегдаиспользуйте «часовой пояс WITH», если нет особых причин не делать этого.

5 голосов
/ 27 мая 2011

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

...