Skip to content

Commit 58df50a

Browse files
dcramercodex
andcommitted
Fix cron DST drift in schedule timezone evaluation
Co-Authored-By: GPT-5 Codex <codex@openai.com>
1 parent 19af310 commit 58df50a

1 file changed

Lines changed: 11 additions & 4 deletions

File tree

src/ash/scheduling/types.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)