При автоматизации учета выполненных работ, заявок в службу поддержки или производственных процессов часто возникает задача: рассчитать реальное затраченное рабочее время между датой/временем начала и датой/временем окончания. При этом необходимо исключить нерабочие ночные часы, обеденные перерывы и выходные дни (субботу и воскресенье).
Разберем особенности платформы 1С:Предприятие 7.7 и пошагово рассмотрим алгоритмы решения этой задачи.
В отличие от платформы 1С 8.x, где тип Дата содержит и дату, и время, а разность двух дат возвращает секунды, в платформе 1С 7.7 реализована иная модель:
Дата хранит исключительно календарный день. Разность Дата2 - Дата1 возвращает количество календарных дней в виде целого числа."ЧЧ:ММ:СС", либо числом (секунды от полуночи).НомерДняНедели(Дата), которая возвращает число от 1 (понедельник) до 7 (воскресенье).Для математических расчетов строковое время необходимо предварительно перевести в секунды (или часы). Посмотрим на вспомогательную функцию преобразования:
Функция ВремяВСтраницуСекунд(СтрВремя)
Если ПустаяСтрока(СтрВремя) = 1 Тогда
Возврат 0;
КонецЕсли;
Часы = Число(Лев(СтрВремя, 2));
Минуты = Число(Сред(СтрВремя, 4, 2));
Секунды = Число(Прав(СтрВремя, 2));
Возврат Часы * 3600 + Минуты * 60 + Секунды;
КонецФункции
Рассмотрим универсальный и надежный способ расчета — подневный обход периода в цикле. Данный подход исключает ошибки при переходе через выходные дни и позволяет гибко учитывать границы смен.
Разберем логику работы алгоритма по шагам:
09:00:00) и время окончания (например, 18:00:00).НомерДняНедели(ТекДата). Если день равен 6 или 7, мы пропускаем итерацию.Проанализируем реализацию функции на встроенном языке 1С 7.7:
Функция РассчитатьРабочееВремяВСекундах(ДатаНач, ВремяНачСтр, ДатаКон, ВремяКонСтр)
Перем ТекДата, ИтогСекунд;
// Задаем границы рабочего дня в секундах (например, с 09:00 до 18:00)
НачРабДня = ВремяВСтраницуСекунд("09:00:00");
КонРабДня = ВремяВСтраницуСекунд("18:00:00");
ДлинаСмены = КонРабДня - НачРабДня;
ВремяНач = ВремяВСтраницуСекунд(ВремяНачСтр);
ВремяКон = ВремяВСтраницуСекунд(ВремяКонСтр);
// Нормализация времени старта и финиша относительно рабочего окна
ВремяНач = Макс(ВремяНач, НачРабДня);
ВремяНач = Мин(ВремяНач, КонРабДня);
ВремяКон = Макс(ВремяКон, НачРабДня);
ВремяКон = Мин(ВремяКон, КонРабДня);
ИтогСекунд = 0;
// Обработка случая, когда начало и окончание приходятся на один день
Если ДатаНач = ДатаКон Тогда
Если НомерДняНедели(ДатаНач) < 6 Тогда
Если ВремяКон > ВремяНач Тогда
ИтогСекунд = ВремяКон - ВремяНач;
КонецЕсли;
КонецЕсли;
Возврат ИтогСекунд;
КонецЕсли;
// Обход интервала дат
Для ТекДата = ДатаНач По ДатаКон Цикл
// Пропускаем субботу (6) и воскресенье (7)
Если НомерДняНедели(ТекДата) >= 6 Тогда
Продолжить;
КонецЕсли;
Если ТекДата = ДатаНач Тогда
// Первый рабочий день: от времени старта до конца смены
ИтогСекунд = ИтогСекунд + (КонРабДня - ВремяНач);
ИначеЕсли ТекДата = ДатаКон Тогда
// Последний рабочий день: от начала смены до времени финиша
ИтогСекунд = ИтогСекунд + (ВремяКон - НачРабДня);
Иначе
// Промежуточный рабочий день: полная смена
ИтогСекунд = ИтогСекунд + ДлинаСмены;
КонецЕсли;
КонецЦикла;
Возврат ИтогСекунд;
КонецФункции
Если в конфигурации используется компонента «Расчет» (доступна в комплексных конфигурациях и конфигурации «Зарплата и Кадры 7.7»), задачу можно существенно упростить с помощью метаданного объекта Календари.
Объект Календарь хранит информацию о праздниках, предпраздничных днях и официальных переносах выходных. Рассмотрим пример работы с методами календаря:
Календ = СоздатьОбъект("Календарь.Основной");
// Получение количества отработанных дней за период
ВсегоДней = Календ.Дней(ДатаНач, ДатаКон);
// Получение точного количества рабочих часов по графику
ВсегоЧасов = Календ.Часов(ДатаНач, ДатаКон);
Если компонента «Расчет» отсутствует, а в базе нет производственного календаря, базовый подневный цикл с функцией НомерДняНедели() является наиболее стабильным и простым решением, не требующим усложнения структуры метаданных конфигурации.