def events(self, after: datetime = None) -> Iterable[ClockEvent]: """ Generator that emits clock events Args: - after (datetime, optional): the first result will be after this date Returns: - Iterable[ClockEvent]: the next scheduled events """ tz = getattr(self.start_date, "tz", "UTC") if after is None: after = pendulum.now(tz) else: after = pendulum.instance(after).in_tz(tz) # if there is a start date, advance to at least one second before the start (so that # the start date itself will be registered as a valid clock date) if self.start_date is not None: after = max(after, self.start_date - timedelta(seconds=1)) assert isinstance(after, datetime) # mypy assertion after = pendulum.instance(after) assert isinstance(after, pendulum.DateTime) # mypy assertion assert isinstance(after.tz, pendulum.tz._Timezone) # mypy assertion # croniter's DST logic interferes with all other datetime libraries except pytz after_localized = pytz.timezone(after.tz.name).localize( datetime( year=after.year, month=after.month, day=after.day, hour=after.hour, minute=after.minute, second=after.second, microsecond=after.microsecond, ) ) # Respect microseconds by rounding up if after_localized.microsecond: after_localized = after_localized + timedelta(seconds=1) cron = croniter(self.cron, after_localized) dates = set() # type: Set[datetime] while True: next_date = pendulum.instance(cron.get_next(datetime)) # because of croniter's rounding behavior, we want to avoid # issuing the after date; we also want to avoid duplicates caused by # DST boundary issues if next_date.in_tz("UTC") == after.in_tz("UTC") or next_date in dates: next_date = pendulum.instance(cron.get_next(datetime)) if self.end_date and next_date > self.end_date: break dates.add(next_date) yield ClockEvent( start_time=next_date, parameter_defaults=self.parameter_defaults )
def next(self, n: int, after: datetime = None) -> List[datetime]: """ Retrieve next scheduled dates. Args: - n (int): the number of future scheduled dates to return - after (datetime, optional): the first result will be after this date Returns: - list: list of next scheduled dates """ tz = getattr(self.start_date, "tz", "UTC") if after is None: after = pendulum.now(tz) else: after = pendulum.instance(after).in_tz(tz) # if there is a start date, advance to at least one second before the start (so that # the start date itself will be registered as a valid schedule date) if self.start_date is not None: after = max(after, self.start_date - timedelta(seconds=1)) assert isinstance(after, datetime) # mypy assertion after = pendulum.instance(after) assert isinstance(after, pendulum.DateTime) # mypy assertion # croniter's DST logic interferes with all other datetime libraries except pytz after_localized = pytz.timezone(after.tz.name).localize( datetime( year=after.year, month=after.month, day=after.day, hour=after.hour, minute=after.minute, second=after.second, microsecond=after.microsecond, )) cron = croniter(self.cron, after_localized) dates = [] # type: List[datetime] for i in range(n): next_date = pendulum.instance(cron.get_next(datetime)) # because of croniter's rounding behavior, we want to avoid # issuing the after date; we also want to avoid duplicates caused by # DST boundary issues if next_date.in_tz("UTC") == after.in_tz( "UTC") or next_date in dates: next_date = pendulum.instance(cron.get_next(datetime)) if self.end_date and next_date > self.end_date: break dates.append(next_date) return dates