Как в 1С определить ближайший день рождения человека?

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

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

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

Особенности работы с датами и 29 февраля

Прежде чем перейти к алгоритмам, проанализируем критические нюансы платформы 1С:

  1. Ошибка при ручной сборке даты: Вызов Дата(2023, 2, 29) в невисокосном году вызовет аварийное завершение программы с исключением «Преобразование значения к типу Дата не может быть выполнено». Использовать конструктор даты с тремя числами для 29 февраля без дополнительных проверок нельзя.
  2. Поведение функции ДобавитьМесяц(): Встроенная функция ДобавитьМесяц(ИсходнаяДата, Смещение) корректно нормализует границы месяцев. Если исходная дата равна 29.02.2020, то при смещении на 12 месяцев платформа корректно вернет 28.02.2021. Важно: смещение всегда нужно рассчитывать строго от исходной даты рождения, иначе при последовательном прибавлении лет високосная дата навсегда останется 28 февраля.
  3. Учет времени: При вычитании дат результат возвращается в секундах. Чтобы избежать погрешности из-за разницы часов и минут, всегда приводим даты к началу дня с помощью НачалоДня().

Вариант 1. Оптимальный алгоритм на встроенном языке (через расчет полных лет)

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

Разберем по шагам реализацию данного метода:

  1. Приводим даты к началу дня.
  2. Вычисляем количество полных исполнившихся лет на текущий момент.
  3. Получаем дату последнего дня рождения через добавление КоличествоЛет * 12 месяцев.
  4. Получаем дату следующего дня рождения через добавление (КоличествоЛет + 1) * 12 месяцев.
  5. Сравниваем модуль разницы между этими датами и текущим днем.

Посмотрим на пример функции:


Функция ПолучитьБлижайшийДеньРождения(Знач ДатаРождения, Знач ТекущаяДата = Неопределено) Экспорт
    Если Не ЗначениеЗаполнено(ДатаРождения) Тогда
        Возврат '00010101';
    КонецЕсли;
    
    Если ТекущаяДата = Неопределено Тогда
        ТекущаяДата = ТекущаяДата();
    КонецЕсли;
    
    ТекДата = НачалоДня(ТекущаяДата);
    ДР = НачалоДня(ДатаРождения);
    
    // 1. Вычисляем количество полных лет
    КоличествоЛет = Год(ТекДата) - Год(ДР);
    Если (Месяц(ДР) * 100 + День(ДР)) > (Месяц(ТекДата) * 100 + День(ТекДата)) Тогда
        КоличествоЛет = КоличествоЛет - 1;
    КонецЕсли;
    
    // 2. Получаем две контрольные точки: прошедший ДР и будущий ДР
    ПрошедшийДР = ДобавитьМесяц(ДР, КоличествоЛет * 12);
    БудущийДР   = ДобавитьМесяц(ДР, (КоличествоЛет + 1) * 12);
    
    // 3. Вычисляем расстояния в секундах
    ИнтервалПрошедший = ТекДата - ПрошедшийДР;
    ИнтервалБудущий   = БудущийДР - ТекДата;
    
    // 4. Возвращаем дату с минимальным интервалом
    Если ИнтервалПрошедший <= ИнтервалБудущий Тогда
        Возврат ПрошедшийДР;
    Иначе
        Возврат БудущийДР;
    КонецЕсли;
КонецФункции

Преимущества такого решения:

  1. Полное отсутствие сбоев на датах 29 февраля.
  2. Сравнение всего двух дат вместо трех.
  3. Минимальное количество операций и высокая скорость работы.

Вариант 2. Быстрый циклический сдвиг через разницу

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


