Warum ist «Date» in TypeScript eine Falle?
\nStellen Sie sich vor: Sie schreiben eine Anwendung, alles funktioniert perfekt. Aber eines schönen Tages kommt ein Bug-Report: «Ein Benutzer aus Tokio sieht das Datum einen Tag früher». Sie öffnen den Code und sehen den Klassiker: let date: Date = "2024-12-01". Oder noch schlimmer — any. Kommt Ihnen bekannt vor?
Die Frage «How do I express a date type in TypeScript?» auf Stack Overflow hat zehntausende Aufrufe. Und das zu Recht. Die Antwort scheint einfach: Verwende Date. Aber in der Praxis ist es komplizierter. TypeScript ist ein Overlay über JavaScript, und Date in JS ist ein Objekt, das sich... eigenartig verhält.
In diesem Artikel behandeln wir nicht nur die Syntax, sondern die korrekte Typisierung von Daten, die Ihr Projekt vor Bugs bewahrt, die Leistung verbessert und den Code für das Team verständlich macht. Sie erfahren, wie Sie mit echten Daten in TypeScript arbeiten, ohne Zeit mit dem Debuggen zu verschwenden.
\n\n1. Basistypisierung: Date, string oder number?
\nDie offensichtlichste Variante ist der eingebaute Typ Date:
let birthday: Date = new Date('1990-05-15');\nFunktioniert? Ja. Aber es gibt einen Haken: TypeScript prüft nicht, ob das Datum gültig ist. Sie können new Date('abc') schreiben — es gibt keinen Kompilierungsfehler, aber zur Laufzeit erhalten Sie Invalid Date. Und der Versuch, .getTime() aufzurufen, gibt NaN zurück.
Eine strengere Variante ist die Verwendung von Zeichenketten im ISO 8601-Format:
\ntype ISODateString = string;\nlet eventDate: ISODateString = '2024-12-01T10:00:00Z';\nVorteil: einfacher Typ, leicht in JSON serialisierbar. Nachteil: Keine Validierung — Sie können 'hello' zuweisen, und TS schweigt.
Die dritte Variante ist der Timestamp (number):
\ntype Timestamp = number;\nlet ms: Timestamp = Date.now();\nIdeal für mathematische Operationen, aber für Menschen nicht lesbar. Und wiederum — jede Zahl wird die Prüfung bestehen.
\n\nZusammenfassung der Basistypen
\nWenn Sie eine schnelle und schmutzige Typisierung benötigen, verwenden Sie Date. Wenn Sie mit APIs arbeiten, verwenden Sie string. Wenn Sie nur Berechnungen durchführen, verwenden Sie number. Aber für ernsthafte Projekte reicht das nicht. Gehen wir tiefer.
2. Benutzerdefinierte Typen und Brand-Typisierung (Branded Types)
\nDamit TypeScript einen Datums-String nicht mit einem normalen String verwechselt, verwendet man Branded Types. Das ist wie ein Aufkleber «Ich bin ein Datum, geprüft!»:
\ntype DateString = string & { __brand: 'DateString' };\n\nfunction toDateString(date: Date): DateString {\n return date.toISOString() as DateString;\n}\n\nfunction processDate(d: DateString) {\n // hier ist d definitiv ein ISO-String\n}\n\nconst iso = toDateString(new Date());\nprocessDate(iso); // OK\n// processDate('hello'); // ❌ Fehler!\nDies fügt Sicherheit zur Kompilierungszeit hinzu. Der Preis ist etwas mehr Code. Aber für große Projekte (insbesondere mit Microservices oder REST APIs) ist dies der Goldstandard.
\n\n3. Bibliotheken für die Arbeit mit Daten in TypeScript
\nRohes Date ist böse. Es ist mutierbar, parst Strings inkonsistent und Zeitzonen sind ein separates Problem. Daher werden in realen Projekten Bibliotheken verwendet. Hier sind drei Favoriten:
3.1. date-fns (leicht und modular)
\nimport { format, parseISO } from 'date-fns';\n\nconst date: Date = parseISO('2024-12-01T10:00:00Z');\nconst formatted: string = format(date, 'dd.MM.yyyy');\nVorteil: Tree-Shaking, immutable (gibt neue Daten zurück), hervorragende Typisierung.
\n\n3.2. Day.js (minimalistisch)
\nimport dayjs, { Dayjs } from 'dayjs';\n\nconst now: Dayjs = dayjs();\nconst formatted: string = now.format('DD.MM.YYYY');\nDay.js ist wie Moment.js, aber ohne das fette Bundle. Der Typ Dayjs ist ein Wrapper, der leicht zwischen Funktionen übergeben werden kann.