Как правильно рассчитать количество отработанных часов и минут в 1С?

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

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

Анализируем проблему: почему стандартный запрос может выдавать неверные данные

Рассмотрим ситуацию, описанную в теме. Основная сложность заключается в том, что сотрудники могут начинать работу в один день, а заканчивать — на следующий. Если наш запрос жестко ограничен одной датой, такие записи просто не попадут в выборку или будут рассчитаны неверно. Проанализируем типичную ошибку в тексте запроса:


ВЫБРАТЬ
    РАЗНОСТЬДАТ(ВремяНачалаРаботы.ВремяНачалаРаботы, ВремяОкончанияРаботы.ВремяОкончанияРаботы, СЕКУНДА) КАК Время
ИЗ
    РегистрСведений.ВремяНачалаРаботы КАК ВремяНачала,
    РегистрСведений.ВремяОкончанияРаботы КАК ВремяОкончанияРаботы
ГДЕ
    ВремяНачалаРаботы.Дата = &Дата И ВремяОкончанияРаботы.Дата = &Дата

В данном примере мы видим две критические ошибки. Во-первых, использование условия Дата = &Дата полностью отсекает случаи, когда работа закончилась после 00:00. Во-вторых, отсутствие связи между таблицами (соединения) порождает так называемое «декартово произведение», когда каждая запись о начале работы соединяется с каждой записью об окончании, что приводит к геометрическому росту количества строк в результате.

Шаг 1: Исправляем структуру хранения и типы данных

Прежде чем приступать к коду, выясним одну важную деталь. В обсуждении упоминалось, что даты хранятся в виде строк. Это критическая архитектурная ошибка. В 1С тип данных Дата — это сложный объект, содержащий и дату, и время. Если вы храните их как строки, вы лишаетесь возможности использовать встроенные функции СУБД для расчета разницы и эффективной индексации.

Если изменить структуру регистра невозможно, нам придется преобразовывать строки в даты внутри запроса или в коде, но правильным решением будет использование стандартного типа Дата. Это позволит нам использовать функцию РАЗНОСТЬДАТ максимально эффективно.

Шаг 2: Правильное соединение таблиц и работа с интервалами

Давайте разберем, как составить запрос так, чтобы он «видел» переход через полночь. Вместо жесткого равенства дат мы будем использовать интервал. Проанализируем следующий подход:


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

Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата));
// Увеличиваем период до конца следующего дня, чтобы захватить ночные смены
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ТекущаяДата + 86400)); 

Обратите внимание, что мы добавили ВНУТРЕННЕЕ СОЕДИНЕНИЕ по сотруднику. Это гарантирует, что мы считаем время конкретного человека, а не среднюю температуру по больнице. Параметры &НачалоПериода и &КонецПериода теперь позволяют захватить события, произошедшие в течение двух суток.

Шаг 3: Перевод секунд в читаемый формат (Часы и Минуты)

Функция РАЗНОСТЬДАТ(..., СЕКУНДА) возвращает целое число секунд. Самая частая ошибка новичков — просто разделить секунды на 3600. В результате получаются числа с бесконечной дробной частью (например, 9.8333333), что неудобно для пользователя и чревато ошибками округления. Рассмотрим, как сделать это правильно, используя целочисленное деление и остаток от деления.

Разберем алгоритм пересчета в коде 1С:


Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    ОбщееКоличествоСекунд = Выборка.СекундВсего;
    
    // Получаем полное количество минут
    ВсегоМинут = Цел(ОбщееКоличествоСекунд / 60);
    
    // Вычисляем количество полных часов
    КолЧасов = Цел(ВсегоМинут / 60);
    
    // Получаем остаток минут (то, что не вошло в полные часы)
    КолМинут = ВсегоМинут % 60;
    
    // Выводим результат пользователю в формате ЧЧ:ММ
    ТекстРезультат = Формат(КолЧасов, "ЧЦ=2; ЧВН=") + ":" + Формат(КолМинут, "ЧЦ=2; ЧВН=");
    Сообщить("Сотрудник отработал: " + ТекстРезультат);
КонецЦикла;

Использование операторов Цел() (целая часть) и % (остаток от деления) позволяет нам получить точные значения без «хвостов» после запятой. Применение функции Формат с параметром ЧВН= гарантирует, что время будет выглядеть красиво (например, "08:05" вместо "8:5").

Шаг 4: Учет ночных часов и обеденных перерывов

В реальных задачах часто требуется не просто общее время, а разделение на дневные и ночные часы (с 22:00 до 06:00), так как они оплачиваются иначе. Посмотрим, как можно реализовать такую проверку программно.

Проанализируем ситуацию: если смена началась в 20:00 и закончилась в 04:00, нам нужно разделить 8 часов на два интервала: 2 часа дневных (20:00 - 22:00) и 6 часов ночных (22:00 - 04:00).

Для этого в цикле обработки выборки мы можем использовать условие:


ГраницаНочи = Дата(Год(Выборка.ДатаНачала), Месяц(Выборка.ДатаНачала), День(Выборка.ДатаНачала), 22, 0, 0);

Если Выборка.ДатаОкончания > ГраницаНочи Тогда
    // Здесь логика разделения интервала
    НочныеСекунды = Выборка.ДатаОкончания - ГраницаНочи;
    ДневныеСекунды = ГраницаНочи - Выборка.ДатаНачала;
КонецЕсли;

Также не забываем про обеденный перерыв. Если в вашей компании принято вычитать час обеда при смене более 8 часов, просто добавьте условие:

Если ВсегоМинут > 480 Тогда ВсегоМинут = ВсегоМинут - 60; КонецЕсли;

Подведем итоги

Для качественного расчета отработанного времени в 1С необходимо придерживаться следующих правил:

  1. Всегда используйте тип данных Дата для хранения времени.
  2. В запросах обязательно используйте СОЕДИНЕНИЕ по идентификатору сотрудника, чтобы избежать дублирования данных.
  3. Для захвата ночных смен расширяйте границы параметров запроса на +24 часа.
  4. Математические операции с временем проводите через целочисленное деление и остаток, чтобы избежать проблем с точностью float.
  5. Форматируйте результат через функцию Формат для приведения к стандартному виду ЧЧ:ММ.

Выполнив эти шаги, вы получите надежный инструмент учета времени, который не «сломается» при наступлении полуночи и будет выдавать понятные пользователю цифры.

← На главную