Różnica między znacznikami czasu ze / bez strefy czasowej w PostgreSQL

186

Czy wartości znaczników czasu są przechowywane inaczej w PostgreSQL, gdy typ danych jest WITH TIME ZONEkontra WITHOUT TIME ZONE? Czy różnice można zilustrować za pomocą prostych przypadków testowych?

Larsenal
źródło
3
Ta pokrewna odpowiedź może być pomocna.
Erwin Brandstetter,

Odpowiedzi:

156

Różnice omówiono w dokumentacji PostgreSQL dla typów daty / godziny . Tak, leczenie TIMElub TIMESTAMPróżni się między jednym WITH TIME ZONElubWITHOUT TIME ZONE . Nie wpływa to na sposób przechowywania wartości; wpływa na sposób ich interpretacji.

Wpływ stref czasowych na te typy danych jest szczegółowo opisany w dokumentach. Różnica wynika z tego, co system może rozsądnie wiedzieć o wartości:

  • Gdy strefa czasowa jest częścią wartości, wartość może być renderowana jako czas lokalny w kliencie.

  • Bez strefy czasowej jako części wartości oczywistą domyślną strefą czasową jest UTC, więc jest renderowana dla tej strefy czasowej.

Zachowanie różni się w zależności od co najmniej trzech czynników:

  • Ustawienie strefy czasowej w kliencie.
  • Typ danych (tj. WITH TIME ZONELub WITHOUT TIME ZONE) wartości.
  • Określa, czy wartość jest określona w określonej strefie czasowej.

Oto przykłady obejmujące kombinacje tych czynników:

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)
duży nos
źródło
88
Poprawnie tylko wtedy, gdy odnosi się do procesu wstawiania / pobierania wartości. Ale czytelnicy powinni zrozumieć, że oba typy danych, timestamp with time zonea timestamp without time zonew Postgres zrobić * nie faktycznie strefa czasowa sklep. Możesz to potwierdzić, rzut oka na stronę dokumentu typu danych: Oba typy zajmują tę samą liczbę oktetów i mają zapisany zakres wartości, więc nie ma miejsca na przechowywanie informacji o strefie czasowej. Tekst strony to potwierdza. Coś mylnego: „bez tz” oznacza „ignoruj ​​przesunięcie przy wstawianiu danych”, a „z tz” oznacza „użyj przesunięcia, aby dostosować się do UTC”.
Basil Bourque,
42
Typy danych są mylące z drugiej strony: mówią „strefa czasowa”, ale tak naprawdę mówimy o przesunięciu względem UTC / GMT. Strefa czasowa to tak naprawdę przesunięcie plus zasady / historia dotyczące czasu letniego (DST) i innych anomalii.
Basil Bourque,
4
Wolałbym powiedzieć, że przesunięcie to strefa czasowa plus reguły dla czasu letniego. Nie można odkryć strefy czasowej z przesunięciem, ale można odkryć przesunięcie, biorąc pod uwagę strefę czasową i reguły DST.
igorsantos07
3
Powołując się na oficjalny dokument : Wszystkie daty i godziny uwzględniające strefę czasową są przechowywane wewnętrznie w UTC. Są one konwertowane na czas lokalny w strefie określonej parametrem konfiguracyjnym TimeZone, zanim zostaną wyświetlone klientowi.
Guillaume Husta
2
@ igorsantos07 Strefa czasowa to zestaw reguł / historii dotyczących zmian DST i innych zmian. Twoje sformułowanie wydaje się zbyteczne. A twoje stwierdzenie, że „przesunięcie to strefa czasowa plus reguły dla czasu letniego” jest po prostu błędne: przesunięcie to tylko liczba godzin, minut i sekund - nic więcej, nic mniej.
Basil Bourque,
34

Staram się wyjaśnić to bardziej zrozumiale niż przywołana dokumentacja PostgreSQL.

Żaden TIMESTAMPwariant nie przechowuje strefy czasowej (ani przesunięcia), pomimo sugerowanych nazw. Różnica polega na interpretacji przechowywanych danych (i zamierzonej aplikacji), a nie na samym formacie przechowywania:

  • TIMESTAMP WITHOUT TIME ZONEprzechowuje lokalną datę i godzinę (aka. kalendarz ścienny i zegar ścienny). Jego strefa czasowa jest nieokreślona, ​​o ile PostgreSQL może powiedzieć (chociaż twoja aplikacja może wiedzieć, co to jest). Dlatego PostgreSQL nie dokonuje konwersji związanej ze strefą czasową na wejściu lub wyjściu. Jeśli wartość została wprowadzona do bazy danych jako '2011-07-01 06:30:30', to bez względu na to, w której strefie czasowej wyświetlisz ją później, nadal będzie podawać rok 2011, miesiąc 07, dzień 01, 06 godzin, 30 minut i 30 sekund (w pewnym formacie). Ponadto, każdy przesunięcie strefy czasowej lub podać na wejściu jest ignorowany przez PostgreSQL, tak '2011-07-01 06:30:30+00'i '2011-07-01 06:30:30+05'są takie same jak po prostu '2011-07-01 06:30:30'. Dla programistów Java: jest to analogiczne do java.time.LocalDateTime.

  • TIMESTAMP WITH TIME ZONEzapisuje punkt na linii czasu UTC. To, jak to wygląda (ile godzin, minut itp.) Zależy od twojej strefy czasowej, ale zawsze odnosi się do tej samej „fizycznej” chwili (jak moment rzeczywistego zdarzenia fizycznego). Dane wejściowe są wewnętrznie konwertowane na UTC i tak są przechowywane. W tym celu musi być znane przesunięcie wejścia, więc jeśli dane wejściowe nie zawierają wyraźnego przesunięcia lub strefy czasowej (jak '2011-07-01 06:30:30'), zakłada się, że znajduje się w bieżącej strefie czasowej sesji PostgreSQL, w przeciwnym razie zostanie użyte jawnie określone przesunięcie lub strefa czasowa (jak w '2011-07-01 06:30:30+05'). Dane wyjściowe są wyświetlane przekonwertowane na bieżącą strefę czasową sesji PostgreSQL. Dla programistów Java: Jest to analogiczne do java.time.Instant(z niższą rozdzielczością), ale w JDBC i JPA 2.2 należy go odwzorować java.time.OffsetDateTime( java.util.Datelubjava.sql.Timestamp oczywiście).

