You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
6.1 KiB
171 lines
6.1 KiB
"""
|
|
Triggers determine the times when a job should be executed.
|
|
"""
|
|
from datetime import datetime, timedelta
|
|
from math import ceil
|
|
|
|
from apscheduler.fields import *
|
|
from apscheduler.util import *
|
|
|
|
__all__ = ('CronTrigger', 'DateTrigger', 'IntervalTrigger')
|
|
|
|
|
|
class CronTrigger(object):
|
|
FIELD_NAMES = ('year', 'month', 'day', 'week', 'day_of_week', 'hour',
|
|
'minute', 'second')
|
|
FIELDS_MAP = {'year': BaseField,
|
|
'month': BaseField,
|
|
'week': WeekField,
|
|
'day': DayOfMonthField,
|
|
'day_of_week': DayOfWeekField,
|
|
'hour': BaseField,
|
|
'minute': BaseField,
|
|
'second': BaseField}
|
|
|
|
def __init__(self, **values):
|
|
self.fields = []
|
|
for field_name in self.FIELD_NAMES:
|
|
exprs = values.get(field_name) or '*'
|
|
field_class = self.FIELDS_MAP[field_name]
|
|
field = field_class(field_name, exprs)
|
|
self.fields.append(field)
|
|
|
|
def _increment_field_value(self, dateval, fieldnum):
|
|
"""
|
|
Increments the designated field and resets all less significant fields
|
|
to their minimum values.
|
|
|
|
:type dateval: datetime
|
|
:type fieldnum: int
|
|
:type amount: int
|
|
:rtype: tuple
|
|
:return: a tuple containing the new date, and the number of the field
|
|
that was actually incremented
|
|
"""
|
|
i = 0
|
|
values = {}
|
|
while i < len(self.fields):
|
|
field = self.fields[i]
|
|
if not field.REAL:
|
|
if i == fieldnum:
|
|
fieldnum -= 1
|
|
i -= 1
|
|
else:
|
|
i += 1
|
|
continue
|
|
|
|
if i < fieldnum:
|
|
values[field.name] = field.get_value(dateval)
|
|
i += 1
|
|
elif i > fieldnum:
|
|
values[field.name] = field.get_min(dateval)
|
|
i += 1
|
|
else:
|
|
value = field.get_value(dateval)
|
|
maxval = field.get_max(dateval)
|
|
if value == maxval:
|
|
fieldnum -= 1
|
|
i -= 1
|
|
else:
|
|
values[field.name] = value + 1
|
|
i += 1
|
|
|
|
return datetime(**values), fieldnum
|
|
|
|
def _set_field_value(self, dateval, fieldnum, new_value):
|
|
values = {}
|
|
for i, field in enumerate(self.fields):
|
|
if field.REAL:
|
|
if i < fieldnum:
|
|
values[field.name] = field.get_value(dateval)
|
|
elif i > fieldnum:
|
|
values[field.name] = field.get_min(dateval)
|
|
else:
|
|
values[field.name] = new_value
|
|
|
|
return datetime(**values)
|
|
|
|
def get_next_fire_time(self, start_date):
|
|
next_date = datetime_ceil(start_date)
|
|
fieldnum = 0
|
|
while 0 <= fieldnum < len(self.fields):
|
|
field = self.fields[fieldnum]
|
|
curr_value = field.get_value(next_date)
|
|
next_value = field.get_next_value(next_date)
|
|
|
|
if next_value is None:
|
|
# No valid value was found
|
|
next_date, fieldnum = self._increment_field_value(next_date,
|
|
fieldnum - 1)
|
|
elif next_value > curr_value:
|
|
# A valid, but higher than the starting value, was found
|
|
if field.REAL:
|
|
next_date = self._set_field_value(next_date, fieldnum,
|
|
next_value)
|
|
fieldnum += 1
|
|
else:
|
|
next_date, fieldnum = self._increment_field_value(next_date,
|
|
fieldnum)
|
|
else:
|
|
# A valid value was found, no changes necessary
|
|
fieldnum += 1
|
|
|
|
if fieldnum >= 0:
|
|
return next_date
|
|
|
|
def __repr__(self):
|
|
field_reprs = ("%s='%s'" % (f.name, str(f)) for f in self.fields
|
|
if str(f) != '*')
|
|
return '%s(%s)' % (self.__class__.__name__, ', '.join(field_reprs))
|
|
|
|
|
|
class DateTrigger(object):
|
|
def __init__(self, run_date):
|
|
self.run_date = convert_to_datetime(run_date)
|
|
|
|
def get_next_fire_time(self, start_date):
|
|
if self.run_date >= start_date:
|
|
return self.run_date
|
|
|
|
def __repr__(self):
|
|
return '%s(%s)' % (self.__class__.__name__, repr(self.run_date))
|
|
|
|
|
|
class IntervalTrigger(object):
|
|
def __init__(self, interval, repeat, start_date=None):
|
|
if not isinstance(interval, timedelta):
|
|
raise TypeError('interval must be a timedelta')
|
|
if repeat < 0:
|
|
raise ValueError('Illegal value for repeat; expected >= 0, '
|
|
'received %s' % repeat)
|
|
|
|
self.interval = interval
|
|
self.interval_length = timedelta_seconds(self.interval)
|
|
if self.interval_length == 0:
|
|
self.interval = timedelta(seconds=1)
|
|
self.interval_length = 1
|
|
self.repeat = repeat
|
|
if start_date is None:
|
|
self.first_fire_date = datetime.now() + self.interval
|
|
else:
|
|
self.first_fire_date = convert_to_datetime(start_date)
|
|
self.first_fire_date -= timedelta(microseconds=\
|
|
self.first_fire_date.microsecond)
|
|
if repeat > 0:
|
|
self.last_fire_date = self.first_fire_date + interval * (repeat - 1)
|
|
else:
|
|
self.last_fire_date = None
|
|
|
|
def get_next_fire_time(self, start_date):
|
|
if start_date < self.first_fire_date:
|
|
return self.first_fire_date
|
|
if self.last_fire_date and start_date > self.last_fire_date:
|
|
return None
|
|
timediff_seconds = timedelta_seconds(start_date - self.first_fire_date)
|
|
next_interval_num = int(ceil(timediff_seconds / self.interval_length))
|
|
return self.first_fire_date + self.interval * next_interval_num
|
|
|
|
def __repr__(self):
|
|
return "%s(interval=%s, repeat=%d, start_date=%s)" % (
|
|
self.__class__.__name__, repr(self.interval), self.repeat,
|
|
repr(self.first_fire_date))
|
|
|