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

186

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

Larsenal
источник
3
Этот связанный ответ может быть полезным.
Эрвин Брандштеттер,

Ответы:

157

Различия описаны в документации 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)
большой нос
источник
88
Исправляйте только если ссылаетесь на процесс вставки / извлечения значений. Но читатели должны понимать, что оба типа данных, timestamp with time zoneа timestamp without time zoneв Postgres * на самом деле не хранят информацию о часовом поясе. Вы можете подтвердить это, заглянув на страницу документа с типом данных: оба типа занимают одинаковое количество октетов и имеют диапазон значений сохранения, поэтому нет места для хранения информации о часовом поясе. Текст страницы подтверждает это. Что-то неправильно: «без tz» означает «игнорировать смещение при вставке данных», а «с tz» означает «использовать смещение для настройки на UTC».
Бэзил Бурк
42
Типы данных являются неправильным во втором смысле: они говорят «часовой пояс», но на самом деле мы говорим о смещении от UTC / GMT. Часовой пояс на самом деле является смещением плюс правила / история о летнем времени (DST) и других аномалиях.
Василий Бурк
4
Я бы скорее сказал, что смещение - это часовой пояс плюс правила для летнего времени. Вы не можете обнаружить часовой пояс с учетом смещения, но вы можете обнаружить смещение с учетом часового пояса и правил летнего времени.
igorsantos07
3
Ссылаясь на официальный документ : все даты и время с
Гийом
2
@ igorsantos07 Часовой пояс - это набор правил / истории изменений DST и других изменений. Ваша формулировка кажется излишней. И ваше утверждение, что «смещение - это часовой пояс плюс правила для летнего времени», просто неверно: смещение - это просто количество часов, минут и секунд - ни больше, ни меньше.
Василий Бурк
34

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

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

  • TIMESTAMP WITHOUT TIME ZONEхранит местное время и дату (ака. дата и время настенного календаря). PostgreSQL может определить его часовой пояс, хотя ваше приложение может знать, что это такое. Следовательно, PostgreSQL не выполняет преобразование, связанное с часовым поясом, при вводе или выводе. Если значение было введено в базу данных как '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хранится как a TIMESTAMP WITH TIME ZONE, который отображается с часовым поясом UTC и дает тот же год, месяц, день, часы, минуты, секунды и микросекунды, что и в местной дате-времени. Но он не предназначен для представления точки на временной шкале, которую говорит интерпретация UTC, это просто способ кодирования локальных полей даты и времени. (Это некоторая группа точек на временной шкале, поскольку реальный часовой пояс не является UTC; мы не знаем, что это такое.)

ddekany
источник
Там нет ничего плохого в получении TIMESTAMP WITH TIME ZONEкак Instant. Оба представляют точку на временной шкале в UTC. Instantна мой взгляд, предпочтительнее, так OffsetDateTimeкак он более самодокументируемый: A TIMESTAMP WITH TIME ZONEвсегда извлекается из базы данных как UTC, а Instantвсегда находится в UTC, так что естественное совпадение, тогда как OffsetDateTimeможет нести другие смещения.
Василий Бурк
@BasilBourque К сожалению, текущая спецификация JDBC, спецификация JPA 2.2, а также документация JDBC PostgreSQL упоминают только OffsetDateTimeкак сопоставленный тип Java. Я не уверен, если Instanceвсе еще неофициально поддерживается где-то.
декабря
вопрос, вы говорите, что любое смещение, которое я указываю во входных данных, например, '2011-07-01 06:30:30+00'и '2011-07-01 06:30:30+05' игнорируется, но я могу это сделать, insert into test_table (date) values ('2018-03-24T00:00:00-05:00'::timestamptz);и оно будет правильно преобразовывать его в utc. где дата является меткой времени без часового пояса. Я пытаюсь понять, какова основная ценность метки времени с часовым поясом и возникают проблемы.
pk1m
@ pk1m Вы усложняете ситуацию с ::timestamptz. При этом вы преобразуете строку в TIMESTAMP WITH TIME ZONE, и когда она будет дополнительно преобразована в WITHOUT TIME ZONE, в ней будут храниться день «настенного календаря» и время настенного времени этого момента, как видно из вашего часового пояса сеанса (который может быть UTC). Это будет только локальная временная метка с неопределенным смещением (без зоны).
Ддеканы
Я работаю с Python, и вот что вставляется при вставке объекта datettime с меткой времени. Мне кажется, есть смысл в использовании метки времени с часовым поясом, но это необязательно для обработки часовых поясов.
pk1m
12

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

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
Сербы
источник
5
Утверждение «не будет преобразовано правильно» просто не соответствует действительности. Вы должны понять, что timestampи timestamptzзначит. timestamptzозначает абсолютный момент времени (UTC), тогда как timestampобозначает то, что часы показывали в определенном часовом поясе. Таким образом, при переходе timestamptzна часовой пояс вы спрашиваете, что часы показывали в Нью-Йорке в этот абсолютный момент времени? тогда как при «преобразовании» a timestampвы спрашиваете, какой был абсолютный момент времени, когда часы в Нью-Йорке показывали x?
fphilipe
AT TIME ZONEКонструкция является мозг тизера самостоятельно, даже если вы уже понимаете WITHvs. WITHOUT TIME ZONEтипы. Так что это любопытный выбор для их объяснения. (: ( AT TIME ZONEпреобразует WITH TIME ZONEметку времени в WITHOUT TIME ZONEметку времени, и наоборот ... не совсем очевидно.)
ddekany
now()::timestamp AT TIME ZONE 'CST'не имеет смысла, если вы не знаете, в какой момент часы для зоны 'CST' будут показывать время, которое показывают ваши локальные часы
Jasen