4. Recurrence
4.1 Purpose
This section defines recurrence semantics, including rule representation, anchor behavior, and per-instance completion/skip state.
4.2 Recurrence applicability
A task is recurring when semantic role recurrence contains a valid tasknotes recurrence string.
If recurrence is absent or empty, the task is non-recurring.
4.3 Rule format
4.3.1 Required format
recurrence MUST be a tasknotes recurrence string.
This syntax is RRULE-derived but is not a full RFC 5545 content line or multi-line iCalendar fragment.
The RRULE parameter portion MUST use RFC 5545 RRULE property syntax.
It MAY include a leading inline DTSTART segment.
If DTSTART is present, it MUST use one of:
DTSTART:YYYYMMDD(date-only)DTSTART:YYYYMMDDTHHMMSSZ(UTC datetime)
Canonical combined form is:
DTSTART:...;FREQ=...
Implementations MAY accept inbound RRULE: prefixes or multi-line iCalendar-like inputs for compatibility, but canonical writes SHOULD use the combined single-field form above.
Examples:
FREQ=DAILYFREQ=WEEKLY;BYDAY=MO,WE,FRFREQ=MONTHLY;BYMONTHDAY=1DTSTART:20260220;FREQ=WEEKLY;BYDAY=FR
4.3.2 Invalid rule handling
Invalid recurrence rules MUST produce a validation error in strict mode and SHOULD produce at least a warning in permissive mode.
4.4 Recurrence anchor
recurrence_anchor controls progression semantics and MUST be one of:
scheduledcompletion
If missing, implementations SHOULD default to scheduled unless collection configuration defines another default.
4.4.1 Recurrence seed precedence
When recurrence generation requires a seed/start date, implementations MUST resolve it in this order:
DTSTARTembedded inrecurrence,- semantic
scheduled, - semantic
date_created.
If no seed can be resolved, recurrence materialization MUST fail deterministically and validation MUST report an error.
4.4.2 Anchor progression behavior
For recurrence_anchor=scheduled, progression is based on the scheduled chain and DTSTART MUST remain fixed after it is set.
For recurrence_anchor=completion, complete-instance operations MUST advance progression by updating DTSTART to the completion target (date or datetime per §4.4.3).
4.4.3 DTSTART update semantics
When recurrence_anchor=completion and instance completion succeeds for resolved target day D:
- Instance-list state (
complete_instances,skipped_instances) MUST always use dayD. - If the caller provided an explicit datetime target,
DTSTARTMUST be rewritten asDTSTART:YYYYMMDDTHHMMSSZusing that target instant normalized to UTC. - Otherwise,
DTSTARTMUST be rewritten asDTSTART:YYYYMMDDfor dayD. - RRULE components other than
DTSTARTMUST be preserved unless an explicit recurrence-edit operation changes them. - If
DTSTARTis absent, completion-anchor progression MUST insert it before RRULE parameters.
4.4.4 Recalculation semantics for recurrence_anchor=completion
When calculating the next candidate occurrence for recurrence_anchor=completion:
- Implementations MUST treat
DTSTARTprogression as the consumed-history mechanism. complete_instancesMUST NOT be used as an exclusion set for future occurrence selection in this mode.skipped_instancesMUST still exclude matching candidate dates.- Candidate selection MUST choose the first RRULE occurrence strictly after the current
DTSTARTanchor (or resolved seed ifDTSTARTis absent).
This matches the completion-anchor progression model where each completion advances the chain by rewriting DTSTART.
This model is intentionally not fully reversible via ordinary uncomplete instance; progression history lives in DTSTART, not only in complete_instances.
4.4.5 DTSTART canonicalization on recurring writes
To ensure stable recurrence materialization across implementations, writers that persist recurring tasks MUST ensure DTSTART is present after create or successful recurring-instance completion.
Rules:
- If persisted
recurrencealready containsDTSTART, keep it unless an explicit operation updates it per §4.4.3. - If persisted
recurrenceomitsDTSTART, writers MUST resolve a seed using §4.4.1 and insertDTSTARTbefore RRULE parameters. - For
recurrence_anchor=scheduled, insertedDTSTARTbecomes the fixed progression baseline and MUST NOT be rewritten by later scheduled-anchor completions. - If seed resolution fails, operation MUST fail deterministically and emit
missing_recurrence_seed.
4.5 Instance state fields
Per-instance state is represented by:
complete_instances: list of date valuesskipped_instances: list of date values
These lists represent day-level instance outcomes.
4.6 Invariants
Implementations MUST enforce:
- Items in instance lists are valid date values.
- No date appears in both lists simultaneously.
- Duplicate dates are normalized (set semantics) or rejected deterministically.
- Validators MUST NOT reject an instance date solely because it is not an RRULE-generated occurrence.
4.7 Instance completion semantics
Operation: complete instance for target date D.
Conforming behavior:
- Add
Dtocomplete_instances. - Remove
Dfromskipped_instancesif present. - Leave base task status unchanged unless explicit policy states otherwise.
- Update
date_modifiedwhen state changes.
Operation MUST be idempotent.
4.8 Instance uncompletion semantics
Operation: uncomplete instance for target date D.
Conforming behavior:
- Remove
Dfromcomplete_instancesif present. - Do not add
Dtoskipped_instancesimplicitly. - For
recurrence_anchor=completion, uncomplete MUST NOT rewrite or roll backDTSTART; restoring prior progression requires an explicit recurrence edit or implementation-specific rewind operation. - Update
date_modifiedwhen state changes.
Operation MUST be idempotent.
4.9 Instance skip semantics
Operation: skip instance for target date D.
Conforming behavior:
- Add
Dtoskipped_instances. - Remove
Dfromcomplete_instancesif present. - Update
date_modifiedwhen state changes.
Operation MUST be idempotent.
4.10 Instance unskip semantics
Operation: unskip instance for target date D.
Conforming behavior:
- Remove
Dfromskipped_instancesif present. - Do not add to
complete_instancesimplicitly. - Update
date_modifiedwhen state changes.
Operation MUST be idempotent.
4.11 Effective instance state
For target date D, effective state MUST be resolved in this order:
- If
Dincomplete_instances: state iscompleted. - Else if
Dinskipped_instances: state isskipped. - Else: state is unresolved/default for that instance.
Because overlap is invalid, this ordering is deterministic.
4.12 Interaction with base status
For recurring tasks:
- Base
statusis task-level metadata and MUST NOT be forcibly rewritten to a completed status on instance completion unless explicitly configured. - Instance state determines completion for recurrence-aware views and operations.
- Dependency blocked/unblocked evaluation for v0.1 follows §10.2.5 and is not recurrence-instance-aware unless explicitly extended by implementation policy.
For non-recurring tasks, completion uses base status semantics (§5).
4.13 Completed-status configuration
Implementations MUST define which status values are treated as complete for non-recurring tasks.
The completed-status list MUST be configurable or schema-driven and MUST NOT rely on a hardcoded single literal.
4.14 Example: complete then skip same day
Initial:
recurrence: FREQ=DAILY
completeInstances: [2026-02-20]
skippedInstances: []
Skip target date 2026-02-20:
recurrence: FREQ=DAILY
completeInstances: []
skippedInstances: [2026-02-20]
4.15 Example: idempotent complete
Initial:
completeInstances: [2026-02-20]
skippedInstances: []
Complete 2026-02-20 again:
- persisted instance lists remain unchanged,
- operation succeeds without duplicate entries.
4.16 Example: anchor semantics
Task:
recurrence: FREQ=WEEKLY;BYDAY=FR
recurrenceAnchor: completion
If implementation supports next-occurrence materialization, it MUST compute next occurrence relative to completion progression when anchor is completion, not solely by scheduled chain.
Implementations using DTSTART progression MUST update DTSTART to the completion date for this mode.
Worked example (recurrence_anchor=completion):
recurrence: DTSTART:20260220;FREQ=DAILY
recurrence_anchor: completion
complete_instances: [2026-02-20, 2026-02-21]
skipped_instances: [2026-02-23]
Conforming recalculation:
- does not exclude
2026-02-22because it is absent fromskipped_instances, - does exclude
2026-02-23because it is skipped, - ignores
complete_instancesfor future exclusion in this mode (§4.4.4).
4.17 Validation examples
Invalid overlap:
completeInstances: [2026-02-20]
skippedInstances: [2026-02-20]
Result: validation error instance_state_overlap.
Invalid date in list:
completeInstances: [2026-02-30]
Result: validation error invalid_date_value.