Skip to content

Commit 059379c

Browse files
committed
Correct Tanakh Yomi calculations by running through entire year
1 parent 3996e4c commit 059379c

File tree

4 files changed

+339
-34
lines changed

4 files changed

+339
-34
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hebcal/learning",
3-
"version": "5.0.3",
3+
"version": "5.0.4",
44
"description": "Daily learning schedules: Daf Yomi, Mishna Yomi, etc",
55
"main": "dist/index.cjs",
66
"module": "dist/index.mjs",

src/masoretic.json

+20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
11
{
2+
"split":{
3+
"Joshua":{
4+
"4.1":"6.27-7.26",
5+
"4.2":"8.1-32"
6+
},
7+
"Jeremiah":{
8+
"9.1":"17:7-25",
9+
"9.2":"17:26-18:18"
10+
},
11+
"Song of Songs":{
12+
"1.1":"1:1-5:1",
13+
"1.2":"5:2-8:14"
14+
},
15+
"Ruth":{
16+
"1.1":"1:1-2:11",
17+
"1.2":"2:12-4:22"
18+
}
19+
},
20+
"regular":{
221
"Joshua":[
322
"1:1-3:6",
423
"3:7-4:23",
@@ -324,4 +343,5 @@
324343
"II Chronicles 34:2-35:5",
325344
"II Chronicles 35:6-36:23"
326345
]
346+
}
327347
}

src/tanakhYomi.js

+114-33
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import masoretic from './masoretic.json.js';
1111
const startDate = new Date(1948, 9, 26);
1212
export const tanakhYomiStart = greg.greg2abs(startDate);
1313

14+
const JOSHUA = 'Joshua';
1415
const JEREMIAH = 'Jeremiah';
1516
const RUTH = 'Ruth';
1617
const SHIR_HASHIRIM = 'Song of Songs';
1718

