В практике разработчика 1С часто возникает задача определить разницу между двумя датами в «человекочитаемом» виде: сколько лет, месяцев и дней прошло между событиями. Типовым примером является расчет трудового стажа сотрудника. В то время как на встроенном языке эта задача решается относительно просто, реализация аналогичной логики непосредственно в запросе (например, для использования в СКД или сложных выборках) требует серьезного подхода. Рассмотрим, как можно реализовать такой алгоритм максимально эффективно и компактно.
Для начала проанализируем, почему нельзя ограничиться простой функцией РАЗНОСТЬДАТ. Эта функция в 1С умеет возвращать разность только в одной единице измерения (только в днях, только в месяцах или только в годах). Суммирование этих результатов не даст корректного стажа, так как месяцы имеют разную продолжительность, а високосные годы вносят свои коррективы. Наша цель — получить три отдельных числовых значения: Лет, Месяцев и Дней.
Разберем логику расчета «от общего к частному»:
Рассмотрим вариант запроса, который позволяет вычислить все три составляющие. Представим, что у нас есть временная таблица ВТ_Периоды с полями ДатаН (дата начала) и ДатаК (дата окончания). Проанализируем следующий код:
ВЫБРАТЬ
ВТ_Периоды.ДатаН КАК ДатаН,
ВТ_Периоды.ДатаК КАК ДатаК,
// Расчет полных лет
ВЫБОР
КОГДА ВТ_Периоды.ДатаК < ВТ_Периоды.ДатаН
ТОГДА 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, 1, 1), чтобы избежать некорректных результатов при незаполненных данных.ГОД(), МЕСЯЦ() и ДЕНЬ() выполняются на стороне СУБД достаточно быстро. Тем не менее, если в таблице миллионы строк, старайтесь минимизировать количество вложенных ВЫБОР.Таким образом, мы получили работающий инструмент для вычисления стажа непосредственно в запросе. Несмотря на кажущуюся громоздкость, такой подход обеспечивает высокую точность и независимость от программного кода на встроенном языке 1С, что особенно критично для сложных аналитических отчетов.