1717from .zman_sensors import get_geo
1818
1919
20- def _get_bedikat_day_year (today_date : date ) -> tuple [int , int ]:
21- """Return (Hebrew year, day in Nisan) for Chametz search day (14 or 12 Nisan)."""
20+ def _get_pesach_info (today_date : date ) -> tuple [int , bool ]:
21+ """Return (Hebrew year, is_deferred) for the upcoming Pesach.
22+
23+ is_deferred is True when 14 Nisan falls on Shabbos (15 Nisan = Sunday).
24+ """
2225 for delta in (0 , 1 ):
2326 civil_year = today_date .year + delta
2427 hy = pl_dates .GregorianDate (civil_year , 4 , 1 ).to_heb ().year
2528
26- # normally 14 Nisan
27- candidate = pl_dates .HebrewDate (hy , 1 , 14 )
28- # but if 15 Nisan falls on a Sunday, use 12 Nisan
29- fifteenth = pl_dates .HebrewDate (hy , 1 , 15 )
30- if fifteenth .to_pydate ().weekday () == 6 :
31- candidate = pl_dates .HebrewDate (hy , 1 , 12 )
32-
33- cdate = candidate .to_pydate ()
29+ fourteenth = pl_dates .HebrewDate (hy , 1 , 14 )
30+ cdate = fourteenth .to_pydate ()
3431 if cdate >= today_date :
35- return hy , candidate .day
32+ deferred = (cdate .weekday () == 5 ) # 14 Nisan is Shabbos
33+ return hy , deferred
3634
37- # fallback to next year's 14 Nisan
38- return hy + 1 , 14
35+ hy_next = pl_dates . GregorianDate ( today_date . year + 2 , 4 , 1 ). to_heb (). year
36+ return hy_next , False
3937
4038
4139class _BaseChumetzSensor (YidCalZmanDevice , RestoreEntity , SensorEntity ):
@@ -83,21 +81,16 @@ async def async_added_to_hass(self) -> None:
8381 async def _midnight_update (self , now : datetime ) -> None :
8482 await self .async_update ()
8583
86- def _compute_target (self , hours_from_dawn : float ) -> datetime :
87- """Compute dawn + hours * sha'ah_zmanit on the correct Nisan date."""
88- now_local = dt_util .now ().astimezone (self ._tz )
89- hy , day = _get_bedikat_day_year (now_local .date ())
90-
91- # Hebrew→civil date
92- heb = pl_dates .HebrewDate (hy , 1 , day )
93- g_py = heb .to_pydate ()
84+ def _compute_for_date (self , civil_date : date , hours_from_dawn : float ) -> tuple [datetime , str ]:
85+ """Compute dawn + hours × sha'ah zmanit for a given civil date.
9486
95- # get geometric sunrise & sunset
96- cal = ZmanimCalendar (geo_location = self ._geo , date = g_py )
87+ Returns (floored_local_dt, raw_iso_string).
88+ """
89+ cal = ZmanimCalendar (geo_location = self ._geo , date = civil_date )
9790 sunrise = cal .sunrise ().astimezone (self ._tz )
9891 sunset = cal .sunset ().astimezone (self ._tz )
9992
100- # MGA “ day” : dawn = sunrise − havdalah, nightfall = sunset + havdalah
93+ # MGA " day" : dawn = sunrise − havdalah, nightfall = sunset + havdalah
10194 dawn = sunrise - timedelta (minutes = self ._havdalah )
10295 nightfall = sunset + timedelta (minutes = self ._havdalah )
10396
@@ -106,25 +99,22 @@ def _compute_target(self, hours_from_dawn: float) -> datetime:
10699
107100 # raw target
108101 raw = dawn + hour_len * hours_from_dawn
102+ raw_iso = raw .isoformat ()
109103
110- # debug attrs (with seconds)
111- self ._attr_extra_state_attributes = {
112- #"dawn": dawn.isoformat(),
113- #"sunrise": sunrise.isoformat(),
114- #"sunset": sunset.isoformat(),
115- #"nightfall": nightfall.isoformat(),
116- #"hour_len_s": hour_len.total_seconds(), # seconds per sha'ah
117- "Sof_Zman_Chumetz_With_Seconds" : raw .isoformat (),
118- }
104+ # floor to the minute
105+ floored = raw .replace (second = 0 , microsecond = 0 )
106+
107+ return floored , raw_iso
119108
120- # floor to the minute (any seconds 0–59)
121- return raw .replace (second = 0 , microsecond = 0 )
122-
123109 # subclasses implement async_update()
124110
125111
126112class SofZmanAchilasChumetzSensor (_BaseChumetzSensor ):
127- """סוף-זמן אכילת חמץ עפ\" י המג\" א (4 שעות זמניות)."""
113+ """סוף-זמן אכילת חמץ עפ\" י המג\" א (4 שעות זמניות).
114+
115+ Always computed on 14 Nisan — even in a deferred year,
116+ the halachic deadline for eating chametz is Shabbos morning.
117+ """
128118
129119 def __init__ (self , hass : HomeAssistant , candle : int , havdalah : int ) -> None :
130120 super ().__init__ (
@@ -140,21 +130,32 @@ def __init__(self, hass: HomeAssistant, candle: int, havdalah: int) -> None:
140130 async def async_update (self , now : datetime | None = None ) -> None :
141131 if not self ._geo :
142132 return
143- target = self ._compute_target (4.0 )
133+
134+ now_local = (now or dt_util .now ()).astimezone (self ._tz )
135+ hy , _ = _get_pesach_info (now_local .date ())
136+
137+ # Always 14 Nisan
138+ civil_14 = pl_dates .HebrewDate (hy , 1 , 14 ).to_pydate ()
139+ target , raw_iso = self ._compute_for_date (civil_14 , 4.0 )
140+
144141 self ._attr_native_value = target .astimezone (timezone .utc )
145-
146- # 3. build your human‐readable string
147- local = target .astimezone (self ._tz )
148- human = self ._format_simple_time (local )
149-
150- # 4. merge it into the existing attributes
151- attrs = {** (self ._attr_extra_state_attributes or {})}
152- attrs ["Sof_Zman_Achilas_Chumetz_Simple" ] = human
153- self ._attr_extra_state_attributes = attrs
142+
143+ human = self ._format_simple_time (target .astimezone (self ._tz ))
144+ self ._attr_extra_state_attributes = {
145+ "Sof_Zman_Chumetz_With_Seconds" : raw_iso ,
146+ "Sof_Zman_Achilas_Chumetz_Simple" : human ,
147+ }
154148
155149
156150class SofZmanSriefesChumetzSensor (_BaseChumetzSensor ):
157- """סוף-זמן שריפת חמץ עפ\" י המג\" א (5 שעות זמניות)."""
151+ """סוף-זמן שריפת חמץ עפ\" י המג\" א (5 שעות זמניות).
152+
153+ Normal year: state + _Simple = 14 Nisan 5th hour.
154+ Deferred year (14 Nisan on Shabbos): state + _Simple = 13 Nisan
155+ Friday 5th hour (physical sriefa before Shabbos). An additional
156+ Sof_Zman_Biur_Simple attribute shows the 14 Nisan Shabbos 5th hour
157+ (halachic deadline for disposing of remaining chametz via bitul/flush).
158+ """
158159
159160 def __init__ (self , hass : HomeAssistant , candle : int , havdalah : int ) -> None :
160161 super ().__init__ (
@@ -170,14 +171,37 @@ def __init__(self, hass: HomeAssistant, candle: int, havdalah: int) -> None:
170171 async def async_update (self , now : datetime | None = None ) -> None :
171172 if not self ._geo :
172173 return
173- target = self ._compute_target (5.0 )
174+
175+ now_local = (now or dt_util .now ()).astimezone (self ._tz )
176+ hy , deferred = _get_pesach_info (now_local .date ())
177+
178+ if deferred :
179+ # Sriefa is Friday (13 Nisan) — state + _Simple
180+ civil_13 = pl_dates .HebrewDate (hy , 1 , 13 ).to_pydate ()
181+ target , raw_iso = self ._compute_for_date (civil_13 , 5.0 )
182+
183+ # Biur is Shabbos (14 Nisan) — attribute only
184+ civil_14 = pl_dates .HebrewDate (hy , 1 , 14 ).to_pydate ()
185+ biur_target , biur_raw_iso = self ._compute_for_date (civil_14 , 5.0 )
186+ else :
187+ # Normal year — sriefa and biur are the same day
188+ civil_14 = pl_dates .HebrewDate (hy , 1 , 14 ).to_pydate ()
189+ target , raw_iso = self ._compute_for_date (civil_14 , 5.0 )
190+ biur_target = None
191+ biur_raw_iso = None
192+
174193 self ._attr_native_value = target .astimezone (timezone .utc )
175194
176- # 3. build your human‐readable string
177- local = target .astimezone (self ._tz )
178- human = self ._format_simple_time (local )
195+ human = self ._format_simple_time (target .astimezone (self ._tz ))
196+ attrs : dict [str , object ] = {
197+ "Sof_Zman_Chumetz_With_Seconds" : raw_iso ,
198+ "Sof_Zman_Sriefes_Chumetz_Simple" : human ,
199+ }
200+
201+ if biur_target is not None :
202+ attrs ["Sof_Zman_Biur_With_Seconds" ] = biur_raw_iso
203+ attrs ["Sof_Zman_Biur_Simple" ] = self ._format_simple_time (
204+ biur_target .astimezone (self ._tz )
205+ )
179206
180- # 4. merge it into the existing attributes
181- attrs = {** (self ._attr_extra_state_attributes or {})}
182- attrs ["Sof_Zman_Sriefes_Chumetz_Simple" ] = human
183207 self ._attr_extra_state_attributes = attrs
0 commit comments