mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-28 04:24:51 +02:00
[aidan] feat/schedule: pin monthly last-day-of-month
This commit is contained in:
@@ -29,6 +29,9 @@ class ScheduleConfig(BaseModel):
|
||||
# Monthly schedules can pin a day-of-month explicitly. None preserves the
|
||||
# legacy "same day as the current reference" behavior for older records.
|
||||
day_of_month: Optional[int] = Field(default=None, ge=1, le=31)
|
||||
# When true, monthly schedules fire on the calendar's last day (28-31)
|
||||
# regardless of day_of_month, so "end of month" survives short months.
|
||||
last_day_of_month: bool = False
|
||||
# IANA zone name (e.g. "America/Los_Angeles") or "local" for legacy
|
||||
# records that predate explicit tz. storage._load_all_from_disk coerces
|
||||
# "local" to the host zone in memory; we leave it on disk until the
|
||||
|
||||
@@ -145,13 +145,17 @@ def _next_fire_after(sched: ScheduleConfig, ref_utc: datetime) -> Optional[datet
|
||||
if sched.repeat_unit == "month":
|
||||
target_day = sched.day_of_month or ref_local.day
|
||||
step = max(1, sched.repeat_every)
|
||||
c = candidate.replace(day=min(target_day, calendar.monthrange(candidate.year, candidate.month)[1]))
|
||||
|
||||
def month_day(year: int, month: int) -> int:
|
||||
last = calendar.monthrange(year, month)[1]
|
||||
return last if sched.last_day_of_month else min(target_day, last)
|
||||
|
||||
c = candidate.replace(day=month_day(candidate.year, candidate.month))
|
||||
while c <= ref_local:
|
||||
total = c.month - 1 + step
|
||||
year = c.year + total // 12
|
||||
month = total % 12 + 1
|
||||
day = min(target_day, calendar.monthrange(year, month)[1])
|
||||
c = c.replace(year=year, month=month, day=day)
|
||||
c = c.replace(year=year, month=month, day=month_day(year, month))
|
||||
return c.astimezone(timezone.utc)
|
||||
|
||||
if candidate <= ref_local:
|
||||
|
||||
Reference in New Issue
Block a user