Одной из классических задач в разработке на платформе 1С является вычисление точного периода между двумя датами. Казалось бы, задача тривиальна, однако на практике программисты сталкиваются с тем, что понятие «месяц» имеет переменную длительность (28, 29, 30 или 31 день). Простое деление разницы в секундах на фиксированное число 30 зачастую дает погрешность, неприемлемую для кадрового или финансового учета. В этой статье мы подробно разберем несколько проверенных способов реализации этой функции, проанализируем типовые алгоритмы и выясним, как правильно обрабатывать календарные особенности.
Рассмотрим ситуацию: нам нужно узнать разницу между 31 января и 28 февраля. Если использовать стандартную математику и делить общее количество дней на 30, мы получим неполный месяц. Однако с точки зрения календаря — это ровно один месяц. Еще сложнее ситуация с високосными годами. Именно поэтому встроенная функция РАЗНОСТЬДАТ в языке запросов 1С работает по принципу пересечения границ календарных периодов, что не всегда подходит для бизнес-логики. Давайте разберем, какие инструменты у нас есть в арсенале.
Одним из самых надежных и проверенных временем способов является алгоритм, который ранее использовался в конфигурации «Управление производственным предприятием». Этот метод последовательно вычитает из конечной даты целые годы, затем месяцы, а остаток считает днями. Рассмотрим этот код подробнее и адаптируем его под современные требования.
Для реализации нам потребуется процедура, которая принимает две даты и возвращает результат через параметры. Мы также можем оформить это в виде функции, возвращающей Структура.
Процедура РазобратьРазностьДат(Дата1, Дата2, Лет = 0, Месяцев = 0, Дней = 0) Экспорт
Лет = 0; Месяцев = 0; Дней = 0;
Если Дата1 > Дата2 Тогда
ВременнаяДата = Дата1;
// Если день месяца у более поздней даты меньше, чем у ранней,
// значит, текущий месяц еще не завершился полностью.
Если День(ВременнаяДата) < День(Дата2) Тогда
Дней = ВременнаяДата - ДобавитьМесяц(ВременнаяДата, -1);
ВременнаяДата = ДобавитьМесяц(ВременнаяДата, -1);
КонецЕсли;
// Считаем целые месяцы и годы
Пока ДобавитьМесяц(Дата2, 12) <= ВременнаяДата Цикл
Лет = Лет + 1;
Дата2 = ДобавитьМесяц(Дата2, 12);
КонецЦикла;
Пока ДобавитьМесяц(Дата2, 1) <= ВременнаяДата Цикл
Месяцев = Месяцев + 1;
Дата2 = ДобавитьМесяц(Дата2, 1);
КонецЦикла;
// Остаток в днях
Дней = Дней + (ВременнаяДата - Дата2) / 86400;
КонецЕсли;
КонецПроцедуры
Важный нюанс: при работе с этим кодом следует использовать НачалоДня() для обеих дат, если время не играет роли в расчетах. В противном случае наличие разницы в несколько часов может привести к тому, что целый день не будет учтен.
В конфигурации «Зарплата и управление персоналом» расчет периодов критически важен для стажа. Рассмотрим, как разработчики фирмы 1С предлагают вычислять количество дней в периоде. Проанализируем пример функции ДнейВПериоде, которая учитывает включение границ дат (начало и окончание).
Функция ДнейВПериоде(Знач ДатаНачала, Знач ДатаОкончания) Экспорт
// Возвращаем количество суток между датами, включая дату окончания
Если НачалоДня(ДатаНачала) > НачалоДня(ДатаОкончания) Тогда
Возврат 0;
Иначе
// Добавляем 1 к разнице, чтобы учесть последний день включительно
Возврат (КонецДня(ДатаОкончания) + 1 - НачалоДня(ДатаНачала)) / (24 * 60 * 60);
КонецЕсли;
КонецФункции
Если же ваша задача требует именно строкового представления разности (например, «2 месяца 5 дней»), рекомендуем заглянуть в модуль ОбщегоНазначения или СтроковыеФункцииКлиентСервер вашей конфигурации. В БСП часто встречаются функции, которые уже умеют склонять слова «день», «месяц», «год» в зависимости от числа.
Иногда требуется получить разность дат сразу в выборке данных, не прибегая к перебору в цикле на встроенном языке. В этом случае мы можем использовать конструкцию ВЫБОР и функцию РАЗНОСТЬДАТ. Однако помните, что РАЗНОСТЬДАТ(..., МЕСЯЦ) возвращает количество переходов через границу месяца.
Рассмотрим пример логики, которую можно реализовать в запросе для более точного расчета:
ВЫБРАТЬ
ВЫБОР
КОГДА РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, МЕСЯЦ) >= 1
ТОГДА РАЗНОСТЬДАТ(&ДатаНачала, КОНЕЦПЕРИОДА(&ДатаНачала, МЕСЯЦ), ДЕНЬ)
ИНАЧЕ РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, ДЕНЬ)
КОНЕЦ КАК ДниВПервомМесяце,
РАЗНОСТЬДАТ(ДОБАВИТЬКДАТЕ(КОНЕЦПЕРИОДА(&ДатаНачала, МЕСЯЦ), СЕКУНДА, 1),
НАЧАЛОПЕРИОДА(&ДатаКонца, МЕСЯЦ), МЕСЯЦ) КАК ПолныхМесяцев
Посмотрим на ситуацию критически: этот метод в запросе достаточно громоздкий. Выясним причину: в запросах нет прямого доступа к календарной логике «длины текущего месяца» без соединения с производственным календарем. Поэтому для сложных расчетов лучше вычислять разницу на стороне сервера 1С в коде модуля.
Проанализируем ситуацию с ДобавитьМесяц. Это ключевая функция платформы 1С. Она работает по следующему правилу: если мы добавляем месяц к 31 августа, мы получим 30 сентября (последний день месяца). Если добавим еще месяц — получим 31 октября. Платформа старается сохранять число месяца, но если в целевом месяце такого числа нет, она устанавливает последний день.
Рекомендации для разработчиков:
(Месяцы * 30) + Дни.НачалоДня() перед любыми вычислениями, чтобы избежать ошибок, связанных с разницей в секундах, если время в датах заполнено произвольно.Выяснив все нюансы, мы видим, что универсального «идеального» решения не существует, так как оно зависит от контекста задачи. Однако использование кода из УПП (Решение 1) покрывает 90% бизнес-задач, связанных с человекочитаемым представлением разности дат.