Прибавление периодов к дате в запросах и коде 1С

· 8 мин

Содержание

Чтобы добавить к дате в 1С минуты, часы или дни, прибавьте нужное число секунд: Дата + 86400 даёт плюс одни сутки, Дата + 3600 — плюс час. Дата внутри платформы — это количество секунд, поэтому обычное сложение работает.

А вот месяцы и годы так не считаются: в месяце то 28, то 31 день. Для них есть отдельная функция ДобавитьМесяц во встроенном языке и оператор ДОБАВИТЬКДАТЕ в запросах. Ниже — какой инструмент под какую задачу и как не промахнуться на краях месяца и в високосный год.

Секунды, сутки и месяцы: сложение дат в коде

Во встроенном языке у даты нет оператора «прибавить месяц». Есть только сложение с числом секунд — им закрывают интервалы до недели включительно.

ПрибавитьКодСекунд
1 минутуДата + 6060
1 часДата + 36003 600
1 суткиДата + 8640086 400
1 неделюДата + 7*86400604 800

Для N дней — умножайте: Дата + N * 86400. Вычитание работает так же, с минусом.

Месяцы, кварталы и годы считает функция ДобавитьМесяц(Дата, КоличествоМесяцев):

ДатаПлюсМесяц   = ДобавитьМесяц(Документ.Дата, 1);
ДатаПлюсКвартал = ДобавитьМесяц(Документ.Дата, 3);
ДатаПлюсГод     = ДобавитьМесяц(Документ.Дата, 12);
ДатаМинусМесяц  = ДобавитьМесяц(Документ.Дата, -1);

Отрицательный аргумент сдвигает дату назад. Квартал — это три месяца, полугодие — шесть, год — двенадцать; отдельных функций под них нет, всё через ДобавитьМесяц.

Частый промах — прибавить 30 * 86400, чтобы «сдвинуть на месяц». В апреле это даст перелёт на день, в феврале — недолёт. Для месяцев используйте только ДобавитьМесяц.

Оператор ДОБАВИТЬКДАТЕ в языке запросов

В запросе период к дате добавляет функция ДОБАВИТЬКДАТЕ(Дата, ТипПериода, Количество). Она удобна, когда сдвиг нужно получить прямо в выборке, а не в коде после неё.

ВЫБРАТЬ
    Заказ.Ссылка,
    Заказ.Дата КАК ДатаЗаказа,
    ДОБАВИТЬКДАТЕ(Заказ.Дата, ДЕНЬ, 3)   КАК КрайнийСрок,
    ДОБАВИТЬКДАТЕ(Заказ.Дата, МЕСЯЦ, 1)  КАК ДатаПродления
ИЗ
    Документ.ЗаказПокупателя КАК Заказ

Тип периода задаётся словом без кавычек. Основные значения:

  • СЕКУНДА, МИНУТА, ЧАС — время;
  • ДЕНЬ, НЕДЕЛЯ, ДЕКАДА — дни и их группы;
  • МЕСЯЦ, КВАРТАЛ, ГОД — длинные периоды.

Чтобы наоборот — узнать, сколько между двумя датами, берут РАЗНОСТЬДАТ(Дата1, Дата2, ТипПериода). Результат — число в выбранных единицах:

ВЫБРАТЬ
    РАЗНОСТЬДАТ(Заказ.Дата, &ТекущаяДата, ДЕНЬ) КАК ДнейПрошло
ИЗ
    Документ.ЗаказПокупателя КАК Заказ

Литерал даты в запросе задаётся функцией ДАТАВРЕМЯ: ДАТАВРЕМЯ(2024, 1, 1) — начало года, ДАТАВРЕМЯ(2024, 12, 31, 23, 59, 59) — конец дня 31 декабря.

Начало и конец периода: границы без ручного счёта

Часто нужна не «дата плюс месяц», а начало или конец периода. Для этого — отдельная группа функций, и не надо вручную подставлять 1-е число или 23:59:59.

Во встроенном языке:

НачМесяца  = НачалоМесяца(ТекущаяДатаСеанса());   // 1-е число, 00:00:00
КонМесяца  = КонецМесяца(ТекущаяДатаСеанса());    // последний день, 23:59:59
НачГода    = НачалоГода(ТекущаяДатаСеанса());
КонКвартала = КонецКвартала(ТекущаяДатаСеанса());

Аналогично есть НачалоДня, КонецДня, НачалоНедели, КонецНедели, НачалоЧаса, НачалоМинуты. В запросах тем же занимаются НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ) и КОНЕЦПЕРИОДА(Дата, МЕСЯЦ).

