Files

48 lines
1.9 KiB
JavaScript

/**
* 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}`
}