Как найти ошибку при наложении одного периода на другой в 1С?

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

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

Математический принцип пересечения интервалов

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

Рассмотрим два интервала: Первый (Начало1, Конец1) и Второй (Начало2, Конец2). Они пересекаются тогда и только тогда, когда выполняется условие:

Начало1 < Конец2 И Начало2 < Конец1

Эта формула удивительно проста, но она учитывает и частичное наложение, и полное поглощение одного периода другим. Если нам нужно учитывать границы «стык в стык» (например, когда 10:00 одного периода и 10:00 другого не считаются ошибкой), мы используем строгие знаки «меньше». Если же касание границ недопустимо, знаки заменяются на «меньше или равно».

Решение задачи с помощью запроса

Если данные уже находятся в базе данных или загружены во временную таблицу, рассмотрим способ поиска пересечений через запрос. Это наиболее производительный метод для больших объемов информации. Основная идея заключается в соединении таблицы самой с собой. Для написания и быстрой отладки таких алгоритмов удобно использовать продвинутую консоль запросов с поддержкой временных таблиц.

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


ВЫБРАТЬ
    Т1.Ссылка КАК Документ1,
    Т2.Ссылка КАК Документ2,
    Т1.ДатаНачала КАК Начало1,
    Т1.ДатаОкончания КАК Конец1,
    Т2.ДатаНачала КАК Начало2,
    Т2.ДатаОкончания КАК Конец2
ИЗ
    Документ.Командировка КАК Т1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Командировка КАК Т2
        ПО Т1.Сотрудник = Т2.Сотрудник
            И Т1.Ссылка <> Т2.Ссылка
            И Т1.ДатаНачала < Т2.ДатаОкончания
            И Т2.ДатаНачала < Т1.ДатаОкончания
ГДЕ
    Т1.Проведен И Т2.Проведен

В данном примере мы выясним причину наложения: условие Т1.ДатаНачала < Т2.ДатаОкончания И Т2.ДатаНачала < Т1.ДатаОкончания гарантирует, что в выборку попадут только пересекающиеся записи. Использование Т1.Ссылка < Т2.Ссылка (вместо <>) позволит исключить зеркальные дубли в результате.

Обработка данных в Таблице Значений (Перебор)

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

  1. Отсортируем таблицу по колонке ДатаНачала для упрощения логики.
  2. Запустим внешний цикл по всем строкам таблицы.
  3. Для каждой строки запустим внутренний цикл, начиная со следующей строки (чтобы не сравнивать назад).
  4. Применим нашу математическую формулу.

Для Инд1 = 0 По ТЗ_Данные.Количество() - 2 Цикл
    Строка1 = ТЗ_Данные[Инд1];
    Для Инд2 = Инд1 + 1 По ТЗ_Данные.Количество() - 1 Цикл
        Строка2 = ТЗ_Данные[Инд2];
        
        // Проверка пересечения
        Если Строка1.ДатаНачала < Строка2.ДатаКонца 
           И Строка2.ДатаНачала < Строка1.ДатаКонца Тогда
            Сообщить("Найдено пересечение периодов!");
            // Дополнительная логика обработки ошибки
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Этот метод прост в реализации, но имеет сложность O(n²), что при большом количестве строк (например, несколько тысяч) может привести к заметным задержкам. Посмотрим, как это можно оптимизировать.

Оптимизированный алгоритм: «Сметающая прямая» (Scanline)

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

Алгоритм действий следующий:

Этот метод работает за время O(n log n), что на порядки быстрее классического перебора.

Проблема «граничных условий» (Стыковка периодов)

Часто в 1С возникает вопрос: считать ли ошибкой ситуацию, когда один период заканчивается ровно в ту же секунду, когда начинается другой? Например: 08:00–10:00 и 10:00–11:00. Чтобы гарантировать корректность работы таких механизмов в разных частях системы, полезно проводить регулярный анализ конфигураций и расширений на наличие ошибок.

Рассмотрим подробнее рекомендации по работе с типом Дата:

  1. Если ваша бизнес-логика допускает стыковку, используйте строгие сравнения (<, >).
  2. Если в 1С используется стандартная точность до секунды, помните, что период «по 10:00:00» обычно включает эту секунду. Чтобы избежать проблем, часто используют методику «открытого конца», когда время окончания записывается как начало следующего возможного периода, но фактически не включается в текущий.
  3. При использовании функции КонецДня() или КонецМесяца() всегда помните о секунде 23:59:59. Для корректного сравнения с началом следующего дня лучше сравнивать «Начало следующего дня» без включения последней секунды предыдущего.

Методика «Острова и промежутки» для объединения периодов

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

Для этого мы последовательно сравниваем текущий интервал с накопленным «максимальным концом» предыдущих. Если текущее начало больше накопленного максимума — мы нашли разрыв и начинаем новый объединенный интервал. Если меньше или равно — мы просто расширяем границу текущего «острова», устанавливая конец в Макс(КонецОстрова, КонецТекущегоИнтервала).

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

← На главную