Skip to content

Commit 69b19cf

Browse files
committed
fixed some period() trigger logic and added more tests
1 parent c50699d commit 69b19cf

File tree

2 files changed

+139
-35
lines changed

2 files changed

+139
-35
lines changed

custom_components/pyscript/trigger.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def timer_trigger_next(cls, time_spec, now):
391391
if cron_match:
392392
if not croniter.is_valid(cron_match.group("cron_expr")):
393393
_LOGGER.error("Invalid cron expression: %s", cron_match)
394-
return None
394+
continue
395395

396396
val = croniter(cron_match.group("cron_expr"), now, dt.datetime).get_next()
397397
if next_time is None or val < next_time:
@@ -408,36 +408,39 @@ def timer_trigger_next(cls, time_spec, now):
408408
next_time = this_t
409409

410410
elif len(match2) == 5:
411-
start = cls.parse_date_time(match2[1].strip(), 0, now)
412-
if match2[3] is not None:
413-
end = cls.parse_date_time(match2[3].strip(), 0, now)
414-
if end < start:
415-
if end <= now:
416-
# try end of tomorrow
417-
end = cls.parse_date_time(match2[3].strip(), 1, now)
418-
else:
419-
# try a start of yesterday
420-
start = cls.parse_date_time(match2[1].strip(), -1, now)
421-
if now < start and (next_time is None or start < next_time):
422-
next_time = start
423-
period = parse_time_offset(match2[2].strip())
424-
if now >= start and period > 0:
425-
secs = period * (1.0 + math.floor((now - start).total_seconds() / period))
426-
this_t = start + dt.timedelta(seconds=secs)
427-
if match2[3] is None:
411+
start_str, period_str = match2[1].strip(), match2[2].strip()
412+
start = cls.parse_date_time(start_str, 0, now)
413+
period = parse_time_offset(period_str)
414+
if period <= 0:
415+
_LOGGER.error("Invalid non-positive period %s in period(): %s", period)
416+
continue
417+
418+
if match2[3] is None:
419+
if now < start and (next_time is None or start < next_time):
420+
next_time = start
421+
if now >= start:
422+
secs = period * (1.0 + math.floor((now - start).total_seconds() / period))
423+
this_t = start + dt.timedelta(seconds=secs)
428424
if now < this_t and (next_time is None or this_t < next_time):
429425
next_time = this_t
430-
else:
431-
if now < this_t <= end and (next_time is None or this_t < next_time):
426+
continue
427+
end_str = match2[3].strip()
428+
end = cls.parse_date_time(end_str, 0, now)
429+
end_offset = 1 if end < start else 0
430+
for day in [-1, 0, 1]:
431+
start = cls.parse_date_time(start_str, day, now)
432+
end = cls.parse_date_time(end_str, day + end_offset, now)
433+
if now < start:
434+
if next_time is None or start < next_time:
435+
next_time = start
436+
break
437+
secs = period * (1.0 + math.floor((now - start).total_seconds() / period))
438+
this_t = start + dt.timedelta(seconds=secs)
439+
if start <= this_t <= end:
440+
if next_time is None or this_t < next_time:
432441
next_time = this_t
433-
if next_time is None or now >= end:
434-
#
435-
# Try tomorrow's start (won't make a difference if spec has
436-
# full date)
437-
#
438-
start = cls.parse_date_time(match2[1].strip(), 1, now)
439-
if now < start and (next_time is None or start < next_time):
440-
next_time = start
442+
break
443+
441444
else:
442445
_LOGGER.warning("Can't parse %s in time_trigger check", spec)
443446
return next_time

