# form.date-parts
Checks a date someone typed into three boxes - day, month, year - the way the
GOV.UK Design System's date input asks, and answers with GOV.UK's own error
message and which boxes to mark in error. The same function runs in the
browser (TypeScript), in a Python API and in a Rust service, so the page and
the server never disagree about a date or word the error differently.
```ts
import { validateDateParts } from "#fune/form.date-parts@^1";
const check = validateDateParts(
{ day: form.dobDay, month: form.dobMonth, year: form.dobYear },
"Date of birth",
{ today: "2026-09-28", timing: "past", notBefore: null, notAfter: null },
);
// { valid: false, date: null, message: "Date of birth must be in the past", fields: ["day", "month", "year"] }
```
```python
check = validate_date_parts(DateParts(day="31", month="4", year="2026"), "Date of birth", DateRules(today=None, timing=None, not_before=None, not_after=None))
# DatePartsCheck(valid=False, date=None, message="Date of birth must be a real date", fields=["day"])
```
`message` goes to `react.form.date-input`'s `error` and `fields` to its
`errorFields`, which marks exactly those boxes.
## The checks, in GOV.UK's order of priority
GOV.UK shows the highest-priority error only: missing or incomplete
information, then information that cannot be correct, then anything else.
| What was typed | Message | Marked |
| --- | --- | --- |
| nothing in any box | `Enter date of birth` | all three |
| some boxes empty | `Date of birth must include a month`, `... a day and year` | the empty ones |
| a year of digits that are not 4 | `Year must include 4 numbers` | year |
| a box that cannot be right (day 32, month 13, letters, year 0000) | `Date of birth must be a real date` | that box, or all three when more than one is wrong |
| a day the month does not have (31 April, 29 February 2023) | `Date of birth must be a real date` | day |
| `timing` past / past-or-today / future / future-or-today | `... must be in the past`, `... must be today or in the past`, `... must be in the future`, `... must be today or in the future` | all three |
| `notBefore` and `notAfter` | `... must be between 1 September 2017 and 30 September 2017` | all three |
| `notBefore` alone | `... must be the same as or after 1 September 2017` | all three |
| `notAfter` alone | `... must be the same as or before 31 August 2017` | all three |
Otherwise it answers `{ valid: true, date: "2007-03-27", message: null, fields: [] }`.
- **The label** is written as it starts a sentence ("Date of birth", "The
date your course ends"). After "Enter" its first letter is lower-cased
("Enter date of birth"), unless it starts with an acronym ("Enter UK arrival
date"): two capitals in a row are left alone. GOV.UK's own example reads
"Enter your date of birth"; pass "Your date of birth" as the label for
that, and every other message then starts "Your date of birth must...".
- **"Year must include 4 numbers"** is GOV.UK's wording verbatim and does not
use the label.
- **Trimming**: each box loses leading and trailing ASCII whitespace (space,
tab, line breaks), the same in all three languages.
- **Numbers** are ASCII digits only: `"٢"` is not a day. A day or month is one
or two digits, so `"2"` and `"02"` are both accepted and `"002"` is not.
- **Month names** are accepted, as GOV.UK asks, in full or as their first
three letters, in any case (`January`, `jan`, `JAN`), plus `sept`. English
only.
- **Dates are proleptic Gregorian**, years 1 to 9999, the range of the
`dates.*` family; the leap-year rule and month lengths come from
`dates.add-days`, not a copy.
- **Which box for 31 April**: GOV.UK says to mark the box that is wrong, or
the whole date when it is not clear. This marks the day, which is where the
fix almost always is.
- **Today is an argument.** Nothing reads the clock, so the check is the same
in a test, on a server in another timezone, and in the browser. Pass the
date that "today" means for your service (usually the UK date).
- Rules are checked before the date: a timing without `today`, a bound that
is not an ISO date, or `notBefore` after `notAfter` throws whatever was
typed, since that is a bug in the caller, not a mistake by the user. So does
an empty label.
## Sources
GOV.UK Design System, "Date input", error messages and month names:
https://design-system.service.gov.uk/components/date-input/ and the "Dates"
pattern https://design-system.service.gov.uk/patterns/dates/. The messages
above were checked word for word against the "Error messages" section of the
component page.