1819
const books = [
19-
['Joshua', 14],
20+
[JOSHUA, 14],
2021
['Judges', 14],
2122
['Samuel', 34],
2223
['Kings', 35],
@@ -63,51 +64,48 @@ export function tanakhYomi(date) {
6364
const hyear = hd.getFullYear();
6465
const rh = HDate.hebrew2abs(hyear, months.TISHREI, 1);
6566
const startAbs = rh + 22;
66-
const table = books.slice();
67-
const longCheshvan = HDate.longCheshvan(hyear);
68-
let longRuth = false;
69-
if (HDate.isLeapYear(hyear) || longCheshvan) {
70-
table[12] = {name: RUTH, blatt: 2}; // Ruth gets 2 days
71-
longRuth = true;
72-
}
73-
let longShirHaShirim = false;
74-
if (longCheshvan) {
75-
table[11] = {name: SHIR_HASHIRIM, blatt: 2}; // Shir HaShirim gets 2 days
76-
longShirHaShirim = true;
77-
}
78-
let longJeremiah = false;
79-
if (longCheshvan && !HDate.shortKislev(hyear)) {
80-
table[5] = {name: JEREMIAH, blatt: 32}; // Jeremiah 9 gets split across two days
81-
longJeremiah = true;
82-
}
83-
8467
if (cday < startAbs) {
85-
let blatt = rh % 7 === 6 ? 10 : 12;
68+
const rhDow = rh % 7;
69+
let blatt = rhDow === 4 ? 11 : rhDow === 6 ? 10 : 12;
8670
for (let i = rh + 2; i < cday; i++) {
8771
const hdate = new HDate(i);
8872
if (!skipDay(hdate)) {
8973
blatt++;
9074
}
9175
}
76+
if (blatt === 26) {
77+
throw new Error(`${hd.toString()} Chronicles ${blatt}`);
78+
}
9279
return new TanakhYomi('Chronicles', blatt);
9380
}
94-
9581
let total = 0;
9682
for (let i = startAbs; i < cday; i++) {
9783
const hdate = new HDate(i);
9884
if (!skipDay(hdate)) {
9985
total++;
10086
}
10187
}
88+
const readingTable = makeReadingTable(hyear);
89+
const table = readingTable.table;
90+
10291
for (let j = 0; j < table.length; j++) {
10392
if (total < table[j].blatt) {
10493
const blatt = total + 1;
10594
const name = table[j].name;
106-
if ((longShirHaShirim && name === SHIR_HASHIRIM) ||
107-
(longRuth && name === RUTH)) {
95+
if ((readingTable.longShirHaShirim && name === SHIR_HASHIRIM) ||
96+
(readingTable.longRuth && name === RUTH)) {
10897
return new TanakhYomi(name, '1.' + blatt);
10998
}
110-
if (longJeremiah && name === JEREMIAH && blatt >= 9) {
99+
if (readingTable.longJoshua && name === JOSHUA && blatt >= 4) {
100+
if (blatt === 4) {
101+
return new TanakhYomi(name, '4.1');
102+
} else if (blatt === 5) {
103+
return new TanakhYomi(name, '4.2');
104+
} else {
105+
return new TanakhYomi(name, blatt - 1);
106+
}
107+
}
108+
if (readingTable.longJeremiah && name === JEREMIAH && blatt >= 9) {
111109
if (blatt === 9) {
112110
return new TanakhYomi(name, '9.1');
113111
} else if (blatt === 10) {
@@ -146,11 +144,84 @@ function skipDay(hd) {
146144
return false;
147145
}
148146

149-
const splitSeder = {
150-
'Jeremiah': {'9.1': '17:7-25', '9.2': '17:26-18:18'},
151-
'Song of Songs': {'1.1': '1:1-5:1', '1.2': '5:2-8:14'},
152-
'Ruth': {'1.1': '1:1-2:11', '1.2': '2:12-4:22'},
153-
};
147+
/**
148+
* @private
149+
* @param {number} year
150+
* @return {number}
151+
*/
152+
function calculateNumDaysToRead(year) {
153+
const startAbs = HDate.hebrew2abs(year, months.TISHREI, 23);
154+
const endAbs = HDate.hebrew2abs(year + 1, months.TISHREI, 22);
155+
let included = 0;
156+
for (let abs = startAbs; abs <= endAbs; abs++) {
157+
const hdate = new HDate(abs);
158+
if (!skipDay(hdate)) {
159+
included++;
160+
}
161+
}
162+
return included;
163+
}
164+
165+
/**
166+
* A common year can have a length of 353, 354 or 355 days
167+
* A leap year can have a length of 383, 384 or 385 days
168+
*
169+
* Common years can have
170+
* 293 chapters - no extra chapters (45%)
171+
* 294 chapters - 1 extra chapter (5%)
172+
* 295 chapters - 2 extra chapters (31%)
173+
* 296 chapters - 3 extra chapters (19%)
174+
* Leap years can have
175+
* 318 chapters - no extra chapters (10%)
176+
* 319 chapters - 1 extra chapter (30%)
177+
* 320 chapters - 2 extra chapters (47%)
178+
* 222 chapters - 4 extra chapters (12%)
179+
*
180+
* @private
181+
* @param {number} year
182+
* @return {any}
183+
*/
184+
function makeReadingTable(year) {
185+
const numDays = calculateNumDaysToRead(year);
186+
const count = HDate.isLeapYear(year) ? numDays - 25 : numDays;
187+
const extra = count - 293;
188+
const table = books.slice();
189+
const result = {
190+
numDays,
191+
table,
192+
longRuth: false,
193+
longShirHaShirim: false,
194+
longJeremiah: false,
195+
longJoshua: false,
196+
};
197+
switch (extra) {
198+
case 0:
199+
return result;
200+
case 4:
201+
// Joshua 4 gets split across two days
202+
table[0] = {name: JOSHUA, blatt: 15};
203+
result.longJoshua = true;
204+
/* FALLTHROUGH */
205+
case 3:
206+
// Jeremiah 9 gets split across two days
207+
table[5] = {name: JEREMIAH, blatt: 32};
208+
result.longJeremiah = true;
209+
/* FALLTHROUGH */
210+
case 2:
211+
// Shir HaShirim gets 2 days
212+
table[11] = {name: SHIR_HASHIRIM, blatt: 2};
213+
result.longShirHaShirim = true;
214+
/* FALLTHROUGH */
215+
case 1:
216+
// Ruth gets 2 days
217+
table[12] = {name: RUTH, blatt: 2};
218+
result.longRuth = true;
219+
break;
220+
default:
221+
throw new Error(`${year} => ${numDays} ${count} ${extra}`);
222+
}
223+
return result;
224+
}
154225

155226
/**
156227
* Returns the Daf Yomi for given date
@@ -163,9 +234,12 @@ export class TanakhYomi extends DafPage {
163234
*/
164235
constructor(name, blatt) {
165236
super(name, blatt);
166-
const seders = masoretic[name];
237+
const seders = masoretic.regular[name];
167238
const verses = typeof blatt === 'number' ?
168-
seders[blatt - 1] : splitSeder[name][blatt];
239+
seders[blatt - 1] : masoretic.split[name][blatt];
240+
if (!verses) {
241+
throw new Error(`${name} ${blatt}`);
242+
}
169243
const firstChar = verses.charCodeAt(0);
170244
this.verses = firstChar >= 48 && firstChar <= 57 ?
171245
`${name} ${verses}` : verses;
@@ -182,10 +256,17 @@ export class TanakhYomi extends DafPage {
182256
locale = locale.toLowerCase();
183257
}
184258
const name = Locale.gettext(this.name, locale);
259+
const blatt = this.blatt;
185260
if (locale === 'he' || locale === 'he-x-nonikud') {
186-
return name + ' ס׳ ' + gematriya(this.blatt);
261+
const prefix = name + ' ס׳ ';
262+
if (typeof blatt === 'string') {
263+
const major = blatt[0];
264+
const minor = blatt[2];
265+
return prefix + gematriya(+major) + minor;
266+
}
267+
return prefix + gematriya(blatt);
187268
}
188-
return name + ' Seder ' + this.blatt;
269+
return name + ' Seder ' + blatt;
189270
}
190271
}
191272

0 commit comments

Comments
 (0)