Skip to content

Commit d33b8fe

Browse files
author
Pinny Markowitz
committed
added AmudYomiBavliDirshu calculator
1 parent 530e745 commit d33b8fe

6 files changed

Lines changed: 160 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99
- `jewish_calendar.is_taanis_bechorim()` function added
1010
- `jewish_calendar.is_shabbos_mevorchim()` function added
1111
- `jewish_calendar.significant_shabbos()` function added
12+
- New `AmudYomiBavliDirshu` calculator for Amud Yomi Bavli following the Dirshu learning schedule
13+
- Support for limudim containing fractional units (required for Amud Yomi)
1214

1315
### Fixed
1416
- Bug in `jewish_date.__add__()` and `jewish_date.__sub__()` returned the base JewishDate type even when inherited.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import unittest
2+
from datetime import date
3+
4+
from zmanim.limudim.calculators.amud_yomi_bavli_dirshu import AmudYomiBavliDirshu
5+
6+
7+
class TestAmudYomiBavliDirshuCalculator(unittest.TestCase):
8+
def test_simple_date(self):
9+
test_date = date(2024, 5, 30)
10+
limud = AmudYomiBavliDirshu().limud(test_date)
11+
self.assertEqual(limud.start_date(), test_date)
12+
self.assertEqual(limud.end_date(), test_date)
13+
self.assertEqual(limud.description(), 'shabbos 53a')
14+
15+
def test_before_cycle_began(self):
16+
test_date = date(2023, 1, 1)
17+
limud = AmudYomiBavliDirshu().limud(test_date)
18+
self.assertIsNone(limud)
19+
20+
def test_first_day_of_cycle(self):
21+
test_date = date(2038, 8, 4)
22+
limud = AmudYomiBavliDirshu().limud(test_date)
23+
self.assertEqual(limud.start_date(), test_date)
24+
self.assertEqual(limud.end_date(), test_date)
25+
self.assertEqual(limud.description(), 'berachos 2a')
26+
27+
def test_last_day_of_cycle(self):
28+
test_date = date(2038, 8, 3)
29+
limud = AmudYomiBavliDirshu().limud(test_date)
30+
self.assertEqual(limud.description(), 'niddah 73a')
31+
32+
def test_end_of_meilah(self):
33+
test_date = date(2038, 2, 10)
34+
limud = AmudYomiBavliDirshu().limud(test_date)
35+
self.assertEqual(limud.description(), 'meilah 22a')
36+
37+
def test_beginning_of_kinnim(self):
38+
test_date = date(2038, 2, 11)
39+
limud = AmudYomiBavliDirshu().limud(test_date)
40+
self.assertEqual(limud.description(), 'kinnim 22b')
41+
42+
def test_end_of_kinnim(self):
43+
test_date = date(2038, 2, 16)
44+
limud = AmudYomiBavliDirshu().limud(test_date)
45+
self.assertEqual(limud.description(), 'kinnim 25a')
46+
47+
def test_beginning_of_tamid(self):
48+
test_date = date(2038, 2, 17)
49+
limud = AmudYomiBavliDirshu().limud(test_date)
50+
self.assertEqual(limud.description(), 'tamid 25b')
51+
52+
def test_end_of_tamid(self):
53+
test_date = date(2038, 3, 5)
54+
limud = AmudYomiBavliDirshu().limud(test_date)
55+
self.assertEqual(limud.description(), 'tamid 33b')
56+
57+
def test_beginning_of_midos(self):
58+
test_date = date(2038, 3, 6)
59+
limud = AmudYomiBavliDirshu().limud(test_date)
60+
self.assertEqual(limud.description(), 'midos 34a')
61+
62+
def test_end_of_midos(self):
63+
test_date = date(2038, 3, 13)
64+
limud = AmudYomiBavliDirshu().limud(test_date)
65+
self.assertEqual(limud.description(), 'midos 37b')
66+
67+
def test_after_midos(self):
68+
test_date = date(2038, 3, 14)
69+
limud = AmudYomiBavliDirshu().limud(test_date)
70+
self.assertEqual(limud.description(), 'niddah 2a')

