/** * Kalendertage fuer die Finanzen als Zeichenkette YYYY-MM-DD ("ymd"). "Heute" ist immer der Berliner Tag * (Europe/Berlin, MESZ/MEZ automatisch). Gerechnet wird mit UTC-Mitternacht des jeweiligen Tages, damit * die Zeitumstellung (23- und 25-Stunden-Tage) keinen Tag verschluckt oder verdoppelt. */ const DAY_MS = 86_400_000 // Formatierer einmal anlegen (neu anlegen ist teuer); sv-SE liefert YYYY-MM-DD const berlinDayFormat = new Intl.DateTimeFormat('sv-SE', { timeZone: 'Europe/Berlin', year: 'numeric', month: '2-digit', day: '2-digit' }) /** Kalendertag in Berlin (YYYY-MM-DD) zu einem Zeitpunkt. */ export function berlinDate(date = new Date()) { return berlinDayFormat.format(date) } /** UTC-Mitternacht (Millisekunden) eines ymd; wirft bei Formfehlern und Tagen, die es nicht gibt (30.02.). */ function dayMs(ymd) { const match = typeof ymd === 'string' ? ymd.match(/^(\d{4})-(\d{2})-(\d{2})$/) : null if (match) { const [year, month, day] = match.slice(1).map(Number) const ms = Date.UTC(year, month - 1, day) const check = new Date(ms) if (check.getUTCFullYear() === year && check.getUTCMonth() === month - 1 && check.getUTCDate() === day) return ms } throw new Error(`Ungültiges Datum: ${typeof ymd === 'string' ? ymd : typeof ymd}`) } const ymdOf = (ms) => new Date(ms).toISOString().slice(0, 10) /** ymd plus n ganze Tage (n darf negativ sein). */ export function addDays(ymd, n) { if (!Number.isInteger(n)) throw new Error('Tage müssen eine ganze Zahl sein.') return ymdOf(dayMs(ymd) + n * DAY_MS) } /** Ganze Tage von fromYmd bis toYmd (toYmd minus fromYmd, negativ wenn toYmd frueher liegt). */ export function daysBetween(fromYmd, toYmd) { return Math.round((dayMs(toYmd) - dayMs(fromYmd)) / DAY_MS) } /** ymd als TT.MM.JJJJ. */ export function formatDe(ymd) { dayMs(ymd) const [year, month, day] = ymd.split('-') return `${day}.${month}.${year}` }