В практике программирования на платформе 1С:Предприятие одной из самых часто встречающихся задач является расчет разницы между двумя датами. Это может быть расчет возраста сотрудника, определение стажа работы или вычисление срока действия договора. Однако начинающие разработчики часто сталкиваются с тем, что стандартная функция языка запросов РАЗНОСТЬДАТ выдает результат, который на первый взгляд кажется ошибочным. В этой статье мы подробно разберем, как на самом деле работает эта функция, почему она ведет себя именно так и как реализовать алгоритм, который будет считать годы, месяцы и дни абсолютно точно.
Проанализируем ситуацию, описанную в первом сообщении темы. Автор пытается вычислить разницу между 18 ноября 2018 года и 16 октября 2019 года. Очевидно, что полный год еще не прошел (не хватает одного месяца и двух дней). Однако функция РАЗНОСТЬДАТ с параметром ГОД возвращает значение 1. Почему так происходит?
Для того чтобы понять причину, нам нужно заглянуть "под капот" платформы. Язык запросов 1С при выполнении на сервере базы данных транслирует функцию РАЗНОСТЬДАТ в SQL-функцию DATEDIFF. Основная особенность этого механизма заключается в том, что он считает количество переходов через границу указанного периода.
Рассмотрим подробнее на примерах:
Таким образом, когда мы пишем РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, ГОД), система просто выполняет арифметическое действие: Год(ДатаКонца) - Год(ДатаНачала). В примере автора: 2019 - 2018 = 1. Логика функции не подразумевает проверку того, наступила ли уже нужная дата в текущем году.
Если нам необходимо получить корректное количество полных лет именно в запросе, не прибегая к обработке результата в коде, мы можем использовать конструкцию ВЫБОР. Идея заключается в том, чтобы сначала получить разницу в годах, а затем проверить, "догнала" ли дата окончания дату начала по месяцам и дням.
Рассмотрим пример реализации такой логики в запросе:
ВЫБРАТЬ
ВЫБОР
КОГДА МЕСЯЦ(&КонецПериода) < МЕСЯЦ(&НачалоПериода)
ТОГДА РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ГОД) - 1
КОГДА МЕСЯЦ(&КонецПериода) = МЕСЯЦ(&НачалоПериода) И ДЕНЬ(&КонецПериода) < ДЕНЬ(&НачалоПериода)
ТОГДА РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ГОД) - 1
ИНАЧЕ РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, ГОД)
КОНЕЦ КАК ПолныхЛет
Этот подход позволяет прямо в базе данных получить "честный" возраст или стаж, учитывая текущий календарный день. Мы анализируем, наступил ли месяц рождения/события и наступил ли день внутри этого месяца.
Часто задача требует не просто узнать количество лет, а разложить разницу на составляющие: X лет, Y месяцев и Z дней. Стандартная функция РАЗНОСТЬДАТ здесь бессильна, так как она считает каждый компонент независимо от другого. Если мы просто возьмем результат в днях и попробуем поделить его на 365, мы получим ошибку из-за високосных лет. Если поделим на 30 — из-за разной длины месяцев.
Проанализируем эффективный алгоритм, предложенный в сообщении 13 форума. Этот метод последовательно уменьшает интервал, сравнивая компоненты дат.
Функция РазностьДатВГодахМесяцахДнях(ДатаНачала, ДатаОкончания)
Результат = Новый Структура("Лет, Месяцев, Дней", 0, 0, 0);
// Сначала определяем количество полных лет
Если Дата(Год(ДатаОкончания), Месяц(ДатаНачала), День(ДатаНачала)) > ДатаОкончания Тогда
Лет = Год(ДатаОкончания) - Год(ДатаНачала) - 1;
Иначе
Лет = Год(ДатаОкончания) - Год(ДатаНачала);
КонецЕсли;
// Переходим к расчету месяцев и дней
Если Месяц(ДатаОкончания) > Месяц(ДатаНачала) Тогда
Если День(ДатаОкончания) >= День(ДатаНачала) Тогда
Месяцев = Месяц(ДатаОкончания) - Месяц(ДатаНачала);
Дней = День(ДатаОкончания) - День(ДатаНачала);
Иначе
Месяцев = Месяц(ДатаОкончания) - Месяц(ДатаНачала) - 1;
Дней = День(ДатаОкончания) + (День(КонецМесяца(ДатаНачала)) - День(ДатаНачала));
КонецЕсли;
ИначеЕсли Месяц(ДатаОкончания) = Месяц(ДатаНачала) Тогда
Если День(ДатаОкончания) >= День(ДатаНачала) Тогда
Месяцев = 0;
Дней = День(ДатаОкончания) - День(ДатаНачала);
Иначе // Случай, когда прошел почти год
Месяцев = 11;
Дней = День(ДатаОкончания) + (День(КонецМесяца(ДатаНачала)) - День(ДатаНачала));
КонецЕсли;
Иначе // Месяц окончания меньше месяца начала
Если День(ДатаОкончания) >= День(ДатаНачала) Тогда
Месяцев = 12 - Месяц(ДатаНачала) + Месяц(ДатаОкончания);
Дней = День(ДатаОкончания) - День(ДатаНачала);
Иначе
Месяцев = 12 - Месяц(ДатаНачала) + Месяц(ДатаОкончания) - 1;
Дней = День(ДатаОкончания) + (День(КонецМесяца(ДатаНачала)) - День(ДатаНачала));
КонецЕсли;
КонецЕсли;
Результат.Лет = Лет;
Результат.Месяцев = Месяцев;
Результат.Дней = Дней;
Возврат Результат;
КонецФункции
Этот код учитывает сложности перехода через границы месяцев. Обратите внимание на логику расчета дней: когда текущий день месяца окончания меньше дня месяца начала, мы "заимствуем" дни из предыдущего месяца, используя функцию КонецМесяца. Это единственный способ корректно обработать ситуацию, когда в одном месяце 31 день, а в другом 28.
Если вы работаете в современной конфигурации (например, ERP, КА, ЗУП или Бухгалтерия 3.0), вам не нужно писать собственные функции "с нуля". В составе Библиотеки Стандартных Подсистем (БСП) уже есть готовые инструменты для решения этой задачи.
Рекомендуем обратить внимание на общий модуль ОбщегоНазначения. В нем часто присутствует функция РазностьДатВГодахМесяцахИДнях. Она реализована с учетом всех нюансов законодательства (что актуально для кадрового учета) и протестирована на тысячах внедрений.
Пример вызова функции из БСП:
Разность = ОбщегоНазначения.РазностьДатВГодахМесяцахИДнях(Объект.ДатаПриема, ТекущаяДата());
Сообщить("Стаж работы: " + Разность.Лет + " лет, " + Разность.Месяцев + " месяцев.");
Проанализируем еще несколько критических моментов, которые часто упускают из виду:
ДобавитьКДате, Месяц, Год), а не математические формулы.Дата содержит и время. Если НачалоПериода установлена на 12:00:00, а КонецПериода на 09:00:00 того же числа, то с точки зрения некоторых алгоритмов полные сутки еще не прошли. Если вам нужна точность только до дней, всегда используйте функцию НачалоДня() для обеих дат перед сравнением.Подводя итог, можно сказать: функция РАЗНОСТЬДАТ в запросах — это инструмент для быстрой оценки количества пройденных календарных границ, а не для точного вычисления интервалов времени. Для получения корректных данных в годах и месяцах всегда используйте либо сложные конструкции с ВЫБОР в запросах, либо проверенные функции из состава БСП или собственные алгоритмы, учитывающие составные части даты.