К статье
Практический материал

Разработка и Конфигурация · 13 мин

Как высчитать возраст в 1С по дате рождения

Как высчитать возраст в 1С по дате рождения: функции на встроенном языке, вычисление в запросе, типовые механизмы ЗУП, проверка результата и частые ошибки.

Задача вычисления возраста встречается в 1С постоянно: в кадровых отчётах, при проверке права на льготы, в печатных формах, при расчёте стажа и северных надбавок. На первый взгляд всё просто — вычесть дату рождения из текущей даты. На практике такой подход даёт ошибку в 1–2 дня вокруг дня рождения, потому что возраст зависит не только от разности лет, но и от того, наступил ли день рождения в текущем году.

Правильный алгоритм учитывает месяц и день: если в текущем году день рождения ещё не наступил, из разности лет нужно вычесть единицу. Ниже разобраны готовые функции на встроенном языке, вычисление возраста прямо в запросе, типовые механизмы конфигураций и способы проверить результат на граничных датах.

Материал применим к платформе 1С:Предприятие 8.2 и 8.3, к типовым конфигурациям (ЗУП, Бухгалтерия, Управление торговлей) и к самописным решениям. Код приведён в виде, готовом для вставки в общий модуль.

Где в 1С хранится дата рождения и почему это важно

Прежде чем считать возраст, нужно понять, откуда брать дату рождения. В типовых конфигурациях она хранится в разных местах: в ЗУП — в реквизите «ДатаРождения» справочника «ФизическиеЛица», в Бухгалтерии — аналогично в справочнике физических лиц, в самописных решениях — в реквизитах справочников сотрудников, клиентов или пациентов.

Обратите внимание на тип реквизита. Если дата рождения хранится как строка (так бывает после некачественных обменов), функции работы с датами не сработают — сначала потребуется преобразование через Дата() или обработку строки. Проверьте также заполненность: пустая дата рождения (Дата(1,1,1)) при вычислении даст огромное число, которое попадёт в отчёт как возраст в тысячи лет.

Базовая функция вычисления возраста на встроенном языке

Универсальная функция принимает дату рождения и дату, на которую считается возраст (по умолчанию — текущая). Логика: берём разность лет, затем проверяем, наступил ли день рождения в году расчётной даты.

Функция ВозрастНаДату(ДатаРождения, НаДату = Неопределено) Экспорт

Если Не ЗначениеЗаполнено(ДатаРождения) Тогда

Возврат Неопределено;

КонецЕсли;

Если НаДату = Неопределено Тогда

НаДату = ТекущаяДатаСеанса();

КонецЕсли;

Если ДатаРождения > НаДату Тогда

Возврат Неопределено;

КонецЕсли;

Лет = Год(НаДату) - Год(ДатаРождения);

Если Месяц(НаДату) < Месяц(ДатаРождения)

Или (Месяц(НаДату) = Месяц(ДатаРождения)

И День(НаДату) < День(ДатаРождения)) Тогда

Лет = Лет - 1;

КонецЕсли;

Возврат Лет;

КонецФункции

Функция корректно обрабатывает три ситуации: день рождения уже прошёл в текущем году, день рождения сегодня (возраст увеличивается именно в этот день) и день рождения ещё впереди. Отдельная проверка защищает от даты рождения в будущем — такое бывает при ошибках ввода, и лучше вернуть Неопределено, чем отрицательный возраст.

Обратите внимание на ТекущаяДатаСеанса() вместо устаревшей ТекущаяДата(): функция сеанса учитывает часовой пояс сервера и корректно работает в клиент-серверном режиме, где локальное время пользователя может отличаться от серверного.

Как добавить функцию в конфигурацию
  1. 1
    Создайте общий модуль, например «РаботаСДатами», с признаками Сервер и ВызовСервера
  2. 2
    Вставьте функцию ВозрастНаДату и включите её в интерфейс (Экспорт)
  3. 3
    Вызовите функцию из формы, отчёта или процедуры проведения, передав дату рождения
  4. 4
    Протестируйте на граничных датах: день рождения сегодня, завтра, вчера, 29 февраля

Вычисление возраста в запросе

Когда возраст нужен в отчёте по списку сотрудников, вызывать функцию в цикле по строкам результата — плохая идея: это множит обращения и усложняет код. Язык запросов 1С позволяет вычислить возраст на стороне СУБД через функции ГОД(), МЕСЯЦ(), ДЕНЬ() и конструкцию ВЫБОР.

ВЫБРАТЬ

ФизическиеЛица.Ссылка КАК ФизЛицо,

ФизическиеЛица.ДатаРождения КАК ДатаРождения,

ГОД(&НаДату) - ГОД(ФизическиеЛица.ДатаРождения)

- ВЫБОР

КОГДА МЕСЯЦ(&НаДату) < МЕСЯЦ(ФизическиеЛица.ДатаРождения)

