todo.reschedule-recurrence
When a repeating todo's due date is moved by hand, move its series with it: the new date becomes the anchor.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 14 tests, run in TypeScript, Python and Rust.
What it does
When someone edits a repeating todo and changes its due date, the series moves with it: the new date becomes the recurrence's anchor.
todo.item's `Recurrence` counts every occurrence from its `anchor`, the series' first date, and `validateDraft` refuses a due date before the anchor. So an edit form that simply stored the new date would either be refused (moved earlier) or keep repeating on the old rhythm (moved later). Moving the anchor is what a person means by "move this to Friday".
For example
reschedule_recurrence(frequency monthly, interval 1, anchor 2026-09-29, 2026-09-29, 2026-10-05)→ frequency monthly, interval 1, anchor 2026-10-05 moving a monthly todo's date moves the series to the new datereschedule_recurrence(frequency weekly, interval 1, anchor 2026-10-01, 2026-10-01, 2026-09-28)→ frequency weekly, interval 1, anchor 2026-09-28 moving it earlier than the anchor, which todo.item would otherwise refusereschedule_recurrence(frequency monthly, interval 1, anchor 2026-01-31, 2026-02-28, 2026-02-28)→ frequency monthly, interval 1, anchor 2026-01-31 an unchanged date keeps a month-end anchor after the series rolled forward
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
def reschedule_recurrence(recurrence: Optional[Recurrence], from_due: Optional[str], to_due: Optional[str]) -> Optional[Recurrence]
| recurrence | Recurrence? | the todo's repeat as stored, or null for a one-off todo |
| from_due | date? | the due date it had |
| to_due | date? | the due date the person gave it |
| returns | Recurrence? | anchored on toDue when the date moved; otherwise unchanged |
Your code names it in one line, in the file that uses it
from fune.todo.reschedule_recurrence import reschedule_recurrence # todo.reschedule-recurrence@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from dataclasses import replace
from typing import Optional
from .dates_add_days import add_days ← from dates.add-days ^1.0.0 · built alongside by fune
from .todo_item import Recurrence ← from todo.item ^1.0.0 · built alongside by fune
def reschedule_recurrence(
recurrence: Optional[Recurrence], from_due: Optional[str], to_due: Optional[str]
) -> Optional[Recurrence]:
"""The repeat a todo keeps when its due date is edited: a moved date becomes the anchor."""
# add_days(x, 0) is the date check: it raises dates.add-days's message.
if from_due is not None:
add_days(from_due, 0)
if to_due is not None:
add_days(to_due, 0)
if recurrence is None:
return None
add_days(recurrence.anchor, 0)
# No date: todo.item's validate_draft says a repeating todo needs one.
if to_due is None or to_due == from_due:
return recurrence
return replace(recurrence, anchor=to_due)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add todo.reschedule-recurrence
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./todo.reschedule-recurrence-1.0.0-python.fune, or fetch it from a terminal with fune pull todo.reschedule-recurrence@1.0.0:python.
The whole function, every language, is one file too: todo.reschedule-recurrence-1.0.0.fune, 9,618 bytes, sha256 32bbf5607d31a2d70a36741adcf309620dd29de47922d0b5e4045c5976eb5451. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
# fune: before todo.reschedule-recurrence
after — your function gets the result and the arguments, and returns the final result.
# fune: after todo.reschedule-recurrence
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
# fune: replace dates.add-days in todo.reschedule-recurrence
# fune: replace todo.item in todo.reschedule-recurrence
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show todo.reschedule-recurrence --steps.
# fune: step todo.reschedule-recurrence after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| moving a monthly todo's date moves the series to the new date | frequency monthly, interval 1, anchor 2026-09-29, 2026-09-29, 2026-10-05 | → | frequency monthly, interval 1, anchor 2026-10-05 |
| moving it earlier than the anchor, which todo.item would otherwise refuse | frequency weekly, interval 1, anchor 2026-10-01, 2026-10-01, 2026-09-28 | → | frequency weekly, interval 1, anchor 2026-09-28 |
| an unchanged date keeps a month-end anchor after the series rolled forward | frequency monthly, interval 1, anchor 2026-01-31, 2026-02-28, 2026-02-28 | → | frequency monthly, interval 1, anchor 2026-01-31 |
| a rolled-forward series moved by two days is anchored on the new date, not the old anchor | frequency monthly, interval 1, anchor 2026-01-31, 2026-02-28, 2026-03-02 | → | frequency monthly, interval 1, anchor 2026-03-02 |
| frequency and interval are kept | frequency weekly, interval 2, anchor 2026-09-28, 2026-09-28, 2026-10-02 | → | frequency weekly, interval 2, anchor 2026-10-02 |
| a one-off todo stays one-off | —, 2026-09-28, 2026-10-01 | → | — |
| clearing the date leaves the repeat for todo.item to report | frequency daily, interval 1, anchor 2026-09-28, 2026-09-28, — | → | frequency daily, interval 1, anchor 2026-09-28 |
| a repeat that had no date gets one: the new date is the anchor | frequency daily, interval 1, anchor 2026-09-28, —, 2026-10-01 | → | frequency daily, interval 1, anchor 2026-10-01 |
| a weekday series moved to a Saturday is anchored there as given | frequency weekdays, interval 1, anchor 2026-09-28, 2026-09-30, 2026-10-03 | → | frequency weekdays, interval 1, anchor 2026-10-03 |
| no dates at all and no repeat | —, —, — | → | — |
Show the other 4 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| an impossible new date is an error | frequency daily, interval 1, anchor 2026-09-28, 2026-09-28, 2026-02-30 | → | error: is not a real calendar date |
| a malformed old date is an error | frequency daily, interval 1, anchor 2026-09-28, 28/09/2026, 2026-10-01 | → | error: is not an ISO date |
| a malformed anchor is an error | frequency monthly, interval 1, anchor 2026-13-01, 2026-09-28, 2026-10-01 | → | error: is not a real calendar date |
| a bad date is an error even for a one-off todo | —, 2026-09-28, 2026-9-30 | → | error: is not an ISO date |
More from the author
| recurrence | fromDue → toDue | result | | --- | --- | --- | | null | anything | null | | any | toDue null | unchanged (validateDraft then says a repeating todo needs a due date) | | any | toDue = fromDue | unchanged | | any | toDue ≠ fromDue | the same frequency and interval, anchored on toDue |
- **An unchanged date keeps the anchor.** A monthly series from 31 January is due 28 February after one roll forward; saving an edit to its title must not re-anchor it on the 28th, or it would lose the month end. - A new date is taken as given, even a Saturday for a weekday series: todo.next-occurrence decides the following dates from there. - Every date given (`fromDue`, `toDue`, the anchor) must be a real ISO date, with or without a recurrence; the messages are dates.add-days's (`"2026-02-30" is not a real calendar date`).
Files
| Path | Bytes |
|---|---|
| README.md | 1,367 |
| impl/python.py | 833 |
| impl/rust.rs | 1,257 |
| impl/typescript.ts | 1,015 |
| vectors.json | 2,954 |