/** Formatierung und Rechnen mit Geldbeträgen, durchgängig in de-DE. */ const WAEHRUNG = new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", minimumFractionDigits: 2, maximumFractionDigits: 2, }); const WAEHRUNG_MIT_VORZEICHEN = new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", minimumFractionDigits: 2, maximumFractionDigits: 2, signDisplay: "exceptZero", }); const ZAHL = new Intl.NumberFormat("de-DE", { minimumFractionDigits: 2, maximumFractionDigits: 2, }); const DATUM = new Intl.DateTimeFormat("de-DE", { day: "2-digit", month: "2-digit", year: "numeric", }); const DATUM_LANG = new Intl.DateTimeFormat("de-DE", { day: "numeric", month: "long", year: "numeric", }); const MONAT = new Intl.DateTimeFormat("de-DE", { month: "long", year: "numeric" }); const WOCHENTAG = new Intl.DateTimeFormat("de-DE", { weekday: "short" }); /** Wandelt einen Betrag der API (String) in eine Zahl. */ export function toNumber(amount: string | number | null | undefined): number { if (amount === null || amount === undefined || amount === "") return 0; const zahl = typeof amount === "number" ? amount : Number.parseFloat(amount); return Number.isFinite(zahl) ? zahl : 0; } export function formatMoney(amount: string | number | null | undefined): string { return WAEHRUNG.format(toNumber(amount)); } /** Wie `formatMoney`, stellt aber auch bei positiven Werten ein Vorzeichen voran. */ export function formatSignedMoney(amount: string | number | null | undefined): string { return WAEHRUNG_MIT_VORZEICHEN.format(toNumber(amount)); } export function formatNumber(value: number): string { return ZAHL.format(value); } /** * Bringt eine Nutzereingabe auf das Format der API. * Akzeptiert "12,50", "12.50" und "1.234,56". */ export function parseAmountInput(input: string): string | null { const bereinigt = input.trim().replace(/\s|€/g, ""); if (!bereinigt) return null; // Deutsche Schreibweise: Punkt trennt Tausender, Komma die Nachkommastellen. const normalisiert = bereinigt.includes(",") ? bereinigt.replace(/\./g, "").replace(",", ".") : bereinigt; const zahl = Number.parseFloat(normalisiert); if (!Number.isFinite(zahl)) return null; return zahl.toFixed(2); } /** Zeigt einen API-Betrag in einem Eingabefeld an ("13.99" -> "13,99"). */ export function toAmountInput(amount: string | number | null | undefined): string { if (amount === null || amount === undefined || amount === "") return ""; return toNumber(amount).toFixed(2).replace(".", ","); } function toDate(value: string | Date): Date { if (value instanceof Date) return value; // Reine Datumsangaben ohne Zeitzone werden als lokaler Tag gelesen. const [jahr, monat, tag] = value.split("-").map(Number); if (jahr && monat && tag) return new Date(jahr, monat - 1, tag); return new Date(value); } export function formatDate(value: string | Date | null | undefined): string { if (!value) return "–"; return DATUM.format(toDate(value)); } export function formatDateLong(value: string | Date | null | undefined): string { if (!value) return "–"; return DATUM_LANG.format(toDate(value)); } export function formatMonth(value: string | Date | null | undefined): string { if (!value) return "–"; return MONAT.format(toDate(value)); } export function formatWeekday(value: string | Date): string { return WOCHENTAG.format(toDate(value)); } /** ISO-Datum (YYYY-MM-DD) eines Datums in lokaler Zeit. */ export function toIsoDate(date: Date): string { const monat = String(date.getMonth() + 1).padStart(2, "0"); const tag = String(date.getDate()).padStart(2, "0"); return `${date.getFullYear()}-${monat}-${tag}`; } export function todayIso(): string { return toIsoDate(new Date()); } export function firstOfMonth(value: string | Date = new Date()): string { const datum = toDate(value); return toIsoDate(new Date(datum.getFullYear(), datum.getMonth(), 1)); } export function addMonthsIso(value: string, months: number): string { const datum = toDate(value); const ziel = new Date(datum.getFullYear(), datum.getMonth() + months, 1); const letzterTag = new Date(ziel.getFullYear(), ziel.getMonth() + 1, 0).getDate(); ziel.setDate(Math.min(datum.getDate(), letzterTag)); return toIsoDate(ziel); } /** "in 5 Tagen", "heute", "vor 2 Tagen" – für Fälligkeitshinweise. */ export function relativeDays(value: string, reference: Date = new Date()): string { const ziel = toDate(value); const heute = new Date(reference.getFullYear(), reference.getMonth(), reference.getDate()); const tage = Math.round((ziel.getTime() - heute.getTime()) / 86_400_000); if (tage === 0) return "heute"; if (tage === 1) return "morgen"; if (tage === -1) return "gestern"; if (tage > 0) return `in ${tage} Tagen`; return `vor ${Math.abs(tage)} Tagen`; }