ТОГДА 1

КОГДА МЕСЯЦ(&НаДату) = МЕСЯЦ(ФизическиеЛица.ДатаРождения)

И ДЕНЬ(&НаДату) < ДЕНЬ(ФизическиеЛица.ДатаРождения)

ТОГДА 1

ИНАЧЕ 0

КОНЕЦ КАК Возраст

ИЗ

Справочник.ФизическиеЛица КАК ФизическиеЛица

ГДЕ

ФизическиеЛица.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)

Параметр &НаДату передаётся при выполнении запроса. Такой подход даёт готовую колонку возраста для всех записей за одно обращение к базе и легко встраивается в СКД-отчёты: вычисляемое поле добавляется прямо в текст запроса набора данных.

В СКД есть альтернатива — вычисляемое поле с выражением на встроенном языке и вызовом функции общего модуля. Это проще в написании, но медленнее на больших выборках, потому что выражение выполняется для каждой строки уже после получения данных. Для списков в сотни записей разница незаметна, для отчётов по десяткам тысяч сотрудников предпочтителен расчёт в запросе.

Где считать возраст

Функция общего модуля

Подходит для разовых вычислений: форма элемента, печатная форма, проверка при записи. Не подходит для массовых отчётов из-за построчных вызовов.

Вычисление в запросе

Оптимально для отчётов и динамических списков: один запрос, готовая колонка. Минус — более громоздкий текст запроса.

Вычисляемое поле СКД

Компромисс для отчётов на СКД без изменения запроса. На больших выборках медленнее, чем расчёт в запросе.

Типовые механизмы ЗУП

Готовые функции и роли уже учитывают кадровые особенности (стаж, возраст для отчётности). Подходят, если конфигурация типовая и дорабатывать ничего не нужно.

Типовые механизмы в конфигурациях

В типовой 1С:ЗУП вычисление возраста уже реализовано. В общих модулях зарплатной библиотеки есть функции вида ЗарплатаКадрыКлиентСервер.ВозрастВГодах (точное имя зависит от редакции конфигурации), а в отчётах по персоналу возраст выводится штатно. Прежде чем писать свой код, поищите в конфигураторе по слову «Возраст» в общих модулях — велика вероятность, что готовая функция уже есть и учтена в обновлениях.

В ЗУП возраст также используется в кадровых отчётах (например, списки сотрудников с возрастом на дату), в механизмах стажа и при формировании сведений для отчётности. Если ваша задача — вывести возраст в типовом отчёте, часто достаточно настроить вариант отчёта, добавив поле в структуру, без программирования.

В Бухгалтерии и Управлении торговлей специализированных функций возраста обычно нет — там применяется собственная функция общего модуля по образцу из предыдущего раздела.

Особые случаи: 29 февраля, возраст в месяцах и днях

Люди, родившиеся 29 февраля, — классический тест на корректность алгоритма. В невисокосном году дня 29.02 не существует, и разные системы трактуют день рождения по-разному: одни считают его 28 февраля, другие — 1 марта. Приведённая выше функция по сути считает, что в невисокосном году день рождения наступает 1 марта, поскольку 28 февраля условие «месяц равен и день меньше» ещё выполняется. Для кадрового учёта такое поведение обычно приемлемо, но если в вашем учёте принято другое правило — зафиксируйте его в коде явно.

Иногда нужен возраст не в годах, а в месяцах (дети до года) или в днях (медицинские и кадровые расчёты). Для дней достаточно разности дат: (НаДату - ДатаРождения) / 86400, поскольку разность дат в 1С даёт результат в секундах. Для полных месяцев логика аналогична годам: разность месяцев с поправкой на день.

// Полных месяцев на дату

Месяцев = (Год(НаДату) - Год(ДатаРождения)) * 12

+ Месяц(НаДату) - Месяц(ДатаРождения);

Если День(НаДату) < День(ДатаРождения) Тогда

Месяцев = Месяцев - 1;

КонецЕсли;

Для строкового представления вида «35 лет 4 месяца» в типовых конфигурациях есть функции склонения и представления интервалов, либо используйте СтрокаСЧисломДляЛюбогоЯзыка() из БСП для корректного склонения «год/года/лет».

Проверка результата и типичные ошибки

Проверять функцию нужно на граничных датах, а не на случайных значениях. Минимальный набор тестов: дата рождения сегодня (возраст должен увеличиться именно в этот день), завтра (возраст ещё не увеличился), вчера (уже увеличился), 29 февраля високосного года с расчётом на 28 февраля и 1 марта невисокосного, пустая дата рождения, дата рождения в будущем.

Контрольные точки

Как проверить результат

Дата рождения = расчётная дата
Норма

Возраст увеличился на 1 в день рождения

Тревожный признак

Возраст увеличивается на день раньше или позже

