import type { ZeitraumPreset } from "../data/types"; /** * Fester "Heute"-Bezugspunkt des Prototyps. * * Wichtig: Die Dummy-Daten sind deterministisch relativ zu diesem Datum erzeugt. * Dadurch bleiben alle Zahlen ueber Reloads und Screenshots hinweg identisch. * In der echten Software wird das durch `new Date()` ersetzt. */ export const HEUTE = new Date("2026-07-30T12:00:00"); export interface Zeitfenster { von: Date; bis: Date; } export function addTage(datum: Date, tage: number): Date { const d = new Date(datum); d.setDate(d.getDate() + tage); return d; } export function addMonate(datum: Date, monate: number): Date { const d = new Date(datum); d.setMonth(d.getMonth() + monate); return d; } export function toISO(datum: Date): string { return datum.toISOString().slice(0, 10); } /** Zeitfenster eines Presets, bezogen auf HEUTE. */ export function zeitfenster(preset: ZeitraumPreset): Zeitfenster { const bis = new Date(HEUTE); bis.setHours(23, 59, 59, 999); switch (preset) { case "heute": { const von = new Date(HEUTE); von.setHours(0, 0, 0, 0); return { von, bis }; } case "7_tage": return { von: addTage(bis, -7), bis }; case "30_tage": return { von: addTage(bis, -30), bis }; case "quartal": return { von: addMonate(bis, -3), bis }; case "jahr": return { von: addMonate(bis, -12), bis }; case "alles": default: return { von: new Date("2000-01-01"), bis }; } } /** Gleich langes Fenster direkt davor: Basis fuer Trend-Vergleiche. */ export function vorperiode(preset: ZeitraumPreset): Zeitfenster { const { von, bis } = zeitfenster(preset); const dauer = bis.getTime() - von.getTime(); return { von: new Date(von.getTime() - dauer), bis: new Date(von.getTime() - 1), }; } export function imFenster(iso: string | null, fenster: Zeitfenster): boolean { if (!iso) return false; const t = new Date(iso).getTime(); return t >= fenster.von.getTime() && t <= fenster.bis.getTime(); } /** ISO-Kalenderwoche als sortierbarer Schluessel: "2026-W31". */ export function isoWoche(datum: Date): string { const d = new Date(Date.UTC(datum.getFullYear(), datum.getMonth(), datum.getDate())); // Donnerstag derselben Woche bestimmt nach ISO 8601 Jahr und Wochennummer. d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7)); const jahresstart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); const woche = Math.ceil(((d.getTime() - jahresstart.getTime()) / 86_400_000 + 1) / 7); return `${d.getUTCFullYear()}-W${String(woche).padStart(2, "0")}`; } /** "2026-W31" -> "KW 31" */ export function formatWoche(schluessel: string): string { return `KW ${schluessel.slice(6)}`; } /** * Passende Buckets fuer eine Zeitreihe. * * Die Aufloesung folgt der Frage, nicht dem Kalender: Bei rund zwei Vorgaengen * pro Tag ist eine Tagesreihe ueber 30 Tage Rauschen, kein Verlauf. Deshalb * Tage nur bis eine Woche, danach Kalenderwochen, ab einem Jahr Monate. */ export function bucketsFuer(preset: ZeitraumPreset): { art: "tag" | "woche" | "monat"; schluessel: string[]; } { const { von, bis } = zeitfenster(preset); if (preset === "heute" || preset === "7_tage") { const schluessel: string[] = []; for (let d = new Date(von); d <= bis; d = addTage(d, 1)) schluessel.push(toISO(d)); return { art: "tag", schluessel }; } if (preset === "30_tage" || preset === "quartal") { const schluessel: string[] = []; for (let d = new Date(von); d <= bis; d = addTage(d, 7)) { const key = isoWoche(d); if (!schluessel.includes(key)) schluessel.push(key); } const letzte = isoWoche(bis); if (!schluessel.includes(letzte)) schluessel.push(letzte); return { art: "woche", schluessel }; } const monate: string[] = []; const start = preset === "alles" ? addMonate(bis, -23) : von; for (let d = new Date(start.getFullYear(), start.getMonth(), 1); d <= bis; d = addMonate(d, 1)) { monate.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`); } return { art: "monat", schluessel: monate }; } /** Bucket-Schluessel eines Datums fuer die gewaehlte Aufloesung. */ export function bucketKey(iso: string, art: "tag" | "woche" | "monat"): string { if (art === "monat") return iso.slice(0, 7); if (art === "woche") return isoWoche(new Date(iso)); return iso.slice(0, 10); }