Files
app/src/utils/czech-holidays.ts
2026-03-24 19:59:14 +01:00

102 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Czech public holidays & work fund calculator.
* Port of PHP CzechHolidays class.
*/
const holidayCache = new Map<number, string[]>();
/** Easter Sunday using the Anonymous Gregorian algorithm */
function getEasterSunday(year: number): string {
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);
const day = ((h + l - 7 * m + 114) % 31) + 1;
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}
/** All Czech public holidays for a year (11 fixed + 2 Easter-based) */
export function getHolidays(year: number): string[] {
if (holidayCache.has(year)) return holidayCache.get(year)!;
const y = String(year);
const holidays = [
`${y}-01-01`, // Den obnovy samostatného českého státu
`${y}-05-01`, // Svátek práce
`${y}-05-08`, // Den vítězství
`${y}-07-05`, // Den slovanských věrozvěstů Cyrila a Metoděje
`${y}-07-06`, // Den upálení mistra Jana Husa
`${y}-09-28`, // Den české státnosti
`${y}-10-28`, // Den vzniku samostatného československého státu
`${y}-11-17`, // Den boje za svobodu a demokracii
`${y}-12-24`, // Štědrý den
`${y}-12-25`, // 1. svátek vánoční
`${y}-12-26`, // 2. svátek vánoční
];
// Easter-based
const easterSunday = getEasterSunday(year);
const easterDate = new Date(easterSunday);
const goodFriday = new Date(easterDate);
goodFriday.setDate(goodFriday.getDate() - 2);
const easterMonday = new Date(easterDate);
easterMonday.setDate(easterMonday.getDate() + 1);
const fmt = (d: Date) =>
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
holidays.push(fmt(goodFriday)); // Velký pátek
holidays.push(fmt(easterMonday)); // Velikonoční pondělí
holidays.sort();
holidayCache.set(year, holidays);
return holidays;
}
/** Check if a date string (YYYY-MM-DD) is a Czech public holiday */
export function isHoliday(dateStr: string): boolean {
const year = parseInt(dateStr.substring(0, 4), 10);
return getHolidays(year).includes(dateStr);
}
/** Business days in a month (Mon-Fri excluding public holidays) */
export function getBusinessDaysInMonth(
year: number,
month: number,
upToDay?: number,
): number {
const holidays = getHolidays(year);
let count = 0;
const daysInMonth = new Date(year, month + 1, 0).getDate();
const maxDay = upToDay ? Math.min(upToDay, daysInMonth) : daysInMonth;
for (let day = 1; day <= maxDay; day++) {
const date = new Date(year, month, day);
const dow = date.getDay();
if (dow !== 0 && dow !== 6) {
const dateStr = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
if (!holidays.includes(dateStr)) {
count++;
}
}
}
return count;
}
/** Monthly work fund in hours (business days × 8) */
export function getMonthlyWorkFund(
year: number,
month: number,
upToDay?: number,
): number {
return getBusinessDaysInMonth(year, month, upToDay) * 8;
}