In PySpark ist die Umwandlung eines Zeitstempels in ein Datum oft mehr als eine kosmetische Anpassung: Sie entscheidet darüber, wie sauber du gruppierst, filterst und Daten an Datenbanken übergibst. Die kurze Formel pyspark timestamp to date beschreibt genau das Problem, das in ETL- und Analysejobs ständig auftaucht. In diesem Beitrag zeige ich die direktesten Funktionen, den Unterschied zwischen echter Datumsspalte und bloßer Formatierung sowie die Fallen bei Zeitzone, Mitternacht und fehlerhaften Eingaben.
Die wichtigsten Punkte zur Umwandlung von Zeitstempeln in PySpark
-
to_dateundcast("date")liefern echte Spalten vom TypDateType. -
date_formaterzeugt nur Text und ist deshalb keine echte Datumsumwandlung. - Die Session-Zeitzone kann beeinflussen, auf welchen Kalendertag ein Timestamp fällt.
- Für Analysen sind Datumsspalten ideal für Gruppierungen, Filter und Partitionen.
- Ich behalte in der Praxis meist den Roh-Timestamp plus eine abgeleitete Datumsspalte.
Wann sich die Umwandlung wirklich lohnt
Ich wandle einen Zeitstempel nicht einfach nur deshalb um, weil es technisch möglich ist. Sinnvoll wird das Datum erst dann, wenn der Kalendertag die fachliche Einheit ist, also wenn ich Tagesumsätze, Login-Raten, Sensorwerte oder Lieferereignisse auswerte. In diesen Fällen will ich keinen Uhrzeitanteil mit mitschleppen, weil er das Modell unnötig kompliziert macht.
Typische Szenarien sind recht klar:
- tägliche Aggregationen in Dashboards und Reports
- Joins auf Datumsschlüssel, zum Beispiel mit Kalender- oder Feiertagstabellen
- Partitionierung großer Tabellen nach Tag statt nach exakter Uhrzeit
- Datentransfers in relationale Datenbanken, wenn dort ein
DATEstatt einesTIMESTAMPerwartet wird
Wichtig ist die Gegenfrage: Brauchst du später noch die genaue Uhrzeit? Dann konvertiere nicht zerstörerisch, sondern ergänze lieber eine abgeleitete Datumsspalte. Genau das ist der Übergang zur eigentlichen Umsetzung.
Der direkte Weg mit to_date und cast
Für echte Timestamp-Spalten ist der direkte Weg angenehm unspektakulär. Die offizielle Spark-Dokumentation setzt to_date bei fehlendem Format praktisch mit col.cast("date") gleich. Ich nehme in solchen Fällen meist die Variante, die im Team am klarsten lesbar ist.
from pyspark.sql import functions as F
df = df.withColumn("event_date", F.col("event_ts").cast("date"))
# Gleichwertig und oft lesbarer
df = df.withColumn("event_date", F.to_date("event_ts"))Wenn die Quelle keine echte Timestamp-Spalte liefert, sondern einen String mit Datum und Uhrzeit, gebe ich das Muster explizit an. Das ist robuster, weil Spark dann weiß, wie es den Text lesen soll.
df = df.withColumn(
"event_date",
F.to_date("event_ts", "yyyy-MM-dd HH:mm:ss")
)Für eine Datenanalyse ist das die sauberste Linie: erst in einen echten Datentyp überführen, dann weiterrechnen. Sobald du dagegen nur ein hübsches Ausgabeformat brauchst, bist du im nächsten Abschnitt besser aufgehoben.

