|
| 1 | +import { |
| 2 | + SimpleHebrewDate, |
| 3 | + months, |
| 4 | + hd2abs, |
| 5 | + abs2hebrew, |
| 6 | + isLeapYear, |
| 7 | + monthsInYear, |
| 8 | + daysInMonth, |
| 9 | +} from './hdate'; |
| 10 | +import {dateYomHaZikaron} from './modern'; |
| 11 | + |
| 12 | +function range(start: number, end: number): number[] { |
| 13 | + const arr = []; |
| 14 | + for (let i = start; i <= end; i++) { |
| 15 | + arr.push(i); |
| 16 | + } |
| 17 | + return arr; |
| 18 | +} |
| 19 | + |
| 20 | +const NONE: TachanunResult = { |
| 21 | + shacharit: false, |
| 22 | + mincha: false, |
| 23 | + allCongs: false, |
| 24 | +}; |
| 25 | + |
| 26 | +export type TachanunResult = { |
| 27 | + /** Tachanun is said at Shacharit */ |
| 28 | + shacharit: boolean; |
| 29 | + /** Tachanun is said at Mincha */ |
| 30 | + mincha: boolean; |
| 31 | + /** All congregations say Tachanun on the day */ |
| 32 | + allCongs: boolean; |
| 33 | +}; |
| 34 | + |
| 35 | +export function tachanun(hdate: SimpleHebrewDate, il: boolean): TachanunResult { |
| 36 | + return tachanun0(hdate, il, true); |
| 37 | +} |
| 38 | + |
| 39 | +function tachanun0( |
| 40 | + hdate: SimpleHebrewDate, |
| 41 | + il: boolean, |
| 42 | + checkNext: boolean |
| 43 | +): TachanunResult { |
| 44 | + const year = hdate.yy; |
| 45 | + const dates = tachanunYear(year, il); |
| 46 | + const abs = hd2abs(hdate); |
| 47 | + if (dates.none.indexOf(abs) > -1) { |
| 48 | + return NONE; |
| 49 | + } |
| 50 | + const dow = abs % 7; |
| 51 | + const ret: TachanunResult = { |
| 52 | + shacharit: false, |
| 53 | + mincha: false, |
| 54 | + allCongs: false, |
| 55 | + }; |
| 56 | + if (dates.some.indexOf(abs) === -1) { |
| 57 | + ret.allCongs = true; |
| 58 | + } |
| 59 | + if (dow !== 6) { |
| 60 | + ret.shacharit = true; |
| 61 | + } |
| 62 | + const tomorrow = abs + 1; |
| 63 | + if (checkNext && dates.yesPrev.indexOf(tomorrow) === -1) { |
| 64 | + const tmp = tachanun0(abs2hebrew(tomorrow), il, false); |
| 65 | + ret.mincha = tmp.shacharit; |
| 66 | + } else { |
| 67 | + ret.mincha = dow !== 5; |
| 68 | + } |
| 69 | + if (ret.allCongs && !ret.mincha && !ret.shacharit) { |
| 70 | + return NONE; |
| 71 | + } |
| 72 | + return ret; |
| 73 | +} |
| 74 | + |
| 75 | +function simpleHD(dd: number, mm: number, yy: number): SimpleHebrewDate { |
| 76 | + return {yy, mm, dd}; |
| 77 | +} |
| 78 | + |
| 79 | +function tachanunYear(year: number, il: boolean): any { |
| 80 | + const leap = isLeapYear(year); |
| 81 | + const miy = monthsInYear(year); |
| 82 | + let av9dt = simpleHD(9, months.AV, year); |
| 83 | + const av9abs = hd2abs(av9dt); |
| 84 | + if (av9abs % 7 === 6) { |
| 85 | + av9dt = abs2hebrew(av9abs + 1); |
| 86 | + } |
| 87 | + let shushPurim = simpleHD(15, months.ADAR_II, year); |
| 88 | + const shushPurimAbs = hd2abs(shushPurim); |
| 89 | + if (shushPurimAbs % 7 === 6) { |
| 90 | + shushPurim = abs2hebrew(shushPurimAbs + 1); |
| 91 | + } |
| 92 | + const empty: SimpleHebrewDate[] = []; |
| 93 | + const none: SimpleHebrewDate[] = empty.concat( |
| 94 | + // Rosh Chodesh - 1st of every month. Also includes RH day 1 (1 Tishrei) |
| 95 | + range(1, miy).map(month => simpleHD(1, month, year)), |
| 96 | + // Rosh Chodesh - 30th of months that have one |
| 97 | + range(1, miy) |
| 98 | + .filter(month => daysInMonth(month, year) === 30) |
| 99 | + .map(month => simpleHD(30, month, year)), |
| 100 | + simpleHD(2, months.TISHREI, year), // Rosh Hashana II |
| 101 | + // entire month of Nisan |
| 102 | + range(1, daysInMonth(months.NISAN, year)).map(mday => |
| 103 | + simpleHD(mday, months.NISAN, year) |
| 104 | + ), |
| 105 | + simpleHD(18, months.IYYAR, year), // Lag BaOmer |
| 106 | + // Rosh Chodesh Sivan thru Isru Chag |
| 107 | + range(1, 8 - (il ? 1 : 0)).map(mday => simpleHD(mday, months.SIVAN, year)), |
| 108 | + av9dt, // Tisha B'Av |
| 109 | + simpleHD(15, months.AV, year), // Tu B'Av |
| 110 | + simpleHD(29, months.ELUL, year), // Erev Rosh Hashanah |
| 111 | + // Erev Yom Kippur thru Isru Chag |
| 112 | + range(9, 24 - (il ? 1 : 0)).map(mday => |
| 113 | + simpleHD(mday, months.TISHREI, year) |
| 114 | + ), |
| 115 | + // Chanukah |
| 116 | + range(25, 33).map(mday => simpleHD(mday, months.KISLEV, year)), |
| 117 | + simpleHD(15, months.SHVAT, year), // Tu BiShvat |
| 118 | + simpleHD(14, months.ADAR_II, year), // Purim |
| 119 | + shushPurim, |
| 120 | + leap ? simpleHD(14, months.ADAR_I, year) : [] // Purim Katan |
| 121 | + ); |
| 122 | + const some: SimpleHebrewDate[] = empty.concat( |
| 123 | + // Until 14 Sivan |
| 124 | + range(1, 13).map(mday => simpleHD(mday, months.SIVAN, year)), |
| 125 | + // Until after Rosh Chodesh Cheshvan |
| 126 | + range(20, 31).map(mday => simpleHD(mday, months.TISHREI, year)), |
| 127 | + simpleHD(14, months.IYYAR, year), // Pesach Sheini |
| 128 | + // Yom Yerushalayim |
| 129 | + year >= 5727 ? simpleHD(28, months.IYYAR, year) : [] |
| 130 | + ); |
| 131 | + // Yom HaAtzma'ut, which changes based on day of week |
| 132 | + const yomHaZikaron = dateYomHaZikaron(year); |
| 133 | + if (yomHaZikaron !== null) { |
| 134 | + some.push(abs2hebrew(hd2abs(yomHaZikaron) + 1)); |
| 135 | + } |
| 136 | + const yesPrev: SimpleHebrewDate[] = [ |
| 137 | + simpleHD(29, months.ELUL, year - 1), // Erev Rosh Hashanah |
| 138 | + simpleHD(9, months.TISHREI, year), // Erev Yom Kippur |
| 139 | + simpleHD(14, months.IYYAR, year), // Pesach Sheini |
| 140 | + ]; |
| 141 | + return { |
| 142 | + none: none.map(hd => hd2abs(hd)).sort((a, b) => a - b), |
| 143 | + some: some.map(hd => hd2abs(hd)).sort((a, b) => a - b), |
| 144 | + yesPrev: yesPrev.map(hd => hd2abs(hd)).sort((a, b) => a - b), |
| 145 | + }; |
| 146 | +} |
0 commit comments