Работа с временными зонами является одной из наиболее сложных задач
при обработке дат в JavaScript-приложениях. Библиотека Joi, как
инструмент валидации схем, предоставляет базовые возможности для
проверки дат, но корректная интерпретация временных зон требует
понимания особенностей форматов времени, поведения Date в
JavaScript и принципов стандарта ISO 8601.
В JavaScript объект Date хранит момент времени в виде
количества миллисекунд, прошедших с 1 января 1970 года по UTC. Это
означает, что внутренне временная зона не хранится, но влияет на
отображение и парсинг строковых значений.
Основные особенности:
Date нормализуются к UTC;Пример:
new Date("2026-05-10T12:00:00Z") // UTC
new Date("2026-05-10T12:00:00+06:00") // с оффсетом
Joi опирается на стандартный объект Date, поэтому все
нюансы временных зон остаются на уровне JavaScript.
Joi предоставляет тип date(), который используется для
проверки значений даты и времени.
import Joi from "joi";
const schema = Joi.object({
createdAt: Joi.date().required()
});
Поддерживаются различные входные форматы:
Date.Однако временная зона в явном виде не валидируется — она лишь интерпретируется при парсинге строки.
Наиболее корректный способ передачи времени — использование ISO 8601.
Примеры:
2026-05-10T10:00:00Z — UTC время;2026-05-10T10:00:00+06:00 — смещение относительно
UTC;2026-05-10T10:00:00-03:00 — отрицательное
смещение.Joi корректно принимает такие строки при использовании
Joi.date().iso():
const schema = Joi.object({
eventTime: Joi.date().iso().required()
});
Ключевая особенность iso() заключается в строгой
проверке формата строки. Любые нестандартные представления даты будут
отклонены.
Joi не интерпретирует временные зоны как бизнес-логику. Она лишь
проверяет корректность формата и возможность преобразования в
Date.
Например:
Joi.date().validate("2026-05-10T12:00:00+06:00");
Joi.date().validate("2026-05-10T06:00:00Z");
Обе строки описывают один и тот же момент времени, но с разным представлением.
Важный момент:
После валидации остаётся только UTC-момент.
При использовании стандартного Date информация о
исходной временной зоне теряется.
const value = new Date("2026-05-10T10:00:00+06:00");
После создания объекта:
+06:00 больше недоступно.Joi не способен восстановить это значение, так как работает поверх
Date.
Иногда требуется строгое использование UTC, чтобы избежать неоднозначности.
const schema = Joi.object({
timestamp: Joi.date().iso().required()
.messages({
"date.format": "Требуется ISO 8601 формат"
})
});
Но iso() не гарантирует наличие Z. Строка с
+06:00 также будет считаться валидной.
Для строгого UTC обычно используют дополнительную проверку:
const schema = Joi.string().pattern(/Z$/).custom((value, helpers) => {
const date = new Date(value);
if (isNaN(date.getTime())) {
return helpers.error("any.invalid");
}
return value;
});
Часто данные приводятся к UTC до попадания в Joi.
function toUTC(dateString) {
return new Date(dateString).toISOString();
}
const schema = Joi.object({
createdAt: Joi.date().iso().required()
});
Такой подход позволяет:
Joi поддерживает числовые значения времени:
Joi.date().timestamp()
Пример:
const schema = Joi.object({
createdAt: Joi.date().timestamp().required()
});
Особенности:
Это делает timestamp наиболее безопасным форматом для распределённых систем.
При необходимости строгого контроля временных зон используется
.custom():
const schema = Joi.object({
date: Joi.string().custom((value, helpers) => {
const match = value.match(/([+-]\d{2}:\d{2}|Z)$/);
if (!match) {
return helpers.error("any.invalid");
}
return value;
})
});
Такой подход позволяет:
Joi не предоставляет средств для сравнения дат с учётом временных зон
как отдельной сущности. Однако после преобразования в Date
можно использовать стандартные операторы:
const schema = Joi.object({
start: Joi.date().required(),
end: Joi.date().greater(Joi.ref("start")).required()
});
Важно понимать:
Основные проблемы возникают не в Joi, а в логике приложения:
Передача локального времени без указания зоны
"2026-05-10 12:00:00"
Смешивание форматов ISO + timestamp + локальные строки
Потеря смещения при сериализации Date.toString()
вместо toISOString()
Предположение о сохранении временной зоны внутри
Date
Joi лишь фиксирует корректность структуры, но не предотвращает архитектурные ошибки.
При проектировании схем с использованием временных данных обычно применяются следующие принципы:
Для более сложной работы с часовыми поясами часто используется дополнительный слой:
Joi при этом выполняет только функцию первичной валидации:
const schema = Joi.object({
date: Joi.string().required()
});
const value = schema.validate(input);
const parsed = DateTime.fromISO(value.date, { zone: "Asia/Almaty" });
Такое разделение ответственности позволяет:
При передаче данных через API важно учитывать, что временная зона должна быть частью строки или полностью исключена.
Наиболее стабильные варианты:
2026-05-10T10:00:00Z1715335200000 (timestamp)Joi в этих случаях выполняет роль фильтра, но не интерпретатора бизнес-смысла.
Если строка не может быть интерпретирована как дата:
Joi.date().validate("invalid-date");
Результат:
ValidationError;При этом временная зона не влияет на сам факт валидности строки — важен только общий формат и возможность парсинга.
Работа с временными зонами в Joi сводится к следующим техническим ограничениям:
Date;