Datum und Uhrzeit mit Claude Code richtig behandeln
Praxisleitfaden für Zeitzonen, DST, Intl, Tests und Datenspeicherung mit Claude Code.
Warum Datum und Uhrzeit mehr als einen Prompt brauchen
Datums- und Zeitfehler werden im Review leicht übersehen, richten in Produktion aber großen Schaden an. Eine Anzeige kann in Deutschland oder Japan korrekt wirken und trotzdem bei US-Sommerzeit, europäischem Monatsabschluss, indischen 30-Minuten-Offsets oder unterschiedlichen Zeitzonen von Server und Browser brechen. Wenn du Claude Code nur “implementiere Date Handling” sagst, bekommst du vielleicht saubere Formatierung, aber keine klare Speicherpolitik, keinen API-Vertrag und keine Grenztests.
Nutze Claude Code als Pair Engineer für die Zeitspezifikation, nicht nur als Codegenerator. Dieser Leitfaden richtet sich an Web-Apps mit Buchungen, Abrechnung, Benachrichtigungen, Support-SLAs und Admin-Dashboards über Zeitzonengrenzen hinweg. Für die Teststrategie siehe Claude Code testing strategies. Für Schemaänderungen passt database migration with Claude Code.
Prüfe vor der Implementierung offizielle Quellen. Für Darstellung ist MDN Intl.DateTimeFormat maßgeblich. Für Temporal nutze TC39 Temporal proposal und MDN Temporal. Für PostgreSQL-Verhalten siehe PostgreSQL Date/Time Types. Für automatische Checks nach Claude-Code-Änderungen siehe Claude Code hooks.
Vokabular und Speicherpolitik zuerst festlegen
Bevor Claude Code Code schreibt, muss das Team die Begriffe fixieren. Sonst enden Reviews in ungenauen Aussagen wie “wir speichern UTC, also ist es sicher” oder “das Produkt ist lokal, also reicht die Landeszeitzone”.
| Begriff | Einfache Bedeutung | Praktische Regel |
|---|---|---|
| instant | Ein weltweit eindeutiger Zeitpunkt, z. B. 2026-06-02T00:00:00Z | Als UTC-basierter Timestamp speichern |
| local date | Kalenderdatum einer Person oder Geschäftsregel | Als YYYY-MM-DD speichern, nicht mit Uhrzeit mischen |
| wall clock time | Uhrzeit auf der Wanduhr, z. B. Meeting um 09:00 | Mit IANA-Zeitzonen-ID speichern |
| IANA timezone | Regionaler Name wie Europe/Berlin oder America/New_York | Nicht durch fixen Offset ersetzen |
| DST | Sommerzeit; manche Tage haben 23 oder 25 Stunden | Übergangstage explizit testen |
In echten Projekten gehören diese Regeln vor der Implementierung in AGENTS.md oder CLAUDE.md.
- API-Timestamps für bereits geschehene Ereignisse sind ISO-8601-Strings mit
Zoder explizitem Offset. - Zukünftige Buchungen behalten
localDate,localTimeundtimeZoneals getrennte Felder. - UI-Formatierung übergibt immer
localeundtimeZoneanIntl.DateTimeFormat; Runtime-Defaults sind tabu.
Die Grenze sieht so aus.
flowchart LR
A["Client: local date/time input"] --> B["API contract: localDate + localTime + timeZone"]
B --> C["Server: validate and convert when needed"]
C --> D["Database: instant in timestamptz + original timeZone"]
D --> E["UI: format with Intl.DateTimeFormat"]
E --> A
Eine sichere Basis mit Intl.DateTimeFormat bauen
Für reine Anzeige starte mit der Standard-API Intl.DateTimeFormat. MDN dokumentiert sie als eingebaute API für sprachsensitives Datums- und Zeitformat, inklusive explizitem timeZone. Gib Claude Code außerdem vor: kein toLocaleString() ohne Argumente und kein direktes Date-Parsing von YYYY-MM-DD, wenn der Wert ein lokales Kalenderdatum meint.
Dieses Modul braucht keine Abhängigkeit. Lege es als src/lib/date-policy.ts an und nutze es in UI, API und Tests.
// src/lib/date-policy.ts
export const TIME_POLICY = {
defaultLocale: 'de-DE',
defaultTimeZone: 'Europe/Berlin',
} as const;
type FormatOptions = {
locale?: string;
timeZone?: string;
includeWeekday?: boolean;
};
function toDate(input: string | Date): Date {
const date = input instanceof Date ? input : new Date(input);
if (!Number.isFinite(date.getTime())) {
throw new Error(`Invalid date value: ${String(input)}`);
}
return date;
}
export function toUtcIso(input: string | Date): string {
if (typeof input === 'string' && !/(Z|[+-]\d{2}:?\d{2})$/i.test(input)) {
throw new Error('Timestamp must include Z or an explicit UTC offset.');
}
return toDate(input).toISOString();
}
function dateParts(date: Date, timeZone: string): Record<string, string> {
const parts = new Intl.DateTimeFormat('en-US', {
timeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).formatToParts(date);
return Object.fromEntries(
parts
.filter((part) => part.type !== 'literal')
.map((part) => [part.type, part.value]),
);
}
export function dayKeyInTimeZone(
input: string | Date,
timeZone = TIME_POLICY.defaultTimeZone,
): string {
const parts = dateParts(toDate(input), timeZone);
return `${parts.year}-${parts.month}-${parts.day}`;
}
export function formatInstant(
input: string | Date,
{
locale = TIME_POLICY.defaultLocale,
timeZone = TIME_POLICY.defaultTimeZone,
includeWeekday = true,
}: FormatOptions = {},
): string {
return new Intl.DateTimeFormat(locale, {
timeZone,
weekday: includeWeekday ? 'short' : undefined,
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23',
timeZoneName: 'short',
}).format(toDate(input));
}
Der Punkt ist nicht, jede Datumslogik in Utilities zu verstecken. Der Punkt ist die Trennung von Instant, lokalem Datumsschlüssel und Anzeige-Label. Bevor Claude Code einen neuen Helper ergänzt, soll es erklären, in welche Kategorie die Anforderung fällt.
Zukünftige lokale Zeiten explizit konvertieren
Intl.DateTimeFormat ist gut für Ausgabe, aber nicht für die Umwandlung von 2026-11-01 01:30 America/New_York in einen sicheren UTC-Instant. Dafür wählt das Projekt eine Bibliothek und prüft aktuelle Dokumentation. Luxon und die API docs dokumentieren explizite Zeitzonen-APIs. date-fns ist stark für funktionale Datumshelfer, aber Zeitzonenanforderungen müssen in den aktuellen offiziellen Docs geprüft werden. Temporal ist bei TC39 Stage 4, trotzdem sind Runtime-Unterstützung und Polyfill-Plan wichtig.
Bei einer Buchung gibt der Nutzer lokale Uhrzeit ein; der Server erzeugt daraus den zu speichernden Instant.
// src/lib/schedule-time.ts
import { DateTime } from 'luxon';
type LocalScheduleInput = {
localDate: string; // YYYY-MM-DD
localTime: string; // HH:mm
timeZone: string; // IANA time zone, for example "America/New_York"
};
export function scheduleToUtcIso(input: LocalScheduleInput): string {
const rawLocal = `${input.localDate}T${input.localTime}`;
const local = DateTime.fromISO(rawLocal, { zone: input.timeZone });
if (!local.isValid) {
throw new Error(local.invalidExplanation ?? `Invalid local time: ${rawLocal}`);
}
const roundTrip = local.setZone(input.timeZone).toFormat("yyyy-MM-dd'T'HH:mm");
if (roundTrip !== rawLocal) {
throw new Error(`Nonexistent local time in ${input.timeZone}: ${rawLocal}`);
}
const iso = local.toUTC().toISO({ suppressMilliseconds: true });
if (!iso) {
throw new Error(`Could not convert local time to UTC: ${rawLocal}`);
}
return iso;
}
Dieser Helper löst die doppelte 01:00-Stunde am Ende der Sommerzeit nicht automatisch. Das Produkt braucht eine Regel: früherer Offset, späterer Offset oder Rückfrage an den Nutzer. Claude Code soll Tests für nicht existierende Zeiten, mehrdeutige Zeiten, Monatsende und Schalttage ergänzen.
Server, Client und Datenbank trennen
Der häufigste Fehler ist, einen Wert an Date zu übergeben, ohne zu entscheiden, ob Browser- oder Serverzeitzone ihn interpretiert. Browser-Inputs, API-Payloads, DB-Spalten, E-Mails und CSV-Exporte sind unterschiedliche Grenzen.
Prüfe das Design mindestens mit diesen drei Fällen.
- Buchungssystem: Eingabe in der Ortszeit des Standorts, Speicherung als UTC-Instant, Anzeige in Zeitzone des Betrachters oder Standorts.
- Abrechnungsschluss: “letzter Tag des Monats 23:59” ist eine lokale Datumsregel in der Vertragszeitzone des Kunden; nicht mit 24-Stunden-Additionen rechnen.
- Support-SLA: Arbeitstage, Feiertage und Bürozeiten variieren je Region, also Deadline-Instant und lokalen Kontext speichern.
Die PostgreSQL-Dokumentation erklärt, dass timestamp with time zone nach UTC konvertiert wird und die ursprüngliche Zeitzone nicht behalten wird. timestamptz allein sagt dir also nicht, dass ein Nutzer America/New_York gewählt hat. Speichere diese Information separat, wenn sie relevant ist.
create table scheduled_events (
id uuid primary key,
title text not null,
starts_at timestamptz not null,
original_time_zone text not null check (original_time_zone <> ''),
local_date date not null,
local_time time not null,
created_at timestamptz not null default now()
);
create index scheduled_events_starts_at_idx
on scheduled_events (starts_at);
Nimm nicht an, dass 2026-06-02T09:00:00+09:00 in einer timestamp without time zone-Spalte den Offset sinnvoll behält. PostgreSQL bestimmt zuerst den Typ; bei without time zone wird die Zeitzonenangabe ignoriert. Ergänze “passt der Spaltentyp zum API-Vertrag?” in Claude Codes Review-Checkliste.
DST und Grenztage mit festen Instants testen
Tests dürfen nicht von “heute”, aktueller Uhrzeit oder Entwickler-Zeitzone abhängen. Fixiere die Eingabe als UTC-Instant und prüfe lokale Datumsschlüssel oder Labels. Mit Vitest kannst du so starten. Weitere Muster stehen in Claude Code Vitest advanced.
// src/lib/date-policy.test.ts
import { describe, expect, it } from 'vitest';
import { dayKeyInTimeZone, formatInstant, toUtcIso } from './date-policy';
describe('date/time policy', () => {
it('requires an explicit offset for API timestamps', () => {
expect(() => toUtcIso('2026-06-02T09:00:00')).toThrow(/offset/);
expect(toUtcIso('2026-06-02T09:00:00+09:00')).toBe('2026-06-02T00:00:00.000Z');
});
it('calculates a local day key across the UTC date boundary', () => {
expect(dayKeyInTimeZone('2026-03-31T15:01:00Z', 'Asia/Tokyo')).toBe('2026-04-01');
expect(dayKeyInTimeZone('2026-04-01T00:30:00Z', 'America/Los_Angeles')).toBe('2026-03-31');
});
it('formats a DST transition instant in the requested time zone', () => {
const label = formatInstant('2026-03-08T07:30:00Z', {
locale: 'en-US',
timeZone: 'America/New_York',
});
expect(label).toMatch(/03:30|3:30/);
expect(label).toMatch(/EDT|GMT-4/);
});
});
Der Test erlaubt EDT oder GMT-4, weil ICU-Daten zwischen CI-Umgebungen variieren können. Lokale Datumsschlüssel und UTC-Konvertierung sind Businesslogik und sollten strikt bleiben.
Regressionen mit Prompts und Hooks verhindern
Gib Claude Code die Regeln vor der Implementierung. Ein längerer Prompt ist in Ordnung, wenn er die gefährlichen Grenzen nennt.
Before implementing date/time changes, read date-policy.ts and the database schema.
Constraints:
- API timestamps for completed events must be ISO strings with Z or an explicit offset
- Future bookings must keep localDate, localTime, and timeZone separate
- Intl.DateTimeFormat must always receive locale and timeZone
- Do not use new Date('YYYY-MM-DD') for local calendar dates
- Add Vitest cases for DST start, DST end, month end, and leap day
- Explain the PostgreSQL timestamp/timestamptz difference in the review notes
Done when:
- npm test -- --run date-time passes
- Updated API response examples are added to README
Im Team kannst du Claude Code hooks nutzen, um nach Edits deterministische Tests laufen zu lassen. Die offiziellen Docs zeigen, dass hooks Shell-Kommandos an Lifecycle-Events ausführen können. Den gesamten Ablauf beschreibt der Claude Code hooks guide.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npm test -- --run date-time"
}
]
}
]
}
}
Bibliothek nach Zeittyp auswählen
Wähle eine Datumsbibliothek nicht nur nach Popularität, sondern nach dem Zeittyp im Produkt. Intl.DateTimeFormat passt für lokalisierte Anzeige, date-fns für fokussierte Datumshelfer, Luxon für IANA-Zeitzonen-Buchungen und Temporal für Designs mit klar getrennten Typen wie Instant, PlainDate und ZonedDateTime. Auch bei Temporal Stage 4 musst du Runtime-Support und Polyfill-Plan prüfen, bevor Claude Code Produktionscode umschreibt.
| Option | Beste Nutzung | Vorsicht |
|---|---|---|
| Intl | Lokalisierte Anzeige mit expliziter Zeitzone | Kein Tool für lokale Zeit zu Instant |
| date-fns | Datumsarithmetik, Funktions-Imports, kleine Helfer | Offizielle Docs für Timezone-Bedarf prüfen |
| Luxon | IANA-Zeitzonen, relative Zeit, ISO-Konvertierung | Mehrdeutige DST-Zeiten brauchen Produktregel |
| Temporal | Trennung von Instant, PlainDate, ZonedDateTime | Runtime-Kompatibilität und Polyfill zählen |
Aus Content- und Monetarisierungssicht ist eine reine Bibliotheksliste schwach. Leser wollen wissen, was gespeichert wird, wo konvertiert wird und was getestet werden muss. Wenn dein Team diese Politik in CLAUDE.md, hooks, PR-Review und Migrationen einbauen möchte, ist Claude Code Training und Beratung der natürliche nächste Schritt. Solo-Entwickler können den Prompt oben in das kostenlose Cheatsheet übernehmen.
Praktische Verifikation
Masas Verifikationsnotiz: Am hilfreichsten war es, einen UTC-Instant zu fixieren, ihn in Tokio, New York und Los Angeles zu formatieren und den lokalen Datumsschlüssel über Tagesgrenzen hinweg zu prüfen. Der am einfachsten reproduzierbare Fehler war, YYYY-MM-DD als lokales Nutzerdatum zu behandeln, in Date umzuwandeln und in einer anderen Zeitzone den Vortag zu sehen. Die Politik dieses Artikels hält lokale Daten als Strings und konvertiert nur gespeicherte Instants und Anzeige-Labels explizit. Dadurch sind Claude-Code-Diffs deutlich leichter zu prüfen.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.