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

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

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

Анализ исходных данных и логика вычисления

Рассмотрим структуру исходных данных. Как правило, во входной таблице присутствуют периоды работы и технологические перерывы (например, обеды). Чтобы упростить дальнейшие расчеты, перерывы и обеды следует рассматривать точно так же, как и рабочее время — то есть как занятые интервалы.

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

  1. Нормализация (объединение) занятых интервалов: приведение таблицы к виду, где периоды отсортированы по времени начала и не пересекаются между собой.
  2. Поиск "окон": вычисление разрывов между временем окончания предыдущего занятого интервала и временем начала следующего.

Решение 1. Программный расчет через Таблицу Значений

Разберем по шагам реализацию на встроенном языке 1С. Этот способ нагляден, легко отлаживается и подходит для обработки произвольных таблиц в памяти.

Сначала подготовим и отсортируем занятые периоды, объединяя накладывающиеся друг на друга строки, после чего соберем свободные слоты:


// Предположим, что ТаблицаЗанятости содержит колонки: ДатаНачала, ДатаОкончания
ТаблицаЗанятости.Сортировать("ДатаНачала Возр, ДатаОкончания Возр");

// 1. Схлопывание пересекающихся и примыкающих интервалов
ТаблицаНормализованная = Новый ТаблицаЗначений;
ТаблицаНормализованная.Колонки.Добавить("ДатаНачала", Новый ОписаниеТипов("Дата"));
ТаблицаНормализованная.Колонки.Добавить("ДатаОкончания", Новый ОписаниеТипов("Дата"));

Для Каждого Стр Из ТаблицаЗанятости Цикл
    Если Стр.ДатаНачала >= Стр.ДатаОкончания Тогда
        Продолжить; // Пропускаем некорректные строки
    КонецЕсли;
    
    Если ТаблицаНормализованная.Количество() = 0 Тогда
        ЗаполнитьЗначенияСвойств(ТаблицаНормализованная.Добавить(), Стр);
    Иначе
        ПоследняяСтр = ТаблицаНормализованная[ТаблицаНормализованная.Количество() - 1];
        Если Стр.ДатаНачала <= ПоследняяСтр.ДатаОкончания Тогда
            // Интервалы пересекаются или соприкасаются - расширяем границу
            ПоследняяСтр.ДатаОкончания = Макс(ПоследняяСтр.ДатаОкончания, Стр.ДатаОкончания);
        Иначе
            // Новый независимый интервал
            ЗаполнитьЗначенияСвойств(ТаблицаНормализованная.Добавить(), Стр);
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

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

Для Индекс = 0 По ТаблицаНормализованная.Количество() - 2 Цикл
    ТекущийКонец = ТаблицаНормализованная[Индекс].ДатаОкончания;
    СледующееНачало = ТаблицаНормализованная[Индекс + 1].ДатаНачала;
    
    Если ТекущийКонец < СледующееНачало Тогда
        НоваяСтр = ТаблицаСвободныхОкон.Добавить();
        НоваяСтр.ДатаНачала = ТекущийКонец;
        НоваяСтр.ДатаОкончания = СледующееНачало;
    КонецЕсли;
КонецЦикла;

Посмотрим на важный нюанс: если требуется учитывать границы суток или рабочий диапазон смены (например, от начала дня до первого выхода или от окончания последней смены до конца суток), границы диапазона можно добавить в качестве фиктивных занятых или контрольных точек до и после основного цикла.

Решение 2. Расчет в запросе с помощью оконных функций

Проанализируем ситуацию, когда расчет необходимо выполнить непосредственно на уровне СУБД в платформе 1С:Предприятие 8.3.20 и выше. Используем оконную функцию LEAD(), которая позволяет получить значение поля из следующей строки выборки.


ВЫБРАТЬ
    Занятость.ДатаНачала КАК ДатаНачала,
    Занятость.ДатаОкончания КАК ДатаОкончания,
    LEAD(Занятость.ДатаНачала) OVER (ORDER BY Занятость.ДатаНачала) КАК СледДатаНачала
ПОМЕСТИТЬ ВТ_СРазметкой
ИЗ
    ВТ_ЗанятыеИнтервалы КАК Занятость
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.ДатаОкончания КАК СвободноС,
    ВТ.СледДатаНачала КАК СвободноПо
ИЗ
    ВТ_СРазметкой КАК ВТ
ГДЕ
    ВТ.СледДатаНачала ЕСТЬ НЕ NULL
    И ВТ.ДатаОкончания < ВТ.СледДатаНачала

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

Особенности работы со временем в 1С

Обратим внимание на ключевые технические моменты:

  1. Дискретность дат: В 1С тип Дата хранит дату и время с точностью до секунды. Если в прикладной логике интервал задается включительно (например, работа завершается в 17:59:59, а следующий интервал начинается в 19:00:00), свободное окно начинается с ДатаОкончания + 1 секунда.
  2. Переход через полночь: Если интервалы хранятся только как время суток без даты (например, смена с 20:00 до 04:00), то при расчете необходимо программно добавлять сутки (86400 секунд) к дате окончания ночной смены, либо работать с полноценными значениями даты и времени.
← На главную