В практике разработчика 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 = 0 По ТЗ_Данные.Количество() - 2 Цикл
Строка1 = ТЗ_Данные[Инд1];
Для Инд2 = Инд1 + 1 По ТЗ_Данные.Количество() - 1 Цикл
Строка2 = ТЗ_Данные[Инд2];
// Проверка пересечения
Если Строка1.ДатаНачала < Строка2.ДатаКонца
И Строка2.ДатаНачала < Строка1.ДатаКонца Тогда
Сообщить("Найдено пересечение периодов!");
// Дополнительная логика обработки ошибки
КонецЕсли;
КонецЦикла;
КонецЦикла;
Этот метод прост в реализации, но имеет сложность O(n²), что при большом количестве строк (например, несколько тысяч) может привести к заметным задержкам. Посмотрим, как это можно оптимизировать.
Для высоконагруженных систем проанализируем ситуацию, когда количество интервалов исчисляется десятками тысяч. Чтобы избежать зависаний интерфейса, такие операции часто выделяют в конвейер обработки фоновых задач. В этом случае лучше применить алгоритм сканирующей прямой. Его суть заключается в том, что мы разбиваем каждый интервал на две точки: «Начало» и «Конец».
Алгоритм действий следующий:
+1 и все даты конца с признаком -1.Этот метод работает за время O(n log n), что на порядки быстрее классического перебора.
Часто в 1С возникает вопрос: считать ли ошибкой ситуацию, когда один период заканчивается ровно в ту же секунду, когда начинается другой? Например: 08:00–10:00 и 10:00–11:00. Чтобы гарантировать корректность работы таких механизмов в разных частях системы, полезно проводить регулярный анализ конфигураций и расширений на наличие ошибок.
Рассмотрим подробнее рекомендации по работе с типом Дата:
<, >).КонецДня() или КонецМесяца() всегда помните о секунде 23:59:59. Для корректного сравнения с началом следующего дня лучше сравнивать «Начало следующего дня» без включения последней секунды предыдущего.Иногда задача стоит шире: не просто найти ошибку, а «схлопнуть» все пересекающиеся периоды в один непрерывный «остров». Выясним причину, почему это важно — это позволяет понять реальную общую длительность занятости ресурса без учета наложений.
Для этого мы последовательно сравниваем текущий интервал с накопленным «максимальным концом» предыдущих. Если текущее начало больше накопленного максимума — мы нашли разрыв и начинаем новый объединенный интервал. Если меньше или равно — мы просто расширяем границу текущего «острова», устанавливая конец в Макс(КонецОстрова, КонецТекущегоИнтервала).
Подведем итог: выбор метода зависит от объема данных и места выполнения кода. В запросах используйте соединение таблиц с фильтром по ссылкам и условием перекрестного сравнения дат. В программном коде для небольших массивов подойдет вложенный цикл, а для больших — алгоритм сортировки событий (Scanline). Всегда уделяйте внимание строгости неравенств, чтобы корректно обрабатывать «стыки» периодов.