Как вычислить разность дат в годах, месяцах и днях с помощью запроса 1С

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

В практике разработчика 1С часто возникает задача определить разницу между двумя датами в «человекочитаемом» виде: сколько лет, месяцев и дней прошло между событиями. Типовым примером является расчет трудового стажа сотрудника. В то время как на встроенном языке эта задача решается относительно просто, реализация аналогичной логики непосредственно в запросе (например, для использования в СКД или сложных выборках) требует серьезного подхода. Рассмотрим, как можно реализовать такой алгоритм максимально эффективно и компактно.

Постановка задачи и базовые принципы

Для начала проанализируем, почему нельзя ограничиться простой функцией РАЗНОСТЬДАТ. Эта функция в 1С умеет возвращать разность только в одной единице измерения (только в днях, только в месяцах или только в годах). Суммирование этих результатов не даст корректного стажа, так как месяцы имеют разную продолжительность, а високосные годы вносят свои коррективы. Наша цель — получить три отдельных числовых значения: Лет, Месяцев и Дней.

Разберем логику расчета «от общего к частному»:

  1. Расчет лет: Вычитаем год начала из года окончания. Если месяц или день окончания меньше месяца или дня начала, уменьшаем результат на 1.
  2. Расчет месяцев: Определяем разницу в месяцах внутри года. Если день окончания меньше дня начала, также корректируем результат.
  3. Расчет дней: Самая сложная часть, где нужно учитывать количество дней в предыдущем месяце от даты окончания.

Реализация алгоритма в одном запросе

Рассмотрим вариант запроса, который позволяет вычислить все три составляющие. Представим, что у нас есть временная таблица ВТ_Периоды с полями ДатаН (дата начала) и ДатаК (дата окончания). Проанализируем следующий код:


ВЫБРАТЬ
    ВТ_Периоды.ДатаН КАК ДатаН,
    ВТ_Периоды.ДатаК КАК ДатаК,
    // Расчет полных лет
    ВЫБОР
        КОГДА ВТ_Периоды.ДатаК < ВТ_Периоды.ДатаН
            ТОГДА 0
        ИНАЧЕ ГОД(ВТ_Периоды.ДатаК) - ГОД(ВТ_Периоды.ДатаН) - 
            ВЫБОР
                КОГДА МЕСЯЦ(ВТ_Периоды.ДатаК) < МЕСЯЦ(ВТ_Периоды.ДатаН)
                    ТОГДА 1
                КОГДА МЕСЯЦ(ВТ_Периоды.ДатаК) > МЕСЯЦ(ВТ_Периоды.ДатаН)
                    ТОГДА 0
                ИНАЧЕ ВЫБОР
                    КОГДА ДЕНЬ(ВТ_Периоды.ДатаК) < ДЕНЬ(ВТ_Периоды.ДатаН)
                        ТОГДА 1
                    ИНАЧЕ 0
                КОНЕЦ
            КОНЕЦ
    КОНЕЦ КАК Лет,

    // Расчет месяцев
    ВЫБОР
        КОГДА ВТ_Периоды.ДатаК < ВТ_Периоды.ДатаН
            ТОГДА 0
        ИНАЧЕ ВЫБОР
            КОГДА МЕСЯЦ(ВТ_Периоды.ДатаК) > МЕСЯЦ(ВТ_Периоды.ДатаН)
                ТОГДА 0
            КОГДА МЕСЯЦ(ВТ_Периоды.ДатаК) = МЕСЯЦ(ВТ_Периоды.ДатаН) И ДЕНЬ(ВТ_Периоды.ДатаК) >= ДЕНЬ(ВТ_Периоды.ДатаН)
                ТОГДА 0
            ИНАЧЕ 12
        КОНЕЦ + (МЕСЯЦ(ВТ_Периоды.ДатаК) - МЕСЯЦ(ВТ_Периоды.ДатаН)) - 
            ВЫБОР
                КОГДА ДЕНЬ(ВТ_Периоды.ДатаК) < ДЕНЬ(ВТ_Периоды.ДатаН)
                    ТОГДА 1
                ИНАЧЕ 0
            КОНЕЦ
    КОНЕЦ КАК Месяцев,

    // Расчет дней
    ВЫБОР
        КОГДА ВТ_Периоды.ДатаК < ВТ_Периоды.ДатаН
            ТОГДА 0
        ИНАЧЕ ВЫБОР
            КОГДА ДЕНЬ(ВТ_Периоды.ДатаК) >= ДЕНЬ(ВТ_Периоды.ДатаН)
                ТОГДА ДЕНЬ(ВТ_Периоды.ДатаК) - ДЕНЬ(ВТ_Периоды.ДатаН) + 1
            ИНАЧЕ ВЫБОР
                КОГДА ДЕНЬ(КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(ВТ_Периоды.ДатаК, МЕСЯЦ, -1), МЕСЯЦ)) < ДЕНЬ(ВТ_Периоды.ДатаН)
                    ТОГДА ДЕНЬ(ВТ_Периоды.ДатаК)
                ИНАЧЕ ДЕНЬ(КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(ВТ_Периоды.ДатаК, МЕСЯЦ, -1), МЕСЯЦ)) - ДЕНЬ(ВТ_Периоды.ДатаН) + ДЕНЬ(ВТ_Периоды.ДатаК)
            КОНЕЦ
        КОНЕЦ
    КОНЕЦ КАК Дней
