К статье
Практический материал

Разработка и Конфигурация · 11 мин

1С: как прибавить к дате рабочие дни

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

Задача прибавить к дате несколько рабочих дней возникает в 1С постоянно: расчёт срока оплаты по договору, дедлайна по задаче, даты отгрузки, срока ответа на претензию. Простое прибавление календарных дней через Дата + 86400 * N не подходит — нужно учитывать выходные и праздники.

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

Ниже разобраны все три подхода с готовым кодом, условиями выбора и типичными ошибками. Примеры работают как в управляемых, так и в обычных формах, на клиенте и на сервере (с учётом ограничений, указанных в каждом разделе).

Почему простое сложение дат не работает

В 1С дата хранится с точностью до секунды, и к ней можно прибавлять число секунд: НоваяДата = ИсходнаяДата + 3 * 86400; сдвинет дату на три календарных дня. Но рабочий день — это не календарный: если стартовая дата пятница, то «плюс один рабочий день» — это понедельник, а не суббота.

Кроме выходных есть праздничные дни и переносы, которые каждый год утверждаются постановлением Правительства. Жёстко зашить их в код нельзя — график меняется ежегодно, и расчёт быстро устареет. Поэтому корректное решение всегда опирается на актуальный календарь.

Способ 1: цикл с проверкой дня недели

Самый простой вариант — перебирать дни по одному и считать только те, что попадают на понедельник–пятницу. Подходит, когда праздники не критичны: внутренние сроки задач, черновые плановые даты.

Функция ПрибавитьРабочиеДни(ИсходнаяДата, КоличествоДней)

Результат = ИсходнаяДата;

Добавлено = 0;

Пока Добавлено < КоличествоДней Цикл

Результат = Результат + 86400; // +1 день

ДеньНед = ДеньНедели(Результат);

Если ДеньНед < 6 Тогда // 1..5 — будни

Добавлено = Добавлено + 1;

КонецЕсли;

КонецЦикла;

Возврат Результат;

КонецФункции

Функция ДеньНедели() возвращает число от 1 (понедельник) до 7 (воскресенье), поэтому условие < 6 отсекает субботу и воскресенье. Цикл гарантированно завершится: каждые 7 итераций добавляется 5 рабочих дней.

Ограничение очевидно: 8 марта, выпавшее на вторник, этот код посчитает рабочим днём. Если для бизнес-процесса это недопустимо — переходите к расчёту по календарю.

Способ 2: расчёт по производственному календарю

Точный способ — опираться на данные календаря. В конфигурациях на базе Библиотеки стандартных подсистем (БСП) есть справочник «Производственные календари» и регистр сведений «ДанныеПроизводственногоКалендаря», где каждому дню сопоставлен вид дня. В ЗУП и ERP графики работы дополнительно материализуются в регистр «ДанныеГрафика».

Алгоритм тот же — цикл по дням, но проверка идёт по календарю:

Функция ЭтоРабочийДень(ДатаПроверки, Календарь)