test/test_limudim_unit.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,22 @@ def test_str_for_a_paged_unit(self):
1212
subject = Unit(['berachos', 3])
1313
self.assertEqual(str(subject), 'berachos 3')
1414

15-
def test_str_for_a_multi_level_unit(self):
15+
def test_str_for_a_multi_level_unit_with_integers(self):
1616
subject = Unit(['berachos', 3, 5, 7, 4, 5])
1717
self.assertEqual(str(subject), 'berachos 3:5:7:4:5')
1818

19+
def test_str_for_a_multi_level_unit_with_a_character(self):
20+
subject = Unit(['berachos', 'a'])
21+
self.assertEqual(str(subject), 'berachos a')
22+
23+
def test_str_for_a_multi_level_unit_with_integer_and_then_a_character(self):
24+
subject = Unit(['berachos', 13, 'a'])
25+
self.assertEqual(str(subject), 'berachos 13a')
26+
27+
def test_str_for_a_multi_level_unit_with_integer_and_then_a_character_and_rlm_marker(self):
28+
subject = Unit(['berachos', 13, u'a\u200f'])
29+
self.assertEqual(str(subject), u'berachos 13a\u200f')
30+
1931
def test_str_for_a_multi_component_primitive_unit(self):
2032
subject = Unit('tazria', 'metzora')
2133
self.assertEqual(str(subject), 'tazria - metzora')
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from datetime import date
2+
from fractions import Fraction
3+
from typing import Optional, Union
4+
5+
from zmanim.hebrew_calendar.jewish_date import JewishDate
6+
from zmanim.limudim.calculators.daf_yomi_bavli import DafYomiBavli
7+
from zmanim.limudim.cycle import Cycle
8+
9+
10+
class AmudYomiBavliDirshu(DafYomiBavli):
11+
Units = {u[0]: u[1] + (0 if i in [0, 2, 5, 8, 9, 10, 11, 12, 21, 27, 29, 30, 31, 32, 33, 35, 36, 39]
12+
else Fraction(1, 2))
13+
for i, u in enumerate(DafYomiBavli.Units.items())}
14+
15+
def initial_cycle_date(self) -> JewishDate:
16+
return self._jewish_date(date(2023, 10, 16))
17+
18+
@staticmethod
19+
def default_units() -> dict:
20+
return AmudYomiBavliDirshu.Units
21+
22+
def unit_step(self) -> Union[int, Fraction]:
23+
return Fraction(1, 2) # Rational numbers preferred over float
24+
25+
def fractional_units(self) -> Optional[tuple]:
26+
return 'a', 'b'
27+
28+
def starting_page(self, units: dict, unit_name: str) -> int:
29+
if unit_name == 'kinnim':
30+
return 22 + self.unit_step()
31+
elif unit_name == 'tamid':
32+
return 25 + self.unit_step()
33+
elif unit_name == 'midos':
34+
return 34
35+
else:
36+
return self.default_starting_page()
37+
38+
def cycle_end_calculation(self, start_date: JewishDate, iteration: int) -> JewishDate:
39+
return start_date + (5406 - 1)
40+
41+
def cycle_units_calculation(self, cycle: Cycle) -> dict:
42+
return self.default_units()

zmanim/limudim/limud_calculator.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from datetime import date
2+
from fractions import Fraction
23
from functools import reduce
4+
from numbers import Number
35
from typing import Optional, Union
46

57
from zmanim.hebrew_calendar.jewish_date import JewishDate
@@ -37,7 +39,7 @@ def perpetual_cycle_anchor(self) -> Optional[Anchor]:
3739
return None
3840

3941
# Number of units to apply over an iteration
40-
def unit_step(self) -> int:
42+
def unit_step(self) -> Union[int, Fraction]:
4143
return 1
4244

