Functional Weave
Code in TypeScript

todo.parse-repeat@1.0.0

README.md

2,376 bytes · view raw

# todo.parse-repeat

Reads what a person types into a todo's Repeat box as a `Recurrence`
(todo.item), or null when it is not a repeat. It is the Repeat box's
counterpart to todo.parse-date-phrase for the Due box.

`every 2 weeks` on a todo due Friday 2 October 2026 is
`{frequency: weekly, interval: 2, anchor: 2026-10-02}`.

## What it reads

The whole box must be one `every ...` phrase, then optionally a start date:

- **The repeat** is todo.parse-quick-add's grammar, exactly (the capability
  calls it rather than keeping a copy): `every day`, `every weekday`,
  `every week`, `every monday` (any weekday name), `every month`,
  `every year`, `every other week`, `every 3 days`, `every ten years`
  (1 to 999). Case-insensitive, ASCII whitespace between words.
- **The start date**, if any, is whatever todo.parse-date-phrase reads as a
  whole: `tomorrow`, `on fri`, `next week`, `2026-10-15`.
- **Anything else is null**: words left over (`every week please`), a date
  first (`tomorrow every week`), `weekly` on its own (a title word to
  quick-add), an empty box, `every 1000 days`. A form shows its own "try
  every week" message for null.

## Where the series starts

The anchor is the series' first date, and so the todo's due date from then
on (todo.item refuses a due date before the anchor).

1. The date typed in the box, if there is one.
2. Otherwise `due`, the todo's due date.
3. Otherwise quick-add's choice: today; for `every weekday` today if it is
   Monday to Friday, else next Monday; for `every <weekday>` the next such day
   strictly after today.

Then, for steps 1 and 2, the start moves forward to the first day the
pattern allows: `every monday` on a todo due Wednesday 30 September starts
Monday 5 October (quick-add, by contrast, lets a date phrase in the same
line win and repeats weekly on Wednesdays; in a Repeat box the weekday is
the point), and `every weekday` from a Saturday starts the Monday after. A
start already on an allowed day stays: `every friday` due Friday 2 October
starts that day.

Month ends and leap days are left to todo.next-occurrence: `every month`
from 31 January is anchored on the 31st, so it falls on each month's last
day.

## Errors

Only a malformed or impossible `today` or `due` throws, with dates.add-days's
message (`"28/09/2026" is not an ISO date`). Text that is not a repeat is
null, never an error.