瀏覽代碼

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
boyska 7 年之前
父節點
當前提交
f2fcd075ce
共有 3 個文件被更改,包括 59 次插入10 次删除
  1. 17 0
      larigira/tests/test_time_every.py
  2. 18 1
      larigira/timeform_frequency.py
  3. 24 9
      larigira/timegen_every.py

+ 17 - 0
larigira/tests/test_time_every.py

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

+ 18 - 1
larigira/timeform_frequency.py

@@ -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 - 9
larigira/timegen_every.py

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