In PostgreSQL landen Zeitwerte oft zuerst als Text aus CSV-Dateien, Logs oder externen Schnittstellen. Genau an dieser Stelle entscheidet sich, ob die Daten später sauber filterbar, vergleichbar und für Analysen brauchbar bleiben. Ich zeige dir deshalb die praktikabelsten Wege für die Umwandlung von Text in Zeitstempel, wann ein direkter Cast reicht, wann die Formatfunktion besser ist und wie du Zeitzonen sowie fehlerhafte Eingaben sauber behandelst.
Die schnellste Entscheidung hängt fast immer vom Eingabeformat ab
- Standardformate wie ISO 8601 lassen sich oft direkt mit einem Cast in `timestamp` oder `timestamptz` umwandeln.
- Für feste Muster wie `13.06.2026 14:30:00` ist `to_timestamp()` meist die kontrolliertere Lösung.
- `to_timestamp(text, text)` liefert einen `timestamptz`-Wert, deshalb spielt die Zeitzone mit hinein.
- Ambige Texte wie `01/02/2026` sollten vor dem Import normalisiert werden, statt auf implizite Deutung zu hoffen.
- Leere oder gemischte Werte gehören in eine Staging-Schicht, nicht direkt in die Zieltabelle.
Der erste Prüfpunkt ist das Format
Die wichtigste Frage ist nicht, ob PostgreSQL einen Text in einen Zeitwert umwandeln kann, sondern wie der Text aufgebaut ist. Für Standardformate funktioniert ein direkter Cast oft am saubersten, weil die Datenbank die übliche ISO- oder SQL-Schreibweise direkt versteht. Die PostgreSQL-Dokumentation trennt hier bewusst zwischen einfachem Cast und den Formatfunktionen, und genau diese Trennung ist in Projekten meist der beste Anhaltspunkt.
| Eingabe | Geeignete Umwandlung | Warum das passt |
|---|---|---|
2026-06-13 14:30:00 |
::timestamp |
Standardform ohne Zeitzone, direkt lesbar. |
2026-06-13 14:30:00+02 |
::timestamptz |
Die Zeitzone ist bereits im Wert enthalten. |
2026-06-13 |
::date::timestamp |
Für reine Datumsangaben, Zeit wird explizit auf Mitternacht gesetzt. |
1718289000 |
to_timestamp(...::double precision) |
Das ist ein Unix-Zeitwert in Sekunden, kein formatiertes Datum. |
SELECT '2026-06-13 14:30:00'::timestamp;
SELECT '2026-06-13 14:30:00+02'::timestamptz;
SELECT '2026-06-13'::date::timestamp;Ich nutze diesen Weg immer dann, wenn die Daten schon in einer stabilen, standardnahen Form vorliegen. Sobald das Format davon abweicht, braucht es mehr Kontrolle, und genau dort wird die Formatfunktion interessant.
Feste Muster mit to_timestamp() parsen
Wenn ein Textfeld ein festes Muster hat, aber kein Standardformat ist, ist to_timestamp() die verlässlichere Variante. Damit gibst du PostgreSQL explizit vor, welches Teilstück Tag, Monat, Jahr oder Uhrzeit ist. So vermeidest du stille Fehlinterpretationen, etwa bei deutschen Datumsangaben mit Punktnotation. Die PostgreSQL-Dokumentation beschreibt diese Funktion genau für solche Fälle, in denen ein einfacher Cast nicht mehr reicht.
SELECT to_timestamp('13.06.2026 14:30:00', 'DD.MM.YYYY HH24:MI:SS');Die wichtigsten Formatbausteine verwende ich in der Praxis meist sehr sparsam:
-
YYYYfür das Jahr -
MMfür den Monat -
DDfür den Tag -
HH24für die 24-Stunden-Uhrzeit -
MIfür Minuten -
SSfür Sekunden -
MSundUSfür Milli- und Mikrosekunden
Wenn Millisekunden oder Mikrosekunden im String stehen, ist das wichtig, weil PostgreSQL Zeitwerte mit bis zu sechs Nachkommastellen speichern kann. Genau deshalb sollte man die Präzision nicht nebenbei ignorieren, sondern bewusst mitnehmen. Ein sauberer Zeitstempel verliert seine Qualität nicht erst bei falschem Datum, sondern oft schon bei falsch gelesenen Sekundenbruchteilen.
SELECT to_timestamp('13.06.2026 14:30:00.123456', 'DD.MM.YYYY HH24:MI:SS.US');Ein Sonderfall ist ein Unix-Zeitwert im Textfeld. Dann brauchst du kein Datumsformat, sondern die numerische Variante von to_timestamp(). Das ist vor allem bei API-Daten und Logexporten praktisch, wenn die Quelle Sekunden seit dem Unix-Epoch liefert:
SELECT to_timestamp('1718289000'::double precision);
SELECT to_timestamp('1718289000000'::double precision / 1000);Der zweite Ausdruck ist sinnvoll, wenn die Quelle Millisekunden statt Sekunden speichert. Damit ist der Weg für unterschiedlich formatierte Texte klarer, aber die nächste Frage bleibt offen: Ist der Zeitpunkt lokal gemeint oder als absoluter Moment in der Welt? Genau daran scheitern viele saubere Importe.
Zeitzonen und Datumslogik solltest du nicht vermischen
Bei Zeitwerten ist die Semantik genauso wichtig wie die Syntax. timestamp speichert einen Zeitpunkt ohne Zeitzone, timestamptz speichert einen absoluten Moment, den PostgreSQL beim Anzeigen in die Sitzungssitzung beziehungsweise Zeitzone übersetzt. Für Analysen über Länder, Systeme oder Sommerzeitgrenzen hinweg ist das ein entscheidender Unterschied.
| Situation | Meine Empfehlung | Worauf du achten musst |
|---|---|---|
| Lokale Uhrzeit aus Deutschland | Als timestamp parsen und dann explizit mit AT TIME ZONE 'Europe/Berlin' normalisieren |
Die Zeitzone steckt nicht im Text und muss fachlich bekannt sein. |
| API liefert UTC oder Offset mit | Direkt als timestamptz speichern |
So bleiben Vergleiche zwischen Systemen konsistent. |
Format wie 01/02/2026
|
Vorher normalisieren oder DateStyle bewusst setzen |
Monat und Tag können sonst vertauscht werden. |
| Nur Datum ohne Uhrzeit | Als date importieren und Zeit nur dann ergänzen, wenn das fachlich stimmt |
Eine angenommene Uhrzeit ist schnell ein versteckter Fehler. |
SET datestyle = 'ISO, DMY';
SELECT '13/06/2026 14:30:00'::timestamp;
SELECT '2026-06-13 14:30:00'::timestamp AT TIME ZONE 'Europe/Berlin';Die wichtigste Regel ist hier ziemlich schlicht: Die Datenbank kann eine Zeitzone nicht erfinden, wenn sie im Text nicht steckt. Wenn diese Semantik klar ist, bleibt noch der robusteste Teil der Praxis: schlechte Eingaben sauber abfangen.
Fehlerhafte oder gemischte Textwerte robust abfangen
Ein direkter Cast ist schnell, aber er bricht beim ersten fehlerhaften Wert ab. Genau das ist bei Importen oft das Problem: Ein einzelner leerer String, ein Tippfehler oder eine anders formatierte Zeile reicht, und die gesamte Abfrage scheitert. Ich arbeite deshalb gern in zwei Schritten: erst Rohdaten in eine Staging-Tabelle, dann kontrollierte Umwandlung in die Zielspalte.
SELECT NULLIF(trim(raw_value), '')::timestamp
FROM staging_events;Das hilft bei leeren oder nur aus Leerzeichen bestehenden Strings. Für gemischte Formate brauchst du zusätzlich eine Vorprüfung. Ein echtes TRY_CAST im Kern von PostgreSQL gibt es nicht, deshalb ist CASE mit einem groben Muster oft die bessere Wahl:
SELECT CASE
WHEN raw_value ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2} ' THEN raw_value::timestamp
WHEN raw_value ~ '^[0-9]{2}\.[0-9]{2}\.[0-9]{4} ' THEN to_timestamp(raw_value, 'DD.MM.YYYY HH24:MI:SS')
ELSE NULL
END AS event_ts
FROM staging_events;Das ist nicht perfekt, aber in der Praxis deutlich ehrlicher als ein blindes Parsing. Regex-Checks validieren nicht jedes mögliche Detail, sie filtern nur die offensichtlich falschen Formen heraus. Für Analysen ist ein bewusstes NULL meist wertvoller als ein still erfundener Zeitpunkt. Wenn viele Ausreißer vorkommen, lege ich zusätzlich oft eine Reject-Liste an, damit problematische Zeilen separat geprüft werden können.
Gerade bei Imports mit mehreren Quellsystemen lohnt sich diese Disziplin. Sobald du weißt, welche Werte wirklich sauber sind, kannst du die Umwandlung viel gezielter in den Datenfluss einbauen.
Die Umwandlung gehört möglichst früh in den Datenfluss
Für Datenanalyse ist es fast immer besser, den Zeitwert einmal sauber zu normalisieren und dann als echte Zeitspalte weiterzuverwenden. Abfragen auf einer fertigen Timestamp-Spalte sind einfacher zu lesen, leichter zu indexieren und oft deutlich robuster als jede wiederholte Konvertierung im WHERE-Block. Wenn du den Rohtext bei jeder Analyse erneut parsest, bezahlst du die Rechenarbeit jedes Mal wieder.
- Rohtext in eine Staging-Tabelle laden.
- Validierte Zeilen in eine Zielspalte mit
timestampodertimestamptzschreiben. - Ungültige Werte separat protokollieren oder in eine Fehlerliste verschieben.
- Für wiederkehrende Analysen mit der normalisierten Zeitspalte arbeiten, nicht mit dem Ursprungstext.
SELECT *
FROM events
WHERE event_ts >= timestamp '2026-06-01 00:00:00';Das ist der Punkt, an dem sich sauberes Schema-Design bezahlt macht. Eine echte Zeitspalte unterstützt Zeitfenster, Aggregationen, Joins und Sortierungen deutlich besser als ein Textfeld, das ständig on the fly interpretiert werden muss. Wenn ich zwischen Komfort und Klarheit wählen muss, entscheide ich mich hier fast immer für Klarheit, weil sie später weniger technische Schulden erzeugt.
Worauf ich bei echten Projekten konsequent achte
Mein praktischer Standard ist simpel: Standardformate direkt casten, Sonderformate mit to_timestamp() kontrolliert parsen, Zeitzonen nie implizit annehmen und Rohdaten erst nach einer klaren Validierung normalisieren. Genau diese Reihenfolge verhindert die typischen Fehler, die in Importen zunächst harmlos aussehen, aber bei Filterungen, Vergleichen oder Zeitreihenanalysen teuer werden.
- Wenn der Text sauber und standardnah ist, nehme ich den direkten Cast.
- Wenn das Format fest, aber speziell ist, nutze ich die Formatmaske.
- Wenn Zeitzonen relevant sind, speichere ich den fachlich richtigen Typ und rechne nicht stillschweigend um.
- Wenn Daten unsauber sind, trenne ich Import, Bereinigung und Analyse konsequent.
Genau darin liegt die eigentliche Stärke einer guten postgres string to timestamp-Strategie: Sie macht aus unzuverlässigem Text eine belastbare Zeitbasis für Analysen, statt nur einen kurzfristig funktionierenden Import zu erzeugen.