Разработка и Конфигурация · 12 мин
1С: как работает разность дат
Как работает РазностьДат в 1С: синтаксис, единицы измерения, особенности расчёта в запросах и на встроенном языке, примеры, ошибки и способы проверки результата.
Функция РазностьДат в 1С — один из самых востребованных инструментов при работе с периодами: расчёт стажа сотрудника, срока хранения товара, просрочки оплаты, возраста документа. При этом она часто вызывает вопросы, потому что ведёт себя не так, как обычное вычитание дат, и результат зависит от выбранной единицы измерения.
Ключевой нюанс: РазностьДат существует в двух вариантах — как функция языка запросов 1С и как вычисление на встроенном языке через обычное вычитание дат. Они работают по-разному, возвращают разные типы данных и применяются в разных сценариях. Путаница между ними — источник большинства ошибок у начинающих разработчиков.
Ниже разобраны синтаксис, единицы измерения, особенности округления, практические примеры для запросов и кода, а также типичные ошибки и способы проверить корректность результата.
Как устроена функция РазностьДат в языке запросов
В языке запросов 1С функция записывается так: РАЗНОСТЬДАТ(ДатаНачала, ДатаОкончания, ЕдиницаИзмерения). Первый параметр — вычитаемая дата, второй — уменьшаемая. Если ДатаОкончания раньше ДатаНачала, результат будет отрицательным. Функция всегда возвращает целое число.
Важно понимать механику расчёта: платформа считает количество полных единиц между датами. Например, между 10:00 утра понедельника и 09:00 утра вторника разность в днях равна нулю, потому что полные сутки ещё не прошли. Это часто становится неожиданностью при расчёте просрочек, когда «вчерашний» документ даёт нулевую просрочку.
Третий параметр — единица измерения — задаётся ключевым словом без кавычек. Доступные значения перечислены ниже.
| Единица | Что считает | Типичное применение |
|---|---|---|
| СЕКУНДА | Полные секунды | Длительность операций, замеры производительности |
| МИНУТА | Полные минуты | Регламентные задания, SLA по времени реакции |
| ЧАС | Полные часы | Учёт рабочего времени, смены |
| ДЕНЬ | Полные сутки (24 часа) | Просрочки, сроки хранения, доставка |
| НЕДЕЛЯ | Полные недели | Плановые периоды, отчётность |
| МЕСЯЦ | Полные календарные месяцы | Стаж, абонентские периоды, амортизация |
| КВАРТАЛ | Полные кварталы | Бухгалтерская и налоговая отчётность |
| ГОД | Полные календарные годы | Возраст, стаж, сроки службы |
Разность дат на встроенном языке: вычитание и его особенности
На встроенном языке 1С отдельной функции «РазностьДат» нет — используется обычный оператор вычитания:
РазностьВСекундах = ДатаОкончания - ДатаНачала;
РазностьВДнях = РазностьВСекундах / 86400;
Результат вычитания двух значений типа Дата — всегда число секунд. Чтобы получить дни, результат делят на 86400 (60 × 60 × 24). Если нужны целые дни по аналогии с запросной функцией, применяют функцию Цел() или Окр() в зависимости от требуемой логики округления.
Если нужна разность в месяцах или годах на встроенном языке, простого деления недостаточно — месяцы имеют разную длину. Здесь либо вычисляют разность через Месяц() и Год() с корректировкой на день месяца, либо выполняют расчёт запросом, где РАЗНОСТЬДАТ корректно обрабатывает календарную специфику.
Функция ПолныхМесяцевМеждуДатами(Дата1, Дата2)
Месяцев = (Год(Дата2) - Год(Дата1)) * 12 + Месяц(Дата2) - Месяц(Дата1);
Если День(Дата2) < День(Дата1) Тогда
Месяцев = Месяцев - 1;
КонецЕсли;
Возврат Месяцев;
КонецФункцииПочему результат отличается от ожидаемого: границы и округление
Самая частая жалоба: «между 31 января и 1 марта функция показывает 0 или 1 месяц, а я ожидал другое». Разность в месяцах считается по календарным границам: полный месяц засчитывается, когда наступила та же дата следующего месяца. С 15 января по 14 февраля — 0 месяцев, с 15 января по 15 февраля — 1 месяц.
Вторая ловушка — время внутри даты. Значение типа Дата в 1С всегда содержит время с точностью до секунды. Дата документа «01.03 23:59:59» и «02.03 00:00:01» формально в разных днях, но разность в днях равна нулю — прошло всего 2 секунды. Если нужна разность по календарным датам без учёта времени, даты предварительно приводят к началу дня.
Практические сценарии использования
Расчёт просрочки оплаты в запросе
Типовая задача — отчёт по дебиторской задолженности с колонкой «дней просрочки». В запросе это выглядит так:
ВЫБРАТЬ
\tРасчеты.Контрагент,
\tРасчеты.СуммаОстаток,
\tРАЗНОСТЬДАТ(Расчеты.ДокументРасчетов.ДатаОплаты, &ТекущаяДата, ДЕНЬ) КАК ДнейПросрочки
ИЗ
\tРегистрНакопления.РасчетыСКонтрагентами.Остатки КАК Расчеты
ГДЕ
\tРАЗНОСТЬДАТ(Расчеты.ДокументРасчетов.ДатаОплаты, &ТекущаяДата, ДЕНЬ) > 0
Обратите внимание: условие на просрочку лучше накладывать в секции ГДЕ по той же функции, чтобы не выводить документы с отрицательной разностью (срок оплаты ещё не наступил).
Расчёт стажа или возраста
Для возраста и стажа используют единицу ГОД, но с проверкой: сотрудник, родившийся 31 декабря, при расчёте 1 января может «потерять» или «получить» год в зависимости от логики. Надёжный вариант — считать полные годы через РАЗНОСТЬДАТ и дополнительно проверять, наступил ли день рождения в текущем году, если требуется юридически точный расчёт.
Группировка по возрасту документов
Разность дат часто применяют в конструкции ВЫБОР для разбивки документов по интервалам: «до 30 дней», «30–60», «свыше 60». Это стандартный приём для отчётов по старению задолженности и складских остатков.
- 1Определите, какая единица измерения нужна бизнес-логике: дни, месяцы или годы
- 2Решите, учитывать ли время: если нет — оберните даты в НАЧАЛОПЕРИОДА(..., ДЕНЬ)
- 3Добавьте поле РАЗНОСТЬДАТ в выборку запроса и задайте псевдоним
- 4Проверьте поведение на граничных случаях: одинаковые даты, обратный порядок дат, пустая дата
- 5Добавьте условие ГДЕ, чтобы отсечь нерелевантные записи (например, отрицательную просрочку)
Пустые даты, NULL и другие граничные случаи
Если один из параметров — пустая дата (01.01.0001), функция вернёт формально корректное, но бессмысленное огромное число. Если параметр имеет значение NULL (например, при левом соединении таблиц), результат тоже будет NULL, и сравнение с ним в условии не сработает ожидаемым образом.
Поэтому перед расчётом стоит обезопасить запрос: использовать ЕСТЬNULL(Дата, &ПустаяДата) или явно фильтровать записи с незаполненными датами. В отчётах на СКД это особенно важно, потому что NULL в вычисляемом поле может «сломать» группировку или итоги.
Производительность и альтернативные подходы
Функция РАЗНОСТЬДАТ вычисляется на сервере СУБД при выполнении запроса. На выборке в сотни тысяч строк она ощутимо увеличивает время выполнения, особенно если участвует в условиях фильтрации — индексы по датам при этом не используются.
Альтернативы в зависимости от задачи: предварительный расчёт пороговой даты на встроенном языке (например, ДатаОтсечки = ТекущаяДатаСеанса() - 30 * 86400) и сравнение ДатаДокумента < &ДатаОтсечки — такой запрос использует индекс и работает значительно быстрее. Для регулярных расчётов стажа или возраста иногда выгоднее хранить вычисленное значение в регистре и обновлять регламентным заданием.
Сравнение вариантов расчёта
Подходит для отчётов и разовых выборок; корректно считает месяцы и годы; медленнее на больших объёмах, не использует индексы в условиях
Подходит для расчётов над одним объектом; результат в секундах, месяцы и годы приходится считать вручную
Оптимально для фильтрации «старше N дней»: быстро, использует индекс, но не даёт само значение разности
Типичные ошибки и как их избежать
Как проверить корректность расчёта
Перед тем как отдавать запрос в продуктив, проверьте его на контрольных примерах. Возьмите пару дат с известным результатом: ровно сутки, сутки минус секунда, ровно месяц, переход через 29 февраля високосного года. Сверьте вывод функции с ожиданием — расхождения сразу покажут, какая единица или обработка времени выбрана неверно.
Как проверить результат
0 при любой единице измерения
Любое ненулевое значение — ошибка в данных или функции
1 день, 24 часа, 1440 минут
0 дней — значит, время не совпадает или даты приведены неверно
Отрицательное число, по модулю равное прямому расчёту
NULL или положительное значение — перепутаны аргументы или потеряны данные
С 15.01 по 15.02 — ровно 1 месяц
0 или 2 месяца — проверьте единицу измерения и исходные даты
В отчётах на СКД удобно выносить единицу измерения и параметр текущей даты в настройки: пользователь сам выбирает, считать просрочку в днях или неделях, а запрос не нужно переписывать. Для этого параметр текущей даты передаётся как &ТекущаяДата и заполняется в коде модуля отчёта через ТекущаяДатаСеанса().
Частые вопросы
Чем РАЗНОСТЬДАТ в запросе отличается от вычитания дат в коде?
РАЗНОСТЬДАТ — функция языка запросов, возвращает целое число в заданной единице (день, месяц, год и др.) и корректно обрабатывает календарные месяцы разной длины. Вычитание дат в коде всегда даёт число секунд, и перевод в месяцы или годы придётся реализовывать вручную.
Почему разность между «вчера» и «сегодня» равна нулю дней?
Потому что функция считает полные сутки (24 часа), а не календарные даты. Если между моментами прошло меньше 24 часов, результат — 0. Для подсчёта календарных дней приведите обе даты к началу дня через НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) и только потом считайте разность.
Как посчитать стаж в формате «лет, месяцев, дней»?
Одной функцией это не делается. Обычно считают последовательно: сначала полные годы через РАЗНОСТЬДАТ(..., ГОД), затем остаток в месяцах, затем остаток в днях, либо реализуют пошаговый расчёт на встроенном языке с корректировкой на день месяца. Готовых типовых функций в конфигурациях для этого часто нет, логику пишут под конкретные требования кадрового учёта.
Что вернёт функция, если одна из дат не заполнена?
Пустая дата (01.01.0001) даст очень большое число — это формально корректный, но бессмысленный результат. Если значение равно NULL (типично для левых соединений), результат тоже будет NULL. Перед расчётом фильтруйте такие записи или подставляйте значение через ЕСТЬNULL.
Можно ли использовать РАЗНОСТЬДАТ в условии соединения таблиц?
Технически можно, но делать этого не стоит: вычисление функции при соединении мешает оптимизатору и резко замедляет запрос на больших таблицах. Правильный подход — вычислить разность во временной таблице или в секции ВЫБРАТЬ, а соединения строить по обычным полям.
Как в запросе отобрать документы старше 30 дней?
Самый быстрый способ — не РАЗНОСТЬДАТ, а сравнение с пороговой датой: вычислите ДатаОтсечки = ТекущаяДатаСеанса() - 30*86400 в коде, передайте параметром и напишите условие ДатаДокумента < &ДатаОтсечки. Такой запрос использует индекс по дате и выполняется значительно быстрее.
Что важно запомнить
РАЗНОСТЬДАТ в запросе считает только полные единицы и возвращает целое число; в коде вычитание дат даёт секунды. Всегда учитывайте время внутри дат, обрабатывайте NULL и пустые даты, а для фильтрации по возрасту документов предпочитайте сравнение с пороговой датой — это быстрее и надёжнее.