Skip to content

Commit 4c30b33

Browse files
committed
Port molad and tachanun from ES6 to TS
1 parent b63c3f4 commit 4c30b33

9 files changed

+717
-8
lines changed

package-lock.json

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hebcal/hdate",
3-
"version": "0.9.3",
3+
"version": "0.9.4",
44
"description": "converts between Hebrew and Gregorian dates using Rata Die (R.D.) algorithm by Dershowitz and Reingold",
55
"author": "Michael J. Radwin (https://github.com/mjradwin)",
66
"contributors": [
@@ -39,7 +39,7 @@
3939
},
4040
"devDependencies": {
4141
"@ava/typescript": "^5.0.0",
42-
"@types/node": "20.12.11",
42+
"@types/node": "20.12.12",
4343
"ava": "^6.1.3",
4444
"gts": "^5.3.0",
4545
"typescript": "^5.4.5"

src/hdate.ts

+9
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ export function hebrew2abs(year: number, month: number, day: number): number {
126126
return EPOCH + elapsedDays(year) + tempabs - 1;
127127
}
128128

129+
/**
130+
* Converts Hebrew date to R.D. (Rata Die) fixed days.
131+
* R.D. 1 is the imaginary date Monday, January 1, 1 on the Gregorian
132+
* Calendar.
133+
*/
134+
export function hd2abs(hdate: SimpleHebrewDate): number {
135+
return hebrew2abs(hdate.yy, hdate.mm, hdate.dd);
136+
}
137+
129138
/**
130139
* @private
131140
*/

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ export * from './hdate';
33
export * from './anniversary';
44
export * from './gematriya';
55
export * from './omer';
6+
export * from './molad';
7+
export * from './tachanun';

src/modern.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {SimpleHebrewDate, hd2abs, months} from './hdate';
2+
3+
const SUN = 0;
4+
const TUE = 2;
5+
const FRI = 5;
6+
const SAT = 6;
7+
8+
const NISAN = months.NISAN;
9+
10+
/**
11+
* Yom HaShoah first observed in 1951.
12+
* When the actual date of Yom Hashoah falls on a Friday, the
13+
* state of Israel observes Yom Hashoah on the preceding
14+
* Thursday. When it falls on a Sunday, Yom Hashoah is observed
15+
* on the following Monday.
16+
* http://www.ushmm.org/remembrance/dor/calendar/
17+
*/
18+
export function dateYomHaShoah(year: number): SimpleHebrewDate | null {
19+
if (year < 5711) {
20+
return null;
21+
}
22+
let nisan27dt = {dd: 27, mm: NISAN, yy: year};
23+
const dow = hd2abs(nisan27dt) % 7;
24+
if (dow === FRI) {
25+
nisan27dt = {dd: 26, mm: NISAN, yy: year};
26+
} else if (dow === SUN) {
27+
nisan27dt = {dd: 28, mm: NISAN, yy: year};
28+
}
29+
return nisan27dt;
30+
}
31+
32+
/**
33+
* Yom HaAtzma'ut only celebrated after 1948
34+
*/
35+
export function dateYomHaZikaron(year: number): SimpleHebrewDate | null {
36+
if (year < 5708) {
37+
return null;
38+
}
39+
let day;
40+
const pesach = {dd: 15, mm: NISAN, yy: year};
41+
const pdow = hd2abs(pesach) % 7;
42+
if (pdow === SUN) {
43+
day = 2;
44+
} else if (pdow === SAT) {
45+
day = 3;
46+
} else if (year < 5764) {
47+
day = 4;
48+
} else if (pdow === TUE) {
49+
day = 5;
50+
} else {
51+
day = 4;
52+
}
53+
return {dd: day, mm: months.IYYAR, yy: year};
54+
}

src/molad.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import {monthsInYear} from './hdate';
2+
3+
/**
4+
* Represents a molad, the moment when the new moon is "born"
5+
*/
6+
export type Molad = {
7+
/** Hebrew year */
8+
readonly year: number;
9+
/** Hebrew month */
10+
readonly month: number;
11+
/** Day of Week (0=Sunday, 6=Saturday) */
12+
readonly dayOfWeek: number;
13+
/** hour of day (0-23) */
14+
readonly hour: number;
15+
/** minutes past hour (0-59) */
16+
readonly minutes: number;
17+
/** parts of a minute (0-17) */
18+
readonly chalakim: number;
19+
};
20+
21+
/**
22+
* Calculates the molad for a Hebrew month
23+
*/
24+
export function molad(year: number, month: number): Molad {
25+
let m_adj = month - 7;
26+
if (m_adj < 0) {
27+
m_adj += monthsInYear(year);
28+
}
29+
30+
const mElapsed =
31+
235 * Math.floor((year - 1) / 19) + // Months in complete 19 year lunar (Metonic) cycles so far
32+
12 * ((year - 1) % 19) + // Regular months in this cycle
33+
Math.floor((7 * ((year - 1) % 19) + 1) / 19) + // Leap months this cycle
34+
m_adj; // add elapsed months till the start of the molad of the month
35+
36+
const pElapsed = 204 + Math.floor(793 * (mElapsed % 1080));
37+
38+
const hElapsed =
39+
5 +
40+
12 * mElapsed +
41+
793 * Math.floor(mElapsed / 1080) +
42+
Math.floor(pElapsed / 1080) -
43+
6;
44+
45+
const parts = (pElapsed % 1080) + 1080 * (hElapsed % 24);
46+
47+
const chalakim = parts % 1080;
48+
49+
const day = 1 + 29 * mElapsed + Math.floor(hElapsed / 24);
50+
51+
return {
52+
year,
53+
month,
54+
dayOfWeek: day % 7,
55+
hour: hElapsed % 24,
56+
minutes: Math.floor(chalakim / 18),
57+
chalakim: chalakim % 18,
58+
};
59+
}

src/tachanun.ts

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
}

test/molad.spec.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const test = require('ava');
2+
const {molad} = require('../dist/cjs/molad');
3+
const {months} = require('../dist/cjs/hdate');
4+
5+
test('Molad', t => {
6+
const items = [
7+
[months.CHESHVAN, 3, 14, 42, 14],
8+
[months.KISLEV, 5, 3, 26, 15],
9+
[months.TEVET, 6, 16, 10, 16],
10+
[months.SHVAT, 1, 4, 54, 17],
11+
[months.ADAR_I, 2, 17, 39, 0],
12+
[months.NISAN, 4, 6, 23, 1],
13+
[months.IYYAR, 5, 19, 7, 2],
14+
[months.SIVAN, 0, 7, 51, 3],
15+
[months.TAMUZ, 1, 20, 35, 4],
16+
[months.AV, 3, 9, 19, 5],
17+
[months.ELUL, 4, 22, 3, 6],
18+
];
19+
20+
for (const item of items) {
21+
const [month, dow, hour, minutes, chalakim] = item;
22+
const m = molad(5769, month);
23+
t.is(m.dayOfWeek, dow);
24+
t.is(m.hour, hour);
25+
t.is(m.minutes, minutes);
26+
t.is(m.chalakim, chalakim);
27+
t.is(m.year, 5769);
28+
t.is(m.month, month);
29+
}
30+
});

0 commit comments

Comments
 (0)