@@ -111,8 +111,12 @@ def _prev_run_time(self, timezone: str = "UTC") -> datetime | None:
111111 )
112112 tz = ZoneInfo ("UTC" )
113113
114- now = datetime .now (tz )
115- prev_local = croniter (self .cron , now ).get_prev (datetime )
114+ now_local = datetime .now (tz )
115+ # croniter can drift by an hour across DST transitions when given
116+ # timezone-aware datetimes; evaluate in naive local wall-clock time.
117+ now_naive = now_local .replace (tzinfo = None )
118+ prev_naive = croniter (self .cron , now_naive ).get_prev (datetime )
119+ prev_local = prev_naive .replace (tzinfo = tz )
116120 return prev_local .astimezone (UTC )
117121 except Exception as e :
118122 logger .warning (
@@ -191,8 +195,11 @@ def _next_run_time(self, timezone: str = "UTC") -> datetime | None:
191195 else :
192196 base_time = self .created_at .astimezone (tz )
193197
194- # Evaluate cron in local timezone, result is timezone-aware
195- next_local = croniter (self .cron , base_time ).get_next (datetime )
198+ # croniter can drift by an hour across DST transitions when given
199+ # timezone-aware datetimes; evaluate in naive local wall-clock time.
200+ base_naive = base_time .replace (tzinfo = None )
201+ next_naive = croniter (self .cron , base_naive ).get_next (datetime )
202+ next_local = next_naive .replace (tzinfo = tz )
196203
197204 # Convert to UTC for consistent storage/comparison
198205 next_utc = next_local .astimezone (UTC )
0 commit comments