Files
CapaKraken/packages/shared/src/constants/publicHolidays.ts
T

130 lines
4.3 KiB
TypeScript

/**
* German public holiday calculator.
* Supports federal holidays + Bavaria (BY) specific holidays.
*
* Easter-based dates use the Gauss/Meeus algorithm.
*/
export interface PublicHoliday {
date: string; // ISO "YYYY-MM-DD"
name: string;
federal: boolean; // true = all states; false = state-specific
states?: string[]; // which state abbreviations observe this holiday
}
/**
* Compute Easter Sunday date for a given year (Gregorian calendar).
* Uses the Anonymous Gregorian algorithm.
*/
function computeEaster(year: number): Date {
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); // 1-based
const day = ((h + l - 7 * m + 114) % 31) + 1;
return new Date(Date.UTC(year, month - 1, day));
}
function addDays(date: Date, days: number): Date {
const d = new Date(date);
d.setUTCDate(d.getUTCDate() + days);
return d;
}
function fmt(date: Date): string {
return date.toISOString().slice(0, 10);
}
function fixed(year: number, month: number, day: number): string {
return fmt(new Date(Date.UTC(year, month - 1, day)));
}
/**
* Return all public holidays for a given year and optional state.
* When state is omitted, returns federal holidays only.
* When state is provided (e.g. "BY"), returns federal + state-specific holidays.
*/
export function getPublicHolidays(year: number, state?: string): PublicHoliday[] {
const easter = computeEaster(year);
const holidays: PublicHoliday[] = [
// Federal holidays (all states)
{ date: fixed(year, 1, 1), name: "Neujahr", federal: true },
{ date: fixed(year, 5, 1), name: "Tag der Arbeit", federal: true },
{ date: fixed(year, 10, 3), name: "Tag der Deutschen Einheit", federal: true },
{ date: fixed(year, 12, 25), name: "1. Weihnachtstag", federal: true },
{ date: fixed(year, 12, 26), name: "2. Weihnachtstag", federal: true },
// Easter-based federal holidays
{ date: fmt(addDays(easter, -2)), name: "Karfreitag", federal: true },
{ date: fmt(easter), name: "Ostersonntag", federal: true },
{ date: fmt(addDays(easter, 1)), name: "Ostermontag", federal: true },
{ date: fmt(addDays(easter, 39)), name: "Christi Himmelfahrt", federal: true },
{ date: fmt(addDays(easter, 49)), name: "Pfingstsonntag", federal: true },
{ date: fmt(addDays(easter, 50)), name: "Pfingstmontag", federal: true },
// Bavaria-specific (BY)
{
date: fixed(year, 1, 6),
name: "Heilige Drei Könige",
federal: false,
states: ["BY", "BW", "ST"],
},
{
date: fmt(addDays(easter, 60)),
name: "Fronleichnam",
federal: false,
states: ["BY", "BW", "HE", "NW", "RP", "SL"],
},
{
date: fixed(year, 8, 15),
name: "Mariä Himmelfahrt",
federal: false,
states: ["BY", "SL"],
},
{
date: fixed(year, 11, 1),
name: "Allerheiligen",
federal: false,
states: ["BY", "BW", "NW", "RP", "SL"],
},
// Other state-specific (not BY but included for completeness)
{
date: fixed(year, 10, 31),
name: "Reformationstag",
federal: false,
states: ["BB", "HB", "HH", "MV", "NI", "SH", "SN", "ST", "TH"],
},
{
date: fixed(year, 11, 18),
name: "Buß- und Bettag",
federal: false,
states: ["SN"],
},
];
if (!state) {
return holidays.filter((h) => h.federal);
}
return holidays.filter((h) => h.federal || h.states?.includes(state));
}
/**
* Check if a given date (ISO string or Date) is a public holiday.
*/
export function isPublicHoliday(date: Date | string, state?: string): boolean {
const d = typeof date === "string" ? date : date.toISOString().slice(0, 10);
const year = parseInt(d.slice(0, 4), 10);
return getPublicHolidays(year, state).some((h) => h.date === d);
}