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:
boyska 2016-12-10 18:40:33 +01:00
parent 3ea156711f
commit f2fcd075ce
No known key found for this signature in database
GPG key ID: 7395DCAE58289CA9
3 changed files with 59 additions and 10 deletions

View file

@ -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',

View file

@ -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'))

View file

@ -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: