В повседневных задачах автоматизации часто возникает необходимость вычислить точную длительность выполнения какого-либо процесса в рабочих часах. Стандартный вычет одной даты из другой дает календарное время, что не подходит для бизнес-задач, где учитывается только рабочее время сотрудников. При разработке систем учета времени или тайм-трекеров нам нужно получить количество часов между датой начала и датой окончания, но с учетом графика работы, например, с 9:00 до 18:00, и с исключением суббот, воскресений и официальных праздников.
Проанализируем ситуацию: у нас есть две полные даты со временем (ДатаНачала и ДатаОкончания). Задача — написать функцию, которая вернет разницу между ними в рабочих часах. Разберем несколько подходов к решению этой задачи, от самого простого и не всегда точного до наиболее корректного и универсального.
Первая идея, которая приходит в голову, — это организовать цикл от начальной до конечной даты и для каждого дня проверять, является ли он рабочим согласно производственному календарю. Если день рабочий, мы прибавляем к итоговому счетчику определенное количество часов (например, 8).
Логика этого метода выглядит так:
НачалоДня(ДатаНачала) до НачалоДня(ДатаОкончания).РегламентированныйПроизводственныйКалендарь, чтобы определить тип дня (рабочий, суббота, воскресенье, праздник).ДатаНачала до конца рабочего дня.ДатаОкончания.Посмотрим на пример реализации такой функции. Обратите внимание, что этот код имеет серьезные допущения и не является универсальным.
Функция РассчитатьРабочиеЧасыПоКалендарю(ДатаНачала, ДатаОкончания)
ИтогоСекунд = 0;
ТекущаяДата = НачалоДня(ДатаНачала);
Пока ТекущаяДата <= ДатаОкончания Цикл
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РегламентированныйПроизводственныйКалендарь.ВидДня
|ИЗ
| РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь
|ГДЕ
| РегламентированныйПроизводственныйКалендарь.ДатаКалендаря = &ДатаКалендаря";
Запрос.УстановитьПараметр("ДатаКалендаря", ТекущаяДата);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Если Выборка.ВидДня = Перечисления.ВидыДнейПроизводственногоКалендаря.Рабочий Тогда
// Определяем границы рабочего дня (здесь жестко заданы 9:00 и 18:00)
НачалоРабочегоДня = ТекущаяДата + 9*3600;
КонецРабочегоДня = ТекущаяДата + 18*3600;
// Определяем фактический интервал для расчета
НачалоИнтервала = Макс(ДатаНачала, НачалоРабочегоДня);
КонецИнтервала = Мин(ДатаОкончания, КонецРабочегоДня);
Если НачалоИнтервала < КонецИнтервала Тогда
ИтогоСекунд = ИтогоСекунд + (КонецИнтервала - НачалоИнтервала);
КонецЕсли;
КонецЕсли;
КонецЕсли;
ТекущаяДата = НачалоДня(ТекущаяДата) + 86400; // Переходим к следующему дню
КонецЦикла;
Возврат ИтогоСекунд / 3600;
КонецФункции
Недостатки этого подхода:
Этот метод можно использовать только в очень простых случаях, когда условия работы строго фиксированы и не меняются. Для более сложных и реальных сценариев он не подходит.
В большинстве типовых конфигураций 1С (ERP, ЗУП, КА, УТ) существует специальный объект для хранения информации о рабочих часах — справочник ГрафикиРаботыСотрудников (или аналогичный, например, ГрафикиРаботы). Этот подход является наиболее правильным и гибким, так как он использует стандартные механизмы платформы.
Основная идея:
Мы получаем данные непосредственно из графика работы, который уже содержит всю информацию о рабочих интервалах, выходных, праздниках и сокращенных днях. Нам не нужно делать запросы к производственному календарю и вручную определять границы рабочего дня.
Разберем по шагам, как реализовать функцию с использованием графиков.
УправлениеПерсоналом или УправлениеВременем.ДатаНачала до ДатаОкончания) и суммируем полученное время в секундах.Рассмотрим пример функции, которая реализует эту логику. Она будет более сложной, но и гораздо более точной и универсальной.
Функция РассчитатьРабочиеЧасыПоГрафику(ДатаНачала, ДатаОкончания, ГрафикРаботы)
Если ДатаНачала >= ДатаОкончания Тогда
Возврат 0;
КонецЕсли;
ИтогоСекунд = 0;
// Получаем данные графика за весь нужный период
// Это более производительно, чем получать в цикле на каждый день
ПараметрыПериода = УправлениеВременем.ПараметрыДляПолученияДанныхГрафика(ГрафикРаботы, ДатаНачала, ДатаОкончания);
ТаблицаДанныхГрафика = УправлениеВременем.ДанныеГрафикаРаботы(ПараметрыПериода);
Для Каждого СтрокаДня Из ТаблицаДанныхГрафика Цикл
Если НЕ СтрокаДня.ЯвляетсяРабочимДнем Тогда
Продолжить;
КонецЕсли;
ДатаДня = СтрокаДня.Дата;
// Для каждого рабочего дня получаем его интервалы
Для Каждого Интервал Из СтрокаДня.Интервалы Цикл
// Начало и конец рабочего интервала в формате даты
НачалоРабочегоИнтервала = ДатаДня + Интервал.Начало; // Начало - секунды от начала дня
КонецРабочегоИнтервала = ДатаДня + Интервал.Окончание; // Окончание - секунды от начала дня
// Рассчитываем пересечение нашего периода с рабочим интервалом
НачалоПересечения = Макс(ДатаНачала, НачалоРабочегоИнтервала);
КонецПересечения = Мин(ДатаОкончания, КонецРабочегоИнтервала);
Если НачалоПересечения < КонецПересечения Тогда
ИтогоСекунд = ИтогоСекунд + (КонецПересечения - НачалоПересечения);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат ИтогоСекунд / 3600;
КонецФункции
// Пример вызова
ПроверяемыйГрафик = Справочники.ГрафикиРаботыСотрудников.НайтиПоНаименованию("Основной (пятидневка)");
Начало = '20231120163000'; // 20 ноября, 16:30
Конец = '20231122110000'; // 22 ноября, 11:00
РезультатВЧасах = РассчитатьРабочиеЧасыПоГрафику(Начало, Конец, ПроверяемыйГрафик);
Сообщить("Количество рабочих часов: " + РезультатВЧасах);
Этот код уже учитывает все особенности графика: сменность, обеденные перерывы (если они заданы как разрыв в интервалах), сокращенные дни. Это надежный и промышленный способ решения задачи.
Если ваша конфигурация использует Библиотеку стандартных подсистем (БСП) актуальной версии, то, скорее всего, вам даже не придется писать собственную функцию. В БСП уже есть готовый инструмент для решения именно этой задачи, а изучить другие возможности библиотеки поможет справочник по методам БСП с примерами.
Ключевая функция: УправлениеВременем.РазностьДатВСекундахПоГрафику()
Эта функция делает все то же самое, что и наш код из второго подхода, но она уже написана, протестирована и поддерживается фирмой "1С" в рамках БСП. Использовать ее — самый простой и надежный путь.
Как ее использовать?
Функция принимает три параметра:
ДатаНачала (Дата) — начало периода.ДатаОкончания (Дата) — окончание периода.ГрафикРаботы (СправочникСсылка) — ссылка на элемент справочника графиков работы.Посмотрим на пример кода. Он получается максимально лаконичным.
// Находим нужный график работы
График = Справочники.ГрафикиРаботыСотрудников.НайтиПоНаименованию("Пятидневка (с 9 до 18)");
Если НЕ ЗначениеЗаполнено(График) Тогда
Сообщить("График работы не найден!");
Возврат;
КонецЕсли;
// Задаем период
ДатаНачала = ТекущаяДата() - 5 * 86400; // 5 дней назад
ДатаОкончания = ТекущаяДата();
// Вызываем готовую функцию БСП
ДлительностьВСЕкундах = УправлениеВременем.РазностьДатВСекундахПоГрафику(ДатаНачала, ДатаОкончания, График);
// Переводим результат в часы
ДлительностьВЧасах = ДлительностьВСЕкундах / 3600;
Сообщить(СтрокаФормат("Количество рабочих часов в периоде с %1 по %2 по графику ""%3"" составило: %4 ч.",
Формат(ДатаНачала, "ДФ=dd.MM.yyyy HH:mm:ss"),
Формат(ДатаОкончания, "ДФ=dd.MM.yyyy HH:mm:ss"),
График,
ДлительностьВЧасах));
Итог:
Для решения задачи расчета рабочего времени всегда рекомендуется использовать стандартные механизмы. Если ваша конфигурация содержит БСП, используйте готовую функцию УправлениеВременем.РазностьДатВСекундахПоГрафику — это наиболее эффективный вариант, особенно когда вы создаете универсальный отчёт для ЗУП или другие сложные аналитические инструменты.