PostgreSQL timestamp vs timestamptz: Was wird tatsächlich gespeichert?
PostgreSQL speichert sowohl timestamp als auch timestamptz als einzelne 64-Bit-Ganzzahl: die Anzahl der Mikrosekunden seit 1970-01-01 00:00:00 UTC. Der Unterschied zeigt sich erst bei der Formatierung für die menschliche Ausgabe.
Warum das oft verwirrt
- Zwei Spalten, ein Datum, zwei verschiedene Abfrageergebnisse
- Deine App fügt
2025-07-29 10:00ein, aber ein anderes Team sieht02:00 - Das Frontend rendert einen ISO-String, der nicht zum Backend-Log passt
Zwei Dosen Pfirsiche: Eine ohne, eine mit Etikett
| Datentyp | Offizieller Name | Gespeicherter Wert | Was bei SELECT passiert |
|---|---|---|---|
timestamp | timestamp without time zone | roher Mikrosekunden-Zähler | Wird unverändert zurückgegeben, Postgres errät niemals eine Zeitzone |
timestamptz | timestamp with time zone | gleicher Mikrosekunden-Zähler | Postgres wendet die Session-TimeZone-Einstellung an, kurz bevor der Text gesendet wird |
Analogie
timestamp= ein Glas Pfirsiche ohne Herkunftsetikett. Du weißt, es ist Obst, aber nicht, wo es eingekocht wurde.timestamptz= ein Glas mit dem stolzen Aufdruck „Hergestellt in UTC+8”. Jeder, der es öffnet, kann entscheiden, ob er die Nährwerttabelle umrechnet.
Unter der Haube: Nur eine große Zahl
2000-01-01 00:00:00 UTC → 0
2000-01-01 00:00:01 UTC → 1 000 000
- Einheit: Mikrosekunden (ein Millionstel einer Sekunde)
- Bereich: 4713 v. Chr. bis 294276 n. Chr.
- Die Speicherung für
timestampundtimestamptzist identisch; die Interpretation unterscheidet sich
Eine 15-Sekunden-Demo
-- Client denkt in Shanghai-Zeit
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');
| Abfrage | Ergebnis | Warum |
|---|---|---|
SELECT created_ts FROM demo; | 2025-07-29 10:00:00 | Rohwert, keine TZ-Berechnung |
SELECT created_tz FROM demo; | 2025-07-29 10:00:00+08 | Tag bei der Ausgabe angewendet |
SET TimeZone = 'UTC'; dann select | 2025-07-29 02:00:00+00 | Gleicher Augenblick, neue Linse |
Zeitstempel-Arithmetik und Intervalle
Einer der praktischsten Aspekte von PostgreSQL-Zeitstempeln ist die Intervall-Arithmetik. Da beide Typen Mikrosekunden-Zähler speichern, kannst du Intervalle direkt addieren und subtrahieren:
-- 3 Stunden und 30 Minuten addieren
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08
-- Differenz zwischen zwei Zeitstempeln finden
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00 (ein Intervall)
-- Bestimmte Felder extrahieren
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800 (Unix-Zeitstempel in Sekunden)
-- Auf Tagesgrenze kürzen (nützlich für tägliche Aggregationen)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08
Die Funktion EXTRACT(EPOCH FROM ...) ist besonders nützlich, wenn du Zeitstempel an externe Systeme übergeben musst, die Unix-Epoch-Sekunden erwarten. Umgekehrt kannst du eine Epoch zurück in einen Zeitstempel konvertieren:
SELECT to_timestamp(1753768800);
-- → 2025-07-29 10:00:00+08 (in der Asia/Shanghai-Session)
Ein wichtiger Punkt: Intervall-Arithmetik mit timestamp (ohne Zeitzone) ignoriert DST-Übergänge vollständig, während timestamptz sie respektiert. Das Addieren von INTERVAL '1 day' zu einem timestamptz-Wert, der eine DST-Grenze überquert, gibt die gleiche Uhrzeit zurück, nicht exakt 24 Stunden später.
Indizierung und Performance
Sowohl timestamp als auch timestamptz werden als 8-Byte-Integer gespeichert, daher gibt es keinen Performance-Unterschied zwischen ihnen bei Speicherung oder Indizierung. B-Tree-Indizes funktionieren auf beiden Typen identisch, da der zugrundeliegende Vergleich nur Integer-Vergleich ist.
Es gibt jedoch einige praktische Überlegungen:
- Range-Abfragen:
WHERE created_at > '2025-07-01'funktioniert effizient mit einem Index auf beiden Typen. Beitimestamptzkonvertiert PostgreSQL den Literal vor dem Vergleich zu UTC, sodass der Index weiterhin genutzt wird. - Partitionsschlüssel: Bei Range-Partitionierung auf Zeitstempel-Spalten ist
timestamptzgenerell sicherer, da Partitionsgrenzen eindeutig sind (immer UTC). Beitimestampkönnte eine Grenze wie'2025-07-01 00:00'für verschiedene Sessions Verschiedenes bedeuten. - Funktionale Indizes: Wenn du häufig nur nach Datum abfragst (Zeit ignorierend), erwäge einen Index auf
date_trunc('day', created_at), um tägliche Aggregationsabfragen zu beschleunigen.
Häufige Fallstricke und schnelle Lösungen
1. Verschiedene Nutzer, verschiedene Uhren
- Ursache: Clients verwenden verschiedene
TimeZone-Einstellungen mittimestamptz - Lösung: Entweder alles als
timestamphalten und sich auf eine Zone einigen, oderSET TimeZone = 'UTC'bei Verbindungsinitialisierung erzwingen
Ein häufiges Muster im Anwendungscode ist das einmalige Setzen der Zeitzone bei der Connection-Pool-Initialisierung:
-- In deiner Verbindungseinrichtung (z. B. pg pool config)
SET timezone = 'UTC';
Dies stellt sicher, dass alle Sessions die gleiche UTC-Darstellung sehen, und deine Anwendungsschicht die Konvertierung in lokale Zeit für die Anzeige übernimmt.
2. „Wanduhrzeit” gespeichert, aber falschen Typ gewählt
- Geschäftskalender (Öffnungszeiten, Fristen) sollten
timestampverwenden - Grenzüberschreitende Workflows (Bestellungen, Logs) sollten UTC in
timestamptzspeichern
Der Test ist einfach: Wenn die Frage lautet „In welchem Moment ist das passiert?”, verwende timestamptz. Wenn die Frage lautet „Was zeigt die Uhr an der Wand?”, verwende timestamp.
3. APIs, die abdriften
- Liefere
timestamptzimmer als ISO-8601-Strings mit Offset (Zoder+08:00) - Lass die UI lokal formatieren
4. Zeitstempel verschiedener Typen vergleichen
Das Mischen von timestamp und timestamptz in Vergleichen oder Joins ist eine häufige Quelle subtiler Bugs:
-- Gefährlich: impliziter Cast wendet Session-Zeitzone an
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- PostgreSQL castet s.start_ts zu timestamptz mittels Session-Zeitzone
-- Verschiedene Sessions können verschiedene Join-Ergebnisse bekommen!
Lösung: Beim Vergleichen verschiedener Typen immer explizit casten, oder innerhalb einer Domäne auf einen Typ standardisieren.
5. ORM-Standardfallen
Viele ORMs (Django, SQLAlchemy, ActiveRecord) verwenden standardmäßig timestamp ohne Zeitzone. Prüfe deine Migrationsdateien. Wenn deine App Nutzer über Zeitzonen hinweg bedient, überschreibe den Standard auf timestamptz. In Django setze USE_TZ = True in den Einstellungen. In SQLAlchemy verwende DateTime(timezone=True).
Spickzettel: Welchen Typ soll ich verwenden?
Nur lokaler Kalender → timestamp
Alles Globale → timestamptz (UTC speichern)
- Finanzberichte, Stundenpläne →
timestamp - Audit-Logs, E-Commerce-Bestellungen →
timestamptz
Nützliche Tools
| Bedarf | Tool |
|---|---|
| Epoch-Wert aus SQL inspizieren | Unix-Zeitstempel-Umrechner |
| JSON mit Zeitfeldern prüfen | JSON Formatter |
Häufig gestellte Fragen
Was ist der Unterschied zwischen timestamp und timestamptz in PostgreSQL?
timestamp (ohne Zeitzone) speichert einen Datum-Zeit-Wert wie er ist, ohne Zeitzonen-Kontext. timestamptz (mit Zeitzone) konvertiert die Eingabe bei der Speicherung zu UTC und konvertiert bei der Abfrage zurück in die Zeitzone der Session. Verwende timestamptz für fast alle Fälle, es verhindert zeitzonen-bezogene Bugs in verteilten Systemen.
Speichert PostgreSQL tatsächlich die Zeitzone in timestamptz?
Nein. Trotz des Namens speichert PostgreSQL nicht die Zeitzone selbst. Es konvertiert die Eingabe zu UTC und speichert nur den UTC-Wert (einen Mikrosekunden-Zähler ab 2000-01-01). Bei der Abfrage konvertiert es von UTC in die Zeitzone, die die timezone-Einstellung deiner Session angibt. Die ursprüngliche Zeitzonen-Information wird verworfen.
Wie ändere ich die Zeitzone für eine PostgreSQL-Session?
Führe SET timezone = 'America/New_York'; aus, um die Session-Zeitzone zu ändern. Das beeinflusst, wie timestamptz-Werte angezeigt und interpretiert werden. Für serverweite Standards setze timezone in postgresql.conf. Verwende immer IANA-Zeitzonennamen (wie Asia/Shanghai) statt Abkürzungen (wie CST), um Mehrdeutigkeit zu vermeiden.
Soll ich timestamp oder timestamptz für die Speicherung von Ereigniszeiten verwenden?
Verwende timestamptz für fast alles: Nutzeraktionen, API-Aufrufe, Audit-Logs und geplante Ereignisse. Verwende timestamp (ohne Zeitzone) nur für abstrakte Zeiten wie „Laden öffnet um 09:00”, die 9 Uhr morgens in der lokalen Zeitzone bedeuten, nicht einen bestimmten UTC-Zeitpunkt.
Wie handhabt PostgreSQL die Sommerzeit mit timestamptz?
PostgreSQL handhabt die Sommerzeit mit timestamptz korrekt, da es intern alles in UTC speichert. Wenn du einen Wert abrufst, konvertiert PostgreSQL von UTC unter Verwendung der aktuellen DST-Regeln für deine Session-Zeitzone. Das bedeutet, derselbe gespeicherte UTC-Zeitpunkt zeigt korrekt verschiedene lokale Zeiten vor und nach einem DST-Übergang.
Siehe auch: Unix-Zeitstempel-Leitfaden
Zusammenfassung
- Beide Postgres-Zeittypen sind Mikrosekunden-Zähler; das Etikett ist der ganze Unterschied
- Den falschen zu wählen führt zu verwirrenden Zeitstempeln und kaputten Berechnungen
- Im Zweifelsfall
timestamptzmit UTC verwenden