День рождения завтра
Норма

Показано старое значение возраста

Тревожный признак

Возраст уже увеличен

Пустая дата рождения
Норма

Функция возвращает Неопределено или 0 по правилу, принятому в решении

Тревожный признак

В отчёт попадает возраст в сотни или тысячи лет

Расчёт в запросе и функцией
Норма

Оба способа дают одинаковое значение на одних данных

Тревожный признак

Результаты расходятся на граничных датах

Чаще всего ошибки сводятся к нескольким повторяющимся сценариям. Их удобно держать перед глазами при ревью кода.

Типичные ошибки
01
Деление разности дат на 365 или 365,25 — погрешность накапливается, возраст «перескакивает» не в день рождения
02
Сравнение только месяцев без дней — весь месяц рождения возраст считается неверно
03
Отсутствие проверки пустой даты — в отчётах появляются значения в тысячи лет
04
Использование ТекущаяДата() на сервере в клиент-серверном режиме — расчёт идёт по времени клиента или сервера непредсказуемо
05
Расчёт возраста функцией в цикле по большой выборке вместо вычисления в запросе — отчёт формируется неприемлемо долго

Практические сценарии применения

Возраст в печатной форме

В макете печатной формы возраст обычно выводится как параметр. Вычислите его в процедуре формирования печатной формы функцией общего модуля и передайте в параметры макета. Для представления вида «42 года» используйте функции склонения числительных из БСП, если она входит в конфигурацию.

Отбор сотрудников по возрасту в отчёте

Если нужен отчёт «сотрудники старше 50 лет» или «моложе 30», не вычисляйте возраст и не сравнивайте его с порогом — это мешает использовать индексы. Разверните условие: возраст ≥ 50 означает дату рождения не позже ДобавитьМесяц(НаДату, -50*12) с поправкой на день. Такой отбор по дате рождения работает быстрее и проще проверяется.

Проверка при записи данных

Для контроля ввода (например, сотрудник не младше 14 и не старше разумного предела) вызовите функцию в обработчике проверки заполнения и выдайте пользователю сообщение с указанием введённой даты. Это отсекает опечатки вида «1902» вместо «1920» ещё на этапе ввода.

Удобно хранить функцию возраста в одном общем модуле вместе с функциями стажа и интервалов дат. Тогда правило расчёта (например, трактовка 29 февраля) зафиксировано в одном месте, и все отчёты дают согласованные значения.
ПЗ
Практическое замечание

Ограничения и границы самостоятельной доработки

Собственная функция возраста — безопасная доработка: общий модуль не затрагивает типовые объекты, и обновление конфигурации его не сломает. Риски появляются, когда вы меняете типовые отчёты или модули напрямую: при обновлении такие изменения могут быть затёрты или вступить в конфликт с новыми версиями объектов.

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

Итог

Что важно запомнить

Возраст считают как разность лет с поправкой на наступление дня рождения в текущем году, а не делением разности дат. Для отчётов вычисление выполняют в запросе, для разовых случаев — функцией общего модуля. Пустые даты и 29 февраля обрабатывают явно, результат проверяют на граничных датах.

Частые вопросы

Чем отличается ТекущаяДата() от ТекущаяДатаСеанса() при расчёте возраста?

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

Как вывести возраст со словом «год», «года» или «лет»?

Используйте функцию СтрокаСЧисломДляЛюбогоЯзыка() из Библиотеки стандартных подсистем: она принимает шаблон вида «;год;;года;годов» и сама выбирает форму по числу. Если БСП в конфигурации нет, напишите простую функцию: смотрите на две последние цифры числа — для 11–14 всегда «лет», иначе по последней цифре 1 — «год», 2–4 — «года», остальное — «лет».

Можно ли посчитать возраст в динамическом списке без доработки запроса?

Нет, динамический список строится только на запросе, и вычисляемые поля в нём не поддерживаются. Добавьте вычисление возраста в текст запроса списка через конструкцию ВЫБОР, как показано выше, либо замените динамический список на таблицу значений, заполняемую программно.

Почему в отчёте у части сотрудников возраст — огромное число?

Почти всегда это незаполненная дата рождения: пустая дата равна 01.01.0001, и разность лет даёт тысячи лет. Добавьте в запрос отбор ДатаРождения <> ДАТАВРЕМЯ(1,1,1), а в функцию — проверку ЗначениеЗаполнено(). Заодно стоит проверить, откуда взялись пустые значения: часто это следствие неполной миграции данных.

Как посчитать возраст на конкретную дату в прошлом или будущем?

Передайте нужную дату вторым параметром функции или в параметр запроса вместо текущей даты. Алгоритм не меняется: разность лет с поправкой на наступление дня рождения. Так считают, например, возраст на дату приёма на работу или на первое января отчётного года.

Нужно ли учитывать время в дате рождения?

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