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


186

Чи зберігаються значення часових позначок в PostgreSQL по-різному, коли тип даних WITH TIME ZONEпорівнюється WITHOUT TIME ZONE? Чи можна проілюструвати відмінності простими тестовими кейсами?


Відповіді:


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 do * насправді не зберігають інформацію про часовий пояс. Ви можете підтвердити це, поглянувши на сторінку doc типу даних: Обидва типи займають однакову кількість октетів і мають діапазон збереження значень, таким чином, немає місця для зберігання інформації про часовий пояс. Текст сторінки це підтверджує. Щось неправильне: "без Тз" означає "ігнорувати зміщення при вставці даних", а "з тз" означає "використовувати зміщення для налаштування на UTC".
Василь Бурк

42
Типи даних є неправильним по-другому: вони кажуть "часовий пояс", але насправді ми говоримо про зсув від UTC / GMT. Часовий пояс - це фактично зміщення плюс правила / історія щодо літнього часу (DST) та інших аномалій.
Василь Бурк

4
Я б сказав, що зміщення - це часовий пояс плюс правила для DST. Ви не можете виявити часовий пояс із зміщенням, але ви можете виявити зміщення з огляду на часовий пояс та правила DST.
igorsantos07

3
Посилаючись на офіційного документа : Усі дати та часові часові
Гійом Хуста

2
@ igorsantos07 Часовий пояс - це набір правил / історії щодо змін DST та інших змін. Ваше формулювання здається зайвим. А ваше твердження про те, що "зміщення - це часовий пояс плюс правила для 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; ми не знаємо, що це.)


Немає нічого поганого в TIMESTAMP WITH TIME ZONEтому, щоб отримати a як a Instant. Обидва представляють крапку на часовій шкалі в UTC. InstantНа мою думку, OffsetDateTimeце надається перевагу над тим , що це більш самодокументування: A TIMESTAMP WITH TIME ZONEзавжди виводиться з бази даних як UTC, а an Instantзавжди в UTC, так що це природна відповідність, тоді як аніме OffsetDateTimeможе виконувати інші компенсації.
Василь Бурк

@BasilBourque На жаль, поточна специфікація JDBC, специфікація JPA 2.2, а також документація PostgreSQL JDBC згадуються 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ви запитуєте, який був абсолютний момент часу, коли годинник у Нью-Йорку показав х?
fphilipe

AT TIME ZONEКонструкція є мозок тізера самостійно, навіть якщо ви вже розумієте WITHvs. WITHOUT TIME ZONEтипи. Тож цікавий вибір для їх пояснення. (: ( AT TIME ZONEперетворює WITH TIME ZONEчасову WITHOUT TIME ZONE
позначку в часову марку

now()::timestamp AT TIME ZONE 'CST'не має сенсу, якщо тільки ви, що в який момент годинник для зони "CST" показував би час, який показує ваш місцевий годинник,
Ясен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.