Задача расчета временных интервалов в 1С кажется тривиальной только на первый взгляд. На практике разработчики сталкиваются с множеством нюансов: от особенностей високосных годов до специфических требований налогового или трудового законодательства. В этой статье мы подробно разберем, как вычислить разницу между датами, почему стандартное вычитание может привести к ошибкам и какие алгоритмы считаются наиболее надежными. Чтобы гарантировать стабильность системы после внесения изменений в такие алгоритмы, можно автоматизировать архивирование и тестирование баз 1С.
В системе 1С тип Дата хранит значение в секундах, прошедших с 0001 года. Если мы просто вычтем одну дату из другой, мы получим разницу в секундах. Разделив ее на 86 400 (количество секунд в сутках), мы узнаем количество дней. Однако при попытке перевести эти дни в месяцы или годы возникают проблемы, так как количество дней в месяцах варьируется (28, 29, 30 или 31), а в году может быть 365 или 366 дней.
Проанализируем ситуацию: если мы считаем стаж или срок аренды, нам важно "человеческое" понимание календарного периода. Рассмотрим основные подходы к решению этой задачи. В случаях, когда такие расчеты необходимо выполнять в закрытых сторонних системах, использование платформы для создания программных роботов позволит автоматизировать ввод данных и формирование документов без участия человека.
Это самый быстрый и простой способ, встроенный в платформу. Несмотря на то что некоторые разработчики опасаются использовать запросы для простых вычислений, функция РАЗНОСТЬДАТ внутри языка запросов работает достаточно эффективно. Разберем пример кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, ГОД) КАК ВсегоЛет,
| РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, МЕСЯЦ) КАК ВсегоМесяцев,
| РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, ДЕНЬ) КАК ВсегоДней";
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаКонца", ДатаКонца);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
// Обратите внимание: РАЗНОСТЬДАТ считает количество ГРАНИЦ периодов.
// Если ДатаНачала = 31.12.2023, а ДатаКонца = 01.01.2024, функция вернет 1 год.
// Для получения полных периодов нужна дополнительная логика.
КонецЕсли;
Важный нюанс: Функция РАЗНОСТЬДАТ в запросе считает количество переходов через границу периода. Если вам нужно получить именно полные календарные годы, этот метод требует ручной корректировки, проверяющей, наступил ли в конечном году день и месяц из даты начала.
Если по каким-то причинам использование запроса невозможно, мы можем вычислить разницу, используя функции встроенного языка Год(), Месяц() и День(). Проанализируем алгоритм расчета полных лет:
Функция ПолучитьПолныхЛет(ДатаНачала, ДатаКонца)
Лет = Год(ДатаКонца) - Год(ДатаНачала);
// Проверяем, наступил ли день рождения (или годовщина) в текущем году
Если (Месяц(ДатаНачала) > Месяц(ДатаКонца))
ИЛИ (Месяц(ДатаНачала) = Месяц(ДатаКонца) И День(ДатаНачала) > День(ДатаКонца)) Тогда
Лет = Лет - 1;
КонецЕсли;
Возврат Макс(Лет, 0);
КонецФункции
Этот метод хорошо подходит для определения возраста человека, так как он точно имитирует логику "наступил ли уже этот день в текущем году".
Выясним причину, по которой профессиональные разработчики предпочитают двигаться "от прошлого к будущему". Согласно юридическим нормам (например, ст. 6.1 НК РФ), сроки исчисляются путем прибавления периодов. В 1С для этого идеально подходит функция ДобавитьМесяц(), которая корректно обрабатывает переходы через 28, 29, 30 и 31 число.
Разберем по шагам алгоритм расчета полных месяцев и оставшихся дней:
НачалоДня(), чтобы время не влияло на результат.
ДатаСтарта = НачалоДня(ДатаНачала);
ДатаФиниша = НачалоДня(ДатаКонца);
ПолныхЛет = 0;
ВременнаяДата = ДатаСтарта;
// Считаем годы
Пока ДобавитьМесяц(ВременнаяДата, 12) <= ДатаФиниша Цикл
ВременнаяДата = ДобавитьМесяц(ВременнаяДата, 12);
ПолныхЛет = ПолныхЛет + 1;
КонецЦикла;
// Считаем месяцы
ПолныхМесяцев = 0;
Пока ДобавитьМесяц(ВременнаяДата, 1) <= ДатаФиниша Цикл
ВременнаяДата = ДобавитьМесяц(ВременнаяДата, 1);
ПолныхМесяцев = ПолныхМесяцев + 1;
КонецЦикла;
// Остаток в днях
ОстатокДней = (ДатаФиниша - ВременнаяДата) / 86400;
Данный подход считается самым точным, так как он использует встроенный механизм платформы ДобавитьМесяц(), который автоматически учитывает високосные года. Например, если прибавить 1 месяц к 31 января, 1С вернет 28 (или 29) февраля. Это гарантирует корректность расчетов с точки зрения бизнес-логики.
Посмотрим на ситуацию с точки зрения законодательства. В бухгалтерском и налоговом учете крайне важно, с какого момента начинается отсчет. Существует понятие некоммутативности: результат может зависеть от того, прибавляем мы срок или вычитаем его. Чтобы исключить ошибки в учете, автоматическая сверка бухгалтерских и налоговых проводок поможет быстро обнаружить расхождения после доработки механизмов расчета периодов.
Важный момент: По правилам НК РФ, если окончание срока приходится на месяц, в котором нет соответствующего числа (например, срок истекает 31-го числа, а в феврале только 28 дней), то срок истекает в последний день этого месяца. Именно поэтому использование ДобавитьМесяц() является предпочтительным — функция работает именно по этому правилу.
Иногда бизнес-логика требует специфического округления (как в Сообщении №5 форума). Если нам нужно считать только "полные календарные месяцы" (от 1-го до 30/31 числа), алгоритм будет следующим:
ДатаНачала не является 1-м числом, мы округляем ее до начала следующего месяца.ДатаКонца не является последним числом месяца, мы берем конец предыдущего месяца.Для реализации такой логики используйте функции НачалоМесяца(), КонецМесяца() и ДобавитьМесяц() с параметром -1.
Если ваша конфигурация разработана на базе Библиотеки стандартных подсистем (БСП), мы рекомендуем не изобретать велосипед, а использовать встроенные функции общего назначения. Проверьте наличие функции ОбщегоНазначения.РазностьДат. Она возвращает структуру с уже рассчитанными годами, месяцами и днями, учитывая все вышеописанные сложности.
Также полезна функция СтроковыеФункцииКлиентСервер.РазностьДатПредставление, которая не только рассчитает разницу, но и вернет красиво оформленную строку, например: "5 лет 3 месяца 12 дней", корректно склоняя существительные.
Для простого определения возраста используйте математический расчет компонентов (год, месяц, день). Для сложных финансовых или кадровых расчетов, где критична точность до дня в рамках разных месяцев, используйте итеративное прибавление через ДобавитьМесяц(). И всегда помните о функции НачалоДня(), чтобы избежать ошибок, вызванных разницей во времени в несколько секунд.