Niektórzy twierdzą, że obie TIMESTAMPwersje przechowują datę i godzinę UTC. W pewnym sensie, ale moim zdaniem jest to mylące. TIMESTAMP WITHOUT TIME ZONEjest przechowywany jak TIMESTAMP WITH TIME ZONE, który renderowany ze strefą czasową UTC daje ten sam rok, miesiąc, dzień, godziny, minuty, sekundy i mikrosekundy, jak w lokalnej dacie i czasie. Ale to nie ma oznaczać punktu na linii czasu, który mówi interpretacja UTC, to po prostu sposób, w jaki są zakodowane lokalne pola daty i godziny. (To jakaś grupa kropek na linii czasu, ponieważ strefa czasu rzeczywistego to nie UTC; nie wiemy, co to jest.)

ddekany
źródło
Nie ma nic złego w odzyskaniu TIMESTAMP WITH TIME ZONEjako Instant. Oba stanowią punkt na osi czasu w UTC. Instantjest, moim zdaniem, OffsetDateTimebardziej preferowane niż bardziej samo-dokumentujące: A TIMESTAMP WITH TIME ZONEjest zawsze pobierane z bazy danych jako UTC, i Instantzawsze jest w UTC, więc jest naturalnym dopasowaniem, podczas gdy OffsetDateTimemoże przenosić inne przesunięcia.
Basil Bourque,
@BasilBourque Niestety, obecna specyfikacja JDBC, specyfikacja JPA 2.2, a także dokumentacja PostgreSQL JDBC tylko wspomina OffsetDateTimeo typie zamapowanego Java. Nie jestem pewien, czy Instancejest gdzieś nieoficjalnie obsługiwany.
ddekany
pytanie, mówisz, że dowolne przesunięcie, które określam w danych wejściowych, takie jak '2011-07-01 06:30:30+00'i '2011-07-01 06:30:30+05' jest ignorowane, ale jestem w stanie to zrobić insert into test_table (date) values ('2018-03-24T00:00:00-05:00'::timestamptz);i poprawnie przekonwertuje to na utc. gdzie data jest datownikiem bez strefy czasowej. Próbuję zrozumieć, jaka jest główna wartość znacznika czasu w strefie czasowej i mam problemy.
pk1m
@ pk1m Komplikujesz sprawy ::timestamptz. W ten sposób konwertujesz ciąg znaków TIMESTAMP WITH TIME ZONE, a kiedy będzie on dalej konwertowany WITHOUT TIME ZONE, na którym będzie przechowywany „kalendarz ścienny” dzień i zegar ścienny tej chwili, jak widać ze strefy czasowej sesji (która może być UTC). Nadal będzie to tylko lokalny znacznik czasu z nieokreślonym przesunięciem (bez strefy).
ddekany
Pracuję z pythonem i to jest wstawiane, gdy wstawia obiekt datettime rozpoznający znacznik czasu. Wydaje mi się, że warto używać znacznika czasu ze strefą czasową, ale nie jest to konieczne do obsługi stref czasowych.
pk1m
12

Oto przykład, który powinien pomóc. Jeśli masz znacznik czasu ze strefą czasową, możesz przekonwertować go na dowolną inną strefę czasową. Jeśli nie masz podstawowej strefy czasowej, nie zostanie poprawnie przekonwertowana.

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

Wynik:

-[ 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
serby
źródło
5
Stwierdzenie „nie zostanie poprawnie przekonwertowane” jest po prostu nieprawdą. Musisz zrozumieć, co timestampi timestamptzznaczy. timestamptzoznacza bezwzględny punkt w czasie (UTC), podczas gdy timestampoznacza to, co zegar pokazywał w określonej strefie czasowej. W związku z tym, timestamptzprzechodząc do strefy czasowej, pytasz, co zegar pokazywał w Nowym Jorku w tym absolutnym momencie? podczas gdy „konwertując” a timestamp, pytasz, jaki był absolutny moment w czasie, gdy zegar w Nowym Jorku pokazywał x?
philipipe
AT TIME ZONEKonstrukt to łamigłówka własną rękę, nawet jeśli już zrozumieć WITHporównaniu WITHOUT TIME ZONEtypy. To ciekawy wybór, aby je wyjaśnić. (: ( AT TIME ZONEkonwertuje WITH TIME ZONEznacznik czasu na WITHOUT TIME ZONEznacznik czasu i odwrotnie ... nie do końca oczywiste.)
ddekany
now()::timestamp AT TIME ZONE 'CST'nie ma sensu, chyba że co, w jakim momencie zegar dla strefy „CST” wskazywałby czas, który aktualnie pokazuje twój lokalny zegar
Jasen