Kluczowe rzeczy do zrozumienia
timestamp without time zone AT TIME ZONE
reinterpretuje timestamp
jako przebywający w tej strefie czasowej w celu przekonwertowania go na UTC .
timestamp with time zone AT TIME ZONE
konwertuje timestamp
do timestamp
w określonej strefie czasowej.
PostgreSQL używa stref czasowych ISO-8601, które określają, że wschód od Greenwich jest dodatni... chyba że używasz specyfikatora strefy czasowej POSIX, w którym to przypadku jest on zgodny z POSIX. Następuje szaleństwo.
Dlaczego pierwszy daje nieoczekiwany wynik
Sygnatury czasowe i strefy czasowe w SQL są okropne. To:
select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';
interpretuje literał nieznanego typu '2011-12-30 00:30:00'
jako timestamp without time zone
, który Pg zakłada, że znajduje się w lokalnej strefie czasowej, chyba że podano inaczej. Gdy używasz AT TIME ZONE
, jest (zgodnie ze specyfikacją) ponownie zinterpretowany jako timestamp with time zone
w strefie czasowej EST5EDT
następnie jest przechowywany jako czas bezwzględny w UTC - więc jest konwertowany z EST5EDT
do UTC, czyli przesunięcie strefy czasowej jest odejmowane . x - (-5)
to x + 5
.
Ten znacznik czasu, dostosowany do przechowywania UTC, jest następnie dostosowywany do Twojego serwera TimeZone
ustawienie wyświetlania, aby było wyświetlane w czasie lokalnym.
Jeśli zamiast tego chcesz powiedzieć „Mam ten znacznik czasu w czasie UTC i chcesz zobaczyć, jaki jest odpowiednik czasu lokalnego w EST5EDT”, jeśli chcesz być niezależny od ustawienia strefy czasowej serwera, musisz napisać coś takiego:
select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
AT TIME ZONE 'EST5EDT';
To mówi „Podany znacznik czasu 2011-12-30 00:30:00, potraktuj go jako znacznik czasu w UTC podczas konwersji na znacznik czasu, a następnie przekonwertuj ten znacznik czasu na czas lokalny w EST5EDT".
Okropne, prawda? Chcę porozmawiać z firmą ktokolwiek zdecydował się na szaloną semantykę AT TIME ZONE
- tak naprawdę powinno to być coś w stylu timestamp CONVERT FROM TIME ZONE '-5'
i timestamptz CONVERT TO TIME ZONE '+5'
. Ponadto timestamp with time zone
powinien faktycznie mieć przy sobie swoją strefę czasową, nie być przechowywany w UTC i automatycznie konwertowany na czas lokalny.
Dlaczego drugi działa (o ile strefa czasowa =UTC)
Twoja oryginalna wersja „działa”:
select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';
będzie poprawne tylko wtedy, gdy strefa czasowa jest ustawiona na UTC, ponieważ rzutowanie tekstu na znacznik czasu zakłada strefę czasową, gdy nie jest ona określona.
Dlaczego trzeci działa
Dwa problemy znoszą się nawzajem.
Druga wersja, która wydaje się działać, jest niezależna od strefy czasowej, ale działa tylko dlatego, że dwa problemy same się znoszą. Po pierwsze, jak wyjaśniono powyżej, timestamp without time zone AT TIME ZONE
reinterpretuje znacznik czasu jako znajdujący się w tej strefie czasowej do konwersji na znacznik czasu UTC; to skutecznie odejmuje przesunięcie strefy czasowej.
Jednak z powodów, których nie znam, PostgreSQL używa znaczników czasu z odwrotnym znakiem do tego, do czego jestem przyzwyczajony w większości miejsc. Zobacz dokumentację:
Inną kwestią, o której należy pamiętać, jest to, że w nazwach stref czasowych POSIX stosuje się dodatnie przesunięcia dla lokalizacji na zachód od Greenwich. Wszędzie indziej PostgreSQL przestrzega konwencji ISO-8601, zgodnie z którą dodatnie przesunięcia stref czasowych znajdują się na wschód od Greenwich.
Oznacza to, że EST5EDT
to to samo co +5
, a nie -5
. Dlatego to działa:ponieważ odejmujesz przesunięcie tz, a nie dodajesz go, ale odejmujesz zanegowane przesunięcie!
To, czego potrzebujesz, aby to poprawić, to:
select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
AT TIME ZONE '+5';