Skip to content
Terug naar blog
Tutorials

PostgreSQL timestamp vs timestamptz: wat staat er intern opgeslagen?

Hoe PostgreSQL timestamp vs timestamptz intern opslaat, waarom tijdzones voor problemen zorgen, en hoe je het juiste type kiest voor je gebruik.

6 min leestijd

PostgreSQL timestamp vs timestamptz: wat wordt er intern opgeslagen?

PostgreSQL beheert zowel timestamp als timestamptz als één 64-bit integer: het aantal microseconden sinds 1970-01-01 00:00:00 UTC. Het verschil treedt pas op bij het formatteren van data voor menselijke weergave.

Waarom leidt dit tot verwarring?

  • Twee kolommen, één datum… twee verschillende query-resultaten
  • Je app voert 2025-07-29 10:00 in, maar een ander team ziet 02:00
  • De frontend toont een ISO-string die niet overeenkomt met de backend-log

Twee potten perziken: één puur, één met etiket

GegevenstypeOfficiële naamOpgeslagen waardeWat er bij SELECT gebeurt
timestamptimestamp without time zoneruwe microsecondetellerOngewijzigd teruggestuurd — Postgres raadt nooit een tijdzone
timestamptztimestamp with time zonedezelfde microsecondetellerPostgres past de sessie-instelling TimeZone toe vlak voor het verzenden van de tekst

Analogie

  • timestamp = een pot perziken zonder herkomstetiket. Je weet dat het fruit is, maar niet waar het is ingemaakt.
  • timestamptz = een pot met het opschrift “Gemaakt in UTC+8.” Iedereen die hem opent kan beslissen of de voedingswaarden moeten worden omgerekend.

Onder de motorkap: gewoon een groot getal

2000-01-01 00:00:00 UTC  → 0
2000-01-01 00:00:01 UTC  → 1 000 000
  • Eenheid: microseconden (één miljoenste seconde)
  • Bereik: 4713 v.Chr. – 294276 n.Chr. — Indiana Jones zou het goedkeuren
  • Opslag voor timestamp en timestamptz is identiek; de interpretatie verschilt

Een demo van 15 seconden

-- Client denkt in Shanghai-tijd
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');
QueryResultaatWaarom
SELECT created_ts FROM demo;2025-07-29 10:00:00Ruwe waarde, geen tijdzoneberekening
SELECT created_tz FROM demo;2025-07-29 10:00:00+08Tag toegepast op uitvoer
SET TimeZone = 'UTC'; daarna selecteren2025-07-29 02:00:00+00Zelfde moment, andere lens

Timestamp-rekenkunde en intervallen

Een van de meest praktische aspecten van PostgreSQL-timestamps is rekenen met intervallen. Omdat beide typen microsecondetellers opslaan, kun je intervallen direct optellen en aftrekken:

-- Voeg 3 uur en 30 minuten toe
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08

-- Bereken het verschil tussen twee timestamps
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00  (een interval)

-- Haal specifieke velden op
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800  (Unix-timestamp in seconden)

-- Afkappen op daggrens (handig voor dagelijkse aggregaties)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08

De functie EXTRACT(EPOCH FROM ...) is bijzonder handig wanneer je timestamps moet doorgeven aan externe systemen die Unix epoch-seconden verwachten. Omgekeerd kun je een epoch ook terugzetten naar een timestamp:

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

Een subtiel maar belangrijk punt: intervalrekenkunde met timestamp (zonder tijdzone) negeert DST-overgangen volledig, terwijl timestamptz er rekening mee houdt. Dit betekent dat het optellen van INTERVAL '1 day' bij een timestamptz-waarde die een DST-grens overschrijdt, correct dezelfde kloktijd retourneert — en niet precies 24 uur later.

Indexering en prestaties

Zowel timestamp als timestamptz worden opgeslagen als 8-byte integers, dus er is geen prestatieverschil tussen de twee voor opslag of indexering. B-tree-indexes werken identiek op beide typen, omdat de onderliggende vergelijking simpelweg een integervergelijking is.

Er zijn echter een paar praktische overwegingen:

  • Bereikquery’s: WHERE created_at > '2025-07-01' werkt efficiënt met een index op beide typen. Bij timestamptz zet PostgreSQL de literal om naar UTC voor de vergelijking, zodat de index nog steeds wordt gebruikt.
  • Partitiesleutels: bij bereikpartitionering op timestamp-kolommen is timestamptz over het algemeen veiliger, omdat partitiegrenswaarden ondubbelzinnig zijn (altijd UTC). Bij timestamp kan een grens zoals '2025-07-01 00:00' voor verschillende sessies een andere betekenis hebben.
  • Functionele indexes: als je regelmatig alleen op datum opvraagt (tijd buiten beschouwing), overweeg dan een index op date_trunc('day', created_at) om dagelijkse aggregatiequery’s te versnellen.

Veelvoorkomende valkuilen en snelle oplossingen

1. Verschillende gebruikers, verschillende klokken

  • Oorzaak: clients gebruiken verschillende TimeZone-instellingen met timestamptz
  • Oplossing: houd alles op timestamp + spreek één zone af, of dwing SET TimeZone = 'UTC' af bij het initialiseren van de verbinding

Een veelgebruikt patroon in applicatiecode is de tijdzone eenmalig instellen bij het aanmaken van de verbindingspool:

-- In je verbindingsinstellingen (bijv. pg pool-configuratie)
SET timezone = 'UTC';

