Разница между временными метками с / без часового пояса в PostgreSQL

130

Значения метки времени хранятся по-разному в PostgreSQL, когда тип данных WITH TIME ZONE по сравнению с WITHOUT TIME ZONE? Можно ли проиллюстрировать различия с помощью простых тестовых примеров?

Теги:
types
timezone

4 ответа

103
Лучший ответ

Различия описаны в документации PostgreSQL для типов даты/времени. Да, обработка TIME или TIMESTAMP различается в зависимости от WITH TIME ZONE или WITHOUT TIME ZONE. Это не влияет на то, как хранятся значения; это влияет на то, как они интерпретируются.

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

  • С часовым поясом как частью значения, значение может быть отображено как локальное время в клиенте.

  • Без часового пояса как части значения очевидным часовым поясом по умолчанию является UTC, поэтому он отображается для этого часового пояса.

Поведение отличается в зависимости как минимум от трех факторов:

  • Настройка часового пояса в клиенте.
  • Тип данных (т. WITH TIME ZONE или WITHOUT TIME ZONE) значения.
  • Указано ли значение в определенном часовом поясе.

Вот примеры, охватывающие комбинации этих факторов:

foo=> SET TIMEZONE TO 'Japan';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+09
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 06:00:00+09
(1 row)

foo=> SET TIMEZONE TO 'Australia/Melbourne';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+11
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 08:00:00+11
(1 row)
  • 67
    Исправляйте только если ссылаетесь на процесс вставки / извлечения значений. Но читатели должны понимать, что оба типа данных, timestamp with time zone и timestamp without time zone , в Postgres * фактически не хранят информацию о часовом поясе. Вы можете подтвердить это, заглянув на страницу документа с типом данных: оба типа занимают одинаковое количество октетов и имеют диапазон значений для сохранения, поэтому нет места для хранения информации о часовом поясе. Текст страницы подтверждает это. Что-то неправильно: «без tz» означает «игнорировать смещение при вставке данных», а «с tz» означает «использовать смещение для настройки на UTC».
  • 31
    Типы данных являются неправильным во втором смысле: они говорят «часовой пояс», но на самом деле мы говорим о смещении от UTC / GMT. Часовой пояс на самом деле является смещением плюс правила / история о летнем времени (DST) и других аномалиях.
Показать ещё 5 комментариев
11

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

Ни один из вариантов TIMESTAMP хранит часовой пояс (или смещение), несмотря на то, что предлагают названия. Разница заключается в интерпретации хранимых данных (и в предполагаемом приложении), а не в самом формате хранения:

  • TIMESTAMP WITHOUT TIME ZONE хранит местную дату-время (также известную как дата настенного календаря и время настенных часов). PostgreSQL может определить его часовой пояс, хотя ваше приложение может знать, что это такое. Следовательно, PostgreSQL не выполняет преобразование, связанное с часовым поясом, при вводе или выводе. Если значение было введено в базу данных как '2011-07-01 06:30:30', то не '2011-07-01 06:30:30', в каком часовом поясе вы отобразите его позже, оно все равно будет '2011-07-01 06:30:30' год 2011, месяц 07, день 01, 06 часов, 30 минут и 30 секунд (в каком-то формате). Кроме того, PostgreSQL игнорирует любое смещение или часовой пояс, которые вы указываете во входных данных, поэтому '2011-07-01 06:30:30+00' и '2011-07-01 06:30:30+05' совпадают. а просто '2011-07-01 06:30:30'. Для разработчиков Java: это аналог java.time.LocalDateTime.

  • TIMESTAMP WITH TIME ZONE сохраняет точку на временной шкале UTC. Как это выглядит (сколько часов, минут и т.д.) Зависит от вашего часового пояса, но оно всегда относится к одному и тому же "физическому" моменту (например, моменту реального физического события). Ввод внутренне преобразован в UTC, и то, как он хранится. Для этого необходимо знать смещение входных данных, поэтому, когда вход не содержит явного смещения или часового пояса (например, '2011-07-01 06:30:30'), предполагается, что он находится в текущем часовом поясе PostgreSQL. сеанс, в противном случае используется явно указанное смещение или часовой пояс (как в '2011-07-01 06:30:30+05'). Вывод отображается в преобразованном в текущий часовой пояс сеанса PostgreSQL. Для разработчиков на Java: он аналогичен java.time.Instant (хотя и с более низким разрешением), но с JDBC и JPA 2.2 вы должны отобразить его на java.time.OffsetDateTime (или на java.util.Date или java.sql.Timestamp конечно).

