Блокировка диапазонов дат в календаре на базе Pikaday реализуется
через механизм фильтрации доступных значений с помощью callback-функции
disableDayFn. Этот подход позволяет не только отключать
отдельные даты, но и строить сложные правила ограничения целых
интервалов, включая динамические диапазоны, зависящие от текущего
состояния приложения, внешних данных или пользовательского выбора.
Основной инструмент управления доступностью дат — функция
disableDayFn, принимающая объект Date и
возвращающая булево значение. Если возвращается true, дата
становится недоступной для выбора.
const picker = new Pikaday({
field: document.getElementById('datepicker'),
disableDayFn: function(date) {
return false;
}
});
В таком виде календарь не ограничивает выбор, однако этот же механизм используется для построения любых правил блокировки, включая диапазоны.
Для отключения фиксированного интервала используется проверка попадания даты в границы диапазона через сравнение временных меток.
const startBlock = new Date('2026-06-10');
const endBlock = new Date('2026-06-20');
const picker = new Pikaday({
field: document.getElementById('datepicker'),
disableDayFn: function(date) {
const time = date.getTime();
return time >= startBlock.getTime() && time <= endBlock.getTime();
}
});
Здесь любой день внутри диапазона 10–20 июня 2026 года становится недоступным для выбора. Такой подход удобен для блокировки отпусков, технических окон или периодов недоступности ресурса.
При работе с диапазонами важно учитывать, что объекты
Date могут содержать различное время суток. Это приводит к
некорректным результатам при сравнении, особенно при работе с часовыми
поясами или пользовательским вводом.
Рекомендуется приводить даты к началу дня:
function normalize(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
const startBlock = normalize(new Date('2026-06-10'));
const endBlock = normalize(new Date('2026-06-20'));
const picker = new Pikaday({
field: document.getElementById('datepicker'),
disableDayFn: function(date) {
const d = normalize(date);
return d >= startBlock && d <= endBlock;
}
});
Такая нормализация устраняет ошибки, связанные с часовыми смещениями и частичным сравнением времени.
Часто диапазон формируется не статически, а зависит от пользовательского выбора, например, даты начала бронирования.
let selectedStart = null;
const picker = new Pikaday({
field: document.getElementById('datepicker'),
onSelect: function(date) {
selectedStart = date;
},
disableDayFn: function(date) {
if (!selectedStart) return false;
const d = date.getTime();
const start = selectedStart.getTime();
const maxRange = 7 * 24 * 60 * 60 * 1000;
return d > start && d <= start + maxRange;
}
});
В этом примере блокируется диапазон в 7 дней после выбранной стартовой даты. Подобная логика используется в системах бронирования, где необходимо ограничить длительность интервала.
При наличии нескольких диапазонов блокировки используется проверка вхождения даты хотя бы в один из интервалов.
const blockedRanges = [
[new Date('2026-06-05'), new Date('2026-06-08')],
[new Date('2026-06-15'), new Date('2026-06-18')],
];
const picker = new Pikaday({
field: document.getElementById('datepicker'),
disableDayFn: function(date) {
const time = date.getTime();
return blockedRanges.some(range => {
const start = range[0].getTime();
const end = range[1].getTime();
return time >= start && time <= end;
});
}
});
Такой подход масштабируется на любое количество диапазонов и может использоваться для отображения сложных календарных ограничений.
В некоторых системах проще задавать разрешённые интервалы, а не
запрещённые. В этом случае логика disableDayFn
инвертируется.
const allowedRanges = [
[new Date('2026-06-01'), new Date('2026-06-10')],
[new Date('2026-06-20'), new Date('2026-06-30')],
];
const picker = new Pikaday({
field: document.getElementById('datepicker'),
disableDayFn: function(date) {
const time = date.getTime();
const isAllowed = allowedRanges.some(range => {
const start = range[0].getTime();
const end = range[1].getTime();
return time >= start && time <= end;
});
return !isAllowed;
}
});
Такой метод удобен, когда доступность определяется внешними бизнес-правилами, например, графиком работы или сезонными окнами.
Часто требуется комбинировать диапазонные ограничения с ограничениями относительно текущей даты.
const today = new Date();
today.setHours(0, 0, 0, 0);
const picker = new Pikaday({
field: document.getElementById('datepicker'),
disableDayFn: function(date) {
const d = date.getTime();
const blockStart = new Date('2026-06-10').getTime();
const blockEnd = new Date('2026-06-20').getTime();
const isInBlock = d >= blockStart && d <= blockEnd;
const isPast = d < today.getTime();
return isInBlock || isPast;
}
});
Здесь одновременно блокируются прошедшие даты и конкретный диапазон в будущем, что характерно для систем планирования.
Реальные сценарии часто требуют объединения разных типов ограничений: диапазонов, отдельных дат и динамических правил.
const blockedDays = [
new Date('2026-06-03'),
new Date('2026-06-12'),
];
const blockedRange = [
new Date('2026-06-20'),
new Date('2026-06-25'),
];
const picker = new Pikaday({
field: document.getElementById('datepicker'),
disableDayFn: function(date) {
const time = date.getTime();
const isSingleBlocked = blockedDays.some(d => d.getTime() === time);
const isRangeBlocked =
time >= blockedRange[0].getTime() &&
time <= blockedRange[1].getTime();
return isSingleBlocked || isRangeBlocked;
}
});
Подобная композиция правил позволяет строить гибкую систему ограничений без модификации внутренней логики календаря.
При увеличении числа диапазонов до сотен или тысяч линейный перебор становится узким местом. В таких случаях применяется предварительная нормализация данных и использование структур для ускоренного поиска, например, отсортированных массивов или интервал-деревьев на уровне приложения.
Оптимизированный вариант с предварительной сортировкой:
blockedRanges.sort((a, b) => a[0] - b[0]);
function isBlocked(time) {
for (let i = 0; i < blockedRanges.length; i++) {
const start = blockedRanges[i][0].getTime();
const end = blockedRanges[i][1].getTime();
if (time < start) return false;
if (time >= start && time <= end) return true;
}
return false;
}
const picker = new Pikaday({
field: document.getElementById('datepicker'),
disableDayFn: function(date) {
return isBlocked(date.getTime());
}
});
Такой подход уменьшает количество сравнений при раннем выходе из цикла.
Особое внимание требуется к граничным условиям. Включение или исключение крайних дат (inclusive vs exclusive) должно быть строго определено. В большинстве реализаций используется включающее сравнение:
>= start<= endЭто обеспечивает предсказуемое поведение интерфейса, особенно при визуальном отображении диапазонов.
При необходимости исключения границ логика меняется на строгую:
return time > start && time < end;
Хотя disableDayFn управляет доступностью, визуальная
индикация диапазонов часто реализуется отдельно через CSS-классы или
кастомизацию рендера дней. Это позволяет пользователю различать
недоступные и доступные, но неактивные периоды.
При интеграции логики блокировки диапазонов важно сохранять синхронность между:
Несогласованность этих уровней приводит к неоднозначному поведению интерфейса.