Задача расчета разницы между днями рождения только на первый взгляд кажется простой. Основная сложность заключается в том, что нам нужно игнорировать год рождения человека, но при этом корректно учитывать високосные годы (29 февраля) и уметь вычислять разницу между «последним» именинником декабря и «первым» именинником января следующего года. В этой статье мы подробно разберем несколько подходов к решению этой задачи, проанализируем типичные ошибки и выберем наиболее надежный алгоритм.
Представим ситуацию: у нас есть список сотрудников с их датами рождения. Нам нужно получить таблицу, где для каждого человека указано, через сколько дней наступит следующий по календарю день рождения (своего коллеги). Рассмотрим ключевые проблемы, которые нам предстоит решить:
РАЗНОСТЬДАТ между 1980 и 2023 годом выдаст тысячи дней, а нам нужно получить значение в диапазоне от 0 до 365.Дата1 < Дата2 здесь не сработает в лоб.Проанализируем самый надежный метод — приведение всех дат к одному базовому году. Чтобы не потерять 29 февраля, в качестве базового года лучше всего выбрать заведомо високосный год, например, 2000-й. Разберем этот алгоритм по шагам.
Сначала создадим временную таблицу, в которой «обнулим» год рождения, заменив его на 2000-й. Для этого используем комбинацию функций ДОБАВИТЬКДАТЕ и ГОД.
ВЫБРАТЬ
ДниРождения.ФИО КАК ФИО,
ДниРождения.ДеньРождения КАК ДеньРождения,
ДОБАВИТЬКДАТЕ(ДниРождения.ДеньРождения, ГОД, 2000 - ГОД(ДниРождения.ДеньРождения)) КАК ДеньРожденияБезГода
ПОМЕСТИТЬ ВТ_НормализованныеДаты
ИЗ
РегистрСведений.ДниРождения КАК ДниРождения;
Теперь нам нужно решить проблему перехода через год. Для этого применим хитрость: создадим вторую временную таблицу, куда поместим те же данные, но добавим к каждой нормализованной дате ровно один год (сделаем их датами 2001 года). Объединив эти наборы, мы получим непрерывную последовательность дней рождения на два года вперед.
ВЫБРАТЬ
Т.ФИО,
Т.ДеньРожденияБезГода
ПОМЕСТИТЬ ВТ_Расширенная
ИЗ
ВТ_НормализованныеДаты КАК Т
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Т.ФИО,
ДОБАВИТЬКДАТЕ(Т.ДеньРожденияБезГода, ГОД, 1)
ИЗ
ВТ_НормализованныеДаты КАК Т;
Теперь мы можем легко найти «следующую» дату для каждого сотрудника из первого (2000-го) года, просто найдя минимальную дату в расширенной таблице, которая больше текущей.
ВЫБРАТЬ
Т1.ФИО КАК ФИО,
Т1.ДеньРожденияБезГода КАК ДатаСегодня,
МИНИМУМ(Т2.ДеньРожденияБезГода) КАК ДатаСледующая
ПОМЕСТИТЬ ВТ_Соединение
ИЗ
ВТ_НормализованныеДаты КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Расширенная КАК Т2 ПО Т1.ДеньРожденияБезГода < Т2.ДеньРожденияБезГода
СГРУППИРОВАТЬ ПО
Т1.ФИО,
Т1.ДеньРожденияБезГода;
Финальным шагом вычисляем разницу в днях с помощью стандартной функции РАЗНОСТЬДАТ.
ВЫБРАТЬ
Т.ФИО,
РАЗНОСТЬДАТ(Т.ДатаСегодня, Т.ДатаСледующая, ДЕНЬ) КАК РазницаДней
ИЗ
ВТ_Соединение КАК Т;
Рассмотрим более простой, но менее точный подход, который предлагался в обсуждении. Функция ДЕНЬГОДА() возвращает порядковый номер дня в году (от 1 до 366). Этот метод хорош для быстрой сортировки, но требует осторожности с високосными годами.
Выясним причину, почему этот метод может ошибаться: в високосном году 1 марта — это 61-й день, а в обычном — 60-й. Если ваши данные содержат людей, рожденных в разные годы, прямое вычитание результатов ДЕНЬГОДА() может дать погрешность в 1 день. Однако, если использовать АВТОНОМЕРЗАПИСИ() для связи текущей строки со следующей, можно получить лаконичный код:
ВЫБРАТЬ
Т1.Сотрудник,
Т1.ДеньГ КАК ДеньСегодня,
Т2.ДеньГ КАК ДеньЗавтра,
Т2.ДеньГ - Т1.ДеньГ КАК Дни
ИЗ
ВТ_Промежуточная П1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Промежуточная П2 ПО П1.НомерСтроки + 1 = П2.НомерСтроки
Важно: Чтобы этот метод работал для последнего человека в списке, вам все равно придется добавить в таблицу «фиктивную» запись первого сотрудника, но с номером дня + 365 (или 366).
Если вы строите отчет, а не просто пишете запрос в консоли, задачу можно решить элегантнее с помощью функций ресурсов СКД. Рассмотрим функцию ВычислитьВыражение.
Этот метод позволяет не перегружать запрос сложными соединениями. Вы можете просто выбрать список сотрудников и их дат рождения, отсортировать их, а затем в вычисляемом поле написать выражение:
РазностьДат(ДеньРождения, ВычислитьВыражение("ДеньРождения", , , "Следующая", "Порядок"), "ДЕНЬ")
Проанализируем преимущества этого способа:
При работе с датами в 1С всегда помните о времени. Если в поле ДеньРождения записана дата со временем (например, 12:00:00), то РАЗНОСТЬДАТ может вернуть неожиданный результат из-за неполных суток. Мы рекомендуем всегда обертывать даты функцией НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) перед любыми вычислениями.
Также, если в вашей базе есть сотрудники с одинаковыми днями рождения (например, два человека родились 15 мая), обычное соединение по условию Т1.Дата < Т2.Дата может привести к тому, что разница между ними будет посчитана как 0 или они «схлопнутся» в одну запись. Чтобы этого избежать, в условие соединения и сортировки всегда добавляйте уникальный идентификатор (например, Ссылка или ФИО), чтобы выстроить однозначную очередь именинников.
Подведем итог: для разовых задач в консоли лучше всего подходит метод с приведением к базовому високосному году (2000-й), так как он наиболее математически точен. Для разработки сложных отчетов стоит обратить внимание на возможности СКД по вычислению значений в соседних строках.