ИЗ
    ВТ_Периоды КАК ВТ_Периоды

Разберем по шагам ключевые моменты этого запроса. Обратите внимание на блок расчета дней. Чтобы корректно вычислить остаток дней, когда день окончания меньше дня начала, мы используем конструкцию ДОБАВИТЬКДАТЕ(ВТ_Периоды.ДатаК, МЕСЯЦ, -1). Это позволяет нам «отступить» на один месяц назад и узнать, сколько дней было в том месяце, чтобы правильно вычислить «хвост» стажа.

Учет границ периодов (включительность дат)

Важным моментом в расчетах 1С является то, как мы трактуем дату окончания. В кадровом учете дата окончания периода обычно включается в расчет. То есть период с 1 сентября по 1 сентября — это 1 день стажа. В приведенном выше коде это реализовано с помощью добавления единицы: ДЕНЬ(ВТ_Периоды.ДатаК) - ДЕНЬ(ВТ_Периоды.ДатаН) + 1.

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

Альтернативный способ расчета дней

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

ВЫРАЗИТЬ(РАЗНОСТЬДАТ(ДатаН, ДатаК, СЕКУНДА) / 86400 КАК Число(15, 0))

Здесь 86400 — это количество секунд в сутках (24 * 60 * 60). Этот метод полезен, если вы работаете с датами, содержащими время, и вам нужна чистая разница в сутках. Однако для расчета именно «календарного стажа» (где важны именно смены дат, а не количество прошедших секунд) лучше использовать функции ДЕНЬ(), МЕСЯЦ() и ГОД(), как в основном примере.

Проблема февраля и високосных лет

Особого внимания заслуживает конец февраля. Например, период с 31.01 по 28.02 (в невисокосный год). Является ли это полным месяцем? С точки зрения математики — нет, ведь прошло всего 28 дней. С точки зрения кадрового учета — часто да, так как прошел «февраль».

Рассмотрим, как ведет себя функция ДОБАВИТЬКДАТЕ в 1С: если к 31 января добавить 1 месяц, система вернет 28 (или 29) февраля. Это встроенная логика платформы, которая упрощает работу с концами месяцев. В нашем запросе мы используем КОНЕЦПЕРИОДА, что помогает корректно обрабатывать такие переходы, проверяя последний день месяца.

Практические рекомендации по использованию

При использовании этого алгоритма в реальных задачах, придерживайтесь следующих правил:

  1. Использование в СКД: Если вы строите отчет в Системе компоновки данных, этот сложный запрос можно вынести в отдельную временную таблицу или использовать вычисляемые поля. Но помните, что вычисляемые поля СКД на базе функций общего модуля (например, ОбщегоНазначения.РазностьДаТВГодахМесяцахИДнях) будут работать медленнее на больших объемах данных, чем чистый запрос.
  2. Проверка на пустые даты: Всегда добавляйте ВЫБОР КОГДА ДатаН = ДАТАВРЕМЯ(1, 1, 1), чтобы избежать некорректных результатов при незаполненных данных.
  3. Оптимизация производительности: Функции ГОД(), МЕСЯЦ() и ДЕНЬ() выполняются на стороне СУБД достаточно быстро. Тем не менее, если в таблице миллионы строк, старайтесь минимизировать количество вложенных ВЫБОР.

Таким образом, мы получили работающий инструмент для вычисления стажа непосредственно в запросе. Несмотря на кажущуюся громоздкость, такой подход обеспечивает высокую точность и независимость от программного кода на встроенном языке 1С, что особенно критично для сложных аналитических отчетов.

← На главную