Skip to content
Powrót do bloga
Poradniki

Co tak naprawdę kryje się w kolumnie typu timestamp w PostgreSQL?

Przystępny przewodnik po tym, jak PostgreSQL przechowuje timestamp i timestamptz, dlaczego strefy czasowe sprawiają kłopoty oraz jak wybrać właściwy typ dla danego zastosowania.

6 min czytania

PostgreSQL timestamp vs timestamptz: co naprawdę jest zapisane pod spodem?

PostgreSQL przechowuje zarówno timestamp, jak i timestamptz jako pojedynczą 64-bitową liczbę całkowitą: liczbę mikrosekund od 1970-01-01 00:00:00 UTC. Różnica ujawnia się dopiero przy formatowaniu danych dla człowieka.

Dlaczego to wszystkich myli?

  • Dwie kolumny, jedna data… i dwa różne wyniki zapytań
  • Aplikacja zapisuje 2025-07-29 10:00, a inny zespół widzi 02:00
  • Frontend wyświetla łańcuch ISO, który nie zgadza się z logiem backendu

Dwie puszki brzoskwiń: jedna bez etykiety, druga z napisem

Typ danychPełna nazwaZapisana wartośćCo dzieje się przy SELECT
timestamptimestamp without time zonesurowa liczba mikrosekundZwracana bez zmian — PostgreSQL nigdy nie zgaduje strefy czasowej
timestamptztimestamp with time zoneta sama liczba mikrosekundPostgreSQL stosuje ustawienie sesji TimeZone tuż przed wysłaniem tekstu

Analogia

  • timestamp = słoik brzoskwiń bez etykiety pochodzenia. Wiadomo, że to owoce, ale nie wiadomo, gdzie zapakowano.
  • timestamptz = słoik z dumnie wybitym napisem „Wyprodukowano w UTC+8”. Każdy, kto go otworzy, sam decyduje, czy przeliczyć tabelę wartości odżywczych.

Pod maską: to po prostu wielka liczba

2000-01-01 00:00:00 UTC  → 0
2000-01-01 00:00:01 UTC  → 1 000 000
  • Jednostka: mikrosekundy (jedna milionowa sekundy)
  • Zakres czasu: 4713 p.n.e. – 294276 n.e. — z aprobatą Indiany Jonesa
  • Sposób przechowywania timestamp i timestamptz jest identyczny; różni się jedynie interpretacja

Demo w 15 sekund

-- Klient myśli czasem szanghajskim
SET TimeZone = 'Asia/Shanghai';

CREATE TABLE demo (
  created_ts timestamp,
  created_tz timestamptz
);

INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
ZapytanieWynikDlaczego
SELECT created_ts FROM demo;2025-07-29 10:00:00Surowa wartość, bez przeliczeń strefy
SELECT created_tz FROM demo;2025-07-29 10:00:00+08Etykieta dodawana przy wyjściu
SET TimeZone = 'UTC'; i ponowny SELECT2025-07-29 02:00:00+00Ten sam moment, inna soczewka

Arytmetyka timestampów i interwały

Jednym z najbardziej praktycznych aspektów timestampów w PostgreSQL jest arytmetyka interwałów. Ponieważ oba typy przechowują liczbę mikrosekund, można bezpośrednio dodawać i odejmować interwały:

-- Dodaj 3 godziny i 30 minut
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08

-- Znajdź różnicę między dwoma timestampami
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00  (interwał)

-- Wyodrębnij konkretne pola
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800  (Unix timestamp w sekundach)

-- Zaokrąglij do granicy doby (przydatne przy agregacjach dziennych)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08

Funkcja EXTRACT(EPOCH FROM ...) jest szczególnie użyteczna, gdy trzeba przekazać timestamp do zewnętrznych systemów oczekujących sekund Unix epoch. Odwrotnie, można przekonwertować epoch z powrotem na timestamp:

SELECT to_timestamp(1753768800);
-- → 2025-07-29 10:00:00+08  (w sesji Asia/Shanghai)

