- Feiertage: isWerktag berücksichtigt jetzt die 9 bundesweiten gesetzlichen Feiertage (Oster-Computus + feste Tage) → ein Feiertag bricht die Streak nicht mehr (zählt wie ein Wochenende neutral). Landesspezifische Feiertage bewusst (noch) nicht enthalten. - Wochenend-Edge: Reflexion an Sa/So setzte die angezeigte Streak auf 0 (Schleife brach am Nicht-Werktag ab). Jetzt wird übersprungen statt gebrochen. - Lookback von 120 auf 400 Tage erhöht, damit lange Streaks nicht abgeschnitten werden. - Streak-Zähllogik in reine, getestete countStreakDays() ausgelagert (8 Streak- + 5 Feiertags-Tests). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
// Bundesweite gesetzliche Feiertage in Deutschland. Diese 9 gelten in ALLEN
|
|
// Bundesländern — sie als Nicht-Werktag zu behandeln ist daher immer korrekt.
|
|
// Landesspezifische Feiertage (z. B. Fronleichnam, Reformationstag) sind hier
|
|
// bewusst NICHT enthalten; die können bei Bedarf pro Bundesland ergänzt werden.
|
|
|
|
const cache = new Map<number, Set<string>>();
|
|
|
|
function isoFromParts(year: number, month: number, day: number): string {
|
|
return new Date(Date.UTC(year, month - 1, day)).toISOString().slice(0, 10);
|
|
}
|
|
|
|
function addDays(year: number, month: number, day: number, offset: number): string {
|
|
const d = new Date(Date.UTC(year, month - 1, day));
|
|
d.setUTCDate(d.getUTCDate() + offset);
|
|
return d.toISOString().slice(0, 10);
|
|
}
|
|
|
|
// Ostersonntag (gregorianisch) nach Meeus/Jones/Butcher.
|
|
function easterSunday(year: number): { month: number; day: number } {
|
|
const a = year % 19;
|
|
const b = Math.floor(year / 100);
|
|
const c = year % 100;
|
|
const d = Math.floor(b / 4);
|
|
const e = b % 4;
|
|
const f = Math.floor((b + 8) / 25);
|
|
const g = Math.floor((b - f + 1) / 3);
|
|
const h = (19 * a + b - d - g + 15) % 30;
|
|
const i = Math.floor(c / 4);
|
|
const k = c % 4;
|
|
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
const month = Math.floor((h + l - 7 * m + 114) / 31); // 3 = März, 4 = April
|
|
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
|
return { month, day };
|
|
}
|
|
|
|
export function germanNationwideHolidays(year: number): Set<string> {
|
|
const cached = cache.get(year);
|
|
if (cached) return cached;
|
|
const e = easterSunday(year);
|
|
const set = new Set<string>([
|
|
isoFromParts(year, 1, 1), // Neujahr
|
|
addDays(year, e.month, e.day, -2), // Karfreitag
|
|
addDays(year, e.month, e.day, 1), // Ostermontag
|
|
isoFromParts(year, 5, 1), // Tag der Arbeit
|
|
addDays(year, e.month, e.day, 39), // Christi Himmelfahrt
|
|
addDays(year, e.month, e.day, 50), // Pfingstmontag
|
|
isoFromParts(year, 10, 3), // Tag der Deutschen Einheit
|
|
isoFromParts(year, 12, 25), // 1. Weihnachtstag
|
|
isoFromParts(year, 12, 26), // 2. Weihnachtstag
|
|
]);
|
|
cache.set(year, set);
|
|
return set;
|
|
}
|
|
|
|
export function isGermanHoliday(iso: string): boolean {
|
|
const year = Number(iso.slice(0, 4));
|
|
if (!Number.isFinite(year)) return false;
|
|
return germanNationwideHolidays(year).has(iso);
|
|
}
|