В вычислении праздничных дней ключевая проблема заключается не в работе с датами как таковыми, а в способе формализации календарных правил. В JavaScript при использовании Day.js праздничные даты обычно описываются в виде набора правил, а не фиксированных значений, поскольку значительная часть праздников зависит от года, дня недели или лунного календаря.
Выделяются несколько устойчивых моделей:
Day.js в этом контексте выступает как слой нормализации и сравнения дат, а не как источник календарной логики.
Базовый способ хранения праздников — массив объектов, где каждая
запись содержит минимум два поля: дату и описание. В Day.js удобнее
всего работать с ISO-строками или объектами dayjs.
import dayjs from 'dayjs';
const holidays = [
{ date: dayjs('2026-01-01'), name: 'Новый год' },
{ date: dayjs('2026-05-09'), name: 'День Победы' },
{ date: dayjs('2026-03-08'), name: 'Международный женский день' }
];
Для сравнения дат важно учитывать, что объекты Day.js неизменяемы, а сравнение выполняется через методы:
const isHoliday = (date) => {
return holidays.some(h =>
h.date.isSame(date, 'day')
);
};
Ключевой момент заключается в уровне точности сравнения. Указание
'day' позволяет игнорировать время суток и сравнивать
только календарные дни.
При работе с праздничными днями все входные значения приводятся к единому формату:
const normalize = (date) => dayjs(date).startOf('day');
Это предотвращает ошибки, связанные с часовыми поясами и различиями во времени:
const target = normalize('2026-05-09T18:45:00');
const found = holidays.some(h =>
h.date.isSame(target, 'day')
);
Day.js оперирует локальным временем по умолчанию, поэтому критически
важно фиксировать границу суток через startOf('day').
При вычислении календарей часто требуется выделить праздничные дни внутри диапазона дат.
const getHolidaysInRange = (start, end) => {
const from = dayjs(start);
const to = dayjs(end);
return holidays.filter(h =>
(h.date.isAfter(from) || h.date.isSame(from, 'day')) &&
(h.date.isBefore(to) || h.date.isSame(to, 'day'))
);
};
Для упрощения часто применяется вспомогательная библиотека
dayjs/plugin/isBetween:
import isBetween from 'dayjs/plugin/isBetween';
dayjs.extend(isBetween);
const getHolidaysInRange = (start, end) => {
return holidays.filter(h =>
h.date.isBetween(start, end, 'day', '[]')
);
};
Диапазон '[]' включает границы интервала, что критично
при календарных расчетах.
Одна из наиболее распространённых задач — исключение праздничных дней из последовательности дат. Это применяется при расчёте сроков исполнения задач и бизнес-планировании.
const isWorkingDay = (date) => {
const d = dayjs(date);
const weekday = d.day(); // 0 - воскресенье, 6 - суббота
const weekend = (weekday === 0 || weekday === 6);
const holiday = holidays.some(h =>
h.date.isSame(d, 'day')
);
return !weekend && !holiday;
};
На основе этой функции можно фильтровать диапазон дат:
const getWorkingDays = (start, end) => {
let current = dayjs(start);
const last = dayjs(end);
const result = [];
while (current.isBefore(last) || current.isSame(last, 'day')) {
if (isWorkingDay(current)) {
result.push(current);
}
current = current.add(1, 'day');
}
return result;
};
Для задач, связанных с деловыми календарями, часто применяется
расширение dayjs-business-days.
import businessDays from 'dayjs-business-days';
dayjs.extend(businessDays);
Конфигурация праздничных дней:
dayjs.updateLocale('en', {
holidays: [
'2026-01-01',
'2026-05-09',
'2026-03-08'
],
holidayFormat: 'YYYY-MM-DD'
});
Проверка рабочего дня:
dayjs('2026-05-09').isBusinessDay();
Добавление рабочих дней с автоматическим пропуском выходных и праздников:
dayjs('2026-01-01').businessDaysAdd(10);
Этот подход переносит основную логику календарных вычислений в расширение, уменьшая количество ручных проверок.
Часть праздничных дат определяется алгоритмически. Наиболее сложный пример — Пасха, от которой зависят другие даты.
Алгоритм вычисления (упрощённая версия, григорианский календарь):
const getEaster = (year) => {
const f = Math.floor;
const G = year % 19;
const C = f(year / 100);
const H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30;
const I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11));
const J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7;
const L = I - J;
const month = 3 + f((L + 40) / 44);
const day = L + 28 - 31 * f(month / 4);
return dayjs(`${year}-${month}-${day}`);
};
На основе Пасхи строятся связанные праздники:
const easter = getEaster(2026);
const holidaysDynamic = [
easter,
easter.add(1, 'day'),
easter.add(49, 'day')
];
В реальных системах праздничный календарь формируется объединением нескольких источников:
const fixedHolidays = [
dayjs('2026-01-01'),
dayjs('2026-05-09')
];
const dynamicHolidays = [
getEaster(2026),
getEaster(2026).add(1, 'day')
];
const allHolidays = [...fixedHolidays, ...dynamicHolidays];
Для ускорения проверок список часто преобразуется в структуру
Set с нормализованными строками:
const holidaySet = new Set(
allHolidays.map(d => d.format('YYYY-MM-DD'))
);
const isHolidayFast = (date) =>
holidaySet.has(dayjs(date).format('YYYY-MM-DD'));
Такой подход снижает сложность проверки с O(n) до O(1).
Разные страны используют различные наборы праздничных дат, поэтому календарь обычно инкапсулируется в объект конфигурации:
const calendars = {
kz: {
holidays: [
'01-01',
'03-08',
'05-09'
]
},
ru: {
holidays: [
'01-01',
'01-07',
'05-09'
]
}
};
Проверка с учётом года:
const isLocalizedHoliday = (date, locale) => {
const d = dayjs(date);
const md = d.format('MM-DD');
return calendars[locale].holidays.includes(md);
};
При обработке календарей за несколько лет важно избегать повторного создания объектов Day.js внутри циклов.
Оптимизации включают:
dayjs() внутри итерацийПример предрасчёта:
const buildCalendarIndex = (holidays) => {
return holidays.reduce((acc, h) => {
acc[h.format('YYYY-MM-DD')] = true;
return acc;
}, {});
};
const holidayIndex = buildCalendarIndex(allHolidays);
При формировании календарного массива используется последовательное добавление дней с проверкой условий:
const buildCalendar = (start, days) => {
const result = [];
let current = dayjs(start);
while (result.length < days) {
const key = current.format('YYYY-MM-DD');
const weekday = current.day();
const isWeekend = weekday === 0 || weekday === 6;
if (!isWeekend && !holidayIndex[key]) {
result.push(current);
}
current = current.add(1, 'day');
}
return result;
};
При увеличении сложности календарных правил логика обычно разделяется на слои:
Такое разделение позволяет независимо изменять любую часть календарной модели без влияния на остальные вычисления, сохраняя детерминированность результатов при работе с праздничными днями.