Lerne, wie man Zeit in JavaScript zuverlässig formatiert und konvertiert: Timestamps, ISO-Strings, Zeitzonen, Sommerzeit, Parsing-Regeln und praxiserprobte Muster.

JavaScript-Zeitfehler sehen selten aus wie „die Uhr geht falsch“. Sie treten als verwirrende kleine Verschiebungen auf: ein Datum, das auf deinem Laptop korrekt ist, aber auf dem Rechner eines Kollegen falsch aussieht, eine API-Antwort, die bis zur Darstellung in einer anderen Zeitzone in Ordnung scheint, oder ein Bericht, der um „einen Tag daneben“ liegt, wenn die Jahreszeit gewechselt wird.
Du wirst typischerweise eines (oder mehrere) dieser Probleme bemerken:
+02:00) ist anders als erwartet.Ein großer Pain-Point ist, dass das Wort Zeit verschiedene Konzepte bezeichnen kann:
2025-12-23T10:00:00Z). Das ist meist das, was man für Logging, Events und API-Speicherung möchte.Das eingebaute Date in JavaScript versucht, all das abzudecken, repräsentiert aber primär ein Instant und neigt gleichzeitig zur lokalen Anzeige, wodurch unbeabsichtigte Konversionen leicht passieren.
Dieser Leitfaden ist bewusst praktisch: wie man vorhersehbare Konversionen zwischen Browsern und Servern erreicht, wie man sicherere Formate wählt (z. B. ISO 8601) und wie man die klassischen Fallen erkennt (Sekunden vs Millisekunden, UTC vs Lokal, und Parsing-Unterschiede). Ziel ist nicht mehr Theorie — sondern weniger „warum hat sich das verschoben?“ Überraschungen.
JavaScript-Zeitfehler beginnen oft damit, dass Repräsentationen vermischt werden, die ähnlich aussehen, es aber nicht sind.
1) Epoch-Millisekunden (Zahl)
Eine einfache Zahl wie 1735689600000 ist typischerweise „Millisekunden seit 1970-01-01T00:00:00Z“. Sie repräsentiert ein Instant ohne Formatierung oder Zeitzone.
2) Date-Objekt (Wrapper um ein Instant)
Ein Date speichert denselben Typ Instant wie ein Timestamp. Der verwirrende Teil: wenn du ein Date druckst, formatiert JavaScript es mit den lokalen Regeln deiner Umgebung, sofern du nichts anderes verlangst.
3) Formatierter String (für Menschen)
Strings wie "2025-01-01", "01/01/2025 10:00" oder "2025-01-01T00:00:00Z" sind nicht einheitlich. Einige sind eindeutig (ISO 8601 mit Z), andere hängen von der Locale ab, und einige enthalten gar keine Zeitzonenangabe.
Dasselbe Instant kann je nach Zeitzone unterschiedlich angezeigt werden:
const instant = new Date("2025-01-01T00:00:00Z");
instant.toLocaleString("en-US", { timeZone: "UTC" });
// "1/1/2025, 12:00:00 AM"
instant.toLocaleString("en-US", { timeZone: "America/Los_Angeles" });
// "12/31/2024, 4:00:00 PM" (vorheriger Tag)
Wähle eine einzige interne Repräsentation (häufig Epoch-Millisekunden oder UTC ISO 8601) und bleibe in deiner App und API konsistent dabei. Konvertiere zu/von Date und formatierten Strings nur an den Grenzen: beim Input-Parsen und bei der Anzeige im UI.
Ein „Timestamp" bedeutet normalerweise Epoch-Zeit (auch Unix-Zeit): die Zeit seit 1970-01-01 00:00:00 UTC. Der Haken: verschiedene Systeme zählen in verschiedenen Einheiten.
JavaScript's Date ist die Quelle der meisten Verwirrung, weil es Millisekunden verwendet. Viele APIs, Datenbanken und Logs verwenden Sekunden.
17040672001704067200000Gleicher Moment, aber die Millisekunden-Version hat drei zusätzliche Ziffern.
Verwende explizite Multiplikation/Division, damit die Einheit klar ist:
// seconds -> Date
const seconds = 1704067200;
const d1 = new Date(seconds * 1000);
// milliseconds -> Date
const ms = 1704067200000;
const d2 = new Date(ms);
// Date -> seconds
const secondsOut = Math.floor(d2.getTime() / 1000);
// Date -> milliseconds
const msOut = d2.getTime();
Date() übergebenDas sieht plausibel aus, ist aber falsch, wenn ts in Sekunden ist:
const ts = 1704067200; // seconds
const d = new Date(ts); // WRONG: treated as milliseconds
Das Ergebnis wird ein Datum in 1970 sein, weil 1.704.067.200 Millisekunden nur etwa 19 Tage nach der Epoch darstellen.
Wenn du dir nicht sicher bist, welche Einheit du hast, füge schnelle Guardrails hinzu:
function asDateFromUnknownEpoch(x) {
// crude heuristic: seconds are ~1e9-1e10, milliseconds are ~1e12-1e13
if (x < 1e11) return new Date(x * 1000); // assume seconds
return new Date(x); // assume milliseconds
}
const input = Number(valueFromApi);
console.log({ input, digits: String(Math.trunc(input)).length });
console.log('as ISO:', asDateFromUnknownEpoch(input).toISOString());
Wenn die „digits“-Zahl ~10 ist, sind es wahrscheinlich Sekunden. Wenn sie ~13 ist, sind es Millisekunden. Gib während des Debuggings toISOString() aus: das ist eindeutig und hilft, Einheit-Fehler sofort zu erkennen.
JavaScript's Date kann verwirrend sein, weil es intern einen einzelnen Instant speichert, aber diesen Instant in unterschiedlichen Zeitzonen darstellen kann.
Intern ist ein Date im Wesentlichen „Millisekunden seit der Unix-Epoche (1970-01-01T00:00:00Z)“. Diese Zahl repräsentiert einen Moment in UTC. Die Verschiebung entsteht, wenn du JavaScript bittest, diesen Moment als Ortszeit (basierend auf Rechner-/Server-Einstellungen) statt als UTC zu formatieren.
Viele Date-APIs haben lokale und UTC-Varianten. Sie liefern unterschiedliche Zahlen für dasselbe Instant:
const d = new Date('2025-01-01T00:30:00Z');
d.getHours(); // Stunde in *lokaler* Zeitzone
d.getUTCHours(); // Stunde in UTC
d.toString(); // lokale Zeit-String
d.toISOString(); // UTC (endet immer mit Z)
Wenn dein Rechner in New York (UTC-5) ist, kann diese UTC-Zeit lokal als „19:30“ am vorherigen Tag erscheinen. Auf einem Server mit UTC-Einstellung erscheint sie als „00:30“. Dasselbe Instant, unterschiedliche Anzeige.
Logs verwenden oft Date#toString() oder interpolieren ein Date implizit — das nutzt die lokale Zeitzone der Umgebung. Das heißt, derselbe Code kann unterschiedliche Zeitstempel auf deinem Laptop, in CI und in Produktion ausgeben.
Speichere und übertrage Zeit als UTC (z. B. Epoch-Millisekunden oder ISO 8601 mit Z). Konvertiere in die Benutzer-Locale nur bei der Anzeige:
toISOString() oder Epoch-MillisekundenIntl.DateTimeFormat formatierenWenn du schnell arbeitest (z. B. mit einem vibe-coding Workflow in Koder.ai), hilft es, das früh in deine API-Verträge einzubauen: benenne Felder klar (createdAtMs, createdAtIso) und sorge dafür, dass Server (Go + PostgreSQL) und Client (React) übereinstimmen, was jedes Feld bedeutet.
Wenn du Daten zwischen Browser, Server und Datenbank senden musst, sind ISO 8601-Strings die sicherste Default-Wahl. Sie sind explizit, breit unterstützt und — am wichtigsten — sie tragen Zeitzoneninformationen.
Zwei gute Austauschformate:
2025-03-04T12:30:00Z2025-03-04T12:30:00+02:00Was bedeutet „Z"?
Z steht für Zulu-Zeit, ein anderer Name für UTC. Also ist 2025-03-04T12:30:00Z „12:30 UTC".
Wann sind Offsets wie +02:00 wichtig?
Offsets sind entscheidend, wenn ein Ereignis an einen lokalen Kontext gebunden ist (Termine, Buchungen, Ladenöffnungszeiten). 2025-03-04T12:30:00+02:00 beschreibt einen Moment, der zwei Stunden vor UTC liegt — und ist nicht dasselbe Instant wie 2025-03-04T12:30:00Z.
Strings wie 03/04/2025 sind eine Falle: ist das der 3. März oder der 4. März? Verschiedene Benutzer und Umgebungen interpretieren das unterschiedlich. Bevorzuge 2025-03-04 (ISO-Datum) oder ein vollständiges ISO-Datetime.
const iso = "2025-03-04T12:30:00Z";
const d = new Date(iso);
const back = d.toISOString();
console.log(iso); // 2025-03-04T12:30:00Z
console.log(back); // 2025-03-04T12:30:00.000Z
Dieses „Round-trip“-Verhalten ist genau das, was du für APIs willst: konsistent, vorhersehbar und zeitzonenbewusst.
Date.parse() wirkt bequem: gib ihm einen String, krieg einen Timestamp. Das Problem ist, dass für alles, was nicht eindeutig ISO 8601 ist, das Parsen auf Browser-Heuristiken beruht. Diese Heuristiken unterscheiden sich zwischen Engines und Versionen, was bedeutet, dass derselbe Input unterschiedlich (oder gar nicht) geparst werden kann, je nachdem, wo dein Code läuft.
Date.parse() variieren kannJavaScript standardisiert das Parsen zuverlässig nur für ISO 8601–artige Strings (und selbst da können Details wie Zeitzone eine Rolle spielen). Für „freundliche“ Formate — wie "03/04/2025", "March 4, 2025" oder "2025-3-4" — können Browser interpretieren:
Wenn du die genaue Form des Strings nicht vorhersagen kannst, kannst du das Ergebnis nicht vorhersagen.
YYYY-MM-DDEine verbreitete Falle ist die einfache Datumsform "YYYY-MM-DD" (z. B. "2025-01-15"). Viele Entwickler erwarten, dass sie als lokale Mitternacht interpretiert wird. In der Praxis behandeln einige Umgebungen diese Form jedoch als UTC-Mitternacht.
Dieser Unterschied ist wichtig: UTC-Mitternacht in Ortszeit umgerechnet kann zum vorherigen Tag in negativen Zeitzonen (z. B. Amerika) werden oder sich stündlich verschieben. So entstehen leicht Bugs wie „Warum weicht mein Datum um einen Tag ab?".
Für Server/API-Input:
2025-01-15T13:45:00Z oder 2025-01-15T13:45:00+02:00."YYYY-MM-DD") und wandle es nicht in ein Date um, es sei denn, du definierst die beabsichtigte Zeitzone.Für Benutzereingabe:
03/04/2025, es sei denn, dein UI erzwingt die Bedeutung.Statt Date.parse() das „Herausfinden" zu überlassen, wähle eines dieser Muster:
new Date(year, monthIndex, day) für lokale Daten verwenden).Wenn Zeitdaten kritisch sind, reicht „es funktioniert auf meinem Rechner" nicht — mache deine Parsing-Regeln explizit und konsistent.
Wenn dein Ziel ist, „ein Datum/Zeit so anzuzeigen, wie Menschen es erwarten", ist das beste Werkzeug in JavaScript Intl.DateTimeFormat. Es nutzt die Locale-Regeln des Benutzers (Reihenfolge, Trennzeichen, Monatsnamen) und vermeidet das fragile manuelle Zusammenbauen von Strings wie month + '/' + day.
Manuelle Formatierung hardcoded oft US-Stil, vergisst führende Nullen oder produziert verwirrende 24/12-Stunden-Ergebnisse. Intl.DateTimeFormat macht außerdem explizit, in welcher Zeitzone du darstellst — wichtig, wenn deine Daten in UTC gespeichert sind, das UI aber die lokale Zeit widerspiegeln soll.
Für „schön formatieren" sind dateStyle und timeStyle am einfachsten:
const d = new Date('2025-01-05T16:30:00Z');
// Locale des Nutzers + lokale Zeitzone des Nutzers
console.log(new Intl.DateTimeFormat(undefined, {
dateStyle: 'medium',
timeStyle: 'short'
}).format(d));
// Erzwinge eine bestimmte Zeitzone (gut für Eventzeiten)
console.log(new Intl.DateTimeFormat('en-GB', {
dateStyle: 'full',
timeStyle: 'short',
timeZone: 'UTC'
}).format(d));
Wenn du konsistente Stundenzyklen brauchst (z. B. Umschalter in den Einstellungen), benutze hour12:
console.log(new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
}).format(d));
Wähle eine Formatierungsfunktion pro „Typ" von Timestamp in deinem UI (Nachrichtenzeit, Logeintrag, Event-Start) und mache die timeZone-Entscheidung bewusst:
Das gibt konsistente, locale-freundliche Ausgaben ohne ein fragiles Set an Custom-Format-Strings.
Daylight Saving Time (DST) ist, wenn eine Zeitzone ihren UTC-Offset (typischerweise um eine Stunde) an bestimmten Daten ändert. Knifflig ist: DST ändert nicht nur den Offset — sie verändert die Existenz bestimmter lokalen Zeiten.
Wenn die Uhr vorgestellt wird (spring forward), tritt ein Bereich lokaler Zeiten nicht auf. In vielen Regionen springt die Uhr z. B. von 01:59 direkt auf 03:00, sodass 02:30 lokale Zeit „fehlt".
Wenn die Uhr zurückgestellt wird (fall back), tritt ein Bereich lokaler Zeiten zweimal auf. Beispielsweise kann 01:30 einmal vor dem Wechsel und einmal danach auftreten — dieselbe Wand-Uhr-Zeit kann also zwei verschiedene Instants bedeuten.
Das sind nicht dasselbe rund um DST-Grenzen:
Wenn heute Nacht DST beginnt, kann „morgen um 9:00" nur 23 Stunden entfernt sein. Wenn DST endet, kann es 25 Stunden sein.
// Scenario: schedule “same local time tomorrow”
const d = new Date(2025, 2, 8, 9, 0); // Mar 8, 9:00 local
const plus24h = new Date(d.getTime() + 24 * 60 * 60 * 1000);
const nextDaySameLocal = new Date(d);
nextDaySameLocal.setDate(d.getDate() + 1);
// Around DST, plus24h and nextDaySameLocal can differ by 1 hour.
setHours überraschen kannWenn du so etwas machst wie date.setHours(2, 30, 0, 0) an einem „spring forward"-Tag, kann JavaScript das auf eine andere gültige Zeit normalisieren (oft 03:30), weil 02:30 in der lokalen Zeit nicht existiert.
setDate) statt Millisekunden-Addition verwenden.Z, damit das Instant eindeutig ist.Eine häufige Fehlerquelle ist, Date zu benutzen, um etwas darzustellen, das kein Kalender-Moment ist.
Ein Timestamp beantwortet „wann ist das passiert?" (ein Instant wie 2025-12-23T10:00:00Z). Eine Dauer beantwortet „wie lange?" (z. B. „3 Minuten 12 Sekunden"). Das sind unterschiedliche Konzepte, und Vermischung führt zu verwirrender Rechnung und unerwarteten Zeitzonen-/DST-Effekten.
Date das falsche Werkzeug für Dauern istDate repräsentiert immer einen Punkt auf der Zeitachse relativ zu einer Epoche. Wenn du „90 Sekunden" als Date speicherst, speicherst du effektiv „1970-01-01 plus 90 Sekunden" in einer bestimmten Zeitzone. Die Formatierung kann dann plötzlich 01:01:30 zeigen, sich um eine Stunde verschieben oder ein Datum anzeigen, das du nie wolltest.
Für Dauern bevorzuge einfache Zahlen:
HH:mm:ss umwandelnEin einfacher Formatter für Countdown-Timer und Medienlängen:
function formatHMS(totalSeconds) {
const s = Math.max(0, Math.floor(totalSeconds));
const hh = String(Math.floor(s / 3600)).padStart(2, "0");
const mm = String(Math.floor((s % 3600) / 60)).padStart(2, "0");
const ss = String(s % 60).padStart(2, "0");
return `${hh}:${mm}:${ss}`;
}
formatHMS(75); // "00:01:15" (Countdown)
formatHMS(5423); // "01:30:23" (Media-Dauer)
Wenn du von Minuten konvertierst, multipliziere zuerst (minutes * 60) und halte den Wert numerisch bis zur Ausgabe.
Wenn du Zeiten in JavaScript vergleichst, ist der sicherste Weg, Zahlen zu vergleichen, nicht formatierte Texte. Ein Date-Objekt ist letztlich ein Wrapper um einen numerischen Timestamp (Epoch-Millisekunden), also solltest du Vergleiche als „Zahl vs Zahl" abhandeln.
Benutze getTime() (oder Date.valueOf(), das dieselbe Zahl zurückgibt), um zuverlässig zu vergleichen:
const a = new Date('2025-01-10T12:00:00Z');
const b = new Date('2025-01-10T12:00:01Z');
if (a.getTime() < b.getTime()) {
// a ist früher
}
// funktioniert auch:
if (+a < +b) {
// unary + ruft valueOf() auf
}
Vermeide den Vergleich formatierter Strings wie "1/10/2025, 12:00 PM" — die sind locale-abhängig und sortieren nicht korrekt. Die Ausnahme sind ISO 8601-Strings im selben Format und derselben Zeitzone (z. B. alle ...Z), die lexikographisch sortierbar sind.
Sortieren nach Zeit ist einfach, wenn du nach Epoch-Millisekunden sortierst:
items.sort((x, y) => new Date(x.createdAt).getTime() - new Date(y.createdAt).getTime());
Filtern innerhalb eines Bereichs ist dasselbe:
const start = new Date('2025-01-01T00:00:00Z').getTime();
const end = new Date('2025-02-01T00:00:00Z').getTime();
const inRange = items.filter(i => {
const t = new Date(i.createdAt).getTime();
return t >= start && t < end;
});
„Anfang des Tages" hängt davon ab, ob du lokale Zeit oder UTC meinst:
// Lokaler Anfang/Ende des Tages
const d = new Date(2025, 0, 10); // 10. Jan in lokaler Zeit
const localStart = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
const localEnd = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
// UTC Anfang/Ende des Tages
const utcStart = new Date(Date.UTC(2025, 0, 10, 0, 0, 0, 0));
const utcEnd = new Date(Date.UTC(2025, 0, 10, 23, 59, 59, 999));
Wähle eine Definition früh und bleibe konsistent bei Vergleichen und Bereichslogik.
Zeitfehler wirken zufällig, bis du herausfindest was du hast (Timestamp? String? Date?) und wo die Verschiebung passiert (Parsing, Zeitzonen-Konversion, Formatierung).
Logge denselben Wert auf drei verschiedene Weisen. Das zeigt schnell, ob das Problem Sekunden vs Millisekunden, Lokal vs UTC oder String-Parsing ist:
console.log('raw input:', input);
const d = new Date(input);
console.log('toISOString (UTC):', d.toISOString());
console.log('toString (local):', d.toString());
console.log('timezone offset (min):', d.getTimezoneOffset());
Worauf du achten solltest:
toISOString() stark daneben liegt (z. B. Jahr 1970 oder weit in der Zukunft), dann vermute Sekunden vs Millisekunden.toISOString() korrekt aussieht, aber toString() verschoben ist, siehst du eine Ortszeit-Anzeige.getTimezoneOffset() sich mit dem Datum ändert, überschreitest du Sommerzeit.Viele „funktioniert auf meinem Rechner"-Berichte sind einfach unterschiedliche Umgebungs-Defaults.
console.log(Intl.DateTimeFormat().resolvedOptions());
console.log('TZ:', process.env.TZ);
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);
Wenn dein Server in UTC läuft, aber dein Laptop in einer lokalen Zone, weichen formatierte Ausgaben ab, sofern du nicht explizit timeZone angibst.
Erstelle Unittests rund um DST-Grenzen und „Edge“-Zeiten:
23:30 → 00:30 ÜberläufeWenn du schnell iterierst, mache diese Tests zur Standard-Ausstattung. Zum Beispiel kannst du bei der Generierung eines React + Go-Apps in Koder.ai eine kleine „Zeit-Vertrag"-Test-Suite vorab anlegen (API-Payload-Beispiele + Parsing-/Formatierungs-Assertions), damit Regressionen vor Deployment auffallen.
"2025-03-02 10:00".locale und (wenn nötig) timeZone.Verlässliches Zeit-Handling in JavaScript ist größtenteils eine Frage der Wahl einer „Quelle der Wahrheit" und der Konsistenz von Speicherung bis Anzeige.
Speichere und rechne in UTC. Betrachte benutzerseitige Lokalzeit als reine Präsentationsentscheidung.
Übertrage Daten zwischen Systemen als ISO 8601-Strings mit explizitem Offset (vorzugsweise Z). Wenn du numerische Epochs senden musst, dokumentiere die Einheit und halte sie konsistent (Millisekunden sind in JS üblich).
Formatiere für Menschen mit Intl.DateTimeFormat (oder toLocaleString) und übergebe eine explizite timeZone, wenn du deterministische Ausgabe brauchst (z. B. immer UTC oder eine spezifische Geschäftsregion).
Z (z. B. 2025-12-23T10:15:00Z). Wenn du Epochs nutzt, benenne Felder wie createdAtMs, damit die Einheit klar ist.Ziehe eine dedizierte DateTime-Library in Betracht, wenn du wiederkehrende Events, komplexe Zeitzonenregeln, DST-sichere Arithmetik („gleiche lokale Zeit morgen") oder viel Parsing inkonsistenter Eingaben brauchst. Der Mehrwert liegt in klareren APIs und weniger Edge-Case-Bugs.
Wenn du tiefer einsteigen willst, schau dir mehr zeitbezogene Guides unter /blog an. Wenn du Tools oder Support evaluierst, siehe /pricing.