Как правильно прибавить рабочие или банковские дни к дате в 1С?

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

В разработке на платформе 1С часто возникает задача прибавить к определенной дате не просто календарные дни, а именно рабочие или банковские. Это критично для расчета сроков оплаты счетов, даты поставки или исполнения обязательств по договору. Простая арифметика Дата + (КоличествоДней * 86400) здесь не поможет, так как она не учитывает выходные и праздничные дни. Рассмотрим подробнее, как решить эту задачу различными способами — от встроенных функций БСП до эффективных запросов.

Способ 1: Использование подсистемы «Календарные графики» из БСП

Если ваша конфигурация построена на базе БСП (Библиотека стандартных подсистем), то самым правильным и «чистым» решением будет использование типовых механизмов. Для этого в БСП предусмотрен программный интерфейс в общем модуле КалендарныеГрафики.

Разберем пример кода, который позволяет получить дату через определенное количество рабочих дней. Для этого нам понадобится ссылка на производственный календарь (например, «РФ»).


&НаСервере
Функция ДобавитьКДатеРабочиеДни(ПередаваемаяДата, КоличествоРабочихДней) Экспорт
    // Получаем ссылку на основной производственный календарь
    ГрафикРаботы = Справочники.ПроизводственныеКалендари.НайтиПоКоду("РФ");
    
    // Если календарь не найден, расчет может быть некорректным
    Если ГрафикРаботы.Пустая() Тогда
        Возврат Дата(1, 1, 1);
    КонецЕсли;

    // Используем метод БСП для расчета даты по календарю
    СледующийДень = КалендарныеГрафики.ДатаПоКалендарю(ГрафикРаботы, ПередаваемаяДата, КоличествоРабочихДней);
    
    Возврат СледующийДень;
КонецФункции

Этот метод хорош тем, что он учитывает все государственные праздники и переносы, которые уже занесены в систему. Важный нюанс: функция ДатаПоКалендарю умеет работать и в обратную сторону — если передать отрицательное число в параметр КоличествоРабочихДней, она вычислит дату, которая была несколько рабочих дней назад.

Способ 2: Реализация через запрос (высокая производительность)

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

Рассмотрим логику запроса для получения даты через N рабочих дней:

  1. Выбираем записи из регистра ДанныеПроизводственныхКалендарей.
  2. Фильтруем по конкретному календарю и условию Дата > &НашаДата.
  3. Отбираем только те дни, где ВидДня = Рабочий.
  4. Сортируем по дате по возрастанию.
  5. Используем конструкцию ВЫБРАТЬ ПЕРВЫЕ N.
  6. Максимальная дата из этого набора и будет искомым результатом.

Посмотрим на пример такого запроса:


ВЫБРАТЬ ПЕРВЫЕ 1
    ВложенныйЗапрос.Дата КАК ИскомаяДата
ИЗ (
    ВЫБРАТЬ ПЕРВЫЕ 5
        Т.Дата КАК Дата
    ИЗ
        РегистрСведений.ДанныеПроизводственныхКалендарей КАК Т
    ГДЕ
        Т.ПроизводственныйКалендарь = &Календарь
        И Т.Дата > &ДатаНачала
        И Т.ВидДня = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий)
    УПОРЯДОЧИТЬ ПО
        Дата ВОЗРАСТАНИЮ
) КАК ВложенныйЗапрос
УПОРЯДОЧИТЬ ПО
    ВложенныйЗапрос.Дата УБЫВАНИЮ

Такой подход работает молниеносно даже на больших объемах данных, так как платформа оптимизирует работу с индексами регистра.

Способ 3: Программный цикл (если нет БСП или сложная логика)

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


Функция ДобавитьКДатеРабочиеДниЦиклом(Знач ИсходнаяДата, КоличествоДней) Экспорт
    ТекущаяДата = ИсходнаяДата;
    ДобавленоДней = 0;
    
    Пока ДобавленоДней < КоличествоДней Цикл
        ТекущаяДата = ТекущаяДата + 86400; // Прибавляем одни сутки
        
        Если ЭтоРабочийДень(ТекущаяДата) Тогда
            ДобавленоДней = ДобавленоДней + 1;
        КонецЕсли;
    КонецЦикла;
    
    Возврат ТекущаяДата;
КонецФункции

Функция ЭтоРабочийДень(ПроверяемаяДата)
    // Здесь может быть запрос к регистру или простая проверка дня недели
    ДеньНедели = ДеньНедели(ПроверяемаяДата);
    Возврат ?(ДеньНедели < 6, Истина, Ложь); // Упрощенно: Пн-Пт — рабочие
КонецФункции

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

Проблема «пустого календаря» на будущий год

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

Чтобы избежать этой ситуации, придерживайтесь следующих правил:

Различие между банковскими и рабочими днями

Важно помнить, что Рабочие дни (по законодательству РФ) и Банковский день — это не всегда одно и то же. Для расчетов внутри РФ они обычно совпадают. Однако, если вы работаете с валютными переводами, могут использоваться календари других стран (например, выходные в США при расчетах в USD). БСП позволяет создавать несколько производственных календарей. В коде всегда явно указывайте, какой именно календарь вы используете в Справочники.ПроизводственныеКалендари, чтобы избежать путаницы между графиком офиса и графиком банковских операций.

Рассмотренные нами методы позволяют гибко настроить расчет дат в любой конфигурации 1С, обеспечивая точность и высокую скорость работы системы.

← На главную