Почему функция РазностьДатПоКалендарю возвращает отрицательное значение при расчете с 31 декабря?

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

В процессе разработки или сопровождения конфигураций на базе 1С:ERP, ЗУП или КА программисты часто сталкиваются с использованием типовых механизмов Библиотеки стандартных подсистем (БСП). Одной из таких функций является ГрафикиРаботы.РазностьДатПоКалендарю. Однако при определенных граничных условиях, а именно при использовании в качестве даты начала последнего дня года (31 декабря), функция начинает вести себя непредсказуемо, возвращая странные отрицательные значения (например, -251), хотя ожидается положительное число дней. Давайте подробно разберем, почему это происходит и как это исправить.

Анализ внутренней логики функции РазностьДатПоКалендарю

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

Рассмотрим алгоритм работы функции по шагам:

  1. Программа определяет, находятся ли даты в одном календарном году или в разных.
  2. Если даты относятся к разным годам, система формирует массив «контрольных точек» (якорей), в который обязательно должны входить дата начала, дата окончания и границы каждого года (31 декабря) между ними.
  3. Затем выполняется запрос к регистру, который получает накопленные итоги на эти даты.
  4. В цикле обхода выборки происходит математическое вычитание и сложение этих итогов для получения итогового результата.

Проблема кроется в одном небольшом условии, которое должно определять необходимость добавления 31 декабря в массив дат. Посмотрим на типовой код, который вызывает ошибку:


ДатыГрафика = Новый Массив;
ДатыГрафика.Добавить(ДатаНачала);
Если Год(ДатаНачала) <> Год(ДатаОкончания) И КонецДня(ДатаНачала) <> КонецГода(ДатаНачала) Тогда
    // Если даты разных годов, то добавляем "границы" годов.
    Для НомерГода = Год(ДатаНачала) По Год(ДатаОкончания) - 1 Цикл
        ДатыГрафика.Добавить(Дата(НомерГода, 12, 31));
    КонецЦикла;
КонецЕсли;
ДатыГрафика.Добавить(ДатаОкончания);

Выясняем причину ошибки

Проанализируем ситуацию, описанную пользователем: ДатаНачала = 31.12.2024, а ДатаОкончания находится в 2025 году.

В этом случае условие КонецДня(ДатаНачала) <> КонецГода(ДатаНачала) возвращает Ложь, так как для 31 декабря конец дня и конец года совпадают. В результате блок добавления границ годов пропускается. Массив ДатыГрафика заполняется некорректно: в нем оказывается только 31.12.2024 и 01.01.2025 без четкого разделения логики перехода.

Когда запрос начинает обрабатывать эти данные, он получает накопленный итог за 2024 год (например, 251 рабочий день) и итог за начало 2025 года (например, 1 день). Из-за отсутствия «точки сброса» накопленного итога, математический алгоритм внутри функции выполняет вычитание 251 из 1 (или аналогичные некорректные действия в зависимости от версии БСП), что и приводит к результату вроде -250 или -251.

Способ решения №1: Исправление типового кода через расширение

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

Рекомендуемое исправление: необходимо убрать проверку на конец года. Рассмотрим исправленный фрагмент кода:


// Измененное условие
Если Год(ДатаНачала) <> Год(ДатаОкончания) Тогда
    Для НомерГода = Год(ДатаНачала) По Год(ДатаОкончания) - 1 Цикл
        // Теперь 31.12 будет добавлено всегда при переходе через год
        ДатыГрафика.Добавить(Дата(НомерГода, 12, 31));
    КонецЦикла;
КонецЕсли;

При таком подходе, если ДатаНачала — 31.12.2024, в массиве окажется две записи с этой датой. Запрос обработает их корректно, и математическая разница между ними составит 0, а затем прибавится корректное количество дней за следующий год.

Способ решения №2: Использование прямого запроса

Если вы не хотите зависеть от логики БСП, которая может измениться при обновлении, или если вам нужно более прозрачное решение, можно написать собственную функцию. Рассмотрим вариант прямого подсчета рабочих дней в регистре сведений КалендарныеГрафики. Это решение работает стабильно и не подвержено ошибкам "граничных дат".

Рассмотрим пример запроса, который решает задачу:


ВЫБРАТЬ
    КОЛИЧЕСТВО(Т1.ДатаГрафика) КАК КоличествоДней
ИЗ
    РегистрСведений.КалендарныеГрафики КАК Т1
ГДЕ
    Т1.Календарь = &Календарь
    И Т1.ДатаГрафика МЕЖДУ &ДатаНачала И &ДатаОкончания
    И Т1.ДеньВключенВГрафик = ИСТИНА

Этот подход имеет ряд преимуществ:

Дополнительные нюансы: Високосные годы

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


Функция Високосный(Год)
    Если Год % 400 = 0 Тогда
        Возврат Истина;
    ИначеЕсли Год % 100 = 0 Тогда
        Возврат Ложь;
    ИначеЕсли Год % 4 = 0 Тогда
        Возврат Истина;
    Иначе
        Возврат Ложь;
    КонецЕсли; 
КонецФункции

В нашем случае (2024 год) год является високосным, что означает наличие 366 календарных дней и, как правило, на один рабочий день больше в графике. Это объясняет, почему числа в расчетах (например, 251 или 252) могут незначительно отличаться в разных годах, но не отменяет логическую ошибку в РазностьДатПоКалендарю.

Резюме

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

  1. Либо создать расширение и подкорректировать условие в общем модуле КалендарныеГрафики, убрав проверку КонецДня(ДатаНачала) <> КонецГода(ДатаНачала).
  2. Либо реализовать получение разницы дат через прямой запрос к регистру сведений КалендарныеГрафики, что будет более универсальным и надежным решением в долгосрочной перспективе.
← На главную