При разработке бизнес-логики в 1С часто возникает задача: определив исходную дату и целевой день недели (в виде числа от 1 до 7 или текстового названия), найти ближайшую соответствующую дату. Рассмотрим подробнее несколько способов решения этой задачи — от простого цикла до оптимизированных математических расчетов без циклов, а также разберем вопросы валидации данных и обработки строк.
Наиболее очевидный и наглядный вариант — последовательный перебор дней с помощью цикла Пока. Мы берем исходную дату и прибавляем к ней по одному дню (86400 секунд), пока функция ДеньНедели() не вернет нужный номер дня.
Рассмотрим пример реализации функции в BSL:
Функция ВычислитьБлижайшуюДатуПоНомеруДняВНеделе(ДатаОтсчета, НомерДня)
// Проверяем корректность номера дня
Если НомерДня < 1 Или НомерДня > 7 Тогда
Возврат Дата('00010101');
КонецЕсли;
ДатаВозврата = НачалоДня(ДатаОтсчета);
Пока ДеньНедели(ДатаВозврата) <> НомерДня Цикл
ДатаВозврата = ДатаВозврата + 86400; // Добавляем 24 часа в секундах
КонецЦикла;
Возврат ДатаВозврата;
КонецФункции
Важный нюанс: Перед запуском цикла обязательно нужно проверять корректность параметра НомерДня. Если передан 0, отрицательное число или число больше 7, цикл станет бесконечным. По этой причине цикличные алгоритмы требуют строгой валидации входящих аргументов.
Вычисление даты без цикла работает быстрее и безопаснее, так как исключает риск зависания системы. Для этого мы вычисляем разницу между нужным и текущим днем недели и прибавляем необходимое число суток в секундах (86400 секунд = 1 день).
Проанализируем алгоритм со смещением через условное выражение:
Функция ПолучитьБлижайшуюДату(ДатаОтсчета, НомерНужногоДня)
ТекДеньНедели = ДеньНедели(ДатаОтсчета);
Разница = НомерНужногоДня - ТекДеньНедели;
Если Разница < 0 Тогда
Разница = Разница + 7;
КонецЕсли;
БлижайшаяДата = НачалоДня(ДатаОтсчета) + 86400 * Разница;
Возврат БлижайшаяДата;
КонецФункции
Если требуется, чтобы при совпадении дня недели с исходным днем результат смещался на следующую неделю (на 7 дней вперед), расчет с помощью остатка от деления % записывается следующим образом:
КоличествоДней = (НомерНужногоДня - ДеньНедели(ДатаОтсчета) + 6) % 7 + 1;
БлижайшаяДата = НачалоДня(ДатаОтсчета) + 86400 * КоличествоДней;
Если же при совпадении дней недели нужно возвращать текущую дату (смещение 0 дней), формула выглядит так:
КоличествоДней = (НомерНужногоДня - ДеньНедели(ДатаОтсчета) + 7) % 7;
БлижайшаяДата = НачалоДня(ДатаОтсчета) + 86400 * КоличествоДней;
Пользователи часто вводят день недели в виде строки (например, «Среда», «ср», «Пятница»). Чтобы перевести текст в числовой номер дня от 1 до 7, удобно использовать объект Соответствие, а также встроенные функции ННиж() и СокрЛП() для очистки текста от лишних пробелов и приведения к нижнему регистру.
Разберем по шагам функцию сопоставления наименования дня недели с его номером:
Функция ПолучитьНомерДняПоТексту(Знач ТекстДня)
ТекстДня = ННиж(СокрЛП(ТекстДня));
КартаДней = Новый Соответствие;
КартаДней.Вставить("понедельник", 1); КартаДней.Вставить("пн", 1);
КартаДней.Вставить("вторник", 2); КартаДней.Вставить("вт", 2);
КартаДней.Вставить("среда", 3); КартаДней.Вставить("ср", 3);
КартаДней.Вставить("четверг", 4); КартаДней.Вставить("чт", 4);
КартаДней.Вставить("пятница", 5); КартаДней.Вставить("пт", 5);
КартаДней.Вставить("суббота", 6); КартаДней.Вставить("сб", 6);
КартаДней.Вставить("воскресенье", 7); КартаДней.Вставить("вс", 7);
НомерДня = КартаДней.Получить(ТекстДня);
Возврат ?(НомерДня = Неопределено, 0, НомерДня);
КонецФункции
Выясним важные аспекты, которые необходимо учитывать при работе с датами в 1С:
Дата всегда содержит время (`ДД.ММ.ГГГГ ЧЧ:ММ:СС`). Если исходная дата содержит время (например, `15.08.2017 14:30:00`), то прибавление секунд сохранит это время. Чтобы получить чистую дату со временем `00:00:00`, используйте функцию НачалоДня().ЗначениеЗаполнено(), чтобы не производить расчеты над пустой датой `00010101`.Если задачу требуется решить прямо в тексте запроса без вызова кода BSL, используются встроенные функции языка запросов ДЕНЬНЕДЕЛИ() и ДОБАВИТЬКДАТЕ().
Посмотрим на пример выражения в запросе:
ВЫБОР
КОГДА ДЕНЬНЕДЕЛИ(&ИсходнаяДата) <= &НомерНужногоДня
ТОГДА ДОБАВИТЬКДАТЕ(&ИсходнаяДата, ДЕНЬ, &НомерНужногоДня - ДЕНЬНЕДЕЛИ(&ИсходнаяДата))
ИНАЧЕ ДОБАВИТЬКДАТЕ(&ИсходнаяДата, ДЕНЬ, 7 + &НомерНужногоДня - ДЕНЬНЕДЕЛИ(&ИсходнаяДата))
КОНЕЦ