При разработке различных учетных и расчетных механизмов в 1С программисты часто сталкиваются с необходимостью вычислить точную разницу между двумя датами в месяцах. Если разница составляет целое число месяцев, задача решается тривиально. Однако, когда даты приходятся на середину месяца, возникает логическая сложность: как правильно рассчитать дробную часть? Ведь в месяцах может быть 28, 29, 30 или 31 день, и простое деление общего количества дней на среднее число (например, на 30 или 30.43) приводит к ощутимым погрешностям и ошибкам в расчетах.
В этой статье мы подробно разберем математическую модель точного пропорционального расчета долей месяцев, рассмотрим, как этот алгоритм применяется в типовых конфигурациях 1С и в базах данных, а также напишем производительный и изящный код на встроенном языке 1С без использования медленных запросов к СУБД.
Чтобы получить математически точное значение, наиболее логично использовать пропорциональный метод расчета. Он заключается в том, что весь период между начальной и конечной датами мы делим на три составляющие:
1.0).Сложив эти три величины, мы получаем точное дробное количество месяцев. Сформулируем это в виде формулы:
Дробное число месяцев = (Дней в первом месяце / Всего дней в первом месяце) + Полные месяцы + (Дней в последнем месяце / Всего дней в последнем месяце)
Разберем классический пример. Нам нужно рассчитать период с 30 января по 25 июня любого невисокосного года:
2 / 31 ≈ 0.0645 месяца.4 полных месяца.25 / 30 ≈ 0.8333 месяца.0.0645 + 4 + 0.8333 = 4.8978 месяца.Этот метод является наиболее справедливым, так как он учитывает реальную календарную длительность каждого конкретного месяца на границах исследуемого периода.
Описанная выше методология — это не просто теоретическая абстракция. Она официально применяется в ключевых механизмах типовых конфигураций 1С.
В конфигурациях 1С:Бухгалтерия и 1С:ERP Управление предприятием этот алгоритм используется при автоматическом списании расходов будущих периодов, когда выбран способ признания расходов «По месяцам». Программа рассчитывает доли первого и последнего месяцев пропорционально календарным дням, на которые распределяются расходы, находит общую сумму долей за весь период и делит общую сумму РБП на это дробное число месяцев. Это позволяет ежемесячно списывать абсолютно точную и справедливую сумму расходов.
Другой пример — составление статистической отчетности в 1С:Зарплата и управление персоналом (например, форма 57-Т). Согласно методологии Росстата, стаж работы сотрудников для заполнения некоторых отчетов должен выводиться в годах с одним десятичным знаком. При этом неполные года пересчитываются через отношение месяцев к 12 (например, 5 месяцев — это 5 / 12 ≈ 0.4 года), а дни переводятся в доли месяцев по схожей пропорциональной системе.
При интеграции систем или анализе производительности полезно знать, как эту задачу решают СУБД. Например, в Oracle SQL и PostgreSQL (с расширением orafce) есть встроенная функция MONTHS_BETWEEN(date1, date2). Однако ее логика отличается от бухгалтерской:
(date1 - date2) / 31.Для бизнес-расчетов в РФ такой подход СУБД часто не подходит, поскольку методологи требуют учета реального количества дней в календарных месяцах (28, 29, 30 или 31 день).
Часто программисты пытаются решить эту задачу "в лоб", обращаясь с запросом к базе данных и используя встроенную функцию СУБД РАЗНОСТЬДАТ. Рассмотрим пример такого кода:
// Пример неоптимального кода с запросом и ручным перебором месяцев
Функция ПолучитьКоличествоМесяцевМетодЗапроса(НачалоПериода, КонецПериода)
Запрос = Новый Запрос("ВЫБРАТЬ РАЗНОСТЬДАТ(&НачалоПериода, &КонецПериода, МЕСЯЦ) КАК КоличествоМесяцев");
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
КоличествоМесяцев = Выборка.КоличествоМесяцев;
НомерНачальногоМесяца = Месяц(НачалоПериода);
// Дальше следует огромная конструкция Если...ИначеЕсли для каждого месяца...
// с ручным вычислением високосных лет
КонецФункции
Почему этот код плохой и его не стоит использовать в реальных проектах?
Если...ИначеЕсли загромождает код и повышает вероятность опечатки.Для реализации быстрого и точного расчета нам понадобятся всего несколько базовых функций работы с датами встроенного языка 1С:
НачалоМесяца() и КонецМесяца() — для определения границ календарных месяцев.День() — для получения числа дней в месяце (если применить к дате конца месяца, мы мгновенно узнаем общее количество дней в этом месяце: например, День(КонецМесяца(Дата)) вернет нам 28, 29, 30 или 31).86400 (количество секунд в сутках).Разберем по шагам готовый и оптимизированный алгоритм, который не обращается к базе данных и работает мгновенно:
Функция ПолучитьКоличествоМесяцев(Знач ДатаНачала, Знач ДатаОкончания) Экспорт
// Приводим даты к началу/концу дня для корректности расчетов
ДатаНачала = НачалоДня(ДатаНачала);
ДатаОкончания = КонецДня(ДатаОкончания);
Если ДатаНачала > ДатаОкончания Тогда
Возврат 0;
КонецЕсли;
// 1. Если обе даты находятся в пределах одного календарного месяца
Если НачалоМесяца(ДатаНачала) = НачалоМесяца(ДатаОкончания) Тогда
ВсегоДнейВМесяце = День(КонецМесяца(ДатаНачала));
// Прибавляем 1, чтобы учесть границы периода включительно
ДнейПериода = (ДатаОкончания - ДатаНачала + 1) / 86400;
Возврат ДнейПериода / ВсегоДнейВМесяце;
КонецЕсли;
// 2. Если период охватывает разные месяцы
// Доля первого (неполного) месяца
ВсегоДнейВПервом = День(КонецМесяца(ДатаНачала));
ДнейВПервом = (КонецМесяца(ДатаНачала) - ДатаНачала + 1) / 86400;
ДоляПервогоМесяца = ДнейВПервом / ВсегоДнейВПервом;
// Доля последнего (неполного) месяца
ВсегоДнейВПоследнем = День(КонецМесяца(ДатаОкончания));
ДнейВПоследнем = (ДатаОкончания - НачалоМесяца(ДатаОкончания) + 1) / 86400;
ДоляПоследнегоМесяца = ДнейВПоследнем / ВсегоДнейВПоследнем;
// Количество полных месяцев между ними
ПолныхМесяцев = (Год(ДатаОкончания) - Год(ДатаНачала)) * 12 + (Месяц(ДатаОкончания) - Месяц(ДатаНачала)) - 1;
Возврат ДоляПервогоМесяца + ПолныхМесяцев + ДоляПоследнегоМесяца;
КонецФункции
КонецМесяца() для февраля високосного года вернет дату 29 февраля, и функция День() корректно вернет 29 дней.Используйте этот алгоритм в своих проектах, чтобы гарантировать высокую точность финансовых и статистических расчетов в вашей системе 1С!