Import design docs (RFCs)

This commit is contained in:
Dustin J. Mitchell 2022-03-05 23:17:51 +00:00 committed by Tomas Babej
parent 07493d5fa6
commit 8747cc9f94
15 changed files with 3259 additions and 0 deletions

203
docs/rfcs/recurrence.md Normal file
View file

@ -0,0 +1,203 @@
---
title: "Taskwarrior - Recurrence"
---
# Draft
This is a draft design document. Your
[feedback](mailto:support@taskwarrior.org?Subject=Feedback) is welcomed.
Recurrence
----------
Recurrence needs an overhaul to improve weaknesses and add new features.
# Terminology
- The hidden 'parent' task is called the template.
- Synthesis is the name for the generation of new recurring task instances
when necessary.
- The synthesized tasks are called instances.
- The index is the zero-based monotonically increasing number of the instance.
- Drift is the accumulated errors in time that cause a due date to slowly
change for each recurring task.
# Criticism of Current Implementation
- The `mask` attribute grows unbounded.
- Only strict recurrence cycles are supported. The example of mowing the lawn
is that you want to mow the lawn every seven days, but when you are four
days late mowing the lawn, the next mowing should be in seven days, not in
three.
- Intances generated on one machine and then synced, may collide with
equivalent unsynced instances tasks on another device, because the UUIDs are
different.
- You cannot `wait` a recurring task and have that wait period propagate to
all other child tasks.
- Task instances cannot individually expire.
# Proposals
## Proposal: Eliminate `mask`, `imaѕk` Attributes
The `mask` attribute in the template is replaced by `last`, which indicates the
most recent instance index synthesized. Because instances are never synthesized
out of order, we only need to store the most recent index. The `imask` attribute
in the instance is no longer needed.
## Proposal: Rename `parent` to `template`
The name `parent` implies subtasks, and confuses those who inspect the
internals. The value remains the UUID of the template. This frees up the
namespace for future use with subtasks.
## Proposal: New 'rtype' attribute
To indicate the flavor of recurrence, support the following values:
* `periodic` - Instances are created on a regular schedule. Example: send birthday flowers. It must occur on a regular schedule, and doesn't matter if you were late last year. This is the default value.
* `chained` - Instances are created back to back, so when one instance ends, the next begins, with the same recurrence. Example: mow the lawn. If you mow two days late, the next instance is not two days early to compensate.
## Proposal: Use relative offsets
The delta between `wait` and `due` date in the template should be reflected in
the delta between `wait` and `due` date in the instance. Similarly,
'scheduled' must be handled the same way.
## Proposal: On load, auto-upgrade legacy tasks
Upgrade template:
- Add `rtype:periodic`
- Add `last:N` where `N` is the length of `mask`
- Delete `mask`
Upgrade instance:
- Rename `parent` to `template`
- Delete `imask`
- Update `wait` if not set to: `wait:due + (template.due - template.wait)`
- Update `scheduled` if not set to:
`scheduled:due + (template.due - template.scheduled)`
## Proposal: Deleting a chained instance
Deleting a `rtype:chained` instance causes the next chained instance to be
synthesized. This gives the illusion that the due date is simply pushed out to
`(now + template.recur)`.
## Proposal: Modification Propagation
TBD
## Proposal: Exotic Dates
Expand date specifications to use pattern phrases:
- `4th thursday in November`
- `4th thursday of November`
- `Friday before easter`
- `next Tuesday`
- `last Tuesday`
- `last July`
- `weekend`
- `3 days before eom`
- `in the morning`
- `4pm`
- `noon`
- `midnight`
Got suggestions?
## Proposal: User-Defined Week Start
TBD
# Implementation
## Implementation: Adding a new `periodic` template
When adding a new periodic template:
task add ... due:D recur:R wait:D-1wk scheduled:D-1wk until:U
Creates:
template.uuid: NEW_UUID
template.description: ...
template.entry: now
template.modified: now
template.due: D
template.recur: R (stored in raw form, ie 'P14D')
template.wait: D-1wk
template.scheduled: D-1wk
template.until: U
template.rtype: periodic
template.last:
Creating the Nth instance (index N):
Clone instance from template.
instance.uuid: NEW_UUID
instance.modified: now
instance.due: template.due + (N * template.recur)
instance.wait: instance.due + (template.due - template.wait)
instance.scheduled: instance.due + (template.due - template.scheduled)
instance.start:
template.last: N
## Implementation: Adding a new `chained` template
When adding a new chained template:
task add ... due:D recur:R wait:D-1wk scheduled:D-1wk until:U rtype:chained
Creates:
template.uuid: NEW_UUID
template.description: ...
template.entry: now
template.modified: now
template.due: D
template.recur: R (stored in raw form, ie 'P14D')
template.wait: D-1wk
template.scheduled: D-1wk
template.until: U
template.rtype: chained
Creating the Nth instance (index N):
Clone instance from template.
instance.uui d: NEW_UUID
instance.mod ified: now
instance.due : instance[N-1].end + template.recur
instance.wai t: instance.due + (template.due - template.wait)
instance.sch eduled: instance.due + (template.due - template.scheduled)
instance.sta rt:
Chained tasks do not obey `rc.recurrence.limit`, and show only one pending task
at a time.
## Implementation: Special handling for months
Certain recurrence periods are inexact:
- P1M
- P1Y
- P1D
When the recurrence period is `P1M` the number of days in a month varies and
causes drift.
When the recurrence period is `P1Y` the number of days in a year varies and
causes drift.
When the recurrence period is `P1D` the number of hours in a day varies due to
daylight savings, and causes drift.
Drift should be avoided by carefully implementing:
instance.due: template.due + (N * template.recur)