fix(streak): Feiertage, Wochenend-Edge & Lookback-Deckel
- 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>
This commit is contained in:
1 parent
1f0c9efd59
commit
bcae44eeb3
7 files changed
+256
-47
No files matched your search
@@ -0,0 +1,60 @@
|
||||
// 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);
|
||||
}
|
||||
Reference in new issue
Block a user