/** * Holiday Functions * German public holidays calculation and checking */ /** * Check if date is weekend */ function isWeekend(date) { const day = date.getDay(); return day === 0 || day === 6; // Sunday or Saturday } /** * Calculate Easter Sunday for a given year (Gauss algorithm) */ function getEasterSunday(year) { 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 new Date(year, month - 1, day); } /** * Get all public holidays for a given year and Bundesland */ function getPublicHolidays(year, bundesland) { const holidays = []; // Fixed holidays (all states) holidays.push({ date: new Date(year, 0, 1), name: 'Neujahr' }); holidays.push({ date: new Date(year, 4, 1), name: 'Tag der Arbeit' }); holidays.push({ date: new Date(year, 9, 3), name: 'Tag der Deutschen Einheit' }); holidays.push({ date: new Date(year, 11, 25), name: '1. Weihnachtstag' }); holidays.push({ date: new Date(year, 11, 26), name: '2. Weihnachtstag' }); // Company-provided holiday: Christmas Eve (24.12) or New Year's Eve (31.12) // Default to Christmas if companyHolidayPreference is not defined const companyHolidayPref = typeof companyHolidayPreference !== 'undefined' ? companyHolidayPreference : 'christmas'; if (companyHolidayPref === 'christmas') { holidays.push({ date: new Date(year, 11, 24), name: 'Heiligabend (Betriebsfrei)' }); } else if (companyHolidayPref === 'newyearseve') { holidays.push({ date: new Date(year, 11, 31), name: 'Silvester (Betriebsfrei)' }); } // Heilige Drei Könige (BW, BY, ST) if (['BW', 'BY', 'ST'].includes(bundesland)) { holidays.push({ date: new Date(year, 0, 6), name: 'Heilige Drei Könige' }); } // Internationaler Frauentag (BE, MV since 2023) if (['BE'].includes(bundesland) || (bundesland === 'MV' && year >= 2023)) { holidays.push({ date: new Date(year, 2, 8), name: 'Internationaler Frauentag' }); } // Weltkindertag (TH since 2019) if (bundesland === 'TH' && year >= 2019) { holidays.push({ date: new Date(year, 8, 20), name: 'Weltkindertag' }); } // Reformationstag (BB, MV, SN, ST, TH, + HB, HH, NI, SH since 2018) const reformationstagStates = ['BB', 'MV', 'SN', 'ST', 'TH']; if (year >= 2018) { reformationstagStates.push('HB', 'HH', 'NI', 'SH'); } if (reformationstagStates.includes(bundesland)) { holidays.push({ date: new Date(year, 9, 31), name: 'Reformationstag' }); } // Allerheiligen (BW, BY, NW, RP, SL) if (['BW', 'BY', 'NW', 'RP', 'SL'].includes(bundesland)) { holidays.push({ date: new Date(year, 10, 1), name: 'Allerheiligen' }); } // Buß- und Bettag (only SN) if (bundesland === 'SN') { // Buß- und Bettag is the Wednesday before November 23 let bussbettag = new Date(year, 10, 23); while (bussbettag.getDay() !== 3) { // 3 = Wednesday bussbettag.setDate(bussbettag.getDate() - 1); } bussbettag.setDate(bussbettag.getDate() - 7); // One week before holidays.push({ date: bussbettag, name: 'Buß- und Bettag' }); } // Easter-dependent holidays const easter = getEasterSunday(year); // Karfreitag (Good Friday) - 2 days before Easter (all states) const goodFriday = new Date(easter); goodFriday.setDate(easter.getDate() - 2); holidays.push({ date: goodFriday, name: 'Karfreitag' }); // Ostermontag (Easter Monday) - 1 day after Easter (all states) const easterMonday = new Date(easter); easterMonday.setDate(easter.getDate() + 1); holidays.push({ date: easterMonday, name: 'Ostermontag' }); // Christi Himmelfahrt (Ascension Day) - 39 days after Easter (all states) const ascension = new Date(easter); ascension.setDate(easter.getDate() + 39); holidays.push({ date: ascension, name: 'Christi Himmelfahrt' }); // Pfingstmontag (Whit Monday) - 50 days after Easter (all states) const whitMonday = new Date(easter); whitMonday.setDate(easter.getDate() + 50); holidays.push({ date: whitMonday, name: 'Pfingstmontag' }); // Fronleichnam (Corpus Christi) - 60 days after Easter (BW, BY, HE, NW, RP, SL, + some communities in SN, TH) if (['BW', 'BY', 'HE', 'NW', 'RP', 'SL'].includes(bundesland)) { const corpusChristi = new Date(easter); corpusChristi.setDate(easter.getDate() + 60); holidays.push({ date: corpusChristi, name: 'Fronleichnam' }); } // Mariä Himmelfahrt (Assumption of Mary) - August 15 (BY in some communities, SL) if (['SL'].includes(bundesland)) { holidays.push({ date: new Date(year, 7, 15), name: 'Mariä Himmelfahrt' }); } return holidays; } /** * Check if date is a public holiday * Returns the holiday name or null */ function getHolidayName(date) { const year = date.getFullYear(); const month = date.getMonth(); const day = date.getDate(); const holidays = getPublicHolidays(year, currentBundesland); // Compare year, month, and day directly (avoid timezone issues) const holiday = holidays.find(h => { return h.date.getFullYear() === year && h.date.getMonth() === month && h.date.getDate() === day; }); return holiday ? holiday.name : null; } /** * Check if date is a public holiday */ function isPublicHoliday(date) { return getHolidayName(date) !== null; } /** * Check if date is weekend or public holiday */ function isWeekendOrHoliday(date) { return isWeekend(date) || isPublicHoliday(date); }