Функция НайтиБлижайшийДР_Сдвигом(Знач ДатаРождения, Знач ТекДата) Экспорт
    ТекДата = НачалоДня(ТекДата);
    ДР = НачалоДня(ДатаРождения);
    
    // Вычисляем смещение лет до текущего календарного года
    Смещение = Год(ТекДата) - Год(ДР);
    
    ДР_Тек = ДобавитьМесяц(ДР, Смещение * 12);
    ДР_Пред = ДобавитьМесяц(ДР, (Смещение - 1) * 12);
    ДР_След = ДобавитьМесяц(ДР, (Смещение + 1) * 12);
    
    РазницаТек  = Макс(ТекДата - ДР_Тек, ДР_Тек - ТекДата);
    РазницаПред = Макс(ТекДата - ДР_Пред, ДР_Пред - ТекДата);
    РазницаСлед = Макс(ТекДата - ДР_След, ДР_След - ТекДата);
    
    МинРазница = Мин(РазницаТек, РазницаПред, РазницаСлед);
    
    Если МинРазница = РазницаТек Тогда
        Возврат ДР_Тек;
    ИначеЕсли МинРазница = РазницаПред Тогда
        Возврат ДР_Пред;
    Иначе
        Возврат ДР_След;
    КонецЕсли;
КонецФункции

Вариант 3. Определение ближайшего дня рождения в запросе 1С

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

Разберем логику запроса:

  1. Присоединяем фиксированную таблицу смещений лет (-1, 0, 1 год).
  2. Формируем варианты дат дня рождения для каждого смещения.
  3. Вычисляем абсолютную разность в днях между полученными датами и текущим периодом.
  4. С помощью вложенного запроса или соединения отбираем запись с минимальной разницей для каждого физического лица.

ВЫБРАТЬ
    Сотрудники.Ссылка КАК Сотрудник,
    Сотрудники.ДатаРождения КАК ДатаРождения,
    ДОБАВИТЬКДАТЕ(
        Сотрудники.ДатаРождения,
        ГОД,
        ГОД(&Период) - ГОД(Сотрудники.ДатаРождения) + Смещения.Коэффициент
    ) КАК ВариантДняРождения
ПОМЕСТИТЬ ВТ_ВсеВарианты
ИЗ
    Справочник.ФизическиеЛица КАК Сотрудники
    ПОЛНОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ -1 КАК Коэффициент
        ОБЪЕДИНИТЬ ВСЕ
        ВЫБРАТЬ 0
        ОБЪЕДИНИТЬ ВСЕ
        ВЫБРАТЬ 1
    ) КАК Смещения ПО ИСТИНА
ГДЕ
    Сотрудники.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Варианты.Сотрудник КАК Сотрудник,
    Варианты.ВариантДняРождения КАК ДеньРождения,
    ВЫБОР
        КОГДА РАЗНОСТЬДАТ(Варианты.ВариантДняРождения, &Период, ДЕНЬ) < 0
            ТОГДА -РАЗНОСТЬДАТ(Варианты.ВариантДняРождения, &Период, ДЕНЬ)
        ИНАЧЕ РАЗНОСТЬДАТ(Варианты.ВариантДняРождения, &Период, ДЕНЬ)
    КОНЕЦ КАК ДнейДоСобытия
ПОМЕСТИТЬ ВТ_Разницы
ИЗ
    ВТ_ВсеВарианты КАК Варианты
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Разницы.Сотрудник КАК Сотрудник,
    МИНИМУМ(ВТ_Разницы.ДнейДоСобытия) КАК МинДней
ПОМЕСТИТЬ ВТ_Минимумы
ИЗ
    ВТ_Разницы КАК ВТ_Разницы
СГРУППИРОВАТЬ ПО
    ВТ_Разницы.Сотрудник
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Разницы.Сотрудник КАК Сотрудник,
    МАКСИМУМ(ВТ_Разницы.ДеньРождения) КАК БлижайшийДеньРождения
ИЗ
    ВТ_Разницы КАК ВТ_Разницы
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Минимумы КАК ВТ_Минимумы
        ПО ВТ_Разницы.Сотрудник = ВТ_Минимумы.Сотрудник
        И ВТ_Разницы.ДнейДоСобытия = ВТ_Минимумы.МинДней
СГРУППИРОВАТЬ ПО
    ВТ_Разницы.Сотрудник

Такой запрос отрабатывает на стороне СУБД, корректно обрабатывает високосные смещения за счет внутренней механики функции ДОБАВИТЬКДАТЕ и позволяет быстро получить результат сразу по всему списку сотрудников или контрагентов.

← На главную