updated apps

This commit is contained in:
2026-07-14 23:57:03 -04:00
parent 6cc7212cef
commit 010e828e9c
797 changed files with 45153 additions and 4246 deletions
@@ -168,15 +168,20 @@ class Schedule:
- the finite-series end (``ends_count`` / ``ends_until``) stops re-arming.
A one_time task keeps its fixed date and ignores season/finite.
"""
# A postponed occurrence wins for the current cycle — until it's been
# completed past the override date (then fall through to normal cadence).
if due_override is not None and (last_performed is None or due_override > last_performed):
return due_override
# Finite series exhausted by completion count → terminally done.
# Finite series exhausted by completion count → terminally done. Checked
# BEFORE the override so postponing a finished series can't resurrect it
# (there is no current cycle left to postpone).
if self.ends_count is not None and times_performed >= self.ends_count:
return None
# A postponed occurrence wins for the current cycle — until it's been
# completed past the override date (then fall through to normal cadence).
# It still respects the series end date.
if due_override is not None and (last_performed is None or due_override > last_performed):
if self.ends_until is not None and due_override > self.ends_until:
return None
return due_override
result = self._compute_next_due(
last_performed=last_performed,
created_at=created_at,
@@ -251,9 +256,16 @@ class Schedule:
periods = 1 if days_gap < 0 else (days_gap // step) + 1
return anchor + timedelta(days=periods * step)
# Calendar units (months/years): step until past last_performed.
# Multiply from the ORIGINAL anchor instead of iterating on the
# clamped result — iterative adds let February permanently drag a
# day-31 anchor to day 28 (Jan 31 → Feb 28 → Mar 28 …); n×interval
# from the anchor recovers the intended day per target month
# (Jan 31 + 2 months = Mar 31). The clamp can still stick across
# CYCLES when a February due itself becomes the next anchor — the
# day_of_month kind is the tool for hard month-end pinning.
candidate = anchor
for _ in range(_MAX_PLANNED_STEPS):
candidate = add_interval(candidate, every, self.unit)
for n in range(1, _MAX_PLANNED_STEPS + 1):
candidate = add_interval(anchor, n * every, self.unit)
if candidate > last_performed:
return candidate
return candidate