Запрос = Новый Запрос(

"ВЫБРАТЬ ВидДня

|ИЗ РегистрСведений.ДанныеПроизводственногоКалендаря

|ГДЕ ПроизводственныйКалендарь = &Календарь

| И Дата = &Дата");

Запрос.УстановитьПараметр("Календарь", Календарь);

Запрос.УстановитьПараметр("Дата", НачалоДня(ДатаПроверки));

Выборка = Запрос.Выполнить().Выбрать();

Если Выборка.Следующий() Тогда

Возврат Выборка.ВидДня = Перечисления.ВидыДнейПроизводственногоКалендаря.Рабочий

Или Выборка.ВидДня = Перечисления.ВидыДнейПроизводственногоКалендаря.Предпраздничный;

КонецЕсли;

Возврат Ложь; // дня нет в календаре — считаем нерабочим

КонецФункции

Дальше цикл аналогичен первому способу: сдвигаем дату на день и инкрементируем счётчик, только если ЭтоРабочийДень() вернула Истина. Предпраздничный день включён в расчёт — он сокращённый, но рабочий; если ваша логика требует иначе, уберите его из условия.

Точные имена справочника, регистра и перечисления зависят от конфигурации и версии БСП. Перед написанием кода откройте конфигуратор и проверьте фактические имена объектов в вашей базе.

Способ 3: расчёт запросом без цикла

Цикл с запросом в каждой итерации — плохое решение по производительности, особенно на сервере и при больших сдвигах. Один запрос может сразу найти нужную дату: берём все рабочие дни после исходной даты, упорядочиваем и берём N-ный.

Запрос = Новый Запрос(

"ВЫБРАТЬ ПЕРВЫЕ 1

| Данные.Дата

|ИЗ

| (ВЫБРАТЬ ПЕРВЫЕ &КоличествоДней

| Данные.Дата КАК Дата

| ИЗ

| РегистрСведений.ДанныеПроизводственногоКалендаря КАК Данные

| ГДЕ

| Данные.ПроизводственныйКалендарь = &Календарь

| И Данные.Дата > &ИсходнаяДата

| И Данные.ВидДня В (&РабочиеВиды)

| УПОРЯДОЧИТЬ ПО

| Данные.Дата) КАК Данные

|УПОРЯДОЧИТЬ ПО

| Данные.Дата УБЫВ");

Идея: вложенный запрос берёт первые N рабочих дней после исходной даты по возрастанию, внешний — последнюю из них (сортировка по убыванию + «ПЕРВЫЕ 1»). В параметр &РабочиеВиды передаётся массив из значений «Рабочий» и «Предпраздничный».

Такой подход выполняется за одно обращение к СУБД и легко масштабируется: если нужно рассчитать даты для тысячи документов, запрос можно расширить соединением с таблицей документов.

Сравнение подходов

Цикл + ДеньНедели()

Быстро написать, не требует календаря. Не учитывает праздники — только для внутренних нестрогих сроков.

Цикл + запрос к календарю

Точный, но N обращений к базе. Приемлем при разовом расчёте в форме документа.

Один запрос по календарю

Точный и быстрый, подходит для пакетных расчётов. Требует заполненного календаря и чуть более сложного кода.

Обратный расчёт и отрицательное количество дней

Иногда нужно не прибавить, а отнять рабочие дни: например, вычислить дату старта задачи от дедлайна. Логика зеркальная — двигаемся назад и считаем рабочие дни в обратном порядке.

Функция ОтнятьРабочиеДни(ИсходнаяДата, КоличествоДней)

Результат = ИсходнаяДата;

Вычтено = 0;

Пока Вычтено < КоличествоДней Цикл

Результат = Результат - 86400;

Если ДеньНедели(Результат) < 6 Тогда

Вычтено = Вычтено + 1;

КонецЕсли;

КонецЦикла;

Возврат Результат;

КонецФункции

Для универсальной функции достаточно проверить знак параметра и выбрать направление сдвига. В варианте с запросом направление задаётся условием Дата < &ИсходнаяДата и сортировкой по убыванию во вложенном запросе.

Отдельный вопрос — как трактовать саму исходную дату, если она выпала на выходной. Обычно её не считают ни рабочей, ни «нулевым» днём: расчёт начинается со следующего дня. Но в некоторых регламентах (например, «срок — 5 рабочих дней, включая день обращения») стартовый день засчитывается. Зафиксируйте это правило в требованиях до написания кода.

Где разместить код и как проверить результат

Функцию расчёта размещайте в общем модуле с признаком «Сервер» (и «Вызов сервера», если нужен вызов из клиентских форм). Название — например, РаботаСКалендарями или РасчетСроков. В типовых конфигурациях перед написанием своего кода проверьте, нет ли готового API: в БСП есть модуль КалендарныеГрафики с функциями получения дат по графику.

Проверку делайте на контрольных примерах, где результат известен заранее: возьмите официальный производственный календарь на текущий год и посчитайте вручную несколько пар «дата + N рабочих дней», включая переходы через праздничные периоды (новогодние каникулы, майские праздники).

Порядок внедрения расчёта
  1. 1
    Определите, нужны ли праздники или достаточно учёта выходных
  2. 2
    Проверьте, что производственный календарь заполнен на нужный период
  3. 3
    Уточните фактические имена регистра и перечисления в вашей конфигурации
  4. 4
    Напишите функцию в общем серверном модуле
  5. 5
    Протестируйте на датах вокруг праздников и на границах года
  6. 6
    Сверьте результат с официальным производственным календарём
Контрольные точки

Как проверить результат

Сдвиг на 1 рабочий день от пятницы
Норма

Получен понедельник (или вторник, если понедельник праздничный)

Тревожный признак

Получена суббота или воскресенье

Расчёт через новогодние каникулы
Норма

Праздничные дни пропущены согласно календарю

Тревожный признак

Дата попала внутрь каникул

Расчёт на будущий год
Норма

Дата соответствует заполненному календарю будущего года

Тревожный признак

Функция возвращает неправильную дату или «зависает» — календарь не заполнен

Типичные ошибки и ограничения

Самая частая ошибка — забыть, что дата содержит время. Если исходная дата документа — 15:30, то и результат будет со временем 15:30, что может ломать сравнения и отборы. Приводите дату к началу дня функцией НачалоДня() до или после расчёта.

Вторая ошибка — запрос в цикле внутри транзакции записи документа. Это удлиняет транзакцию и повышает риск блокировок. Вычисляйте дату заранее, до начала транзакции, или кэшируйте данные календаря в таблицу значений.

Третья — молчаливая обработка незаполненного календаря. Если функция считает отсутствующий день нерабочим, при большом сдвиге цикл может уйти далеко вперёд или вообще не завершиться при пустом календаре. Добавьте защиту: ограничение на максимальное число итераций (например, КоличествоДней × 3 + 31) и явное исключение с понятным текстом.

Типичные ошибки
01
Прибавление секунд без учёта выходных — срок попадает на субботу или праздник
02
Запрос к календарю в каждой итерации цикла — медленно и нагружает СУБД
03
Отсутствие НачалоДня() — время в дате ломает сравнения и отборы
04
Нет проверки заполненности календаря — бесконечный цикл или неверная дата
05
Игнорирование того, считается ли исходный день первым днём срока — расчёт расходится с регламентом на один день

Частые вопросы

Есть ли в 1С готовая функция «прибавить рабочие дни»?

В самой платформе — нет. В конфигурациях на базе Библиотеки стандартных подсистем есть общий модуль КалендарныеГрафики с функциями расчёта дат по графику работы. Проверьте его наличие в вашей конфигурации до написания собственного кода.

Как прибавить рабочие часы, а не дни?

Задача сложнее: нужно идти по графику с интервалами рабочего времени (в ЗУП это регистр с данными графика по часам). Алгоритм — двигаться по рабочим дням и внутри каждого прибавлять доступные часы, пока не исчерпан нужный объём. Для большинства прикладных задач достаточно округления до дней.

Что делать, если производственный календарь не заполнен на следующий год?

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

Работает ли этот код на тонком клиенте и в веб-клиенте?

Функции ДеньНедели(), НачалоДня() и арифметика дат работают везде. Запросы к базе — только на сервере, поэтому функцию с запросом размещайте в серверном общем модуле с признаком «Вызов сервера» и вызывайте из клиентского кода.

Как учесть, что в компании шестидневная рабочая неделя?

Условие ДеньНедели() < 6 замените на < 7 (рабочие — понедельник–суббота). При расчёте по календарю ничего менять не нужно: создайте отдельный график работы с шестидневкой и передавайте его в функцию вместо общего производственного календаря.

Можно ли посчитать количество рабочих дней между двумя датами?

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

Итог

Что важно запомнить

Встроенной функции прибавления рабочих дней в 1С нет. Для грубого расчёта достаточно цикла с ДеньНедели(), для точного — запроса к производственному календарю. Всегда приводите дату к началу дня, проверяйте заполненность календаря на нужный период и заранее фиксируйте правило учёта стартового дня.