diff --git a/appdaemon/adapi.py b/appdaemon/adapi.py index aba26b6f2..ea098276d 100644 --- a/appdaemon/adapi.py +++ b/appdaemon/adapi.py @@ -14,9 +14,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload -from appdaemon import dependency +from appdaemon import dependency, utils from appdaemon import exceptions as ade -from appdaemon import utils from appdaemon.appdaemon import AppDaemon from appdaemon.entity import Entity from appdaemon.events import EventCallback @@ -24,6 +23,7 @@ from appdaemon.models.config.app import AppConfig from appdaemon.parse import resolve_time_str from appdaemon.state import StateCallbackType + from .types import TimeDeltaLike T = TypeVar("T") @@ -3225,6 +3225,7 @@ async def run_every( start: str | dt.time | dt.datetime | None = None, interval: TimeDeltaLike = 0, *args, + immediate: bool = False, random_start: TimeDeltaLike | None = None, random_end: TimeDeltaLike | None = None, pin: bool | None = None, @@ -3257,6 +3258,8 @@ async def run_every( - If this is a ``timedelta`` object, the current date will be assumed. *args: Arbitrary positional arguments to be provided to the callback function when it is triggered. + immediate (bool, optional): Whether to immediately fire the callback or wait until the first interval if the + start time is now. random_start (int, optional): Start of range of the random time. random_end (int, optional): End of range of the random time. pin (bool, optional): Optional setting to override the default thread pinning behavior. By default, this is @@ -3310,7 +3313,7 @@ def timed_callback(self, **kwargs): ... # example callback """ interval = utils.parse_timedelta(interval) - next_period = await self.AD.sched.get_next_period(interval, start) + next_period = await self.AD.sched.get_next_period(interval, start, immediate=immediate) self.logger.debug( "Registering %s for run_every in %s intervals, starting %s", diff --git a/appdaemon/scheduler.py b/appdaemon/scheduler.py index 5d1f0b0db..94a363a78 100644 --- a/appdaemon/scheduler.py +++ b/appdaemon/scheduler.py @@ -483,6 +483,8 @@ async def get_next_period( self, interval: TimeDeltaLike, start: time | datetime | str | None = None, + *, + immediate: bool = False, ) -> datetime: interval = utils.parse_timedelta(interval) start = "now" if start is None else start @@ -493,7 +495,7 @@ async def get_next_period( assert isinstance(aware_start, datetime) and aware_start.tzinfo is not None # Skip forward to the next period if start is in the past - while aware_start < now: + while aware_start < now or (immediate and aware_start <= now): aware_start += interval return aware_start @@ -901,7 +903,7 @@ async def parse_datetime( # Need to force timezone during time-travel mode if now is None: now = await self.get_now() - now = now.astimezone(self.AD.tz) + now = utils.ensure_timezone(now, self.AD.tz) return parse.parse_datetime( input_=input_, now=now, diff --git a/docs/HISTORY.md b/docs/HISTORY.md index 98f117ef8..3f65f1ef6 100644 --- a/docs/HISTORY.md +++ b/docs/HISTORY.md @@ -9,6 +9,7 @@ - Reload modified apps on SIGUSR2 - contributed by [chatziko](https://github.com/chatziko) - Using urlib to create endpoints from URLs - contributed by [cebtenzzre](https://github.com/cebtenzzre) - Added {py:meth}`~appdaemon.plugins.hass.hassapi.Hass.process_conversation` and {py:meth}`~appdaemon.plugins.hass.hassapi.Hass.reload_conversation` to the {ref}`Hass API `. +- Added `immediate` kwargs to `run_every` to control semantics around `start == "now"` **Fixes**