Subtelny, ale ważny szczegół: arytmetyka interwałów na typie timestamp (bez strefy czasowej) całkowicie ignoruje przejścia DST, podczas gdy timestamptz je uwzględnia. Oznacza to, że dodanie INTERVAL '1 day' do wartości timestamptz przekraczającej granicę DST poprawnie zwróci ten sam czas zegarowy — niekoniecznie dokładnie 24 godziny później.

Indeksowanie i wydajność

Zarówno timestamp, jak i timestamptz są przechowywane jako 8-bajtowe liczby całkowite, więc nie ma między nimi różnicy wydajnościowej w zakresie przechowywania ani indeksowania. Indeksy B-tree działają identycznie dla obu typów, ponieważ porównanie sprowadza się do porównania liczb całkowitych.

Warto jednak pamiętać o kilku praktycznych kwestiach:

  • Zapytania zakresowe: WHERE created_at > '2025-07-01' działa wydajnie z indeksem na obu typach. W przypadku timestamptz PostgreSQL konwertuje literał na UTC przed porównaniem, więc indeks nadal jest wykorzystywany.
  • Klucze partycjonowania: przy partycjonowaniu zakresowym po kolumnach timestampowych timestamptz jest zazwyczaj bezpieczniejszy, ponieważ granice partycji są jednoznaczne (zawsze UTC). Przy timestamp granica taka jak '2025-07-01 00:00' może oznaczać różne rzeczy dla różnych sesji.
  • Indeksy funkcyjne: jeśli często zadajesz zapytania tylko po dacie (bez czasu), warto rozważyć indeks na date_trunc('day', created_at), by przyspieszyć dzienne agregacje.

Częste pułapki i szybkie rozwiązania

1. Różni użytkownicy, różne zegary

  • Przyczyna: klienci używają różnych ustawień TimeZone z timestamptz
  • Rozwiązanie: albo trzymaj wszystko w timestamp i ustal jedną strefę, albo wymuszaj SET TimeZone = 'UTC' przy inicjalizacji połączenia

Częsty wzorzec w kodzie aplikacji to ustawianie strefy czasowej raz, przy inicjalizacji puli połączeń:

-- W konfiguracji połączenia (np. konfiguracja puli pg)
SET timezone = 'UTC';

Dzięki temu wszystkie sesje widzą tę samą reprezentację UTC, a warstwa aplikacji odpowiada za konwersję na czas lokalny przy wyświetlaniu.

2. Przechowywanie „czasu zegarowego” w niewłaściwym typie

  • Kalendarze biznesowe (godziny otwarcia, terminy) powinny używać timestamp
  • Procesy międzynarodowe (zamówienia, logi) powinny przechowywać UTC w timestamptz

Test jest prosty: jeśli pytanie brzmi „w jakim momencie to się wydarzyło?”, użyj timestamptz. Jeśli pytanie brzmi „co pokazuje zegar na ścianie?”, użyj timestamp.

3. API, które dryfują

  • Zawsze przesyłaj timestamptz jako łańcuchy ISO-8601 z offsetem (Z lub +08:00)
  • Pozwól, by interfejs użytkownika sformatował je lokalnie

4. Porównywanie timestampów różnych typów

Mieszanie timestamp i timestamptz w porównaniach lub złączeniach to częste źródło subtelnych błędów:

-- Niebezpieczne: niejawna konwersja stosuje strefę czasową sesji
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- PostgreSQL rzutuje s.start_ts na timestamptz, używając strefy czasowej sesji
-- Różne sesje mogą zwracać różne wyniki złączenia!

Rozwiązanie: zawsze rzutuj jawnie przy porównywaniu typów lub ustandaryzuj jeden typ na domenę.

5. Pułapki domyślnych ustawień ORM

Wiele ORM-ów (Django, SQLAlchemy, ActiveRecord) domyślnie używa timestamp bez strefy czasowej. Sprawdź pliki migracji — jeśli aplikacja obsługuje użytkowników w różnych strefach czasowych, nadpisz domyślny typ na timestamptz. W Django ustaw USE_TZ = True w ustawieniach. W SQLAlchemy użyj DateTime(timezone=True).

