Recurring tasks live in pt_recurrence. The parser is hand-written and
compiles natural-language input to an RRULE-style string + a mode flag
(fixed or completion).
fixed—every— next occurrence anchored to the previous scheduled time, not when the task was completed. Best for calendar-like cadences (every monday at 9am).completion—every!(bang suffix) — next occurrence anchored to now on completion. Best for "every five days from when I last did it" tasks (every! 5 days).
| Input | RRULE shape | Mode |
|---|---|---|
every day |
FREQ=DAILY |
fixed |
every weekday |
FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR |
fixed |
every monday |
FREQ=WEEKLY;BYDAY=MO |
fixed |
every monday at 9am |
FREQ=WEEKLY;BYDAY=MO + time |
fixed |
every mon, wed, fri |
FREQ=WEEKLY;BYDAY=MO,WE,FR |
fixed |
every 1, 15, 27 |
FREQ=MONTHLY;BYMONTHDAY=1,15,27 |
fixed |
every 2 months |
FREQ=MONTHLY;INTERVAL=2 |
fixed |
every! 5 days |
FREQ=DAILY;INTERVAL=5 |
completion |
every! 2 weeks |
FREQ=WEEKLY;INTERVAL=2 |
completion |
Use a quick-add recurrence phrase, such as pt add "Standup every weekday at 9am".
The parser does not support every other tuesday, every last friday, or a
starting ... suffix.
On pt done for a recurring task, the engine advances the same row to its
next deadline, preserving its UUID, PT-N and history:
fixed: advance from the current deadline, skipping missed occurrences until the next deadline is after the completion time. A plain monthly rule (every month,every 2 months) counts whole intervals from its anchor, the deadline set at creation or by the last deadline edit, so a month-end day survives short months: Jan 31 → Feb 28 → Mar 31.completion: compute the next RRULE occurrence from now.
The next occurrence is status_v2='todo' (legacy status='pending') with
snoozed_until cleared. A previous claim ends with completion, so the next
occurrence can be claimed again. A task.recurrence_advanced event records
the new deadline; no new task is created.
pt edit <PT-N> --deadline 2099-01-01 # move the next occurrence
This also updates pt_recurrence.next_occurrence and re-anchors a monthly
rule on the new date. Clearing the deadline on a recurring task is rejected.
There is no pt edit --recurrence flag; create recurrence through quick-add,
and dismiss the task to stop working it.
CREATE TABLE pt_recurrence (
task_uuid TEXT PRIMARY KEY REFERENCES tasks(id) ON DELETE CASCADE,
rrule TEXT NOT NULL, -- RFC 5545
mode TEXT NOT NULL, -- 'fixed' | 'completion'
original_input TEXT NOT NULL, -- 'every monday at 9am'
next_occurrence TEXT NOT NULL, -- ISO datetime UTC
anchor TEXT -- V019: operator-set occurrence
);next_occurrence mirrors the task's deadline, which is what filters and
views read. anchor is NULL on rows created before V019; those keep
advancing from the current deadline until their next deadline edit.