Работа с датами в Zod опирается на тип z.date(), который
обеспечивает строгую проверку значений Date и позволяет
строить сложные правила сравнения: диапазоны времени, ограничения
относительно текущего момента, валидацию последовательности дат и
контроль бизнес-логики.
Тип z.date() принимает только корректные экземпляры
Date. Любые строки, числа или некорректные значения
отклоняются без дополнительной обработки.
import { z } from "zod";
const schema = z.date();
schema.parse(new Date()); // корректно
schema.parse("2024-01-01"); // ошибка
schema.parse(123456789); // ошибка
Такое поведение важно для предотвращения неявных преобразований, которые часто становятся источником ошибок в логике сравнения дат.
Для ограничения диапазона используются методы min и
max. Они задают нижнюю и верхнюю границу допустимого
значения.
const schema = z.date({
required_error: "Дата обязательна"
}).min(new Date("2020-01-01")).max(new Date("2030-01-01"));
Если значение выходит за пределы диапазона, схема возвращает ошибку валидации.
Для более точного контроля применяются операторы:
gte — больше или равноlte — меньше или равноgt — строго большеlt — строго меньшеconst schema = z.date().gte(new Date("2022-01-01")).lt(new Date("2023-01-01"));
Такой подход удобен при моделировании бизнес-правил, например ограничений периода регистрации или действия лицензии.
Частый сценарий — проверка относительно текущего момента. В этом
случае важно учитывать, что new Date() вычисляется при
создании схемы, а не при каждом вызове parse.
const now = new Date();
const schema = z.date().max(now);
Если требуется динамическое сравнение, используется
refine.
const schema = z.date().refine((date) => date <= new Date(), {
message: "Дата не может быть в будущем"
});
При работе с сущностями часто требуется проверить взаимосвязь нескольких дат: начало и конец периода.
const schema = z.object({
start: z.date(),
end: z.date()
}).refine((data) => data.end > data.start, {
message: "Дата окончания должна быть позже даты начала",
path: ["end"]
});
Такой подход позволяет выражать зависимости между полями, которые
невозможно описать через одиночные ограничения
min/max.
superRefine позволяет добавлять несколько ошибок и более
детально описывать причину нарушения условий.
const schema = z.object({
start: z.date(),
end: z.date()
}).superRefine((data, ctx) => {
if (data.end <= data.start) {
ctx.addIssue({
path: ["end"],
message: "end должен быть позже start",
code: z.ZodIssueCode.custom
});
}
const diff = data.end.getTime() - data.start.getTime();
const days = diff / (1000 * 60 * 60 * 24);
if (days > 30) {
ctx.addIssue({
path: ["end"],
message: "Период не может превышать 30 дней",
code: z.ZodIssueCode.custom
});
}
});
В реальных API часто приходят строки ISO-формата. Для этого
используется preprocess или coerce.
const schema = z.object({
date: z.coerce.date().min(new Date("2020-01-01"))
});
z.coerce.date() автоматически преобразует строку в
Date перед проверкой.
Эквивалент через preprocess:
const schema = z.object({
date: z.preprocess((val) => new Date(val), z.date())
});
Date в JavaScript включает временную часть, поэтому
сравнение может давать неожиданные результаты, если не нормализовать
значения.
const normalize = (d) => new Date(d.setHours(0, 0, 0, 0));
const schema = z.object({
date: z.date().refine((d) => normalize(d) >= normalize(new Date("2024-01-01")))
});
Это важно при работе с календарными событиями, где время суток не имеет значения.
Распространённая задача — определить пересечение двух диапазонов дат.
const intervalSchema = z.object({
start: z.date(),
end: z.date()
});
const schema = intervalSchema.refine((data) => data.end > data.start);
Для проверки пересечений двух интервалов:
const hasOverlap = (a, b) =>
a.start <= b.end && b.start <= a.end;
После проверки даты часто требуется преобразование в timestamp или ISO-строку.
const schema = z.date().transform((date) => date.toISOString());
Или в Unix timestamp:
const schema = z.date().transform((date) => date.getTime());
Некорректная работа с датами чаще всего связана с:
Datenew Date() внутри схемы без учёта
динамикиИспользование строгой схемы через Zod устраняет часть этих проблем за счёт явной типизации и централизованной валидации.
При моделировании доменных сущностей даты часто участвуют в ограничениях:
const subscriptionSchema = z.object({
start: z.date(),
end: z.date()
}).superRefine((data, ctx) => {
const now = new Date();
if (data.start < now) {
ctx.addIssue({
path: ["start"],
message: "Начало подписки не может быть в прошлом",
code: z.ZodIssueCode.custom
});
}
if (data.end <= data.start) {
ctx.addIssue({
path: ["end"],
message: "Окончание должно быть позже начала",
code: z.ZodIssueCode.custom
});
}
});
При передаче данных через JSON даты становятся строками, что требует
обязательного преобразования обратно в Date до
сравнения.
const input = {
start: "2024-01-01T10:00:00Z",
end: "2024-01-10T10:00:00Z"
};
const schema = z.object({
start: z.coerce.date(),
end: z.coerce.date()
});
schema.parse(input);
Без коэрсинга сравнение через gte/lte становится
невозможным из-за несоответствия типов.
При работе с коллекциями событий часто требуется сортировка или проверка последовательности.
const eventSchema = z.array(z.date()).refine((dates) => {
for (let i = 1; i < dates.length; i++) {
if (dates[i] < dates[i - 1]) return false;
}
return true;
});
Сложные сценарии сравнения дат обычно строятся постепенно:
DateТакой подход позволяет сохранять предсказуемость и строгость модели данных при использовании Zod.