Разработка и Конфигурация · 11 мин
Как вычесть день в запросе 1С
Как вычесть день из даты в запросе 1С через ДОБАВИТЬКДАТЕ с отрицательным значением, а также РАЗНОСТЬДАТ. Примеры кода, ошибки и проверка результата.
Задача вычесть день из даты в запросе 1С возникает постоянно: при отборе документов «за вчера», при расчёте сроков оплаты, при построении отчётов с периодом до заданной даты. В языке запросов 1С нет оператора «минус» для дат, поэтому используется функция ДОБАВИТЬКДАТЕ с отрицательным количеством периодов. Это стандартный и единственный корректный способ арифметики дат внутри запроса.
Важно понимать ограничение: функция работает только с типом «Дата» и возвращает значение того же типа. Если вычесть день из даты-времени, время суток сохранится. Например, из 15.03 14:30 при вычитании одного дня получится 14.03 14:30, а не начало дня. Для получения начала или конца дня дополнительно применяют НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА.
Ниже разобран синтаксис функции, готовые примеры запросов, типичные ошибки и способы проверить, что дата рассчитана правильно. Примеры актуальны для всех современных версий платформы 1С:Предприятие 8.3, поскольку синтаксис языка запросов в этой части не менялся.
Синтаксис функции ДОБАВИТЬКДАТЕ
Функция имеет три параметра: исходная дата (или выражение, возвращающее дату), тип добавляемого периода и количество периодов. Тип периода задаётся ключевым словом без кавычек: СЕКУНДА, МИНУТА, ЧАС, ДЕНЬ, НЕДЕЛЯ, МЕСЯЦ, КВАРТАЛ, ПОЛУГОДИЕ, ГОД, ДЕКАДА.
Количество — целое число. Чтобы вычесть период, передаётся отрицательное число. Таким образом, «минус один день» записывается как ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -1), «минус неделя» — ДОБАВИТЬКДАТЕ(&Дата, НЕДЕЛЯ, -1), «минус три месяца» — ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, -3).
Третий параметр может быть не только литералом, но и выражением — полем таблицы или результатом вычисления. Это позволяет, например, вычесть из даты документа количество дней, хранящееся в реквизите справочника: ДОБАВИТЬКДАТЕ(Документ.Дата, ДЕНЬ, -Контрагент.СрокОтсрочки).
Базовый пример: вычесть один день
Самый частый сценарий — отобрать данные за день, предшествующий заданной дате. Запрос передаёт параметр с датой и вычисляет из неё вчерашнее число:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -1) КАК ВчерашняяДата";
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Результат = Запрос.Выполнить().Выгрузить();
Если параметр содержит дату со временем (например, текущую дату сеанса), результат сохранит то же время. Когда нужна именно календарная дата без времени, оборачивайте выражение в НАЧАЛОПЕРИОДА:
НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -1), ДЕНЬ)
Такая конструкция вернёт 00:00:00 вчерашнего дня. Аналогично КОНЕЦПЕРИОДА(..., ДЕНЬ) даст 23:59:59 — это удобно для условий «меньше или равно» при отборе по периоду.
- 1Определите, какая дата является исходной: параметр запроса, поле документа или функция вроде &ТекущаяДата
- 2Напишите выражение ДОБАВИТЬКДАТЕ(ИсходнаяДата, ДЕНЬ, -1) в списке выбираемых полей или в условии ГДЕ
- 3При необходимости отсечь время оберните результат в НАЧАЛОПЕРИОДА или КОНЕЦПЕРИОДА
- 4Выполните запрос в консоли запросов и сверьте полученную дату с ожидаемой
Вычитание дня в условии отбора
На практике вычитание дня чаще нужно не в результате, а в условии ГДЕ. Пример: выбрать все документы реализации за вчерашний день относительно текущей даты:
ВЫБРАТЬ
РеализацияТоваровУслуг.Ссылка,
РеализацияТоваровУслуг.Дата
ИЗ
Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ГДЕ
РеализацияТоваровУслуг.Дата МЕЖДУ НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&ТекущаяДата, ДЕНЬ, -1), ДЕНЬ)
И КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&ТекущаяДата, ДЕНЬ, -1), ДЕНЬ)
И РеализацияТоваровУслуг.Проведен
Обратите внимание: дважды вычислять границы периода в тексте запроса необязательно. Часто эффективнее вычислить начало и конец вчерашнего дня один раз в коде на встроенном языке и передать их как два параметра. Это упрощает текст запроса и снижает вероятность ошибки в границах периода.
Ещё один рабочий приём — сравнение через отсечение периода у поля: НАЧАЛОПЕРИОДА(Реализация.Дата, ДЕНЬ) = НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -1), ДЕНЬ). Учтите, однако, что применение функции к полю таблицы мешает использованию индекса по дате, и на больших таблицах запрос может выполняться заметно медленнее.
Вычитание месяцев и лет: особенности границ
Вычитание дня работает предсказуемо всегда, а вот вычитание месяца или года имеет нюанс на границах месяцев. Если из 31 марта вычесть один месяц, результатом будет последний день февраля — 28 или 29 число в зависимости от високосности года. Функция не выдаёт ошибку и не «проваливается» в март, а подгоняет дату под последний существующий день целевого месяца.
Аналогично вычитание года из 29 февраля високосного года даст 28 февраля невисокосного. Это нужно учитывать в расчётах сроков: если бизнес-логика требует иного поведения (например, строго то же число либо ошибка), придётся реализовывать проверку отдельно в коде.
Для перехода через границу месяца при вычитании дней никаких специальных действий не требуется: из 1 марта при вычитании одного дня корректно получится последний день февраля, из 1 января — 31 декабря предыдущего года. Календарная арифметика выполняется платформой автоматически.
Способы получить «вчера»
Подходит, когда расчёт нужен прямо в тексте запроса или в СКД. Ограничение — громоздкий текст при повторном использовании результата.
Подходит для программных запросов: чище текст, лучше производительность. Требует вычисления до выполнения запроса.
Подходит для отчётов на СКД: пользователь сам выбирает период «Вчера». Не применимо в произвольных запросах вне СКД.
Контрольная проверка: РАЗНОСТЬДАТ
Обратная задача — узнать, сколько дней между двумя датами — решается функцией РАЗНОСТЬДАТ(Дата1, Дата2, ДЕНЬ). Она возвращает количество полных периодов между датами и полезна для проверки результата вычитания: если вы вычли из даты один день, разность между исходной и полученной датой должна быть равна единице.
Пример проверочного запроса:
ВЫБРАТЬ
&Дата КАК ИсходнаяДата,
ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -1) КАК МинусДень,
РАЗНОСТЬДАТ(ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -1), &Дата, ДЕНЬ) КАК РазницаДней
Учтите особенность РАЗНОСТЬДАТ: функция считает полные периоды. Между 15.03 23:00 и 16.03 01:00 разница в днях будет ноль, хотя календарные даты различаются. Если нужна разница в календарных днях, сначала приведите обе даты к началу дня через НАЧАЛОПЕРИОДА.
Как проверить результат
Дата ровно на один календарный день меньше исходной, время совпадает с ожиданием
Смещение на два дня или сброс времени, которого вы не планировали
Из 1-го числа получается последний день предыдущего месяца
Ошибка выполнения запроса или пустая дата
В выборку попали документы только вчерашнего дня
Попали документы сегодняшнего дня — проверьте границы периода
Типичные ошибки при вычитании даты
Первая и самая частая ошибка — попытка написать &Дата - 1. Язык запросов 1С не поддерживает арифметику дат оператором «минус», и запрос завершится с ошибкой синтаксиса или типов. Используйте только ДОБАВИТЬКДАТЕ с отрицательным параметром.
Вторая ошибка — указание типа периода в кавычках: ДОБАВИТЬКДАТЕ(&Дата, "ДЕНЬ", -1). Тип периода — это ключевое слово языка запросов, а не строка. Кавычки приведут к ошибке «Неверные параметры функции».
Третья ошибка — забыть про состав даты. Если поле или параметр имеет составной тип (например, дата или строка), функция может вернуть NULL для строковых значений. Проверяйте типы данных в консоли запросов, а при необходимости приводите значение через ВЫРАЗИТЬ(... КАК ДАТА).
Четвёртая ошибка касается пустых дат. Вычитание дня из пустой даты (01.01.0001) даст значение до начала летоисчисления, и сравнения с таким результатом будут вести себя неочевидно. Если поле может быть незаполненным, добавьте условие проверки: ВЫБОР КОГДА Поле = ДАТАВРЕМЯ(1,1,1) ТОГДА NULL ИНАЧЕ ДОБАВИТЬКДАТЕ(...) КОНЕЦ.
Вычитание дня вне запроса: сравнение с кодом
Во встроенном языке 1С дату можно вычитать напрямую: Вчера = ТекущаяДатаСеанса() - 86400; — здесь единица равна одной секунде, поэтому день — это 86400 секунд. Этот способ проще, но работает только в коде, не в тексте запроса и не в выражениях СКД.
Практический вывод простой: если запрос формируется программно, вычислите нужные даты в коде и передайте их параметрами — так текст запроса останется читаемым, а СУБД сможет использовать индексы. Функцию ДОБАВИТЬКДАТЕ оставьте для случаев, когда расчёт должен выполняться внутри запроса: в СКД, в динамических списках, в произвольных выражениях отчётов.
При работе с датами в распределённых базах и при обменах помните про часовые пояса: ТекущаяДатаСеанса() возвращает дату сеанса с учётом часового пояса, а «вчера» на сервере и у пользователя в другом поясе может различаться. Для критичных отборов фиксируйте дату явно, а не вычисляйте её на каждой стороне.
Что важно запомнить
Вычесть день в запросе 1С можно только функцией ДОБАВИТЬКДАТЕ(Дата, ДЕНЬ, -1) — оператор «минус» для дат не работает. Для отборов по дням отсекайте время через НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА, а в программных запросах предпочтительнее вычислять границы в коде и передавать параметрами.
Частые вопросы
Можно ли вычесть день оператором «минус» в запросе 1С?
Нет. Язык запросов не поддерживает арифметику дат через «плюс» и «минус» — такая запись вызовет ошибку. Единственный способ внутри запроса — функция ДОБАВИТЬКДАТЕ с отрицательным количеством периодов. Оператор «минус» для дат работает только во встроенном языке, где разность измеряется в секундах.
Как вычесть день и получить начало вчерашнего дня?
Объедините две функции: НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -1), ДЕНЬ). Результат — вчерашняя дата с временем 00:00:00. Для конца дня используйте КОНЕЦПЕРИОДА с теми же аргументами — получите 23:59:59.
Что произойдёт при вычитании дня из первого числа месяца?
Функция корректно перейдёт на последний день предыдущего месяца: из 1 марта получится 28 или 29 февраля в зависимости от високосности года, из 1 января — 31 декабря предыдущего года. Никаких специальных проверок для границ месяцев и лет не требуется.
Как вычесть переменное количество дней, хранящееся в реквизите?
Передайте поле третьим параметром со знаком минус: ДОБАВИТЬКДАТЕ(Документ.Дата, ДЕНЬ, -Контрагент.СрокДней). Если реквизит может быть незаполнен, оберните его в ЕСТЬNULL(Контрагент.СрокДней, 0), иначе результат для таких строк будет NULL.
Почему запрос с ДОБАВИТЬКДАТЕ в условии работает медленно?
Если функция применяется к полю таблицы (например, к дате документа), СУБД не может использовать индекс по этому полю и вынуждена сканировать таблицу целиком. Решение — вычислить граничные даты заранее и сравнивать поле с параметрами: Документ.Дата МЕЖДУ &Начало И &Конец.
Как проверить, что дата вычислена правильно?
Выполните запрос в консоли запросов и добавьте контрольное поле РАЗНОСТЬДАТ(ВычисленнаяДата, ИсходнаяДата, ДЕНЬ) — оно должно вернуть 1. Дополнительно проверьте граничные случаи: первое число месяца, 1 января и 1 марта в високосный год.