Z
Zod•2y ago
Svish

Date validation

Question: Any good examples on how to do min/max date validation with zod?
11 Replies
Scott Trinh
Scott Trinh•2y ago
I would just use refine if you don't mind the ZodEffect output.
Svish
Svish•2y ago
What I've ended up with currently is a file with various transformations and refinements, including these two for dates:
type Refinement<T> = RefinementEffect<T>['refinement'];

export const refineDateIsAfter: (min: Date | 'today') => Refinement<Date> =
(min) => (value, ctx) => {
const minimumIsToday = min === 'today';
const minimum = min === 'today' ? startOfToday() : min;
if (isBefore(value, minimum)) {
ctx.addIssue(
createIssue({
code: 'too_small',
type: 'date',
minimum,
minimumIsToday,
received: value,
})
);
}
};

export const refineDateIsBefore: (max: Date | 'today') => Refinement<Date> =
(max) => (value, ctx) => {
const maximumIsToday = max === 'today';
const maximum = max === 'today' ? endOfToday() : max;

if (isAfter(value, maximum)) {
ctx.addIssue(
createIssue({
code: 'too_big',
type: 'date',
maximum,
maximumIsToday,
received: value,
})
);
}
};
type Refinement<T> = RefinementEffect<T>['refinement'];

export const refineDateIsAfter: (min: Date | 'today') => Refinement<Date> =
(min) => (value, ctx) => {
const minimumIsToday = min === 'today';
const minimum = min === 'today' ? startOfToday() : min;
if (isBefore(value, minimum)) {
ctx.addIssue(
createIssue({
code: 'too_small',
type: 'date',
minimum,
minimumIsToday,
received: value,
})
);
}
};

export const refineDateIsBefore: (max: Date | 'today') => Refinement<Date> =
(max) => (value, ctx) => {
const maximumIsToday = max === 'today';
const maximum = max === 'today' ? endOfToday() : max;

if (isAfter(value, maximum)) {
ctx.addIssue(
createIssue({
code: 'too_big',
type: 'date',
maximum,
maximumIsToday,
received: value,
})
);
}
};
Used like this:
const d = z
.date()
.superRefine(refineDateIsBefore('today'))
const d = z
.date()
.superRefine(refineDateIsBefore('today'))
Scott Trinh
Scott Trinh•2y ago
honestly, I think we'd be open to a min and max method on the ZodDate structure, I just don't think anyone has suggested it.
Svish
Svish•2y ago
To hook it into the errorMap and stuff it ended up being kind of weird though... Created a schema for custom issues:
export const CustomIssue = z.object({
code: z.literal('custom'),
params: z.union([
z.object({
code: z.literal(ZodIssueCode.invalid_type),
expected: z.literal('kontonummer'),
}),
z.object({
code: z.literal(ZodIssueCode.too_small),
type: z.literal('date'),
minimum: z.union([z.date(), z.literal('today')]),
minimumIsToday: z.boolean(),
received: z.date(),
}),
z.object({
code: z.literal(ZodIssueCode.too_big),
type: z.literal('date'),
maximum: z.union([z.date(), z.literal('today')]),
maximumIsToday: z.boolean(),
received: z.date(),
}),
]),
});

function createIssue(
params: z.infer<typeof CustomIssue.shape.params>
): z.infer<typeof CustomIssue> {
return { code: 'custom', params };
}
export const CustomIssue = z.object({
code: z.literal('custom'),
params: z.union([
z.object({
code: z.literal(ZodIssueCode.invalid_type),
expected: z.literal('kontonummer'),
}),
z.object({
code: z.literal(ZodIssueCode.too_small),
type: z.literal('date'),
minimum: z.union([z.date(), z.literal('today')]),
minimumIsToday: z.boolean(),
received: z.date(),
}),
z.object({
code: z.literal(ZodIssueCode.too_big),
type: z.literal('date'),
maximum: z.union([z.date(), z.literal('today')]),
maximumIsToday: z.boolean(),
received: z.date(),
}),
]),
});

function createIssue(
params: z.infer<typeof CustomIssue.shape.params>
): z.infer<typeof CustomIssue> {
return { code: 'custom', params };
}
Scott Trinh
Scott Trinh•2y ago
I'm personally a minimalist when it comes to the core library, but I think the precedent is here already
Svish
Svish•2y ago
GitHub
Request: ZodDate min and max · Issue #1089 · colinhacks/zod
I&#39;m happy to give this a shot myself if you think it&#39;s worthwhile to add! I get that this can currently be achieved with a refinement: z.date().refine((d) =&gt; d &gt;= new ...
Scott Trinh
Scott Trinh•2y ago
cool, yeah, thanks for weighing in.
Svish
Svish•2y ago
Yeah, I agree it makes sense to not go too crazy. Like, I don't mind at all to add my own validation of norwegian bank account numbers for example 😂 But, yeah, min and max for dates, are pretty basic and common I would say. Forms where you choose a date without any sort of limitation are quite rare I feel.
Scott Trinh
Scott Trinh•2y ago
yeah, agreed. FWIW, a lot of people look at Zod (especially coming from Yup) and wonder about it's lack of features like this, but I think if you consider what Zod is trying to be, it makes sense. Zod is a runtime equivalent to TypeScript types, not a form validation library. Lots of overlap there, but philosophically it tries to be very unopinionated about what kind of IO you're doing (forms, database, disk, etc etc)
Svish
Svish•2y ago
Yeah, that's true Thanks for the help (again). Maybe I'll look into that min/max date issue, if I manage to get over my mild hatred of yarn 😛
Scott Trinh
Scott Trinh•2y ago
(same 🥲 )