Прибавление периодов к дате в запросах и коде 1С
Содержание
Чтобы добавить к дате в 1С минуты, часы или дни, прибавьте нужное число секунд: Дата + 86400 даёт плюс одни сутки, Дата + 3600 — плюс час. Дата внутри платформы — это количество секунд, поэтому обычное сложение работает.
А вот месяцы и годы так не считаются: в месяце то 28, то 31 день. Для них есть отдельная функция ДобавитьМесяц во встроенном языке и оператор ДОБАВИТЬКДАТЕ в запросах. Ниже — какой инструмент под какую задачу и как не промахнуться на краях месяца и в високосный год.
Секунды, сутки и месяцы: сложение дат в коде
Во встроенном языке у даты нет оператора «прибавить месяц». Есть только сложение с числом секунд — им закрывают интервалы до недели включительно.
| Прибавить | Код | Секунд |
|---|---|---|
| 1 минуту | Дата + 60 | 60 |
| 1 час | Дата + 3600 | 3 600 |
| 1 сутки | Дата + 86400 | 86 400 |
| 1 неделю | Дата + 7*86400 | 604 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.2024 | 29.02.2024 | 2024-й високосный, в феврале 29 дней |
| 31.01.2023 | 28.02.2023 | 2023-й обычный, 28 дней |
| 31.03.2024 | 30.04.2024 | в апреле 30 дней |
| 15.01.2024 | 15.02.2024 | 15-е число есть и там |
То же правило работает и в обратную сторону: ДобавитьМесяц(Дата(2024, 3, 31), -1) вернёт 29.02.2024.
Проверить, високосный ли год, можно без формул с остатками от деления — просто спросив длину февраля:
Если День(КонецМесяца(Дата(Год, 2, 1))) = 29 Тогда
Сообщить("Год високосный");
КонецЕсли;
Если же считать сдвиг сложением секунд, края месяца и 29 февраля учтены не будут — это ещё один довод в пользу ДобавитьМесяц для любых месячных и годовых шагов.
Быстрая выборка по датам: границы вместо функций над полем
Когда даты участвуют в отборе, важно не тормозить запрос. Правило одно: не оборачивайте поле даты в функцию внутри условия — так индекс по дате не используется, и платформа читает всю таблицу.
Плохо (функция над каждой строкой):
ГДЕ ГОД(Продажа.Дата) = 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.