Валидация диапазона

Работа с диапазонами дат в Pikaday требует объединения встроенных ограничений календаря и пользовательской логики, поскольку библиотека изначально ориентирована на выбор одной даты, а диапазон реализуется надстройкой через состояние приложения.

Ограничение допустимых дат через minDate и maxDate

Базовый механизм валидации диапазона строится на свойствах 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

При реализации диапазона обычно вводятся две переменные состояния:

  • 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;
    }
}

Такой подход часто используется в интерфейсах, где пользовательский ввод считается приоритетным над строгими ограничениями.

Динамическое обновление ограничений Pikaday

После выбора начальной даты удобно динамически ограничивать допустимый диапазон для второй даты через 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 для сложной валидации

Свойство 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';
    }
}

Важно отделять состояние календаря от состояния формы:

  • Pikaday управляет выбором даты
  • приложение управляет логикой диапазона

Блокировка отправки формы при ошибочном диапазоне

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);
    }
}

Итоговая модель валидации диапазона

Комбинированный подход к валидации включает несколько уровней:

  • ограничение допустимых дат через minDate/maxDate
  • динамическая блокировка через setMinDate/setMaxDate
  • логическая проверка startDate/endDate
  • защита от пересечений с существующими диапазонами
  • ограничение длины периода
  • визуальная синхронизация состояния интерфейса