Почему РАЗНОСТЬДАТ в 1С считает год неверно и как рассчитать разницу между датами правильно?

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

В практике программирования на платформе 1С:Предприятие одной из самых часто встречающихся задач является расчет разницы между двумя датами. Это может быть расчет возраста сотрудника, определение стажа работы или вычисление срока действия договора. Однако начинающие разработчики часто сталкиваются с тем, что стандартная функция языка запросов РАЗНОСТЬДАТ выдает результат, который на первый взгляд кажется ошибочным. В этой статье мы подробно разберем, как на самом деле работает эта функция, почему она ведет себя именно так и как реализовать алгоритм, который будет считать годы, месяцы и дни абсолютно точно.

Разбираемся в логике работы РАЗНОСТЬДАТ

Проанализируем ситуацию, описанную в первом сообщении темы. Автор пытается вычислить разницу между 18 ноября 2018 года и 16 октября 2019 года. Очевидно, что полный год еще не прошел (не хватает одного месяца и двух дней). Однако функция РАЗНОСТЬДАТ с параметром ГОД возвращает значение 1. Почему так происходит?

Для того чтобы понять причину, нам нужно заглянуть "под капот" платформы. Язык запросов 1С при выполнении на сервере базы данных транслирует функцию РАЗНОСТЬДАТ в SQL-функцию DATEDIFF. Основная особенность этого механизма заключается в том, что он считает количество переходов через границу указанного периода.

Рассмотрим подробнее на примерах:

  1. Если мы считаем разницу в годах между 31 декабря 2022 года и 1 января 2023 года, функция вернет 1, так как произошла смена номера календарного года. По факту прошел всего 1 день.
  2. Если мы считаем разницу в месяцах между 31 января и 1 февраля, функция вернет 1, так как произошел переход из января в февраль, хотя физически прошли сутки.

Таким образом, когда мы пишем РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, ГОД), система просто выполняет арифметическое действие: Год(ДатаКонца) - Год(ДатаНачала). В примере автора: 2019 - 2018 = 1. Логика функции не подразумевает проверку того, наступила ли уже нужная дата в текущем году.

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

Если нам необходимо получить корректное количество полных лет именно в запросе, не прибегая к обработке результата в коде, мы можем использовать конструкцию ВЫБОР. Идея заключается в том, чтобы сначала получить разницу в годах, а затем проверить, "догнала" ли дата окончания дату начала по месяцам и дням.

Рассмотрим пример реализации такой логики в запросе:


ВЫБРАТЬ
    ВЫБОР
        КОГДА МЕСЯЦ(&КонецПериода) < МЕСЯЦ(&НачалоПериода)
            ТОГДА РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ГОД) - 1
        КОГДА МЕСЯЦ(&КонецПериода) = МЕСЯЦ(&НачалоПериода) И ДЕНЬ(&КонецПериода) < ДЕНЬ(&НачалоПериода)
            ТОГДА РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ГОД) - 1
        ИНАЧЕ РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ГОД)
    КОНЕЦ КАК ПолныхЛет

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

Решение задачи на языке 1С (через код)

Часто задача требует не просто узнать количество лет, а разложить разницу на составляющие: X лет, Y месяцев и Z дней. Стандартная функция РАЗНОСТЬДАТ здесь бессильна, так как она считает каждый компонент независимо от другого. Если мы просто возьмем результат в днях и попробуем поделить его на 365, мы получим ошибку из-за високосных лет. Если поделим на 30 — из-за разной длины месяцев.

Проанализируем эффективный алгоритм, предложенный в сообщении 13 форума. Этот метод последовательно уменьшает интервал, сравнивая компоненты дат.