Ściąga: którego typu użyć?

Tylko kalendarz lokalny → timestamp
Cokolwiek globalnego   → timestamptz (przechowuj UTC)
  • Raporty finansowe, plany zajęć → timestamp
  • Logi audytowe, zamówienia e-commerce → timestamptz

Sprawdź to w kilka sekund z Go Tools

PotrzebaNarzędzieJak
Sprawdzić wartość epoch z SQLKonwerter Unix timestampWklej 1690622400 i kliknij Konwertuj
Uporządkować masowy JSON z polami czasowymiFormatowanie JSONWklej payload, sformatuj i przejrzyj

Wszystkie narzędzia działają w całości w przeglądarce — żadne dane nie opuszczają komputera.

Najczęściej zadawane pytania

Jaka jest różnica między timestamp a timestamptz w PostgreSQL?

timestamp (bez strefy czasowej) przechowuje wartość daty i czasu w niezmienionej postaci, bez kontekstu strefy czasowej. timestamptz (ze strefą czasową) konwertuje dane wejściowe na UTC, by je zapisać, a przy odczycie konwertuje z powrotem na strefę czasową sesji. W niemal wszystkich przypadkach lepiej użyć timestamptz — eliminuje błędy związane ze strefami czasowymi w systemach rozproszonych.

Czy PostgreSQL faktycznie przechowuje strefę czasową w timestamptz?

Nie — wbrew nazwie PostgreSQL nie przechowuje samej strefy czasowej. Konwertuje wartość wejściową na UTC i zapisuje wyłącznie wartość UTC (liczbę mikrosekund od 2000-01-01). Przy odczycie konwertuje z UTC na strefę czasową wskazaną przez ustawienie timezone sesji. Pierwotna informacja o strefie czasowej zostaje odrzucona.

Jak zmienić strefę czasową dla sesji PostgreSQL?

Wykonaj SET timezone = 'America/New_York';, by zmienić strefę czasową sesji. Wpływa to na sposób wyświetlania i interpretowania wartości timestamptz. Aby ustawić wartości domyślne dla całego serwera, ustaw timezone w postgresql.conf. Zawsze używaj nazw stref IANA (np. Asia/Shanghai), a nie skrótów (np. CST), by uniknąć niejednoznaczności.

Czy do zapisywania czasu zdarzeń lepiej użyć timestamp czy timestamptz?

Niemal we wszystkich przypadkach używaj timestamptz — działania użytkowników, wywołania API, logi audytowe, zaplanowane zdarzenia. Typu timestamp (bez strefy czasowej) używaj wyłącznie do abstrakcyjnych godzin niezwiązanych z konkretnym momentem, np. „sklep otwiera się o 09:00” oznacza godzinę 9 rano w lokalnej strefie czasowej, a nie konkretny moment UTC.

Jak PostgreSQL obsługuje czas letni (DST) w timestamptz?

PostgreSQL poprawnie obsługuje DST, gdy używany jest timestamptz, ponieważ wewnętrznie wszystko przechowuje w UTC. Przy odczycie wartości PostgreSQL konwertuje ją z UTC, korzystając z aktualnych reguł DST dla strefy czasowej sesji. Oznacza to, że ten sam zapisany moment UTC poprawnie wyświetla różne czasy lokalne przed zmianą czasu i po niej.

Pełny przewodnik po Unix timestamp — w tym obsługa precyzji, dobre praktyki dla stref czasowych oraz przykłady kodu w JavaScripcie, Pythonie i Go — znajdziesz w naszym przewodniku po Unix timestamp.

Podsumowanie

  • Oba typy czasu w PostgreSQL to liczniki mikrosekund; cała różnica tkwi w etykiecie
  • Wybranie złego typu prowadzi do mylących timestampów i błędnej arytmetyki
  • Testuj, konwertuj i weryfikuj poprawność za pomocą właściwych narzędzi, by zaoszczędzić godziny debugowania

Powiązane artykuły

Zobacz wszystkie artykuły