Как программно рассчитать количество рабочих дней в месяце для пятидневки и шестидневки

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки в среде 1С часто возникает задача определения количества рабочих дней в произвольном месяце. Это необходимо для расчета заработной платы, планирования графиков или формирования аналитической отчетности. Существует несколько подходов к решению этой задачи: от простого математического расчета до использования встроенных механизмов платформы, таких как производственные календари. Рассмотрим подробнее каждый из этих методов, проанализируем их преимущества и выясним, какой из них лучше применять в конкретных ситуациях.

Метод 1: Использование производственного календаря (Рекомендуемый)

Наиболее надежным и профессиональным способом является использование данных производственного календаря. В типовых конфигурациях 1С (таких как «Бухгалтерия предприятия», «ЗУП» или «ERP») уже существуют встроенные механизмы для хранения информации о рабочих, выходных и праздничных днях. Этот метод позволяет учитывать государственные праздники и официальные переносы выходных, чего не может сделать ни одна математическая формула.

Для реализации этого метода мы обратимся к регистру сведений ДанныеПроизводственногоКалендаря. Разберем пример запроса, который вернет количество рабочих дней за указанный период:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	КОЛИЧЕСТВО(ДанныеПроизводственногоКалендаря.Дата) КАК КолВоДней
    |ИЗ
    |	РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
    |ГДЕ
    |	ДанныеПроизводственногоКалендаря.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
    |	И ДанныеПроизводственногоКалендаря.ВидДня В (&ВидыРабочихДней)";

// Для пятидневки рабочими считаются "Рабочий"
// Для шестидневки добавляется "Суббота" (если она не праздничная)
МассивВидов = Новый Массив;
МассивВидов.Добавить(Перечисления.ВидыДнейПроизводственногоКалендаря.Рабочий);
Если НужноШестидневку Тогда
    МассивВидов.Добавить(Перечисления.ВидыДнейПроизводственногоКалендаря.Суббота);
КонецЕсли;

Запрос.УстановитьПараметр("ДатаНачала", НачалоМесяца(НужнаяДата));
Запрос.УстановитьПараметр("ДатаОкончания", КонецМесяца(НужнаяДата));
Запрос.УстановитьПараметр("ВидыРабочихДней", МассивВидов);

Результат = Запрос.Выполнить().Выгрузить();

Преимущество этого метода: Полная точность с учетом законодательства РФ. Недостаток: Требуется наличие заполненного производственного календаря в информационной базе.

Метод 2: Математический расчет без циклов и запросов

Если нам не нужно учитывать праздничные дни, а требуется лишь сухая логика календаря (сколько в месяце понедельников-пятниц или понедельников-суббот), мы можем применить математический алгоритм. Это работает быстрее, чем запрос к базе данных, и не зависит от заполнения справочников.

Проанализируем логику: в любом месяце (даже в феврале) есть как минимум 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С самостоятельно вычисляет количество дней, учитывая, является ли год високосным. Нам не нужно писать дополнительные проверки для февраля.

Метод 3: Универсальный запрос через таблицу чисел

Иногда возникает задача получить данные в запросе, но при этом производственный календарь в системе может быть не заполнен. В этом случае мы можем "сгенерировать" календарь прямо в памяти запроса. Посмотрим на этот "хитрый" способ:

  1. Создадим временную таблицу, содержащую числа от 0 до 30.
  2. Прибавим эти числа к дате начала месяца с помощью функции ДОБАВИТЬКДАТЕ.
  3. Отсечем даты, которые вышли за пределы текущего месяца.
  4. С помощью функции ДЕНЬНЕДЕЛИ отфильтруем нужные нам дни.

// Пример логики формирования дней в запросе
"ВЫБРАТЬ 0 КАК Число ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ... ОБЪЕДИНИТЬ ВЫБРАТЬ 30"
// Далее в основном тексте:
"ВЫБРАТЬ
|	КОЛИЧЕСТВО(*) КАК РабочихДней
|ИЗ
|	ВТ_Числа КАК ВТ_Числа
|ГДЕ
|	ДОБАВИТЬКДАТЕ(&НачалоМесяца, ДЕНЬ, ВТ_Числа.Число) <= &КонецМесяца
|	И ДЕНЬНЕДЕЛИ(ДОБАВИТЬКДАТЕ(&НачалоМесяца, ДЕНЬ, ВТ_Числа.Число)) < &ВыходнойПорог"

Для пятидневки &ВыходнойПорог будет равен 6 (все дни, где индекс меньше 6, то есть Пн-Пт). Для шестидневки — 7 (Пн-Сб).

Метод 4: Использование графиков работы

Если ваша задача решается в контексте кадрового учета или расчета зарплаты, лучше всего использовать объект ГрафикиРаботы. Это позволяет максимально гибко настроить расписание: например, если в компании суббота рабочая, но сокращенная, или если график "сутки через трое".

Проанализируем программный вызов в типовых конфигурациях:


// В типовых решениях (ЗУП, ERP) используется общий модуль КалендарныеГрафики
КоличествоДней = КалендарныеГрафики.КоличествоДнейВПериодеПоГрафику(СсылкаНаГрафик, ДатаНачала, ДатаОкончания);

Преимущество: Это самый "чистый" код, так как вся бизнес-логика вынесена в настройки пользователя. Программисту не нужно менять код, если компания решит перейти с пятидневки на шестидневку.

Важные нюансы

Выясним причину, по которой часто возникают ошибки в подобных расчетах:

  1. Високосный год: Как мы уже упоминали, всегда используйте встроенную функцию КонецМесяца(). Никогда не закладывайте "28 дней" для февраля жестко в код.
  2. Первый день недели: Помните, что в 1С функция ДеньНедели() возвращает 1 для понедельника и 7 для воскресенья. В некоторых других языках программирования (например, в SQL или Java) отсчет может начинаться с 0 или с воскресенья.
  3. Праздники: Математический метод (Метод 2) никогда не узнает о празднике 1 мая, если он выпадает на среду. Если ваша задача связана с реальными выплатами сотрудникам, используйте только Метод 1 или Метод 4.

Таким образом, мы рассмотрели четыре различных способа решения задачи. Выбор конкретного метода зависит от требований к точности и наличия данных в вашей конфигурации. Для простых расчетов "на лету" подойдет математическая формула, а для серьезного учета — производственный календарь.

← На главную