Добавить к дате в запросе 1С можно одной функцией — ДОБАВИТЬКДАТЕ. Она сдвигает дату вперёд или назад на заданное число секунд, дней, месяцев или лет прямо в тексте запроса, без обработки выборки в коде. Ниже — синтаксис со всеми единицами, прибавление значения из реквизита, аналоги во встроенном языке и в БСП, обратная функция РАЗНОСТЬДАТ и ошибки, на которых чаще всего теряют время. Общие правила работы с датами в запросе — в статье о датах в запросе 1С.
Арифметики над датами в языке запросов нет: выражение Док.Дата + 1 запрос не примет. Сдвинуть дату можно только функцией. Текущей даты в языке запросов тоже нет — передавайте её параметром, например ТекущаяДатаСеанса().
ДОБАВИТЬКДАТЕ(<Дата>, <ТипУвеличения>, <Величина>)
СЕКУНДА, МИНУТА, ЧАС, ДЕНЬ, НЕДЕЛЯ, МЕСЯЦ, КВАРТАЛ, ГОД, а также ДЕКАДА и ПОЛУГОДИЕ.МИНУТА, 90, а не ЧАС, 1.5.Что даёт каждая единица, если &Дата = 31.01.2024 10:00:00:
| Выражение | Результат | Комментарий |
|---|---|---|
ДОБАВИТЬКДАТЕ(&Дата, СЕКУНДА, -1) | 31.01.2024 09:59:59 | граница «до даты, не включая» |
ДОБАВИТЬКДАТЕ(&Дата, МИНУТА, 90) | 31.01.2024 11:30:00 | дробные часы — через минуты |
ДОБАВИТЬКДАТЕ(&Дата, ЧАС, 24) | 01.02.2024 10:00:00 | то же, что день |
ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, 1) | 01.02.2024 10:00:00 | время сохраняется |
ДОБАВИТЬКДАТЕ(&Дата, НЕДЕЛЯ, 2) | 14.02.2024 10:00:00 | ровно 14 дней |
ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, 1) | 29.02.2024 10:00:00 | 31 февраля нет — последний день месяца |
ДОБАВИТЬКДАТЕ(&Дата, КВАРТАЛ, 1) | 30.04.2024 10:00:00 | три месяца, в апреле 30 дней |
ДОБАВИТЬКДАТЕ(&Дата, ГОД, -1) | 31.01.2023 10:00:00 | год назад |
Месяцы, кварталы и годы прибавляются по календарю, а не фиксированным числом дней. Если в целевом месяце нужного числа нет, получается последний день месяца. Обратного правила нет: 29.02.2024 плюс месяц — 29.03, а не 31.03. Поэтому сдвиг туда и обратно не всегда возвращает исходную дату: 31.01.2024 плюс месяц и минус месяц даёт 29.01.2024.
ДЕКАДА и ПОЛУГОДИЕ применяйте к уже выровненной дате — после НАЧАЛОПЕРИОДА(..., ДЕКАДА) или НАЧАЛОПЕРИОДА(..., ПОЛУГОДИЕ) — и проверяйте результат в консоли запросов.
ВЫБРАТЬ
Заказ.Ссылка КАК Заказ,
ДОБАВИТЬКДАТЕ(Заказ.Дата, ДЕНЬ, 1) КАК ЧерезСуткиПослеЗаказа,
ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(Заказ.Дата, ДЕНЬ), ДЕНЬ, 1) КАК НачалоСледующегоДня
ИЗ
Документ.ЗаказКлиента КАК Заказ
ГДЕ
Заказ.Дата >= ДОБАВИТЬКДАТЕ(&ТекущаяДата, ДЕНЬ, -30)
ДЕНЬ, 1 сохраняет время документа. Если нужна граница суток, сначала обрежьте время через НАЧАЛОПЕРИОДА, потом прибавляйте. В условии ГДЕ функция применена к параметру: её значение вычисляется один раз, а СУБД ищет по индексу на поле Дата. Условие ДОБАВИТЬКДАТЕ(Заказ.Дата, ДЕНЬ, 30) >= &ТекущаяДата даст тот же отбор, но функция будет считаться для каждой строки и индекс не сработает. Для секунд, минут, часов, дней и недель переносите сдвиг на сторону параметра со сменой знака.
| Нужно | Выражение |
|---|---|
| начало прошлого месяца | ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&Дата, МЕСЯЦ), МЕСЯЦ, -1) |
| конец прошлого месяца | ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&Дата, МЕСЯЦ), СЕКУНДА, -1) |
| последний день следующего месяца | КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, 1), МЕСЯЦ) |
| начало следующего квартала | ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&Дата, КВАРТАЛ), КВАРТАЛ, 1) |
Если нужен именно последний день месяца, не полагайтесь на прибавление месяцев к произвольной дате — оборачивайте результат в КОНЕЦПЕРИОДА.
Функция работает и в параметрах виртуальных таблиц. Продажи за тот же период прошлого года:
ВЫБРАТЬ
Продажи.Номенклатура КАК Номенклатура,
Продажи.КоличествоОборот КАК КоличествоГодНазад
ИЗ
РегистрНакопления.Продажи.Обороты(
ДОБАВИТЬКДАТЕ(&НачалоПериода, ГОД, -1),
КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&КонецПериода, ГОД, -1), МЕСЯЦ), , ) КАК Продажи
КОНЕЦПЕРИОДА на правой границе нужен, когда период — целые месяцы. Без него 28.02.2025 минус год даст 28.02.2024, и продажи 29 февраля выпадут. Для произвольного периода внутри месяца обёртку уберите.
Третий параметр не обязан быть константой. Типичная задача — платёжный календарь: дата отгрузки плюс отсрочка в днях из договора. Функция ниже возвращает документы, срок оплаты которых наступает с сегодняшнего дня по седьмой включительно. Код серверный: запрос на клиенте не выполнить, а таблицу значений на клиент не передать.
// Документы реализации, срок оплаты которых наступает в ближайшие дни.
// Срок оплаты = дата документа + отсрочка из договора в календарных днях.
// Место: общий модуль с флагом «Сервер» или модуль объекта отчёта/обработки.
// Имена документа и реквизитов условные: подставьте свои.
Функция ДокументыКОплатеВБлижайшиеДни(Знач ДатаКонтроля, Знач ДнейВперед = 7) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК Документ,
| Реализация.Контрагент КАК Контрагент,
| Реализация.СуммаДокумента КАК Сумма,
| ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(Реализация.Дата, ДЕНЬ), ДЕНЬ,
| ЕСТЬNULL(Реализация.Договор.СрокОплатыДней, 0)) КАК СрокОплаты
|ПОМЕСТИТЬ ВТСроки
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Проведен
| И Реализация.Дата >= ДОБАВИТЬКДАТЕ(&ДатаКонтроля, ГОД, -1)
| И Реализация.Дата < ДОБАВИТЬКДАТЕ(&ДатаКонтроля, ДЕНЬ, &ДнейВперед + 1)
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТСроки.Документ КАК Документ,
| ВТСроки.Контрагент КАК Контрагент,
| ВТСроки.Сумма КАК Сумма,
| ВТСроки.СрокОплаты КАК СрокОплаты,
| РАЗНОСТЬДАТ(&ДатаКонтроля, ВТСроки.СрокОплаты, ДЕНЬ) КАК ДнейДоОплаты
|ИЗ
| ВТСроки КАК ВТСроки
|ГДЕ
| ВТСроки.СрокОплаты МЕЖДУ &ДатаКонтроля И ДОБАВИТЬКДАТЕ(&ДатаКонтроля, ДЕНЬ, &ДнейВперед)
|
|УПОРЯДОЧИТЬ ПО
| СрокОплаты,
| Сумма УБЫВ";
Запрос.УстановитьПараметр("ДатаКонтроля", НачалоДня(ДатаКонтроля));
Запрос.УстановитьПараметр("ДнейВперед", ДнейВперед);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Разбор ключевых строк:
НАЧАЛОПЕРИОДА(Реализация.Дата, ДЕНЬ) — срок оплаты считаем от начала дня отгрузки, иначе он унаследует время документа и сравнение с &ДатаКонтроля собьётся на сутки.ЕСТЬNULL(Реализация.Договор.СрокОплатыДней, 0) — если договор не заполнен, поле через точку вернёт NULL, а ДОБАВИТЬКДАТЕ с NULL тоже даст NULL, и документ молча выпадет из отбора.Реализация.Дата в первом запросе используют индекс и отсекают старые документы. Окно в год — допущение, что отсрочки короче года; если бывают длиннее, расширьте его.ГДЕ.РАЗНОСТЬДАТ(&ДатаКонтроля, ВТСроки.СрокОплаты, ДЕНЬ) — сколько дней осталось. Обе даты — начало дня, поэтому результат точный.Оплаты функция не учитывает: чтобы оставить только неоплаченные документы, соедините результат с остатками регистра расчётов с клиентами. Как строить выборки документов по периоду и условиям — в статье о запросах к документам.
В коде к дате прибавляются секунды: Дата + 1 — это плюс одна секунда, а не день. Функции «ДобавитьДень» в платформе нет, дни считают умножением на 86400. Месяцы и годы — только через ДобавитьМесяц(): 30 * 86400 вместо месяца ошибается на коротких и длинных месяцах.
| Задача | В запросе | Во встроенном языке |
|---|---|---|
| плюс N минут | ДОБАВИТЬКДАТЕ(Д, МИНУТА, N) | Д + 60 * N |
| плюс N часов | ДОБАВИТЬКДАТЕ(Д, ЧАС, N) | Д + 3600 * N |
| плюс N дней | ДОБАВИТЬКДАТЕ(Д, ДЕНЬ, N) | Д + 86400 * N |
| плюс N недель | ДОБАВИТЬКДАТЕ(Д, НЕДЕЛЯ, N) | Д + 7 * 86400 * N |
| плюс N месяцев | ДОБАВИТЬКДАТЕ(Д, МЕСЯЦ, N) | ДобавитьМесяц(Д, N) |
| плюс N кварталов | ДОБАВИТЬКДАТЕ(Д, КВАРТАЛ, N) | ДобавитьМесяц(Д, 3 * N) |
| плюс N лет | ДОБАВИТЬКДАТЕ(Д, ГОД, N) | ДобавитьМесяц(Д, 12 * N) |
| дней между датами | РАЗНОСТЬДАТ(Д1, Д2, ДЕНЬ) | (НачалоДня(Д2) - НачалоДня(Д1)) / 86400 |
// Модуль формы: функция доступна и на клиенте, и на сервере.
&НаКлиентеНаСервереБезКонтекста
Функция СдвигиДаты(Знач БазоваяДата)
НачалоСуток = НачалоДня(БазоваяДата);
Результат = Новый Структура;
Результат.Вставить("Завтра", НачалоСуток + 86400);
Результат.Вставить("ЧерезНеделю", НачалоСуток + 7 * 86400);
Результат.Вставить("ЧерезМесяц", ДобавитьМесяц(НачалоСуток, 1));
Результат.Вставить("ГодНазад", ДобавитьМесяц(НачалоСуток, -12));
Результат.Вставить("КонецПрошлогоМесяца", НачалоМесяца(НачалоСуток) - 1);
Результат.Вставить("ДнейДоКонцаГода", (НачалоДня(КонецГода(НачалоСуток)) - НачалоСуток) / 86400);
Возврат Результат;
КонецФункции
ДобавитьМесяц() так же, как запрос, переносит 31-е число на последний день короткого месяца. Разность двух дат в коде — число секунд, поэтому для дней обе даты сначала приводят к началу дня.
ДОБАВИТЬКДАТЕ и ДобавитьМесяц() считают календарные дни. Если срок задан в рабочих днях, в типовых конфигурациях на БСП обычно есть подсистема «Календарные графики» и функция КалендарныеГрафики.ДатаПоКалендарю():
// Сервер. БСП, подсистема «Календарные графики» (сверено по БСП 3.1.10).
Функция ДатаЧерезРабочиеДни(Знач ДатаОт, Знач РабочихДней) Экспорт
Календарь = КалендарныеГрафики.ОсновнойПроизводственныйКалендарь();
Если Не ЗначениеЗаполнено(Календарь) Тогда
Возврат Неопределено;
КонецЕсли;
// Ложь - вернуть Неопределено вместо исключения, если календарь на нужный год не заполнен.
Возврат КалендарныеГрафики.ДатаПоКалендарю(Календарь, ДатаОт, РабочихДней, Ложь);
КонецФункции
Функция отсчитывает дни со следующего после ДатаОт дня и возвращает начало дня; по производственному календарю предпраздничные дни считаются рабочими. Первым параметром можно передать и график работы (справочник «Календари»), если в конфигурации есть подсистема «Графики работы». Обратная функция — КалендарныеГрафики.РазностьДатПоКалендарю(). Внутри запроса рабочие дни считают соединением с регистром сведений ДанныеПроизводственногоКалендаря — это уже отдельная задача.
РАЗНОСТЬДАТ(Начало, Конец, ТипПериода) возвращает целое число периодов между датами; если начало позже конца, результат отрицательный. Типы периода — СЕКУНДА, МИНУТА, ЧАС, ДЕНЬ, МЕСЯЦ, КВАРТАЛ, ГОД: недели, декады и полугодия здесь нет. Единицы мельче указанной функция отбрасывает, поэтому считает границы периодов, а не полные периоды: между 31.01 и 01.02 — один месяц, между 23:59 и 00:01 следующих суток — один день. Для возраста или стажа в полных годах такой результат не годится: сравнивайте дату с ДОБАВИТЬКДАТЕ(ДатаРождения, ГОД, N) или считайте в коде.
Док.Дата + 1 или &Дата - 86400 запрос не выполнит: платформа сообщит об ошибке в тексте запроса с позицией {(строка, колонка)}. Нужна ДОБАВИТЬКДАТЕ.ДЕНЬ, а не "ДЕНЬ", со строкой запрос не пройдёт проверку синтаксиса. В выражениях СКД по документации наоборот — строка. Путаница возникает, когда выражение копируют из параметра схемы в запрос набора данных.Объект.Дата + 1 сдвигает дату на секунду. На форме разницы не видно, а отбор «за день» потом не находит документ.ЕСТЬNULL.МЕЖДУ &Начало И &Конец, где &Конец = 31.01 00:00:00, теряет весь последний день. Берите КОНЕЦПЕРИОДА(&Конец, ДЕНЬ) или условие < ДОБАВИТЬКДАТЕ(&Конец, ДЕНЬ, 1) от начала дня.ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(&Дата, МЕСЯЦ), СЕКУНДА, -1) — последняя секунда прошлого месяца. Равносильный вариант — КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, -1), МЕСЯЦ).
Да, третий параметр принимает поле или выражение: ДОБАВИТЬКДАТЕ(Док.Дата, ДЕНЬ, Док.Договор.СрокОплатыДней). Реквизит должен быть числовым. Если значение может отсутствовать, оберните его в ЕСТЬNULL(..., 0).
В запросе набора данных — так же, как в обычном запросе. В выражениях параметров и вычисляемых полях работает язык выражений СКД, и там тип увеличения пишется строкой: ДобавитьКДате(&НачалоПериода, "Месяц", -1).
ДОБАВИТЬКДАТЕ производственный календарь не учитывает. В коде используйте КалендарныеГрафики.ДатаПоКалендарю() из БСП, в запросе — соединение с данными производственного календаря.
NULL пришёл в дату или в величину: поле через точку от пустой ссылки или поле правой таблицы левого соединения. Найдите источник в консоли запросов и подставьте значение по умолчанию через ЕСТЬNULL.
Если нужен платёжный календарь, отчёт о просрочках в рабочих днях или сравнение с прошлым годом на доработанной конфигурации, быстрее отдать задачу программисту. Такие отчёты на СКД и обработки делаются в рамках услуги отчёты и обработки 1С. Опишите задачу на странице «Разместить задание» — оценка бесплатная, ответ в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.