Задача проверки пересечения двух интервалов дат и нахождения результирующего отрезка регулярно встречается в прикладной разработке на платформе 1С:Предприятие. С ней сталкиваются при контроле пересечения графиков сотрудников, проверке занятости ресурсов, расчете периодических регистров и на собеседованиях. Рассмотрим подробнее, почему попытка решить эту задачу через множественные ветвления Если ... ИначеЕсли часто приводит к ошибкам, и разберем математически корректные способы решения на встроенном языке и в запросах.
Проанализируем типичные ошибки, которые возникают при попытке написать проверку «в лоб» через ручное сравнение границ:
НачПериода1, НачПерио2 и НачПериод), из-за чего часть логических блоков просто не работает.МЕЖДУ в запросах: проверка попадания хотя бы одной из границ второго периода внутрь первого отрезка не определяет случай, когда первый интервал целиком находится внутри второго (тогда ни начало, ни конец второго периода не попадают между границами первого).Выясним универсальный математический принцип. Пусть заданы два отрезка времени [A1, A2] и [B1, B2]. Пересечение этих отрезков всегда вычисляется через минимум и максимум без сложных каскадов условий:
Макс(A1, B1) — самая поздняя из начальных дат.Мин(A2, B2) — самая ранняя из конечных дат.Макс(A1, B1) <= Мин(A2, B2). Если вычисленное начало строго больше вычисленного конца, периоды не пересекаются.По условию задачи незаполненная начальная дата означает «-бесконечность», а незаполненная конечная дата — «+бесконечность». Разберем, как платформа 1С обрабатывает граничные значения дат:
Дата(1, 1, 1) (или '00010101000000') является минимально возможным значением типа Дата в платформе. При вычислении Макс(Дата1, Дата2) пустая дата уступает любой реальной дате.Дата(3999, 12, 31, 23, 59, 59) (или '39991231235959'). Если конечная дата не заполнена, ее необходимо явно заменить на максимальную, иначе функция Мин() выберет пустую дату как наименьшую.НачалоДня(), а конечные — к КонецДня().Рассмотрим по шагам реализацию функции на встроенном языке без обращения к СУБД. Этот способ наиболее производителен при работе в памяти:
// Подготовка границ первого периода
Начало1 = ?(ЗначениеЗаполнено(НачПериода1), НачалоДня(НачПериода1), '00010101000000');
Конец1 = ?(ЗначениеЗаполнено(КонПериода1), КонецДня(КонПериода1), '39991231235959');
// Подготовка границ второго периода
Начало2 = ?(ЗначениеЗаполнено(НачПериода2), НачалоДня(НачПериода2), '00010101000000');
Конец2 = ?(ЗначениеЗаполнено(КонПериода2), КонецДня(КонПериода2), '39991231235959');
// Проверка корректности переданных периодов
Если Начало1 > Конец1 Или Начало2 > Конец2 Тогда
ВызватьИсключение "Некорректно заданы границы исходных периодов (дата начала позже даты окончания).";
КонецЕсли;
// Вычисление границ пересечения
НачалоПересечения = Макс(Начало1, Начало2);
КонецПересечения = Мин(Конец1, Конец2);
Если НачалоПересечения <= КонецПересечения Тогда
// Если дата равна минимальной или максимальной, можно вывести строковое обозначение бесконечности
СтрНачало = ?(НачалоПересечения = '00010101000000', "-бесконечность", Формат(НачалоПересечения, "ДФ=dd.MM.yyyy HH:mm:ss"));
СтрКонец = ?(КонецПересечения = '39991231235959', "+бесконечность", Формат(КонецПересечения, "ДФ=dd.MM.yyyy HH:mm:ss"));
Сообщить("Пересечение есть: с " + СтрНачало + " по " + СтрКонец);
Иначе
Сообщить("Пересечения нет");
КонецЕсли;
Посмотрим на пример, когда проверку пересечений необходимо встроить в текст запроса (например, при сопоставлении строк табличной части или выборке данных из регистров сведений). Применим условие пересечения A1 <= B2 И A2 >= B1:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВЫБОР
| КОГДА &Начало1 > &Начало2 ТОГДА &Начало1
| ИНАЧЕ &Начало2
| КОНЕЦ КАК НачалоПересечения,
| ВЫБОР
| КОГДА &Конец1 < &Конец2 ТОГДА &Конец1
| ИНАЧЕ &Конец2
| КОНЕЦ КАК КонецПересечения
|ГДЕ
| &Начало1 <= &Конец2
| И &Конец1 >= &Начало2";
Запрос.УстановитьПараметр("Начало1", ?(ЗначениеЗаполнено(НачПериода1), НачалоДня(НачПериода1), '00010101000000'));
Запрос.УстановитьПараметр("Конец1", ?(ЗначениеЗаполнено(КонПериода1), КонецДня(КонПериода1), '39991231235959'));
Запрос.УстановитьПараметр("Начало2", ?(ЗначениеЗаполнено(НачПериода2), НачалоДня(НачПериода2), '00010101000000'));
Запрос.УстановитьПараметр("Конец2", ?(ЗначениеЗаполнено(КонПериода2), КонецДня(КонПериода2), '39991231235959'));
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Сообщить("Пересечение найдено: " + Выборка.НачалоПересечения + " - " + Выборка.КонецПересечения);
Иначе
Сообщить("Пересечения нет");
КонецЕсли;
Резюме: Использование математических функций Макс() и Мин() в сочетании с явной подстановкой максимальной даты '39991231235959' для открытых интервалов позволяет получить компактный, устойчивый к ошибкам и легко читаемый код.