UNPKG

@hebcal/core

Version:

A perpetual Jewish Calendar API

322 lines (315 loc) 13.6 kB
/*! @hebcal/core v5.10.1, distributed under GPLv2 https://www.gnu.org/licenses/gpl-2.0.txt */ import { months, HDate } from '@hebcal/hdate'; import QuickLRU from 'quick-lru'; import { flags } from './event.js'; import { dateYomHaShoah, dateYomHaZikaron } from './modern.js'; import { getSedra } from './sedra.js'; import { staticHolidays, holidayDesc, staticModernHolidays } from './staticHolidays.js'; import { YomKippurKatanEvent } from './YomKippurKatanEvent.js'; import { HolidayEvent, RoshHashanaEvent, ChanukahEvent, AsaraBTevetEvent, RoshChodeshEvent } from './HolidayEvent.js'; /* Hebcal - A Jewish Calendar Generator Copyright (c) 1994-2020 Danny Sadinoff Portions copyright Eyal Schachter and Michael J. Radwin https://github.com/hebcal/hebcal-es6 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** @private */ function observedInIsrael(ev) { return ev.observedInIsrael(); } /** @private */ function observedInDiaspora(ev) { return ev.observedInDiaspora(); } /** @private */ function holidayFilter(il) { return il ? observedInIsrael : observedInDiaspora; } /** * Returns an array of Events on this date (or `undefined` if no events) * @param date Hebrew Date, Gregorian date, or absolute R.D. day number * @param [il] use the Israeli schedule for holidays */ function getHolidaysOnDate(date, il) { const hd = HDate.isHDate(date) ? date : new HDate(date); const hdStr = hd.toString(); const yearMap = getHolidaysForYear_(hd.getFullYear()); const events = yearMap.get(hdStr); // if il isn't a boolean return both diaspora + IL for day if (typeof il === 'undefined' || typeof events === 'undefined') { return events; } const filtered = events.filter(holidayFilter(il)); return filtered; } const CHAG = flags.CHAG; const IL_ONLY = flags.IL_ONLY; const LIGHT_CANDLES_TZEIS = flags.LIGHT_CANDLES_TZEIS; const CHANUKAH_CANDLES = flags.CHANUKAH_CANDLES; const MINOR_FAST = flags.MINOR_FAST; const SPECIAL_SHABBAT = flags.SPECIAL_SHABBAT; const MODERN_HOLIDAY = flags.MODERN_HOLIDAY; const MAJOR_FAST = flags.MAJOR_FAST; const MINOR_HOLIDAY = flags.MINOR_HOLIDAY; const EREV = flags.EREV; const SUN = 0; const TUE = 2; const THU = 4; const FRI = 5; const SAT = 6; const NISAN = months.NISAN; const TAMUZ = months.TAMUZ; const AV = months.AV; const TISHREI = months.TISHREI; const KISLEV = months.KISLEV; const TEVET = months.TEVET; const ADAR_I = months.ADAR_I; const ADAR_II = months.ADAR_II; const emojiIsraelFlag = { emoji: '🇮🇱' }; const chanukahEmoji = '🕎'; const yearCache = new QuickLRU({ maxSize: 400 }); const KEYCAP_DIGITS = [ '0️⃣', '1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', ]; /** * Lower-level holidays interface, which returns a `Map` of `Event`s indexed by * `HDate.toString()`. These events must filtered especially for `flags.IL_ONLY` * or `flags.CHUL_ONLY` depending on Israel vs. Diaspora holiday scheme. * @private */ function getHolidaysForYear_(year) { if (typeof year !== 'number') { throw new TypeError(`bad Hebrew year: ${year}`); } else if (year < 1 || year > 32658) { throw new RangeError(`Hebrew year ${year} out of range 1-32658`); } const cached = yearCache.get(year); if (cached) { return cached; } const RH = new HDate(1, TISHREI, year); const pesach = new HDate(15, NISAN, year); const map = new Map(); function add(...events) { for (const ev of events) { const key = ev.date.toString(); const arr = map.get(key); if (typeof arr === 'object') { if (arr[0].getFlags() & EREV) { arr.unshift(ev); } else { arr.push(ev); } } else { map.set(key, [ev]); } } } for (const h of staticHolidays) { const hd = new HDate(h.dd, h.mm, year); const ev = new HolidayEvent(hd, h.desc, h.flags); if (h.emoji) ev.emoji = h.emoji; if (h.chmDay) ev.cholHaMoedDay = h.chmDay; add(ev); } // standard holidays that don't shift based on year add(new RoshHashanaEvent(RH, year, CHAG | LIGHT_CANDLES_TZEIS)); // Variable date holidays const tzomGedaliahDay = RH.getDay() === THU ? 4 : 3; add(new HolidayEvent(new HDate(tzomGedaliahDay, TISHREI, year), holidayDesc.TZOM_GEDALIAH, MINOR_FAST)); // first SAT after RH add(new HolidayEvent(new HDate(HDate.dayOnOrBefore(SAT, 7 + RH.abs())), holidayDesc.SHABBAT_SHUVA, SPECIAL_SHABBAT)); const rchTevet = HDate.shortKislev(year) ? new HDate(1, TEVET, year) : new HDate(30, KISLEV, year); add(new HolidayEvent(rchTevet, holidayDesc.CHAG_HABANOT, MINOR_HOLIDAY)); // yes, we know Kislev 30-32 are wrong // HDate() corrects the month automatically for (let candles = 2; candles <= 8; candles++) { const hd = new HDate(23 + candles, KISLEV, year); add(new ChanukahEvent(hd, `Chanukah: ${candles} Candles`, MINOR_HOLIDAY | CHANUKAH_CANDLES, { chanukahDay: candles - 1, emoji: chanukahEmoji + KEYCAP_DIGITS[candles], })); } add(new ChanukahEvent(new HDate(32, KISLEV, year), holidayDesc.CHANUKAH_8TH_DAY, MINOR_HOLIDAY, { chanukahDay: 8, emoji: chanukahEmoji })); add(new AsaraBTevetEvent(new HDate(10, TEVET, year), holidayDesc.ASARA_BTEVET, MINOR_FAST)); const pesachAbs = pesach.abs(); add(new HolidayEvent(new HDate(HDate.dayOnOrBefore(SAT, pesachAbs - 43)), holidayDesc.SHABBAT_SHEKALIM, SPECIAL_SHABBAT), new HolidayEvent(new HDate(HDate.dayOnOrBefore(SAT, pesachAbs - 30)), holidayDesc.SHABBAT_ZACHOR, SPECIAL_SHABBAT), new HolidayEvent(new HDate(pesachAbs - (pesach.getDay() === TUE ? 33 : 31)), holidayDesc.TAANIT_ESTHER, MINOR_FAST)); const haChodeshAbs = HDate.dayOnOrBefore(SAT, pesachAbs - 14); add(new HolidayEvent(new HDate(haChodeshAbs - 7), holidayDesc.SHABBAT_PARAH, SPECIAL_SHABBAT), new HolidayEvent(new HDate(haChodeshAbs), holidayDesc.SHABBAT_HACHODESH, SPECIAL_SHABBAT), new HolidayEvent(new HDate(HDate.dayOnOrBefore(SAT, pesachAbs - 1)), holidayDesc.SHABBAT_HAGADOL, SPECIAL_SHABBAT), new HolidayEvent( // if the fast falls on Shabbat, move to Thursday pesach.prev().getDay() === SAT ? pesach.onOrBefore(THU) : new HDate(14, NISAN, year), holidayDesc.TAANIT_BECHOROT, MINOR_FAST)); add(new HolidayEvent(new HDate(HDate.dayOnOrBefore(SAT, new HDate(1, TISHREI, year + 1).abs() - 4)), holidayDesc.LEIL_SELICHOT, MINOR_HOLIDAY, { emoji: '🕍' })); if (pesach.getDay() === SUN) { add(new HolidayEvent(new HDate(16, ADAR_II, year), holidayDesc.PURIM_MESHULASH, MINOR_HOLIDAY)); } if (HDate.isLeapYear(year)) { add(new HolidayEvent(new HDate(14, ADAR_I, year), holidayDesc.PURIM_KATAN, MINOR_HOLIDAY, { emoji: '🎭️' })); add(new HolidayEvent(new HDate(15, ADAR_I, year), holidayDesc.SHUSHAN_PURIM_KATAN, MINOR_HOLIDAY, { emoji: '🎭️' })); } const nisan27dt = dateYomHaShoah(year); if (nisan27dt) { add(new HolidayEvent(nisan27dt, holidayDesc.YOM_HASHOAH, MODERN_HOLIDAY)); } const yomHaZikaronDt = dateYomHaZikaron(year); if (yomHaZikaronDt) { add(new HolidayEvent(yomHaZikaronDt, holidayDesc.YOM_HAZIKARON, MODERN_HOLIDAY, emojiIsraelFlag), new HolidayEvent(yomHaZikaronDt.next(), holidayDesc.YOM_HAATZMA_UT, MODERN_HOLIDAY, emojiIsraelFlag)); } for (const h of staticModernHolidays) { if (year >= h.firstYear) { let hd = new HDate(h.dd, h.mm, year); const dow = hd.getDay(); if (h.friSatMovetoThu && (dow === FRI || dow === SAT)) { hd = hd.onOrBefore(THU); } else if (h.friPostponeToSun && dow === FRI) { hd = new HDate(hd.abs() + 2); } else if (h.satPostponeToSun && dow === SAT) { hd = hd.next(); } const mask = h.chul ? MODERN_HOLIDAY : MODERN_HOLIDAY | IL_ONLY; const ev = new HolidayEvent(hd, h.desc, mask); if (!h.suppressEmoji) { ev.emoji = '🇮🇱'; } add(ev); } } let tamuz17 = new HDate(17, TAMUZ, year); let tamuz17attrs; if (tamuz17.getDay() === SAT) { tamuz17 = new HDate(18, TAMUZ, year); tamuz17attrs = { observed: true }; } add(new HolidayEvent(tamuz17, holidayDesc.TZOM_TAMMUZ, MINOR_FAST, tamuz17attrs)); let av9dt = new HDate(9, AV, year); let av9title = holidayDesc.TISHA_BAV; let av9attrs; if (av9dt.getDay() === SAT) { av9dt = av9dt.next(); av9attrs = { observed: true }; av9title += ' (observed)'; } const av9abs = av9dt.abs(); add(new HolidayEvent(new HDate(HDate.dayOnOrBefore(SAT, av9abs)), holidayDesc.SHABBAT_CHAZON, SPECIAL_SHABBAT), new HolidayEvent(av9dt.prev(), holidayDesc.EREV_TISHA_BAV, EREV | MAJOR_FAST, av9attrs), new HolidayEvent(av9dt, av9title, MAJOR_FAST, av9attrs), new HolidayEvent(new HDate(HDate.dayOnOrBefore(SAT, av9abs + 7)), holidayDesc.SHABBAT_NACHAMU, SPECIAL_SHABBAT)); const monthsInYear = HDate.monthsInYear(year); for (let month = 1; month <= monthsInYear; month++) { const monthName = HDate.getMonthName(month, year); if ((month === NISAN ? HDate.daysInMonth(HDate.monthsInYear(year - 1), year - 1) : HDate.daysInMonth(month - 1, year)) === 30) { add(new RoshChodeshEvent(new HDate(1, month, year), monthName)); add(new RoshChodeshEvent(new HDate(30, month - 1, year), monthName)); } else if (month !== TISHREI) { add(new RoshChodeshEvent(new HDate(1, month, year), monthName)); } } // Begin: Yom Kippur Katan // start at Iyyar because one may not fast during Nisan for (let month = months.IYYAR; month <= monthsInYear; month++) { const nextMonth = month + 1; // Yom Kippur Katan is not observed on the day before Rosh Hashanah. // Not observed prior to Rosh Chodesh Cheshvan because Yom Kippur has just passed. // Not observed before Rosh Chodesh Tevet, because that day is Hanukkah. if (nextMonth === TISHREI || nextMonth === months.CHESHVAN || nextMonth === TEVET) { continue; } let ykk = new HDate(29, month, year); const dow = ykk.getDay(); if (dow === FRI || dow === SAT) { ykk = ykk.onOrBefore(THU); } const nextMonthName = HDate.getMonthName(nextMonth, year); const ev = new YomKippurKatanEvent(ykk, nextMonthName); add(ev); } const sedra = getSedra(year, false); const beshalachHd = sedra.find(15); add(new HolidayEvent(beshalachHd, holidayDesc.SHABBAT_SHIRAH, SPECIAL_SHABBAT)); // Birkat Hachamah appears only once every 28 years const birkatHaChama = getBirkatHaChama(year); if (birkatHaChama) { const hd = new HDate(birkatHaChama); add(new HolidayEvent(hd, holidayDesc.BIRKAT_HACHAMAH, MINOR_HOLIDAY, { emoji: '☀️' })); } yearCache.set(year, map); return map; } /** * Birkat Hachamah appears only once every 28 years. * Although almost always in Nisan, it can occur in Adar II. * - 27 Adar II 5461 (Gregorian year 1701) * - 29 Adar II 5993 (Gregorian year 2233) * * Due to drift, this will eventually slip into Iyyar * - 2 Iyyar 7141 (Gregorian year 3381) * @private */ function getBirkatHaChama(year) { const leap = HDate.isLeapYear(year); const startMonth = leap ? ADAR_II : NISAN; const startDay = leap ? 20 : 1; const baseRd = HDate.hebrew2abs(year, startMonth, startDay); for (let day = 0; day <= 40; day++) { const abs = baseRd + day; const elapsed = abs + 1373429; if (elapsed % 10227 === 172) { return abs; } } return 0; } /** * Returns an array of holidays for the year * @param year Hebrew year * @param il use the Israeli schedule for holidays */ function getHolidaysForYearArray(year, il) { const yearMap = getHolidaysForYear_(year); const startAbs = HDate.hebrew2abs(year, TISHREI, 1); const endAbs = HDate.hebrew2abs(year + 1, TISHREI, 1) - 1; let events = []; const myFilter = il ? observedInIsrael : observedInDiaspora; for (let absDt = startAbs; absDt <= endAbs; absDt++) { const hd = new HDate(absDt); const holidays = yearMap.get(hd.toString()); if (holidays) { const filtered = holidays.filter(myFilter); events = events.concat(filtered); } } return events; } export { getHolidaysForYearArray, getHolidaysForYear_, getHolidaysOnDate }; //# sourceMappingURL=holidays.js.map