В разработке на платформе 1С часто возникает задача посчитать разницу между двумя датами. Если с днями или секундами все просто, то расчет разницы в месяцах может вызвать затруднения, особенно при переходе через год. Попытка вычесть номер одного месяца из другого приводит к неверным результатам. Для поиска ошибок и контроля качества данных после внедрения новых расчетов применяется автоматическое сравнение бухгалтерских и налоговых проводок между базами.
Проанализируем ситуацию. Автор столкнулся с проблемой, когда при вычислении разницы между 15.01.2025 и 04.09.2024 его код выдавал "-8".
МояПеременная = Месяц(Выборка.ДатаПоказания) - Месяц(Выборка.ДатаПредыдущегоПоказания);
// Результат: 1 - 9 = -8
Выясним причину этой ошибки. Функция Месяц() возвращает порядковый номер месяца в году (от 1 до 12), а не абсолютное значение, которое можно было бы вычитать. Поэтому такой подход в корне неверен, как только даты попадают в разные годы.
Рассмотрим несколько надежных способов, как корректно посчитать количество месяцев между датами.
Это самый универсальный способ, который работает непосредственно во встроенном языке 1С без использования запросов. Идея заключается в том, чтобы перевести обе даты в "абсолютное" количество месяцев с начала летосчисления и затем найти разницу.
Формула выглядит так: общее количество месяцев для одной даты вычисляется как Год * 12 + Месяц. Вычтя одно значение из другого, мы получим искомую разницу.
Посмотрим на пример из темы:
Реализуем это на встроенном языке 1С:
ДатаНачала = Дата('20240904');
ДатаКонца = Дата('20250115');
РазницаМесяцев = (Год(ДатаКонца) - Год(ДатаНачала)) * 12 + Месяц(ДатаКонца) - Месяц(ДатаНачала);
// Рассчитаем: (2025 - 2024) * 12 + 1 - 9 = 1 * 12 - 8 = 4
// Результат: 4
Этот код вернет 4. Результат означает, что период между датами затрагивает 4 календарных месяца: сентябрь, октябрь, ноябрь, декабрь и январь. Но будьте внимательны: этот способ не учитывает дни и не скажет, сколько полных месяцев прошло.
Часто в задачах требуется узнать, сколько именно полных месяцев прошло между датами. Например, между 30 сентября и 15 января прошло три полных месяца (октябрь, ноябрь, декабрь). Чтобы учесть это, нужно доработать предыдущий алгоритм. Добавим простое условие: если день конечной даты меньше дня начальной, значит, последний месяц не завершился, и его нужно вычесть из результата.
Оформим это в виде удобной функции:
Функция ПолныхМесяцевМеждуДатами(ДатаНачала, ДатаКонца)
// Проверяем, чтобы даты не были пустыми
Если ДатаНачала = '00010101' ИЛИ ДатаКонца = '00010101' Тогда
Возврат 0;
КонецЕсли;
РазницаМесяцев = (Год(ДатаКонца) - Год(ДатаНачала)) * 12 + Месяц(ДатаКонца) - Месяц(ДатаНачала);
// Если день конечной даты меньше, чем день начальной, то последний месяц неполный
Если День(ДатаКонца) < День(ДатаНачала) Тогда
РазницаМесяцев = РазницаМесяцев - 1;
КонецЕсли;
Возврат РазницаМесяцев;
КонецФункции
// Пример использования
Разница = ПолныхМесяцевМеждуДатами(Дата('20240904'), Дата('20250115')); // Вернет 4
Разница2 = ПолныхМесяцевМеждуДатами(Дата('20240904'), Дата('20250103')); // Вернет 3
В первом примере результат 4, так как с 4 сентября по 4 января прошло 4 полных месяца, а у нас 15 января. Во втором примере результат 3, так как 3 января меньше 4 сентября, и неполный январь отбрасывается.
Если вы получаете даты с помощью запроса, то самым правильным, производительным и рекомендуемым методом является использование встроенной функции РазностьДат(). Платформа 1С сама выполнит все необходимые вычисления корректно. Запросы с расчетом временных интервалов часто требуются при настройке синхронизации, которую можно реализовать через автоматический обмен данными между базами 1С по расписанию.
Синтаксис функции:
РазностьДат(ДатаНачала, ДатаОкончания, ТипРазности)
Где ТипРазности может быть "ГОД", "КВАРТАЛ", "МЕСЯЦ", "ДЕНЬ" и т.д.
Посмотрим на пример запроса. Допустим, у нас есть РегистрСведений.ПоказанияСчетчиков, и нам нужно получить разницу в месяцах между текущим и предыдущим показанием.
ВЫБРАТЬ
ПоказанияСчетчиков.Ссылка,
ПоказанияСчетчиков.ДатаПоказания,
ПоказанияСчетчиков.ДатаПредыдущегоПоказания,
РАЗНОСТЬДАТ(
ПоказанияСчетчиков.ДатаПредыдущегоПоказания,
ПоказанияСчетчиков.ДатаПоказания,
МЕСЯЦ
) КАК КоличествоМесяцев
ИЗ
РегистрСведений.ПоказанияСчетчиков КАК ПоказанияСчетчиков
Преимущества этого подхода:
ЕСТЬNULL.
// Пример с обработкой пустой начальной даты
РАЗНОСТЬДАТ(
ЕСТЬNULL(ПоказанияСчетчиков.ДатаПредыдущегоПоказания, НАЧАЛОПЕРИОДА(ПоказанияСчетчиков.ДатаПоказания, МЕСЯЦ)),
ПоказанияСчетчиков.ДатаПоказания,
МЕСЯЦ
)
На форуме был предложен вариант, основанный на вычислении разницы в секундах и делении на среднее количество секунд в месяце.
// Разница в секундах
РазницаСекунд = ДатаКонца - ДатаНачала;
// Переводим в месяцы, принимая в месяце 30 дней
РазницаМесяцев = РазницаСекунд / 86400 / 30;
Этот метод категорически не рекомендуется для точных вычислений. Его главная проблема — погрешность. Месяцы имеют разную продолжительность (от 28 до 31 дня), и использование среднего значения "30" приведет к неверным результатам, особенно на больших интервалах. Результат такого вычисления будет дробным числом (в примере автора 4,43), что может потребовать дополнительного округления и усложнит логику.
Такой подход может быть оправдан только в тех редких случаях, когда требуется очень грубая оценка и точность не имеет никакого значения.
Мы рассмотрели три способа расчета разницы дат в месяцах. Сделаем выводы и сформулируем рекомендации:
<strong>РазностьДат()</strong>. Это самый надежный, быстрый и правильный способ.<strong>(Год(Кон) - Год(Нач)) * 12 + Месяц(Кон) - Месяц(Нач)</strong>.Выбирая правильный метод, вы обеспечите точность вычислений и сделаете ваш код более надежным и понятным. Для технического контроля системы и проверки её стабильности после внесения подобных изменений в конфигурацию применяется автоматическое архивирование, обновление и тестирование 1С по расписанию.