Функции комбинируются. Первый день следующего месяца — это начало месяца от даты, сдвинутой вперёд:

ПервоеЧислоСледующего = НачалоМесяца(ДобавитьМесяц(Дата, 1));

КонецМесяца и КОНЕЦПЕРИОДА возвращают время 23:59:59, а не 00:00:00. Если нужна «чистая» дата последнего дня без времени, оберните результат в НачалоДня.

Високосный год и переход через край месяца

Главное удобство ДобавитьМесяц — она сама следит за длиной месяца. Если в целевом месяце дня с таким номером нет, функция ставит последний день месяца.

Исходная датаДобавитьМесяц(Дата, 1)Почему
31.01.202429.02.20242024-й високосный, в феврале 29 дней
31.01.202328.02.20232023-й обычный, 28 дней
31.03.202430.04.2024в апреле 30 дней
15.01.202415.02.202415-е число есть и там

То же правило работает и в обратную сторону: ДобавитьМесяц(Дата(2024, 3, 31), -1) вернёт 29.02.2024.

Проверить, високосный ли год, можно без формул с остатками от деления — просто спросив длину февраля:

Если День(КонецМесяца(Дата(Год, 2, 1))) = 29 Тогда
    Сообщить("Год високосный");
КонецЕсли;

Если же считать сдвиг сложением секунд, края месяца и 29 февраля учтены не будут — это ещё один довод в пользу ДобавитьМесяц для любых месячных и годовых шагов.

Проверьте себя
Что вернёт `Дата(2024, 1, 31) + 86400`?
Чему равен `ДобавитьМесяц(Дата(2024, 1, 31), 1)`?
Какой оператор добавляет период к дате в языке запросов?

Быстрая выборка по датам: границы вместо функций над полем

Когда даты участвуют в отборе, важно не тормозить запрос. Правило одно: не оборачивайте поле даты в функцию внутри условия — так индекс по дате не используется, и платформа читает всю таблицу.

Плохо (функция над каждой строкой):

ГДЕ ГОД(Продажа.Дата) = 2024

Хорошо (сравнение с границами, индекс работает):

ГДЕ Продажа.Дата >= &НачалоГода
    И Продажа.Дата < &НачалоСледующегоГода

Границы при этом считают в коде и передают параметрами:

Запрос.УстановитьПараметр("НачалоГода", НачалоГода(Дата));
Запрос.УстановитьПараметр("НачалоСледующегоГода", НачалоГода(ДобавитьМесяц(Дата, 12)));

Для периодов надёжнее полуоткрытый интервал: >= начало И < начало следующего, а не МЕЖДУ. Оператор МЕЖДУ включает обе границы, и документ со временем 23:59:59 на верхней границе может попасть или не попасть в выборку в зависимости от того, какое время вы подставили.

Для остатков и оборотов регистров период задают не в условии, а параметром виртуальной таблицы — Остатки(&Период) или Обороты(&Начало, &Конец). Это и быстрее, и корректнее, чем фильтровать итоговую выборку.

Преобразование и форматирование даты

Дату собирают из частей или из строки, а показывают пользователю через форматирование — это разные операции.

Создать значение даты:

Д1 = Дата(2024, 3, 15);            // только дата
Д2 = Дата(2024, 3, 15, 12, 30, 0); // с временем
Д3 = Дата("20240315120000");       // из строки ГГГГММДДччммсс
Д4 = '20240315';                   // литерал прямо в коде

Пустая дата — это Дата(1, 1, 1) или литерал '00010101'. Именно с ней сравнивают поле, когда проверяют «дата не заполнена»:

Если Элемент.ДатаЗакрытия = '00010101' Тогда
    // дата не указана
КонецЕсли;

Показать дату строкой — функция Формат:

ЗадачаВызовРезультат
ЧисломФормат(Дата, "ДФ=dd.MM.yyyy")15.03.2024
С временемФормат(Дата, "ДФ=dd.MM.yyyy HH:mm")15.03.2024 12:30
ПрописьюФормат(Дата, "ДЛФ=D")15 марта 2024 г.
Дата и времяФормат(Дата, "ДЛФ=DT")15 марта 2024 г. 12:30:00

В запросе привести значение к дате помогает ВЫРАЗИТЬ(Поле КАК ДАТА) — например, когда поле составного типа и нужно работать только с его датной частью.

Обратная операция — разложить дату на составляющие. Для этого есть функции, которые возвращают число:

Г  = Год(Дата);        // 2024
М  = Месяц(Дата);      // 3
Д  = День(Дата);       // 15
ДН = ДеньНедели(Дата); // 1 — понедельник … 7 — воскресенье
Кв = Цел((Месяц(Дата) - 1) / 3) + 1; // квартал: 1–4

ДеньНедели удобна, когда срок нельзя назначать на выходной: получили номер дня, и если это 6 или 7 — сдвинули вперёд. В языке запросов те же функции называются ГОД, МЕСЯЦ, ДЕНЬ, ДЕНЬНЕДЕЛИ, КВАРТАЛ и работают над полем выборки.

Готовые формулы для типовых сроков

Большинство задач с датами в учёте сводятся к нескольким шаблонам. Вот рабочие заготовки — подставляйте свои поля.

Срок оплаты счёта — дата плюс N дней. Отсрочка почти всегда в днях, поэтому сложение секунд подходит:

СрокОплаты = Счет.Дата + Счет.ОтсрочкаДней * 86400;

Дата окончания договора — плюс год или несколько месяцев. Здесь уже нужна ДобавитьМесяц, иначе на 29 февраля будет сбой:

ОкончаниеДоговора = ДобавитьМесяц(Договор.Дата, Договор.СрокМесяцев);

Следующее плановое действие по графику — плюс квартал. Например, дата очередного обслуживания или сверки:

СледующаяСверка = ДобавитьМесяц(ПоследняяСверка, 3);

Возраст или стаж в полных годах — через разницу дат. В коде считают по разнице и делению, в запросе — через РАЗНОСТЬДАТ:

ВЫБРАТЬ
    Сотрудник.Наименование,
    РАЗНОСТЬДАТ(Сотрудник.ДатаПриема, &ТекущаяДата, ГОД) КАК СтажЛет
ИЗ
    Справочник.Сотрудники КАК Сотрудник

Для отчёта за период границы считайте один раз и передавайте параметрами: НачалоМесяца(Дата) и НачалоМесяца(ДобавитьМесяц(Дата, 1)). Тогда и расчёт корректный, и запрос использует индекс по дате.

Частые ошибки при работе с датами

Ошибки с датами редко падают с сообщением — чаще дают тихо неверный результат. Вот те, что встречаются постоянно.

  • Дата + 1 вместо + 86400. Прибавляется одна секунда, а не день. Единица измерения сложения — всегда секунда.
  • Месяц через 30 * 86400. В 7 месяцах из 12 это промах на 1–3 дня, 29 февраля игнорируется. Только ДобавитьМесяц.
  • ТекущаяДата() на сервере. В клиент-серверной базе она берёт дату компьютера сервера, без учёта часового пояса сеанса. Для бизнес-логики используйте ТекущаяДатаСеанса().
  • Верхняя граница без учёта 23:59:59. Отбор <= КонецДня с документом, у которого время ровно 23:59:59, ведёт себя не так, как ожидают. Берите полуоткрытый интервал < начало следующего дня.
  • Склейка даты со строкой без Формат. Выражение "Срок: " + Дата выдаст дату во внутреннем виде. Оборачивайте: "Срок: " + Формат(Дата, "ДФ=dd.MM.yyyy").

Если расчёт сроков идёт в проведении документа или в регламентном задании, проверьте пример на 31-м числе и на феврале високосного года — именно там всплывает большинство дефектов.

Частые вопросы

Чем `ТекущаяДата()` отличается от `ТекущаяДатаСеанса()`?
ТекущаяДата() возвращает дату того компьютера, где исполняется код: на сервере — серверную, без часового пояса сеанса. ТекущаяДатаСеанса() учитывает часовой пояс сеанса, поэтому в клиент-серверной базе берут её.
Как получить последний день месяца как чистую дату без времени?
КонецМесяца даёт последний день со временем 23:59:59. Чтобы убрать время, оберните результат: НачалоДня(КонецМесяца(Дата)) — получите тот же день с временем 00:00:00.
Можно ли вычесть одну дату из другой?
Да. Во встроенном языке Дата1 - Дата2 вернёт разницу в секундах — разделите на 86400, чтобы получить дни. В запросе для этого есть РАЗНОСТЬДАТ с нужным типом периода.
Как прибавить рабочие дни, а не календарные?
Штатной функции нет: платформа не знает про выходные и праздники. Рабочие дни считают по производственному календарю — регистру сведений, который заполняется данными календаря и учитывает переносы.
Почему `Дата + 1` не прибавляет сутки?
Потому что сложение даты с числом — это прибавление секунд, а не дней. Дата + 1 сдвинет значение на одну секунду. Сутки — это Дата + 86400.