В процессе разработки в среде 1С часто возникает задача определения количества рабочих дней в произвольном месяце. Это необходимо для расчета заработной платы, планирования графиков или формирования аналитической отчетности. Существует несколько подходов к решению этой задачи: от простого математического расчета до использования встроенных механизмов платформы, таких как производственные календари. Рассмотрим подробнее каждый из этих методов, проанализируем их преимущества и выясним, какой из них лучше применять в конкретных ситуациях.
Наиболее надежным и профессиональным способом является использование данных производственного календаря. В типовых конфигурациях 1С (таких как «Бухгалтерия предприятия», «ЗУП» или «ERP») уже существуют встроенные механизмы для хранения информации о рабочих, выходных и праздничных днях. Этот метод позволяет учитывать государственные праздники и официальные переносы выходных, чего не может сделать ни одна математическая формула.
Для реализации этого метода мы обратимся к регистру сведений ДанныеПроизводственногоКалендаря. Разберем пример запроса, который вернет количество рабочих дней за указанный период:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КОЛИЧЕСТВО(ДанныеПроизводственногоКалендаря.Дата) КАК КолВоДней
|ИЗ
| РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
|ГДЕ
| ДанныеПроизводственногоКалендаря.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
| И ДанныеПроизводственногоКалендаря.ВидДня В (&ВидыРабочихДней)";
// Для пятидневки рабочими считаются "Рабочий"
// Для шестидневки добавляется "Суббота" (если она не праздничная)
МассивВидов = Новый Массив;
МассивВидов.Добавить(Перечисления.ВидыДнейПроизводственногоКалендаря.Рабочий);
Если НужноШестидневку Тогда
МассивВидов.Добавить(Перечисления.ВидыДнейПроизводственногоКалендаря.Суббота);
КонецЕсли;
Запрос.УстановитьПараметр("ДатаНачала", НачалоМесяца(НужнаяДата));
Запрос.УстановитьПараметр("ДатаОкончания", КонецМесяца(НужнаяДата));
Запрос.УстановитьПараметр("ВидыРабочихДней", МассивВидов);
Результат = Запрос.Выполнить().Выгрузить();
Преимущество этого метода: Полная точность с учетом законодательства РФ. Недостаток: Требуется наличие заполненного производственного календаря в информационной базе.
Если нам не нужно учитывать праздничные дни, а требуется лишь сухая логика календаря (сколько в месяце понедельников-пятниц или понедельников-суббот), мы можем применить математический алгоритм. Это работает быстрее, чем запрос к базе данных, и не зависит от заполнения справочников.
Проанализируем логику: в любом месяце (даже в феврале) есть как минимум 28 дней. Это ровно 4 полные недели. В каждой полной неделе 5 рабочих дней для пятидневки и 6 для шестидневки. Следовательно, базовое количество рабочих дней — это 4 * 5 = 20 (или 4 * 6 = 24). Нам остается лишь проверить остаток дней (29-й, 30-й и 31-й) и выяснить, на какие дни недели они выпадают.
Разберем реализацию функции:
Функция КоличествоРабочихДнейМатематически(ДатаМесяца, Шестидневка = Ложь) Экспорт
ДнейВМесяце = День(КонецМесяца(ДатаМесяца));
ДатаНачала = НачалоМесяца(ДатаМесяца);
ДеньНеделиНачала = ДеньНедели(ДатаНачала); // 1 (Пн) ... 7 (Вс)
КоэффициентНедели = ?(Шестидневка, 6, 5);
ВыходнойДень = ?(Шестидневка, 7, 6); // Для пятидневки первый выходной - 6 (Сб)
// Считаем дни в полных 4-х неделях
ИтогоДней = 4 * КоэффициентНедели;
// Проверяем оставшиеся дни (сверх 28)
ОстатокДней = ДнейВМесяце - 28;
Для Счетчик = 0 По ОстатокДней - 1 Цикл
ТекущийДеньНедели = (ДеньНеделиНачала + 28 + Счетчик - 1) % 7 + 1;
Если Шестидневка Тогда
// Только воскресенье (7) - выходной
Если ТекущийДеньНедели <> 7 Тогда
ИтогоДней = ИтогоДней + 1;
КонецЕсли;
Иначе
// Суббота (6) и воскресенье (7) - выходные
Если ТекущийДеньНедели < 6 Тогда
ИтогоДней = ИтогоДней + 1;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат ИтогоДней;
КонецФункции
Обратите внимание, что мы используем конструкцию День(КонецМесяца(ДатаМесяца)). Платформа 1С самостоятельно вычисляет количество дней, учитывая, является ли год високосным. Нам не нужно писать дополнительные проверки для февраля.
Иногда возникает задача получить данные в запросе, но при этом производственный календарь в системе может быть не заполнен. В этом случае мы можем "сгенерировать" календарь прямо в памяти запроса. Посмотрим на этот "хитрый" способ:
ДОБАВИТЬКДАТЕ.ДЕНЬНЕДЕЛИ отфильтруем нужные нам дни.
// Пример логики формирования дней в запросе
"ВЫБРАТЬ 0 КАК Число ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ... ОБЪЕДИНИТЬ ВЫБРАТЬ 30"
// Далее в основном тексте:
"ВЫБРАТЬ
| КОЛИЧЕСТВО(*) КАК РабочихДней
|ИЗ
| ВТ_Числа КАК ВТ_Числа
|ГДЕ
| ДОБАВИТЬКДАТЕ(&НачалоМесяца, ДЕНЬ, ВТ_Числа.Число) <= &КонецМесяца
| И ДЕНЬНЕДЕЛИ(ДОБАВИТЬКДАТЕ(&НачалоМесяца, ДЕНЬ, ВТ_Числа.Число)) < &ВыходнойПорог"
Для пятидневки &ВыходнойПорог будет равен 6 (все дни, где индекс меньше 6, то есть Пн-Пт). Для шестидневки — 7 (Пн-Сб).
Если ваша задача решается в контексте кадрового учета или расчета зарплаты, лучше всего использовать объект ГрафикиРаботы. Это позволяет максимально гибко настроить расписание: например, если в компании суббота рабочая, но сокращенная, или если график "сутки через трое".
Проанализируем программный вызов в типовых конфигурациях:
// В типовых решениях (ЗУП, ERP) используется общий модуль КалендарныеГрафики
КоличествоДней = КалендарныеГрафики.КоличествоДнейВПериодеПоГрафику(СсылкаНаГрафик, ДатаНачала, ДатаОкончания);
Преимущество: Это самый "чистый" код, так как вся бизнес-логика вынесена в настройки пользователя. Программисту не нужно менять код, если компания решит перейти с пятидневки на шестидневку.
Выясним причину, по которой часто возникают ошибки в подобных расчетах:
КонецМесяца(). Никогда не закладывайте "28 дней" для февраля жестко в код.ДеньНедели() возвращает 1 для понедельника и 7 для воскресенья. В некоторых других языках программирования (например, в SQL или Java) отсчет может начинаться с 0 или с воскресенья.Таким образом, мы рассмотрели четыре различных способа решения задачи. Выбор конкретного метода зависит от требований к точности и наличия данных в вашей конфигурации. Для простых расчетов "на лету" подойдет математическая формула, а для серьезного учета — производственный календарь.