Функция РазностьДатВГодахМесяцахДнях(ДатаНачала, ДатаОкончания)
    Результат = Новый Структура("Лет, Месяцев, Дней", 0, 0, 0);
    
    // Сначала определяем количество полных лет
    Если Дата(Год(ДатаОкончания), Месяц(ДатаНачала), День(ДатаНачала)) > ДатаОкончания Тогда
        Лет = Год(ДатаОкончания) - Год(ДатаНачала) - 1;
    Иначе
        Лет = Год(ДатаОкончания) - Год(ДатаНачала);
    КонецЕсли;
    
    // Переходим к расчету месяцев и дней
    Если Месяц(ДатаОкончания) > Месяц(ДатаНачала) Тогда
        Если День(ДатаОкончания) >= День(ДатаНачала) Тогда
            Месяцев = Месяц(ДатаОкончания) - Месяц(ДатаНачала);
            Дней = День(ДатаОкончания) - День(ДатаНачала);
        Иначе
            Месяцев = Месяц(ДатаОкончания) - Месяц(ДатаНачала) - 1;
            Дней = День(ДатаОкончания) + (День(КонецМесяца(ДатаНачала)) - День(ДатаНачала));
        КонецЕсли;
    ИначеЕсли Месяц(ДатаОкончания) = Месяц(ДатаНачала) Тогда
        Если День(ДатаОкончания) >= День(ДатаНачала) Тогда
            Месяцев = 0;
            Дней = День(ДатаОкончания) - День(ДатаНачала);
        Иначе // Случай, когда прошел почти год
            Месяцев = 11;
            Дней = День(ДатаОкончания) + (День(КонецМесяца(ДатаНачала)) - День(ДатаНачала));
        КонецЕсли;
    Иначе // Месяц окончания меньше месяца начала
        Если День(ДатаОкончания) >= День(ДатаНачала) Тогда
            Месяцев = 12 - Месяц(ДатаНачала) + Месяц(ДатаОкончания);
            Дней = День(ДатаОкончания) - День(ДатаНачала);
        Иначе
            Месяцев = 12 - Месяц(ДатаНачала) + Месяц(ДатаОкончания) - 1;
            Дней = День(ДатаОкончания) + (День(КонецМесяца(ДатаНачала)) - День(ДатаНачала));
        КонецЕсли;
    КонецЕсли;
    
    Результат.Лет = Лет;
    Результат.Месяцев = Месяцев;
    Результат.Дней = Дней;
    
    Возврат Результат;
КонецФункции

Этот код учитывает сложности перехода через границы месяцев. Обратите внимание на логику расчета дней: когда текущий день месяца окончания меньше дня месяца начала, мы "заимствуем" дни из предыдущего месяца, используя функцию КонецМесяца. Это единственный способ корректно обработать ситуацию, когда в одном месяце 31 день, а в другом 28.

Использование стандартных подсистем (БСП)

Если вы работаете в современной конфигурации (например, ERP, КА, ЗУП или Бухгалтерия 3.0), вам не нужно писать собственные функции "с нуля". В составе Библиотеки Стандартных Подсистем (БСП) уже есть готовые инструменты для решения этой задачи.

Рекомендуем обратить внимание на общий модуль ОбщегоНазначения. В нем часто присутствует функция РазностьДатВГодахМесяцахИДнях. Она реализована с учетом всех нюансов законодательства (что актуально для кадрового учета) и протестирована на тысячах внедрений.

Пример вызова функции из БСП:


Разность = ОбщегоНазначения.РазностьДатВГодахМесяцахИДнях(Объект.ДатаПриема, ТекущаяДата());
Сообщить("Стаж работы: " + Разность.Лет + " лет, " + Разность.Месяцев + " месяцев.");

Важные нюансы и советы

Проанализируем еще несколько критических моментов, которые часто упускают из виду:

  1. Високосные годы: При расчете стажа за длительный период (например, 20 лет) погрешность при простом делении общего количества дней на 365 составит около 5 дней. Всегда используйте календарные функции платформы (ДобавитьКДате, Месяц, Год), а не математические формулы.
  2. Время в дате: Помните, что в 1С тип Дата содержит и время. Если НачалоПериода установлена на 12:00:00, а КонецПериода на 09:00:00 того же числа, то с точки зрения некоторых алгоритмов полные сутки еще не прошли. Если вам нужна точность только до дней, всегда используйте функцию НачалоДня() для обеих дат перед сравнением.
  3. Границы периодов в ЗУП: В кадровом учете часто требуется включать дату окончания в расчет (т.е. разница между 1-м и 31-м числом должна составлять полный месяц). В этом случае к дате окончания нужно добавлять 1 секунду или использовать специализированные функции из программного интерфейса Зарплаты и Управления Персоналом.

Подводя итог, можно сказать: функция РАЗНОСТЬДАТ в запросах — это инструмент для быстрой оценки количества пройденных календарных границ, а не для точного вычисления интервалов времени. Для получения корректных данных в годах и месяцах всегда используйте либо сложные конструкции с ВЫБОР в запросах, либо проверенные функции из состава БСП или собственные алгоритмы, учитывающие составные части даты.

← На главную