Skip to content
Zurück zum Blog
Tutorials

Was steckt in einer PostgreSQL-timestamp-Spalte?

Ein verständlicher Leitfaden, wie PostgreSQL timestamp vs timestamptz speichert, warum Zeitzonen zubeißen und wie du den richtigen Typ für deinen Anwendungsfall wählst.

6 Min. Lesezeit

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:00 ein, aber ein anderes Team sieht 02:00
  • Das Frontend rendert einen ISO-String, der nicht zum Backend-Log passt

Zwei Dosen Pfirsiche: Eine ohne, eine mit Etikett

DatentypOffizieller NameGespeicherter WertWas bei SELECT passiert
timestamptimestamp without time zoneroher Mikrosekunden-ZählerWird unverändert zurückgegeben, Postgres errät niemals eine Zeitzone
timestamptztimestamp with time zonegleicher Mikrosekunden-ZählerPostgres 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 timestamp und timestamptz ist 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');
AbfrageErgebnisWarum
SELECT created_ts FROM demo;2025-07-29 10:00:00Rohwert, keine TZ-Berechnung
SELECT created_tz FROM demo;2025-07-29 10:00:00+08Tag bei der Ausgabe angewendet
SET TimeZone = 'UTC'; dann select2025-07-29 02:00:00+00Gleicher 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. Bei timestamptz konvertiert PostgreSQL den Literal vor dem Vergleich zu UTC, sodass der Index weiterhin genutzt wird.
  • Partitionsschlüssel: Bei Range-Partitionierung auf Zeitstempel-Spalten ist timestamptz generell sicherer, da Partitionsgrenzen eindeutig sind (immer UTC). Bei timestamp kö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 mit timestamptz
  • Lösung: Entweder alles als timestamp halten und sich auf eine Zone einigen, oder SET 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 timestamp verwenden
  • Grenzüberschreitende Workflows (Bestellungen, Logs) sollten UTC in timestamptz speichern

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 timestamptz immer als ISO-8601-Strings mit Offset (Z oder +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

BedarfTool
Epoch-Wert aus SQL inspizierenUnix-Zeitstempel-Umrechner
JSON mit Zeitfeldern prüfenJSON 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 timestamptz mit UTC verwenden

Verwandte Artikel

Alle Artikel anzeigen