timegen_cron.py 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. import logging
  2. from datetime import datetime, timedelta
  3. from croniter import croniter
  4. from .timegen_every import Alarm
  5. log = logging.getLogger("time-cron")
  6. class CronAlarm(Alarm):
  7. description = "Frequency specified by cron-like format. nerds preferred"
  8. def __init__(self, obj):
  9. super().__init__()
  10. self.cron_format = obj["cron_format"]
  11. if "exclude" in obj:
  12. if type(obj["exclude"]) is str:
  13. self.exclude = [
  14. line.strip() for line in obj["exclude"].split("\n") if line.strip()
  15. ]
  16. else:
  17. self.exclude = [excl for excl in obj["exclude"] if excl.strip()]
  18. else:
  19. self.exclude = []
  20. if not croniter.is_valid(self.cron_format):
  21. raise ValueError("Invalid cron_format: `%s`" % self.cron_format)
  22. for exclude in self.exclude:
  23. if not croniter.is_valid(exclude):
  24. raise ValueError("Invalid exclude: `%s`" % exclude)
  25. def is_excluded(self, dt):
  26. base = dt - timedelta(seconds=1)
  27. for exclude in self.exclude:
  28. nt = croniter(exclude, base).get_next(datetime)
  29. if nt == dt:
  30. return True
  31. return False
  32. def next_ring(self, current_time=None):
  33. if current_time is None:
  34. current_time = datetime.now()
  35. # cron granularity is to the minute
  36. # thus, doing 2000 attemps guarantees at least 32hours.
  37. # if your event is no more frequent than 10minutes, this is 13days
  38. for _ in range(2000):
  39. nt = croniter(self.cron_format, current_time).get_next(datetime)
  40. if not self.is_excluded(nt):
  41. return nt
  42. current_time = nt
  43. return None
  44. def has_ring(self, current_time=None):
  45. # cron specification has no possibility of being over
  46. return self.next_ring(current_time) is not None