4345
# Are units components of some larger grouping? (e.g. pages or mishnayos)
@@ -94,30 +96,32 @@ def skip_unit(self) -> Optional[Unit]:
9496
def is_skip_interval(self, interval: Interval) -> bool:
9597
return False
9698

99+
def base_unit(self) -> Union[int, Fraction]:
100+
return self.unit_step() if self.fractional_units() else 1
101+
97102
def tiered_units_for_interval(self, units: Union[dict, list], interval: Interval) -> Optional[Unit]:
98103
iteration = interval.iteration
99-
offset = ((iteration - 1) * self.unit_step()) + 1
100-
if self.unit_step() > 1:
101-
offset2 = (offset - 1) + self.unit_step()
102-
else:
103-
offset2 = None
104+
offset = ((iteration - 1) * self.unit_step()) + self.base_unit()
105+
offset2 = (offset - 1) + self.unit_step() if self.unit_step() > 1 else None
104106
offsets = list(filter(None.__ne__, [offset, offset2]))
105107
targets = [[o, []] for o in offsets]
106108
results = self.find_offset_units(units, targets)
107109
if {r[0] for r in results} != {0}:
108110
return None
109111
paths = list(map(lambda r: r[1], results))
112+
if self.fractional_units():
113+
paths = list(map(lambda p: self._resolve_fractional_path(p), paths))
110114
return Unit(*paths)
111115

112-
def find_offset_units(self, units: dict, targets: list) -> list:
116+
def find_offset_units(self, units: Union[dict, list], targets: list) -> list:
113117
def unit_reducer(t: list, name: str):
114118
attributes = units[name]
115-
if isinstance(attributes, int):
119+
if isinstance(attributes, (int, Fraction)):
116120
start = self.starting_page(units, name)
117-
length = (attributes - start) + 1
121+
length = (attributes - start) + self.base_unit()
118122

119123
head = [e for e in t if e[0] == 0]
120-
tail = [[0, p + [name, (start + o) - 1]] if o <= length else [o - length, p] for o, p in t if o != 0]
124+
tail = [[0, p + [name, (start + o) - self.base_unit()]] if o <= length else [o - length, p] for o, p in t if o != 0]
121125
return head + tail
122126
else:
123127
head = [e for e in t if e[0] == 0]
@@ -138,3 +142,12 @@ def find_cycle(self, date: JewishDate):
138142
@staticmethod
139143
def _jewish_date(date):
140144
return date if isinstance(date, JewishDate) else JewishDate(date)
145+
146+
def _resolve_fractional_path(self, path: Union[list, Number]) -> Union[list, Number]:
147+
if isinstance(path, Number):
148+
index = int((path - int(path)) * len(self.fractional_units()))
149+
return [int(path), self.fractional_units()[index]]
150+
elif isinstance(path, list):
151+
return path[:-1] + self._resolve_fractional_path(path[-1])
152+
else:
153+
return path

zmanim/limudim/unit.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,16 @@ def _render_with_root(self, component: list) -> str:
3939

4040
@staticmethod
4141
def _render_extension(extension: list) -> str:
42-
return ':'.join(map(str, extension))
42+
if len(extension) == 1:
43+
return str(extension[0])
44+
else:
45+
delimiter = '' if Unit._is_character_extension(extension[-1]) else ':'
46+
return Unit._render_extension(extension[:-1]) + delimiter + str(extension[-1])
47+
48+
@staticmethod
49+
def _is_character_extension(component: Any) -> bool:
50+
# single character extensions are appended directly as 'amud', R-to-L markers should be ignored
51+
return isinstance(component, str) and not component.isnumeric() and len(component.replace(u'\u200f', '')) == 1
4352

4453
def _render_secondary(self, second_component: list, first_component: list) -> str:
4554
if second_component is None:

0 commit comments

Comments
 (0)