Skip to main content
Version: v0.1.5

Validate a date-parts value

Check a value from a parser before you store or convert it. This page shows the three ways to ask, and how to read the answer. The date-parts contract lists every rule a value is checked against.

Get every fault at once​

validateParts() takes any argument and never throws. A valid value comes back with its recognized components and its precision:

import { validateParts } from "@cosyte/dates";

validateParts({ year: 1988, month: 5, day: 7 }); // => { valid: true, parts: { year: 1988, month: 5, day: 7 }, precision: "day" }

An invalid value comes back with every fault, not only the first. Each issue names the component, the code of the rule it broke, and a message:

import { validateParts } from "@cosyte/dates";

const result = validateParts({ year: 88, month: 13, day: 40 });
result.valid; // => false

const faults = result.valid
? []
: result.issues.map((issue) => `${issue.component}: ${issue.code}`);
faults; // => ["year: year-not-four-digits", "month: out-of-range", "day: out-of-range"]

Branch on result.valid before you read parts or issues: TypeScript narrows the result to ValidPartsResult or InvalidPartsResult on that check. Each code is listed, with what raises it, in Errors and issue codes.

Use a type guard​

isValidParts() is the same check as a boolean, and narrows an unknown to DateParts:

import { isValidParts } from "@cosyte/dates";

const fromParser: unknown = { year: 2024, month: 2, day: 29 };

// Inside the guard, fromParser is typed as DateParts.
const day = isValidParts(fromParser) ? fromParser.day : undefined;
day; // => 29
isValidParts({ year: 2025, month: 2, day: 29 }); // => false

Throw with the faults attached​

assertValidParts() returns the recognized components, or throws a DatePartsError whose issues are the same array validateParts() would return:

import { DatePartsError, assertValidParts } from "@cosyte/dates";

assertValidParts({ year: 1988, month: 5 }); // => { year: 1988, month: 5 }

let codes: string[] = [];
try {
assertValidParts({ year: 2025, month: 2, day: 29 });
} catch (error) {
if (error instanceof DatePartsError) codes = error.issues.map((issue) => issue.code);
}
codes; // => ["no-such-calendar-date"]

Every converter (toISO, toTemporal, toZonedDateTime, toInstant) and precisionOf validates its argument the same way and throws the same DatePartsError, so you do not need to validate before you convert. Validate first when you want to report the faults without a try.

Pass only the components the contract names​

Validation reads year, month, day, hour, minute, second, fraction and offsetMinutes, and nothing else. A property with any other name is ignored and left out of parts:

import { validateParts } from "@cosyte/dates";

const result = validateParts({
year: 2026,
month: 3,
day: 9,
hour: 8,
minute: 5,
millisecond: 250,
});
result.valid ? result.precision : undefined; // => "minute"

If your source holds sub-second time under another name, such as milliseconds, convert it to fraction, a share of one second (250 milliseconds is 0.25), before you validate.

Log a fault without logging the date​

Most messages quote only the component at fault and its value:

import { validateParts } from "@cosyte/dates";

const result = validateParts({ year: 1988, month: 13, day: 7 });
result.valid ? "" : result.issues[0]?.message; // => "month must be an integer in 1 to 12 (spec-native, January is 1, not 0); received the number 13"

A no-such-calendar-date message does not. It names the day, the month and the year, which is the whole date:

import { validateParts } from "@cosyte/dates";

const result = validateParts({ year: 1988, month: 4, day: 31 });
result.valid ? "" : result.issues[0]?.message; // => "day 31 does not exist: April 1988 has 30 days"

The message of a DatePartsError joins its issues' messages, so it can carry the whole date too. A date of birth is PHI. To log nothing from the value, log the component and code of each issue and leave out the message.

Reference​