def remaining_delta(self, last_run_at, tz=None, ffwd=ffwd): # pylint: disable=redefined-outer-name # caching global ffwd tz = tz or self.tz last_run_at = self.maybe_make_aware(last_run_at) now = self.maybe_make_aware(self.now()) dow_num = last_run_at.isoweekday() % 7 # Sunday is day 0, not day 7 execute_this_date = (last_run_at.month in self.month_of_year and last_run_at.day in self.day_of_month and dow_num in self.day_of_week) execute_this_hour = (execute_this_date and last_run_at.day == now.day and last_run_at.month == now.month and last_run_at.year == now.year and last_run_at.hour in self.hour and last_run_at.minute < max(self.minute)) if execute_this_hour: next_minute = min(minute for minute in self.minute if minute > last_run_at.minute) delta = ffwd(minute=next_minute, second=0, microsecond=0) else: next_minute = min(self.minute) execute_today = (execute_this_date and last_run_at.hour < max(self.hour)) if execute_today: next_hour = min(hour for hour in self.hour if hour > last_run_at.hour) delta = ffwd(hour=next_hour, minute=next_minute, second=0, microsecond=0) else: next_hour = min(self.hour) all_dom_moy = (self._orig_day_of_month == '*' and self._orig_month_of_year == '*') if all_dom_moy: next_day = min( [day for day in self.day_of_week if day > dow_num] or self.day_of_week) add_week = next_day == dow_num delta = ffwd( weeks=add_week and 1 or 0, weekday=(next_day - 1) % 7, hour=next_hour, minute=next_minute, second=0, microsecond=0, ) else: delta = self._delta_to_next(last_run_at, next_hour, next_minute) return self.to_local(last_run_at), delta, self.to_local(now)
def test_remaining(): # Relative remaining(datetime.utcnow(), timedelta(hours=1), relative=True) """ The upcoming cases check whether the next run is calculated correctly """ eastern_tz = pytz.timezone("US/Eastern") tokyo_tz = pytz.timezone("Asia/Tokyo") # Case 1: `start` in UTC and `now` in other timezone start = datetime.now(pytz.utc) now = datetime.now(eastern_tz) delta = timedelta(hours=1) assert str(start.tzinfo) == str(pytz.utc) assert str(now.tzinfo) == str(eastern_tz) rem_secs = remaining(start, delta, now).total_seconds() # assert remaining time is approximately equal to delta assert rem_secs == pytest.approx(delta.total_seconds(), abs=1) # Case 2: `start` and `now` in different timezones (other than UTC) start = datetime.now(eastern_tz) now = datetime.now(tokyo_tz) delta = timedelta(hours=1) assert str(start.tzinfo) == str(eastern_tz) assert str(now.tzinfo) == str(tokyo_tz) rem_secs = remaining(start, delta, now).total_seconds() assert rem_secs == pytest.approx(delta.total_seconds(), abs=1) """ Case 3: DST check Suppose start (which is last_run_time) is in EST while next_run is in EDT, then check whether the `next_run` is actually the time specified in the start (i.e. there is not an hour diff due to DST). In 2019, DST starts on March 10 """ start = eastern_tz.localize( datetime(month=3, day=9, year=2019, hour=10, minute=0)) # EST now = eastern_tz.localize( datetime(day=11, month=3, year=2019, hour=1, minute=0)) # EDT delta = ffwd(hour=10, year=2019, microsecond=0, minute=0, second=0, day=11, weeks=0, month=3) # `next_actual_time` is the next time to run (derived from delta) next_actual_time = eastern_tz.localize( datetime(day=11, month=3, year=2019, hour=10, minute=0)) # EDT assert start.tzname() == "EST" assert now.tzname() == "EDT" assert next_actual_time.tzname() == "EDT" rem_time = remaining(start, delta, now) next_run = now + rem_time assert next_run == next_actual_time
def test_remaining(): # Relative remaining(datetime.utcnow(), timedelta(hours=1), relative=True) """ The upcoming cases check whether the next run is calculated correctly """ eastern_tz = pytz.timezone("US/Eastern") tokyo_tz = pytz.timezone("Asia/Tokyo") # Case 1: `start` in UTC and `now` in other timezone start = datetime.now(pytz.utc) now = datetime.now(eastern_tz) delta = timedelta(hours=1) assert str(start.tzinfo) == str(pytz.utc) assert str(now.tzinfo) == str(eastern_tz) rem_secs = remaining(start, delta, now).total_seconds() # assert remaining time is approximately equal to delta assert rem_secs == pytest.approx(delta.total_seconds(), abs=1) # Case 2: `start` and `now` in different timezones (other than UTC) start = datetime.now(eastern_tz) now = datetime.now(tokyo_tz) delta = timedelta(hours=1) assert str(start.tzinfo) == str(eastern_tz) assert str(now.tzinfo) == str(tokyo_tz) rem_secs = remaining(start, delta, now).total_seconds() assert rem_secs == pytest.approx(delta.total_seconds(), abs=1) """ Case 3: DST check Suppose start (which is last_run_time) is in EST while next_run is in EDT, then check whether the `next_run` is actually the time specified in the start (i.e. there is not an hour diff due to DST). In 2019, DST starts on March 10 """ start = eastern_tz.localize(datetime(month=3, day=9, year=2019, hour=10, minute=0)) # EST now = eastern_tz.localize(datetime(day=11, month=3, year=2019, hour=1, minute=0)) # EDT delta = ffwd(hour=10, year=2019, microsecond=0, minute=0, second=0, day=11, weeks=0, month=3) # `next_actual_time` is the next time to run (derived from delta) next_actual_time = eastern_tz.localize(datetime(day=11, month=3, year=2019, hour=10, minute=0)) # EDT assert start.tzname() == "EST" assert now.tzname() == "EDT" assert next_actual_time.tzname() == "EDT" rem_time = remaining(start, delta, now) next_run = now + rem_time assert next_run == next_actual_time
def test_radd_with_unknown_gives_NotImplemented(self): x = ffwd(year=2012) assert x.__radd__(object()) == NotImplemented
def test_repr(self): x = ffwd(year=2012) assert repr(x)
def _delta_to_next(self, last_run_at, next_hour, next_minute, next_second): """Find next delta. Takes a :class:`~datetime.datetime` of last run, next minute and hour, and returns a :class:`~celery.utils.time.ffwd` for the next scheduled day and time. Only called when ``day_of_month`` and/or ``month_of_year`` cronspec is specified to further limit scheduled task execution. """ datedata = AttributeDict(year=last_run_at.year) days_of_month = sorted(self.day_of_month) months_of_year = sorted(self.month_of_year) def day_out_of_range(year, month, day): try: datetime(year=year, month=month, day=day) except ValueError: return True return False def roll_over(): for _ in range(2000): flag = (datedata.dom == len(days_of_month) or day_out_of_range( datedata.year, months_of_year[datedata.moy], days_of_month[datedata.dom]) or (self.maybe_make_aware( datetime(datedata.year, months_of_year[datedata.moy], days_of_month[datedata.dom])) < last_run_at)) if flag: datedata.dom = 0 datedata.moy += 1 if datedata.moy == len(months_of_year): datedata.moy = 0 datedata.year += 1 else: break else: # Tried 2000 times, we're most likely in an infinite loop raise RuntimeError('unable to rollover, ' 'time specification is probably invalid') if last_run_at.month in self.month_of_year: datedata.dom = bisect(days_of_month, last_run_at.day) datedata.moy = bisect_left(months_of_year, last_run_at.month) else: datedata.dom = 0 datedata.moy = bisect(months_of_year, last_run_at.month) if datedata.moy == len(months_of_year): datedata.moy = 0 roll_over() while 1: th = datetime(year=datedata.year, month=months_of_year[datedata.moy], day=days_of_month[datedata.dom]) if th.isoweekday() % 7 in self.day_of_week: break datedata.dom += 1 roll_over() return ffwd(year=datedata.year, month=months_of_year[datedata.moy], day=days_of_month[datedata.dom], hour=next_hour, minute=next_minute, second=next_second, microsecond=0)
def test_radd_with_unknown_gives_NotImplemented(self): x = ffwd(year=2012) self.assertEqual(x.__radd__(object()), NotImplemented)
def test_repr(self): x = ffwd(year=2012) self.assertTrue(repr(x))