# todo.parse-date-phrase
Reads what a person types into a due-date box, such as `tomorrow`,
`next friday`, `in 2 weeks` or `2026-10-01`, and returns the date it names,
counted from `today`. Returns null when the text is not a date phrase, so a
form can say "I didn't understand that" without catching anything. It only
throws when `today` itself is not a real ISO date (dates.add-days's message,
e.g. `"28/09/2026" is not an ISO date (YYYY-MM-DD)`), or when the answer would
fall outside 0001-9999.
`today` is an argument, not the clock: the caller passes the person's local
date, so the answer is the same on every server and in every test.
todo.parse-quick-add uses it to find the due date inside a whole quick-add
line.
## Grammar
The whole text must be one phrase. Letters are matched case-insensitively
(ASCII only: `TODAY` in full-width letters is not a keyword). Words are
separated by one or more ASCII whitespace characters (space, tab, CR, LF, VT,
FF), and whitespace at either end is ignored.
| Phrase | Means | Example, today Monday 2026-09-28 |
| --- | --- | --- |
| `today`, `tod` | today | 2026-09-28 |
| `tomorrow`, `tmr`, `tmrw` | today + 1 day | 2026-09-29 |
| `yesterday` | today - 1 day (a due date may be overdue) | 2026-09-27 |
| a weekday: `monday`/`mon`, `tuesday`/`tue`/`tues`, `wednesday`/`wed`, `thursday`/`thu`/`thur`/`thurs`, `friday`/`fri`, `saturday`/`sat`, `sunday`/`sun` | the next such day strictly after today (1 to 7 days on) | `friday` = 2026-10-02; `monday` = 2026-10-05 |
| `next week` | Monday of next ISO week | 2026-10-05 |
| `next <weekday>` | that weekday in next ISO week (Monday to Sunday) | `next friday` = 2026-10-09 |
| `next month` | the 1st of next month | 2026-10-01 |
| `next year` | 1 January next year | 2027-01-01 |
| `weekend`, `this weekend` | the coming Saturday; today if today is Saturday or Sunday | 2026-10-03 |
| `in N day(s)` | today + N days | `in 3 days` = 2026-10-01 |
| `in N week(s)` | today + 7N days | `in a week` = 2026-10-05 |
| `in N month(s)` | today + N months, clamped to the month end (dates.add-months) | `in 1 month` from 2026-01-31 = 2026-02-28 |
| `in N year(s)` | today + 12N months, clamped (29 Feb becomes 28 Feb) | `in a year` from 2028-02-29 = 2029-02-28 |
| `YYYY-MM-DD` | that date, if it is a real date (years 0001-9999) | `2026-10-01` |
| `on <phrase>`, `due <phrase>` | the same as the phrase | `due friday`, `on 2026-10-01` |
N is 1 to 999 in ASCII digits with no leading zero (`in 03 days` is not a
phrase), or one of the words `a`, `an`, `one` ... `ten`. Units may be singular
or plural whatever N is. Only one `on` or `due` is allowed, and only in front.
Anything else is null: `friday week`, `in 3 lightyears`, `in 0 days`,
`in 1000 days`, `2026-02-30`, `next`, an empty string, or a phrase with other
words around it (`pay rent tomorrow`; finding a phrase inside a line is
todo.parse-quick-add's job).
## Decisions
- **A weekday name never means today.** On a Friday, `friday` is a week
away: someone typing a weekday means a day to come, and `today` is the word
for today.
- **`next <weekday>` means next week's.** On Monday 28 September, `friday`
is 2 October and `next friday` is 9 October. People disagree about this
one; the ISO-week reading at least gives the two phrases different answers,
and a UI can show the date it picked.
- **`weekend` on a Sunday is today.** It is still the weekend, and jumping to
next Saturday would push the task six days out.
- **`in 1 month` clamps**, as dates.add-months does: 31 January + 1 month is
28 February (29 in a leap year), never 3 March.
- **An impossible ISO date is null, not an error.** It is something typed,
like any other unrecognised text; only `today` is the caller's
responsibility.