timegen frequency support weekdays
you can now select on which weekdays an event should be played. The upgrade path should be clean, with old events assumed to be "all days". FIX #28
This commit is contained in:
parent
3ea156711f
commit
f2fcd075ce
3 changed files with 59 additions and 10 deletions
|
@ -30,6 +30,15 @@ def onehour(now, request):
|
|||
'interval': intervals[request.param],
|
||||
'end': now + days(1)})
|
||||
|
||||
@pytest.fixture(params=[1, '1'])
|
||||
def onehour_monday(request):
|
||||
monday = request.param
|
||||
yield FrequencyAlarm({
|
||||
'interval': 3600*12,
|
||||
'weekdays': [monday], # mondays only
|
||||
'start': 0
|
||||
})
|
||||
|
||||
|
||||
@pytest.fixture(params=['seconds', 'human', 'coloned'])
|
||||
def tenseconds(now, request):
|
||||
|
@ -111,6 +120,14 @@ def test_freq_ring(now, onehour):
|
|||
eq_(len(allr), 49, pprint(allr))
|
||||
|
||||
|
||||
def test_weekday_skip(onehour_monday):
|
||||
t = datetime.fromtimestamp(0)
|
||||
for _ in range(20): # 20 is an arbitrary number
|
||||
t = onehour_monday.next_ring(t)
|
||||
print(_, t, t.isoweekday())
|
||||
assert t.isoweekday() == 1 # monday; don't get confused by .weekday()
|
||||
|
||||
|
||||
def test_single_registered():
|
||||
timegenerate({
|
||||
'kind': 'single',
|
||||
|
|
|
@ -2,7 +2,7 @@ from datetime import datetime
|
|||
from pytimeparse.timeparse import timeparse
|
||||
from flask_wtf import Form
|
||||
from wtforms import StringField, DateTimeField, validators, \
|
||||
SubmitField, ValidationError
|
||||
SubmitField, ValidationError, SelectMultipleField
|
||||
|
||||
|
||||
class FrequencyAlarmForm(Form):
|
||||
|
@ -22,6 +22,18 @@ class FrequencyAlarmForm(Form):
|
|||
description='Date after which no alarm will ring, '
|
||||
'expressed as YYYY-MM-DD HH:MM:SS; if omitted, the '
|
||||
'alarm will always ring')
|
||||
weekdays = SelectMultipleField('Days on which the alarm should be played',
|
||||
choices=[('1', 'Monday'),
|
||||
('2', 'Tuesday'),
|
||||
('3', 'Wednesday'),
|
||||
('4', 'Thursday'),
|
||||
('5', 'Friday'),
|
||||
('6', 'Saturday'),
|
||||
('7', 'Sunday')],
|
||||
default=list('1234567'),
|
||||
validators=[validators.required()],
|
||||
description='The alarm will ring only on '
|
||||
'selected weekdays')
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
def populate_from_timespec(self, timespec):
|
||||
|
@ -31,6 +43,10 @@ class FrequencyAlarmForm(Form):
|
|||
self.start.data = datetime.fromtimestamp(timespec['start'])
|
||||
if 'end' in timespec:
|
||||
self.end.data = datetime.fromtimestamp(timespec['end'])
|
||||
if 'weekdays' in timespec:
|
||||
self.weekdays.data = timespec['weekdays']
|
||||
else:
|
||||
self.weekdays.data = list('1234567')
|
||||
self.interval.data = timespec['interval']
|
||||
|
||||
def validate_interval(form, field):
|
||||
|
@ -48,6 +64,7 @@ def frequencyalarm_receive(form):
|
|||
'kind': 'frequency',
|
||||
'nick': form.nick.data,
|
||||
'interval': form.interval.data,
|
||||
'weekdays': form.weekdays.data,
|
||||
}
|
||||
if form.start.data:
|
||||
obj['start'] = int(form.start.data.strftime('%s'))
|
||||
|
|
|
@ -24,6 +24,7 @@ class Alarm(object):
|
|||
raise NotImplementedError()
|
||||
|
||||
def has_ring(self, time=None):
|
||||
'''returns True IFF the alarm will ring exactly at ``time``'''
|
||||
raise NotImplementedError()
|
||||
|
||||
def all_rings(self, current_time=None):
|
||||
|
@ -74,6 +75,8 @@ class FrequencyAlarm(Alarm):
|
|||
self.interval = timeparse(obj['interval'])
|
||||
assert type(self.interval) is int
|
||||
self.end = getdate(obj['end']) if 'end' in obj else None
|
||||
self.weekdays = [int(x) for x in obj['weekdays']] if \
|
||||
'weekdays' in obj else None
|
||||
|
||||
def next_ring(self, current_time=None):
|
||||
'''if current_time is None, it is now()'''
|
||||
|
@ -87,15 +90,27 @@ class FrequencyAlarm(Alarm):
|
|||
assert self.start <= current_time <= self.end
|
||||
else:
|
||||
assert self.start <= current_time
|
||||
n_interval = (
|
||||
(current_time - self.start).total_seconds() // self.interval
|
||||
) + 1
|
||||
ring = self.start + timedelta(seconds=self.interval * n_interval)
|
||||
if ring == current_time:
|
||||
ring += timedelta(seconds=self.interval)
|
||||
if self.end is not None and ring > self.end:
|
||||
return None
|
||||
return ring
|
||||
# this "infinite" loop is required by the weekday exclusion: in
|
||||
# fact, it is necessary to retry until a valid event/weekday is
|
||||
# found. a "while True" might have been more elegant (and maybe
|
||||
# fast), but this gives a clear upper bound to the cycle.
|
||||
for _ in range(60*60*24*7 // self.interval):
|
||||
n_interval = (
|
||||
(current_time - self.start).total_seconds() // self.interval
|
||||
) + 1
|
||||
ring = self.start + timedelta(seconds=self.interval * n_interval)
|
||||
if ring == current_time:
|
||||
ring += timedelta(seconds=self.interval)
|
||||
if self.end is not None and ring > self.end:
|
||||
return None
|
||||
if self.weekdays is not None \
|
||||
and ring.isoweekday() not in self.weekdays:
|
||||
current_time = ring
|
||||
continue
|
||||
return ring
|
||||
log.warning("Can't find a valid time for event; "
|
||||
"something went wrong")
|
||||
return None
|
||||
|
||||
def has_ring(self, current_time=None):
|
||||
if current_time is None:
|
||||
|
|
Loading…
Reference in a new issue