Zo zien alle sessies dezelfde UTC-weergave, en de applicatielaag verzorgt de omzetting naar lokale tijd voor weergave.

2. “Kloktijd” opslaan maar het verkeerde type gekozen

  • Bedrijfskalenders (openingstijden, deadlines) moeten timestamp gebruiken
  • Grensoverschrijdende workflows (bestellingen, logs) moeten UTC opslaan in timestamptz

De test is eenvoudig: als de vraag is “op welk moment in de tijd is dit gebeurd?”, gebruik dan timestamptz. Als de vraag is “wat geeft de klok op de muur aan?”, gebruik dan timestamp.

3. API’s die verschuiven

  • Stuur timestamptz altijd als ISO-8601-strings met de offset (Z of +08:00)
  • Laat de UI lokaal formatteren

4. Timestamps van verschillende typen vergelijken

Het combineren van timestamp en timestamptz in vergelijkingen of joins is een veelvoorkomende bron van subtiele fouten:

-- Gevaarlijk: impliciete cast past de sessietijdzone toe
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- PostgreSQL cast s.start_ts naar timestamptz via de sessietijdzone
-- Verschillende sessies kunnen andere join-resultaten geven!

Oplossing: gebruik altijd een expliciete cast bij vergelijkingen tussen typen, of standaardiseer op één type per domein.

5. ORM-standaardinstellingen als valkuil

Veel ORM’s (Django, SQLAlchemy, ActiveRecord) staan standaard op timestamp zonder tijdzone. Controleer je migratiebestanden — als je app gebruikers in meerdere tijdzones bedient, overschrijf dan de standaard naar timestamptz. Stel in Django USE_TZ = True in de instellingen in. Gebruik in SQLAlchemy DateTime(timezone=True).

Overzicht: welk type gebruik je?

Alleen lokale kalender  → timestamp
Alles globaal           → timestamptz (sla UTC op)
  • Financiële rapporten, lesroosters → timestamp
  • Auditlogs, e-commercebestellingen → timestamptz

Controleer het in seconden met Go Tools

BehoefteToolHoe
Controleer de epoch-waarde uit SQLEpoch ConverterPlak 1690622400, klik op Omrekenen
Bulk-JSON met tijdvelden opruimenJSON FormatterGooi de payload erin, prettify & scan

Alle tools draaien volledig in je browser — je data verlaat nooit je computer.

Veelgestelde vragen

Wat is het verschil tussen timestamp en timestamptz in PostgreSQL?

timestamp (without time zone) slaat een datum-tijdwaarde op zoals hij is, zonder tijdzonecontext. timestamptz (with time zone) zet de invoer om naar UTC voor opslag en zet deze bij opvragen terug naar de tijdzone van de sessie. Gebruik timestamptz voor bijna alle gevallen — dit voorkomt tijdzonegerelateerde fouten in gedistribueerde systemen.

Slaat PostgreSQL de tijdzone daadwerkelijk op in timestamptz?

Nee — ondanks de naam slaat PostgreSQL de tijdzone zelf niet op. Het zet de invoer om naar UTC en slaat alleen de UTC-waarde op (een microsecondeteller vanaf 2000-01-01). Bij opvragen wordt de waarde van UTC omgezet naar de tijdzone die is opgegeven in de sessie-instelling timezone. De oorspronkelijke tijdzone-informatie wordt weggegooid.

Hoe verander ik de tijdzone voor een PostgreSQL-sessie?

Voer SET timezone = 'America/New_York'; uit om de sessietijdzone te wijzigen. Dit heeft invloed op hoe timestamptz-waarden worden weergegeven en geïnterpreteerd. Stel voor serverwide-standaardinstellingen timezone in via postgresql.conf. Gebruik altijd IANA-tijdzonenamen (zoals Asia/Shanghai) in plaats van afkortingen (zoals CST) om dubbelzinnigheid te voorkomen.

Moet ik timestamp of timestamptz gebruiken voor het opslaan van gebeurtenistijden?

Gebruik timestamptz voor bijna alles — gebruikersacties, API-aanroepen, auditlogs en geplande gebeurtenissen. Gebruik timestamp (without timezone) alleen voor abstracte tijden die niet aan een specifiek moment zijn gekoppeld, zoals “winkel opent om 09:00”, wat 9 uur ‘s ochtends in de lokale tijdzone betekent, niet een specifiek UTC-moment.

Hoe gaat PostgreSQL om met zomertijd bij timestamptz?

PostgreSQL verwerkt zomertijd correct bij gebruik van timestamptz, omdat alles intern in UTC wordt opgeslagen. Bij het opvragen zet PostgreSQL de UTC-waarde om via de actuele zomertijdregels voor de sessietijdzone. Dit betekent dat hetzelfde opgeslagen UTC-moment vóór en na een zomertijdovergang correct andere lokale tijden weergeeft.

Voor een uitgebreide gids over Unix-timestamps — inclusief precisieafhandeling, aanbevolen aanpak voor tijdzones en codevoorbeelden in JavaScript, Python en Go — zie onze Unix-timestamp-gids.

Samenvatting

  • Beide Postgres-tijdtypen zijn microsecondetellers; het label is het enige verschil
  • Het verkeerde type kiezen leidt tot verwarrende timestamps en gebroken berekeningen
  • Test, reken om en voer een snelle controle uit met de juiste tools om uren debuggen te besparen

Gerelateerde artikelen

Alle artikelen bekijken