Welche Methode ich in der Praxis bevorzuge
Die Wahl der Funktion hängt weniger von Geschmack als von der beabsichtigten Semantik ab. Ich trenne sehr bewusst zwischen einem echten Datum, einer formatierten Zeichenkette und einem auf Tagesgrenzen gestutzten Zeitstempel.
| Methode | Ergebnis | Wann ich sie nehme | Grenze |
|---|---|---|---|
col.cast("date") |
DateType |
Wenn die Spalte bereits timestamp- oder datumnah ist und ich kurze Syntax will. | Bei frei gemischten Textformaten fehlt die explizite Kontrolle. |
to_date(col) |
DateType |
Wenn ich die Lesbarkeit priorisiere; für Timestamp-Werte ist es praktisch dasselbe wie der Cast. | Für spezifische Textformate brauche ich das Formatargument. |
to_date(col, "yyyy-MM-dd HH:mm:ss") |
DateType |
Wenn Rohdaten als String mit Zeitanteil ankommen. | Das Muster muss exakt zum Input passen. |
date_format(col, "dd.MM.yyyy") |
StringType |
Für Exporte, Reports oder die Anzeige in deutscher Schreibweise. | Das ist keine echte Datumsspalte mehr. |
date_trunc("day", col) |
TimestampType |
Wenn ich den Tagesbeginn als Timestamp brauche. | Das ist eine Trunkierung, keine Umwandlung in DateType. |
Die einfache Regel dahinter lautet: Für Logik DateType, für Darstellung StringType, für Tagesgrenzen TimestampType. Genau diese Unterscheidung verhindert viele spätere Missverständnisse. Der nächste Punkt ist trotzdem der heikelste, weil er in produktiven Jobs gern übersehen wird: die Zeitzone.
Zeitzone und Mitternacht sind die eigentlichen Stolpersteine
Spark trennt heute klar zwischen TimestampType mit lokaler Session-Zeitzone und TimestampNTZType ohne Zeitzone. Ein Datum hat laut Datentyp-Definition dagegen keine Zeitzone. Das klingt trocken, ist in der Praxis aber entscheidend: Wenn ein Zeitstempel knapp vor oder nach Mitternacht liegt, kann die Session-Zeitzone darüber entscheiden, auf welchen Kalendertag er am Ende fällt.
Warum die Session-Zeitzone zählt
Die aktuelle Session-Zeitzone lässt sich mit SET TIME ZONE festlegen. Ich mache das in Jobs bewusst explizit, statt mich auf implizite Server- oder Notebook-Einstellungen zu verlassen. Sonst kann derselbe Datensatz je nach Laufumgebung ein anderes Datum ergeben, was besonders bei grenznahen Uhrzeiten unnötig schwer zu debuggen ist.
Was bei String-Rohdaten schiefgeht
Wenn Rohdaten aus mehreren Systemen kommen, sehe ich oft gemischte Formate und uneinheitliche Zeitzonenangaben. Dann ist das eigentliche Problem nicht die Konvertierung selbst, sondern die fehlende Vorarbeit. Ich prüfe daher zuerst, ob der Input wirklich einheitlich ist, und verarbeite erst danach in einem sauberen Schritt weiter. Ein blindes Umwandeln spart Minuten, verursacht aber später gern Stunden an Fehlersuche.
Lesen Sie auch: Datenerweiterung (Data Augmentation) - Sinnvoll nutzen & Fehler vermeiden
Wie ich das praktisch absichere
- Ich setze die Session-Zeitzone in produktiven Jobs fest, oft auf
UTC. - Ich teste Grenzwerte rund um Mitternacht, nicht nur „normale“ Tageszeiten.
- Ich halte den Original-Timestamp zusätzlich vor, wenn spätere Detailanalysen möglich sind.
- Ich nutze
TimestampNTZTypenur dann bewusst, wenn mir die Zeitzonenlogik wirklich erspart bleiben soll.
Damit ist die Konvertierung nicht nur syntaktisch korrekt, sondern auch fachlich stabil. Im nächsten Schritt geht es darum, wie ich die Spalte in Analyse- und Datenbankpipelines nutze, ohne mir an anderer Stelle neue Probleme einzuhandeln.
So setze ich das in Analyse- und Datenbankpipelines ein
Der häufigste praktische Fall ist die Tagesaggregation. Ich bilde aus dem Zeitstempel eine eigene Datumsspalte und verwende genau diese für groupBy, Filter und Joins. Das macht die Logik leichter lesbar und in vielen Fällen auch besser wartbar.
from pyspark.sql import functions as F
daily = df.withColumn("event_date", F.to_date("event_ts"))
daily_counts = (
daily.groupBy("event_date")
.agg(F.count("*").alias("events"))
)Wenn ich in eine Datenbank oder ein Data-Lake-Format schreibe, entscheide ich nach Zweck. Für reine Fachlogik ist eine echte DateType-Spalte besser als ein formatierter Text. Für BI-Tools und Exporte kann zusätzlich eine Anzeige-Spalte sinnvoll sein, zum Beispiel im deutschen Format dd.MM.yyyy. Ich würde diese Darstellung aber nie mit der eigentlichen Datenhaltung verwechseln.
daily.write.partitionBy("event_date").mode("overwrite").parquet("/data/events")Bei großen Faktentabellen kann eine Partitionierung nach Datum nützlich sein, weil Tagesfilter dann direkter greifen. Die Kehrseite ist klar: Zu viele Partitionen oder zu feine Zeitauflösung machen das Layout schnell unübersichtlich. Darum lohnt sich die Datumsspalte vor allem dann, wenn du wirklich auf Tagesebene arbeitest.
Für Joins mit Kalender-, Feiertags- oder Reporting-Tabellen ist die abgeleitete Datumsspalte ebenfalls die sauberere Wahl. Dann bleibt der Rohwert für spätere Detailfragen erhalten, während die Analyse auf dem stabilen Tagesbezug basiert.
Was ich für robuste PySpark-Jobs konsequent beibehalte
Wenn ich solche Pipelines baue, halte ich mich an vier einfache Regeln: erstens echte Datumsspalten für Logik, zweitens Formatierung nur für Anzeige, drittens eine fest gesetzte Session-Zeitzone und viertens den Original-Timestamp immer dann, wenn spätere Detailanalysen realistisch sind. Genau diese Trennung verhindert, dass aus einer kleinen Umwandlung ein schwer nachvollziehbarer Datenfehler wird.
Für mich ist das die praktikabelste Antwort auf das Thema: Nicht jedes Timestamp-Feld muss verschwinden, aber jedes Feld braucht einen klaren Zweck. Wenn du den Kalendertag als fachliche Einheit brauchst, nimm to_date oder cast("date"); wenn du nur ein Anzeigeformat willst, formatiere explizit; und wenn du genaue Zeitbezüge später noch brauchst, bewahre die Rohspalte mit auf.