Некоторые говорят, что оба варианта TIMESTAMP хранят дату и время в формате UTC. Вроде, но, по моему мнению, это сбивает с толку. TIMESTAMP WITHOUT TIME ZONE хранится как TIMESTAMP WITH TIME ZONE, которая отображается с часовым поясом UTC и дает тот же год, месяц, день, часы, минуты, секунды и микросекунды, что и в местной дате-времени. Но это не означало представлять точку на временной шкале, о которой говорит интерпретация UTC, это просто способ кодирования локальных полей даты и времени. (Это некоторый кластер точек на временной шкале, поскольку реальный часовой пояс не является UTC; мы не знаем, что это такое.)

  • 0
    Нет ничего плохого в том, чтобы получить TIMESTAMP WITH TIME ZONE за Instant . Оба представляют точку на временной шкале в UTC. Instant , на мой взгляд, предпочтительнее, чем OffsetDateTime поскольку он более самодокументируется: TIMESTAMP WITH TIME ZONE всегда извлекается из базы данных как UTC, а Instant всегда находится в UTC, так что естественное совпадение, тогда как OffsetDateTime может переносить другие смещения.
  • 0
    @BasilBourque К сожалению, текущая спецификация JDBC, спецификация JPA 2.2, а также документация JDBC PostgreSQL упоминают только OffsetDateTime в качестве сопоставленного типа Java. Я не уверен, что Instance до сих пор неофициально поддерживается.
Показать ещё 4 комментария
9

Вот пример, который должен помочь. Если у вас есть временная метка с часовым поясом, вы можете преобразовать ее в любой другой часовой пояс. Если у вас нет базового часового пояса, он не будет правильно преобразован.

SELECT now(),
   now()::timestamp,
   now() AT TIME ZONE 'CST',
   now()::timestamp AT TIME ZONE 'CST'

Выход:

-[ RECORD 1 ]---------------------------
now      | 2018-09-15 17:01:36.399357+03
now      | 2018-09-15 17:01:36.399357
timezone | 2018-09-15 08:01:36.399357
timezone | 2018-09-16 02:01:36.399357+03
  • 4
    Утверждение «не будет преобразовано правильно» просто не соответствует действительности. Вы должны понимать, что означают timestamptz timestamp и timestamptz . timestamptz означает абсолютный момент времени (UTC), тогда как timestamp обозначает то, что часы показывали в определенном часовом поясе. Таким образом, при преобразовании timestamptz в часовой пояс вы спрашиваете, что часы показывали в Нью-Йорке в этот абсолютный момент времени? тогда как при «преобразовании» timestamp вы спрашиваете, какой был абсолютный момент времени, когда часы в Нью-Йорке показывали x?
  • 0
    Конструкция AT TIME ZONE сама по себе представляет собой головоломку, даже если вы уже WITHOUT TIME ZONE типами WITH и WITHOUT TIME ZONE . Так что это любопытный выбор для их объяснения. (: ( AT TIME ZONE преобразует WITH TIME ZONE метку WITH TIME ZONE метку WITHOUT TIME ZONE , и наоборот ... не совсем очевидно.)
0
SELECT MAX('2017-07-06 12:20:48.446+00') - MIN('2017-06-06 12:20:48.446+00') 
as time_to_take  from table_name

Ещё вопросы

Сообщество Overcoder
Наверх
Меню