mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Import design docs (RFCs)
This commit is contained in:
parent
07493d5fa6
commit
8747cc9f94
15 changed files with 3259 additions and 0 deletions
203
docs/rfcs/recurrence.md
Normal file
203
docs/rfcs/recurrence.md
Normal 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)
|
Loading…
Add table
Add a link
Reference in a new issue