tests/test_unit_trigger.py

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Unit tests for time trigger functions."""
2+
23
from datetime import datetime as dt
4+
import locale
35

46
from custom_components.pyscript.function import Function
57
from custom_components.pyscript.trigger import TrigTime
@@ -55,11 +57,6 @@
5557
["+5 min", 0, dt(2019, 9, 1, 0, 5, 0, 0)],
5658
]
5759

58-
parseDateTimeTests2 = [
59-
["sunday", 0, dt(2019, 9, 8, 0, 0, 0, 0)],
60-
["monday", 0, dt(2019, 9, 9, 0, 0, 0, 0)],
61-
]
62-
6360

6461
async def test_parse_date_time(hass, caplog):
6562
"""Run time parse datetime tests."""
@@ -73,6 +70,11 @@ async def test_parse_date_time(hass, caplog):
7370
hass.config.elevation = 0
7471
hass.config.time_zone = "America/Los_Angeles"
7572

73+
#
74+
# Set English since the week names use locale
75+
#
76+
locale.setlocale(locale.LC_ALL, "en_US")
77+
7678
Function.init(hass)
7779
TrigTime.init(hass)
7880

@@ -89,14 +91,42 @@ async def test_parse_date_time(hass, caplog):
8991
out = TrigTime.parse_date_time(spec, date_offset, now)
9092
assert out == expect
9193

94+
95+
parseDateTimeTestsDayNames = [
96+
["thu", 0, dt(2019, 9, 5, 0, 0, 0, 0)],
97+
["fri noon", 0, dt(2019, 9, 6, 12, 0, 0, 0)],
98+
["sunday", 0, dt(2019, 9, 8, 0, 0, 0, 0)],
99+
["monday", 0, dt(2019, 9, 9, 0, 0, 0, 0)],
100+
]
101+
102+
103+
async def test_parse_date_time_day_names(hass, caplog):
104+
"""Run time parse datetime on day of week names."""
105+
#
106+
# Hardcode a location and timezone so we can check sunrise
107+
# and sunset.
108+
#
109+
hass.config.latitude = 38
110+
hass.config.longitude = -122
111+
hass.config.elevation = 0
112+
hass.config.time_zone = "America/Los_Angeles"
113+
114+
#
115+
# Set English since the week names use locale
116+
#
117+
locale.setlocale(locale.LC_ALL, "en_US")
118+
119+
Function.init(hass)
120+
TrigTime.init(hass)
121+
92122
#
93123
# This set of tests assumes it's currently 13:00 on 2019/9/3
94124
#
95125
now = dt(2019, 9, 3, 13, 0, 0, 0)
96126
with patch("homeassistant.helpers.condition.dt_util.utcnow", return_value=now), patch(
97127
"homeassistant.util.dt.utcnow", return_value=now
98128
):
99-
for test_data in parseDateTimeTests2:
129+
for test_data in parseDateTimeTestsDayNames:
100130
spec, date_offset, expect = test_data
101131
out = TrigTime.parse_date_time(spec, date_offset, now)
102132
assert out == expect
@@ -148,14 +178,17 @@ async def test_parse_date_time(hass, caplog):
148178
)
149179
def test_timer_active_check(hass, spec, now, expected):
150180
"""Run time active check tests."""
181+
#
182+
# Set English since the week names use locale
183+
#
184+
locale.setlocale(locale.LC_ALL, "en_US")
151185
Function.init(hass)
152186
TrigTime.init(hass)
153187
out = TrigTime.timer_active_check(spec, now)
154188
assert out == expected
155189

156190

157191
timerTriggerNextTests = [
158-
[["period(sunset, 60s, sunrise)"], [dt(2019, 9, 1, 19, 15, 16)]],
159192
[["once(2019/9/1 8:00)"], [None]],
160193
[["once(2019/9/1 15:00)"], [dt(2019, 9, 1, 15, 0, 0, 0)]],
161194
[["once(15:00)"], [dt(2019, 9, 1, 15, 0, 0, 0)]],
@@ -199,6 +232,55 @@ def test_timer_active_check(hass, spec, now, expected):
199232
dt(2019, 9, 2, 6, 0, 0, 0),
200233
dt(2019, 9, 2, 18, 0, 0, 0),
201234
dt(2019, 9, 2, 22, 0, 0, 0),
235+
dt(2019, 9, 3, 2, 0, 0, 0),
236+
dt(2019, 9, 3, 6, 0, 0, 0),
237+
dt(2019, 9, 3, 18, 0, 0, 0),
238+
],
239+
],
240+
[
241+
["period(18:00, 12 hr, 6:00)"],
242+
[
243+
dt(2019, 9, 1, 18, 0, 0, 0),
244+
dt(2019, 9, 2, 6, 0, 0, 0),
245+
dt(2019, 9, 2, 18, 0, 0, 0),
246+
dt(2019, 9, 3, 6, 0, 0, 0),
247+
],
248+
],
249+
[
250+
["period(18:00, 12.0001 hr, 6:00)"],
251+
[
252+
dt(2019, 9, 1, 18, 0, 0, 0),
253+
dt(2019, 9, 2, 18, 0, 0, 0),
254+
dt(2019, 9, 3, 18, 0, 0, 0),
255+
dt(2019, 9, 4, 18, 0, 0, 0),
256+
],
257+
],
258+
[
259+
["period(6:00, 12 hr, 18:00)"],
260+
[
261+
dt(2019, 9, 1, 18, 0, 0, 0),
262+
dt(2019, 9, 2, 6, 0, 0, 0),
263+
dt(2019, 9, 2, 18, 0, 0, 0),
264+
dt(2019, 9, 3, 6, 0, 0, 0),
265+
dt(2019, 9, 3, 18, 0, 0, 0),
266+
dt(2019, 9, 4, 6, 0, 0, 0),
267+
],
268+
],
269+
[
270+
["period(6:00, 12.0001 hr, 18:00)"],
271+
[dt(2019, 9, 2, 6, 0, 0, 0), dt(2019, 9, 3, 6, 0, 0, 0), dt(2019, 9, 4, 6, 0, 0, 0)],
272+
],
273+
[
274+
["period(sunset, 4 hours, sunrise)"],
275+
[
276+
dt(2019, 9, 1, 19, 39, 15),
277+
dt(2019, 9, 1, 23, 39, 15),
278+
dt(2019, 9, 2, 3, 39, 15),
279+
dt(2019, 9, 2, 19, 37, 46),
280+
dt(2019, 9, 2, 23, 37, 46),
281+
dt(2019, 9, 3, 3, 37, 46),
282+
dt(2019, 9, 3, 19, 36, 17),
283+
dt(2019, 9, 3, 23, 36, 17),
202284
],
203285
],
204286
[
@@ -269,8 +351,23 @@ def test_timer_active_check(hass, spec, now, expected):
269351

270352
def test_timer_trigger_next(hass):
271353
"""Run trigger next tests."""
354+
#
355+
# Hardcode a location and timezone so we can check sunrise
356+
# and sunset.
357+
#
358+
hass.config.latitude = 38
359+
hass.config.longitude = -122
360+
hass.config.elevation = 0
361+
hass.config.time_zone = "America/Los_Angeles"
362+
#
363+
364+
# Set English since the week names use locale
365+
#
366+
locale.setlocale(locale.LC_ALL, "en_US")
367+
272368
Function.init(hass)
273369
TrigTime.init(hass)
370+
274371
for test_data in timerTriggerNextTests:
275372
now = dt(2019, 9, 1, 13, 0, 0, 100000)
276373
spec, expect_seq = test_data
@@ -377,6 +474,10 @@ def test_timer_trigger_next(hass):
377474

378475
def test_timer_trigger_next_month_rollover(hass):
379476
"""Run month rollover tests."""
477+
#
478+
# Set English since the week names use locale
479+
#
480+
locale.setlocale(locale.LC_ALL, "en_US")
380481
Function.init(hass)
381482
TrigTime.init(hass)
382483
for test_data in timerTriggerNextTestsMonthRollover:

0 commit comments

Comments
 (0)