Работа с диапазонами дат в Pikaday требует объединения встроенных ограничений календаря и пользовательской логики, поскольку библиотека изначально ориентирована на выбор одной даты, а диапазон реализуется надстройкой через состояние приложения.
Базовый механизм валидации диапазона строится на свойствах minDate и maxDate, которые задают жёсткие границы выбора.
const picker = new Pikaday({
field: document.getElementById('date'),
minDate: new Date(2026, 0, 1),
maxDate: new Date(2026, 11, 31)
});
Эти параметры выполняют первичную фильтрацию:
minDate недоступны для выбораmaxDate блокируютсяОднако данный механизм не учитывает бизнес-логику диапазонов, например взаимосвязь «начало–конец».
При реализации диапазона обычно вводятся две переменные состояния:
startDate — начало периодаendDate — конец периодаlet startDate = null;
let endDate = null;
Поведение выбора определяется логикой onSelect.
const picker = new Pikaday({
field: document.getElementById('range'),
onSelect: function(date) {
if (!startDate || (startDate && endDate)) {
startDate = date;
endDate = null;
} else {
endDate = date;
}
validateRange();
}
});
Основное правило валидации: конец диапазона не может быть раньше начала.
function validateRange() {
if (startDate && endDate && endDate < startDate) {
endDate = null;
return false;
}
return true;
}
Сравнение дат выполняется через числовое представление объектов
Date, что позволяет избежать ошибок локализации.
Вместо простого сброса конечной даты может применяться автоматическая нормализация диапазона:
function normalizeRange() {
if (startDate && endDate && endDate < startDate) {
const temp = startDate;
startDate = endDate;
endDate = temp;
}
}
Такой подход часто используется в интерфейсах, где пользовательский ввод считается приоритетным над строгими ограничениями.
После выбора начальной даты удобно динамически ограничивать
допустимый диапазон для второй даты через setMinDate.
const picker = new Pikaday({
field: document.getElementById('range'),
onSelect: function(date) {
if (!startDate || endDate) {
startDate = date;
endDate = null;
picker.setMinDate(startDate);
} else {
endDate = date;
picker.setMaxDate(endDate);
}
}
});
Такой механизм позволяет визуально блокировать некорректные варианты ещё до выбора.
Свойство disable предоставляет более гибкий способ контроля доступных дат.
const picker = new Pikaday({
field: document.getElementById('range'),
disable: [
function(date) {
if (startDate && !endDate) {
return date < startDate;
}
return false;
}
]
});
Логика здесь может быть расширена до любых бизнес-правил:
Частый сценарий — запрет выбора диапазона больше заданного количества дней.
const MAX_RANGE_DAYS = 10;
function isTooLong(start, end) {
const diff = (end - start) / (1000 * 60 * 60 * 24);
return diff > MAX_RANGE_DAYS;
}
Интеграция в валидацию:
function validateRange() {
if (!startDate || !endDate) return true;
if (endDate < startDate) {
endDate = null;
return false;
}
if (isTooLong(startDate, endDate)) {
endDate = null;
return false;
}
return true;
}
Валидация диапазона в реальных интерфейсах почти всегда связана с формой.
function updateUI() {
const errorBox = document.getElementById('error');
if (!validateRange()) {
errorBox.textContent = 'Некорректный диапазон дат';
errorBox.style.display = 'block';
} else {
errorBox.style.display = 'none';
}
}
Важно отделять состояние календаря от состояния формы:
document.getElementById('form').addEventListener('submit', function(e) {
if (!startDate || !endDate || !validateRange()) {
e.preventDefault();
}
});
При работе с бронированиями или событиями требуется проверка пересечений.
function overlaps(existingStart, existingEnd, newStart, newEnd) {
return newStart <= existingEnd && newEnd >= existingStart;
}
Использование:
function validateAgainstExisting(ranges) {
return !ranges.some(range =>
overlaps(range.start, range.end, startDate, endDate)
);
}
Pikaday не предоставляет встроенной подсветки диапазонов, поэтому применяется кастомная логика через CSS-классы.
function highlightRange() {
const cells = document.querySelectorAll('.pika-day');
cells.forEach(cell => {
const time = new Date(cell.getAttribute('data-pika-year'),
cell.getAttribute('data-pika-month'),
cell.getAttribute('data-pika-day'));
cell.classList.remove('in-range', 'range-start', 'range-end');
if (startDate && time.getTime() === startDate.getTime()) {
cell.classList.add('range-start');
}
if (endDate && time.getTime() === endDate.getTime()) {
cell.classList.add('range-end');
}
if (startDate && endDate && time > startDate && time < endDate) {
cell.classList.add('in-range');
}
});
}
Чтобы избежать рассинхронизации состояния, визуальное обновление вызывается после каждого изменения выбора:
onSelect: function(date) {
if (!startDate || endDate) {
startDate = date;
endDate = null;
} else {
endDate = date;
}
validateRange();
highlightRange();
}
В сложных сценариях возможно появление частично заполненного диапазона. Для предотвращения таких ситуаций используется финальная нормализация состояния:
function sanitizeState() {
if (!startDate) {
endDate = null;
}
if (startDate && !endDate) {
picker.setMinDate(startDate);
}
}
Комбинированный подход к валидации включает несколько уровней: