Как правильно рассчитать количество полных лет, месяцев и дней между двумя датами в 1С

Программист 1С v8.3 (Управляемые формы)
← На главную

Задача расчета временных интервалов в 1С кажется тривиальной только на первый взгляд. На практике разработчики сталкиваются с множеством нюансов: от особенностей високосных годов до специфических требований налогового или трудового законодательства. В этой статье мы подробно разберем, как вычислить разницу между датами, почему стандартное вычитание может привести к ошибкам и какие алгоритмы считаются наиболее надежными. Чтобы гарантировать стабильность системы после внесения изменений в такие алгоритмы, можно автоматизировать архивирование и тестирование баз 1С.

Почему нельзя просто вычитать даты

В системе 1С тип Дата хранит значение в секундах, прошедших с 0001 года. Если мы просто вычтем одну дату из другой, мы получим разницу в секундах. Разделив ее на 86 400 (количество секунд в сутках), мы узнаем количество дней. Однако при попытке перевести эти дни в месяцы или годы возникают проблемы, так как количество дней в месяцах варьируется (28, 29, 30 или 31), а в году может быть 365 или 366 дней.

Проанализируем ситуацию: если мы считаем стаж или срок аренды, нам важно "человеческое" понимание календарного периода. Рассмотрим основные подходы к решению этой задачи. В случаях, когда такие расчеты необходимо выполнять в закрытых сторонних системах, использование платформы для создания программных роботов позволит автоматизировать ввод данных и формирование документов без участия человека.

Способ 1. Использование запроса и функции РАЗНОСТЬДАТ

Это самый быстрый и простой способ, встроенный в платформу. Несмотря на то что некоторые разработчики опасаются использовать запросы для простых вычислений, функция РАЗНОСТЬДАТ внутри языка запросов работает достаточно эффективно. Разберем пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, ГОД) КАК ВсегоЛет,
    |    РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, МЕСЯЦ) КАК ВсегоМесяцев,
    |    РАЗНОСТЬДАТ(&ДатаНачала, &ДатаКонца, ДЕНЬ) КАК ВсегоДней";

Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаКонца", ДатаКонца);

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    // Обратите внимание: РАЗНОСТЬДАТ считает количество ГРАНИЦ периодов.
    // Если ДатаНачала = 31.12.2023, а ДатаКонца = 01.01.2024, функция вернет 1 год.
    // Для получения полных периодов нужна дополнительная логика.
КонецЕсли;

Важный нюанс: Функция РАЗНОСТЬДАТ в запросе считает количество переходов через границу периода. Если вам нужно получить именно полные календарные годы, этот метод требует ручной корректировки, проверяющей, наступил ли в конечном году день и месяц из даты начала.

Способ 2. Математический расчет через компоненты даты

Если по каким-то причинам использование запроса невозможно, мы можем вычислить разницу, используя функции встроенного языка Год(), Месяц() и День(). Проанализируем алгоритм расчета полных лет:


Функция ПолучитьПолныхЛет(ДатаНачала, ДатаКонца)
    Лет = Год(ДатаКонца) - Год(ДатаНачала);
    
    // Проверяем, наступил ли день рождения (или годовщина) в текущем году
    Если (Месяц(ДатаНачала) > Месяц(ДатаКонца)) 
        ИЛИ (Месяц(ДатаНачала) = Месяц(ДатаКонца) И День(ДатаНачала) > День(ДатаКонца)) Тогда
        Лет = Лет - 1;
    КонецЕсли;
    
    Возврат Макс(Лет, 0);
КонецФункции

Этот метод хорошо подходит для определения возраста человека, так как он точно имитирует логику "наступил ли уже этот день в текущем году".

Способ 3. Метод "Стрелы времени" (итеративное прибавление)

Выясним причину, по которой профессиональные разработчики предпочитают двигаться "от прошлого к будущему". Согласно юридическим нормам (например, ст. 6.1 НК РФ), сроки исчисляются путем прибавления периодов. В 1С для этого идеально подходит функция ДобавитьМесяц(), которая корректно обрабатывает переходы через 28, 29, 30 и 31 число.

Разберем по шагам алгоритм расчета полных месяцев и оставшихся дней:

  1. Приведем обе даты к началу дня с помощью НачалоДня(), чтобы время не влияло на результат.
  2. В цикле будем добавлять к начальной дате по одному году, пока не превысим конечную дату.
  3. Затем будем добавлять по одному месяцу к полученному значению.
  4. Остаток разницы вычислим в днях.

ДатаСтарта = НачалоДня(ДатаНачала);
ДатаФиниша = НачалоДня(ДатаКонца);

ПолныхЛет = 0;
ВременнаяДата = ДатаСтарта;

// Считаем годы
Пока ДобавитьМесяц(ВременнаяДата, 12) <= ДатаФиниша Цикл
    ВременнаяДата = ДобавитьМесяц(ВременнаяДата, 12);
    ПолныхЛет = ПолныхЛет + 1;
КонецЦикла;

// Считаем месяцы
ПолныхМесяцев = 0;
Пока ДобавитьМесяц(ВременнаяДата, 1) <= ДатаФиниша Цикл
    ВременнаяДата = ДобавитьМесяц(ВременнаяДата, 1);
    ПолныхМесяцев = ПолныхМесяцев + 1;
КонецЦикла;

// Остаток в днях
ОстатокДней = (ДатаФиниша - ВременнаяДата) / 86400;

Данный подход считается самым точным, так как он использует встроенный механизм платформы ДобавитьМесяц(), который автоматически учитывает високосные года. Например, если прибавить 1 месяц к 31 января, 1С вернет 28 (или 29) февраля. Это гарантирует корректность расчетов с точки зрения бизнес-логики.

Юридические аспекты: НК РФ и "некоммутативность"

Посмотрим на ситуацию с точки зрения законодательства. В бухгалтерском и налоговом учете крайне важно, с какого момента начинается отсчет. Существует понятие некоммутативности: результат может зависеть от того, прибавляем мы срок или вычитаем его. Чтобы исключить ошибки в учете, автоматическая сверка бухгалтерских и налоговых проводок поможет быстро обнаружить расхождения после доработки механизмов расчета периодов.

Важный момент: По правилам НК РФ, если окончание срока приходится на месяц, в котором нет соответствующего числа (например, срок истекает 31-го числа, а в феврале только 28 дней), то срок истекает в последний день этого месяца. Именно поэтому использование ДобавитьМесяц() является предпочтительным — функция работает именно по этому правилу.

Округление периодов

Иногда бизнес-логика требует специфического округления (как в Сообщении №5 форума). Если нам нужно считать только "полные календарные месяцы" (от 1-го до 30/31 числа), алгоритм будет следующим:

  1. Если ДатаНачала не является 1-м числом, мы округляем ее до начала следующего месяца.
  2. Если ДатаКонца не является последним числом месяца, мы берем конец предыдущего месяца.
  3. Рассчитываем разницу между этими двумя "выровненными" датами.

Для реализации такой логики используйте функции НачалоМесяца(), КонецМесяца() и ДобавитьМесяц() с параметром -1.

Готовые решения в БСП

Если ваша конфигурация разработана на базе Библиотеки стандартных подсистем (БСП), мы рекомендуем не изобретать велосипед, а использовать встроенные функции общего назначения. Проверьте наличие функции ОбщегоНазначения.РазностьДат. Она возвращает структуру с уже рассчитанными годами, месяцами и днями, учитывая все вышеописанные сложности.

Также полезна функция СтроковыеФункцииКлиентСервер.РазностьДатПредставление, которая не только рассчитает разницу, но и вернет красиво оформленную строку, например: "5 лет 3 месяца 12 дней", корректно склоняя существительные.

Подведем итоги

Для простого определения возраста используйте математический расчет компонентов (год, месяц, день). Для сложных финансовых или кадровых расчетов, где критична точность до дня в рамках разных месяцев, используйте итеративное прибавление через ДобавитьМесяц(). И всегда помните о функции НачалоДня(), чтобы избежать ошибок, вызванных разницей во времени в несколько секунд.

← На главную