Как в печатной форме 1С получить предыдущую дату с исключением выходных дней?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При формировании печатных форм первичных документов, заявлений и служебных записок нередко возникает задача автоматического вычисления даты, отстоящей от текущей или даты документа на несколько дней назад. Типичный сценарий: дата заявления должна быть на 3 дня раньше даты создания документа, но при этом она не может приходиться на нерабочие дни — субботу или воскресенье. Если расчетная дата попадает на выходной, ее необходимо сдвинуть на предшествующую рабочую пятницу.

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

Важные архитектурные особенности работы со временем в печатных формах

Прежде чем приступать к расчету дней недели, проанализируем базовые правила платформы при работе со временем:

  1. Привязка к дате документа: Использование функции ТекущаяДата() в коде печатной формы является частой ошибкой. Если открыть и распечатать документ полугодовой давности, функция ТекущаяДата() подставит сегодняшний день, нарушив хронологию. Рекомендуется производить расчет от реквизита самого документа, например Шапка.Дата или СсылкаНаОбъект.Дата. Если бизнес-логика строго требует текущего момента, в клиент-серверной архитектуре безопаснее использовать ТекущаяДатаСеанса(), чтобы избежать разницы часовых поясов клиента и сервера.
  2. Очистка времени: В 1С тип Дата содержит как календарную дату, так и время (часы, минуты, секунды). При простом вычитании секунд (например, 86400 * 3) сохраняется время суток. Перед анализом дней недели и сравнением дат обязательно приводим значение к началу дня с помощью функции НачалоДня().
  3. Количество секунд в сутках: Одни сутки содержат 86400 секунд (24 * 60 * 60), соответственно, трое суток равны 259200 секундам (72 * 60 * 60).

Решение 1. Использование функции ДеньНедели и цикла

Рассмотрим прямолинейный и наглядный алгоритм, предложенный в обсуждении. Мы вычитаем необходимое количество дней, а затем с помощью цикла проверяем, не попали ли мы на субботу (день 6) или воскресенье (день 7). Если попали — продолжаем отнимать по одному дню до тех пор, пока не получим рабочий день.

Посмотрим на пример функции:


Функция ПолучитьДатуЗаявления(ИсходнаяДата) Экспорт

    // Приводим дату к началу суток
    РасчетнаяДата = НачалоДня(ИсходнаяДата) - 259200; // Отнимаем 3 дня

    // Проверяем день недели: 6 - суббота, 7 - воскресенье
    Пока ДеньНедели(РасчетнаяДата) = 6 Или ДеньНедели(РасчетнаяДата) = 7 Цикл
        РасчетнаяДата = РасчетнаяДата - 86400; // Сдвигаем еще на сутки назад
    КонецЦикла;

    Возврат РасчетнаяДата;

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

Выясним, как работает этот алгоритм на практике:

  1. Если документ создан во вторник, вычитание 3 дней дает субботу (день 6). Цикл отнимает еще один день и возвращает пятницу прошлой недели.
  2. Если документ создан в среду, вычитание 3 дней дает воскресенье (день 7). Цикл делает две итерации: с воскресенья на субботу, а затем с субботы на пятницу.
  3. Для остальных дней недели вычитание 3 дней не попадает на субботу или воскресенье, и цикл не выполняется.

Решение 2. Быстрый расчет без цикла по условию

Цикл для проверки субботы и воскресенья не является строго обязательным. Мы можем заранее определить, на какой день выпадает расчетная дата или день недели исходного документа, и применить точное смещение за одну операцию.

Проанализируем такой вариант функции:


Функция ПолучитьДатуЗаявленияБыстро(ИсходнаяДата) Экспорт

    РасчетнаяДата = НачалоДня(ИсходнаяДата) - (3 * 86400);
    НомерДня = ДеньНедели(РасчетнаяДата);

    Если НомерДня = 6 Тогда
        // Суббота: сдвигаем на 1 день назад (пятница)
        РасчетнаяДата = РасчетнаяДата - 86400;
    ИначеЕсли НомерДня = 7 Тогда
        // Воскресенье: сдвигаем на 2 дня назад (пятница)
        РасчетнаяДата = РасчетнаяДата - (2 * 86400);
    КонецЕсли;

    Возврат РасчетнаяДата;

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

Этот способ работает мгновенно в памяти, не требует повторных проверок в цикле и легко читается в коде модуля печатной формы.

Решение 3. Учет производственного календаря и праздников

Рассмотренные выше варианты отлично справляются с субботами и воскресеньями, однако они не учитывают государственные праздничные дни и официальные переносы (например, новогодние каникулы, майские праздники, 12 июня или 4 ноября). Если дата заявления выпадет на праздничный понедельник, алгоритм с функцией ДеньНедели() сочтет его рабочим днем.

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

Разберем пример запроса к регистру, который найдет ближайшую предшествующую рабочую дату:


Функция ПолучитьПредшествующуюРабочуюДату(ИсходнаяДата) Экспорт

    ЦелеваяДата = НачалоДня(ИсходнаяДата) - (3 * 86400);

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

    Запрос.УстановитьПараметр("ЦелеваяДата", ЦелеваяДата);
    Выборка = Запрос.Выполнить().Выбрать();

    Если Выборка.Следующий() Тогда
        Возврат Выборка.ДатаКалендаря;
    КонецЕсли;

    // Резервный возврат, если календарь в базе не заполнен
    Возврат ЦелеваяДата;

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

Преимущества запроса к производственному календарю: алгоритм автоматически отработает любые праздничные дни и предпраздничные переносы в соответствии с законодательством без необходимости ежегодно модифицировать программный код.

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

← На главную