Вычитаем месяцы из даты: встроенный язык, запросы и СКД
Содержание
Отнять от даты один или несколько месяцев в 1С 8.3 проще всего функцией ДобавитьМесяц с отрицательным аргументом: ДобавитьМесяц(Дата, -1). Она есть и в 8.3, и в 8.2, вызывается из любого модуля и сама разбирается с разной длиной месяцев.
Та же задача во встроенном языке, в языке запросов и в СКД решается разными функциями. Ниже — как вычесть месяц от текущей даты, как получить начало и конец прошлого месяца и почему нельзя отнимать месяц вычитанием секунд.
В официальной документации нет функций с именами ДатаДоб() или НачМесяца(). Рабочие имена встроенного языка — ДобавитьМесяц(), НачалоМесяца(), КонецМесяца(). Их и используйте.
Главный инструмент: функция ДобавитьМесяц
ДобавитьМесяц(<Дата>, <ЧислоМесяцев>) сдвигает дату на указанное число месяцев. Чтобы отнять — передайте отрицательное число.
// минус один месяц от текущей даты сеанса
ПрошлыйМесяц = ДобавитьМесяц(ТекущаяДатаСеанса(), -1);
// минус три месяца от конкретной даты
Квартал = ДобавитьМесяц(Дата(2026, 1, 15), -3); // 15.10.2025
Главное достоинство функции — она сама обрабатывает «неудобные» даты. Если в месяце-получателе нужного числа нет, возвращается последний день этого месяца:
// 31 марта минус месяц = 29 февраля (2024 — високосный)
Результат = ДобавитьМесяц(Дата(2024, 3, 31), -1); // 29.02.2024
Не пытайтесь воспроизвести это руками через Число() и Дата() — логика «последнего дня» уже встроена, свой код только добавит ошибок.
Функция доступна и на клиенте, и на сервере, и в общих модулях — отдельное подключение не нужно. Число месяцев может быть любым целым: -1 отнимает один месяц, -18 — полтора года, положительное число прибавляет. Возвращается значение того же типа «Дата», что и аргумент, поэтому результат сразу можно сравнивать с другими датами или записывать в реквизит.
Время в результате сохраняется от исходной даты. Если нужны «чистые» сутки без часов и минут, оберните результат в НачалоДня:
ДатаБезВремени = НачалоДня(ДобавитьМесяц(ДокументДата, -1));
Несколько частых формулировок одной задачи и готовый код:
| Что нужно | Код встроенного языка |
|---|---|
| Отнять 1 месяц | ДобавитьМесяц(Дата, -1) |
| Отнять 6 месяцев (полгода) | ДобавитьМесяц(Дата, -6) |
| Отнять год | ДобавитьМесяц(Дата, -12) |
| Вычесть 2 года | ДобавитьМесяц(Дата, -24) |
Начало и конец прошлого месяца
Для отчётов обычно нужна не произвольная дата минус месяц, а границы прошлого месяца целиком. Их дают НачалоМесяца и КонецМесяца.
// первый день прошлого месяца, 00:00:00
НачалоПрошлого = НачалоМесяца(ДобавитьМесяц(ТекущаяДатаСеанса(), -1));
// последний день прошлого месяца, 23:59:59
КонецПрошлого = КонецМесяца(ДобавитьМесяц(ТекущаяДатаСеанса(), -1));
Порядок вызовов не важен — результат совпадёт, если сначала сдвинуть, а потом взять границу, или наоборот. Но сдвиг первым читается понятнее: «от сегодняшней даты шагнули на месяц назад, потом взяли начало месяца».
КонецМесяца возвращает время 23:59:59. Если используете эту дату как верхнюю границу периода в отчёте, учитывайте секунды: документ, проведённый в 23:59:58, в период попадёт, а созданный ровно в полночь следующих суток — уже нет.
Полезные производные для стандартных периодов:
- 📅 начало прошлого квартала —
НачалоКвартала(ДобавитьМесяц(Дата, -3)); - 📅 начало прошлого года —
НачалоГода(ДобавитьМесяц(Дата, -12)); - 📅 ровно месяц назад с сохранением числа —
ДобавитьМесяц(Дата, -1)без обёрток.
ДобавитьМесяц(Дата, -N); для границ периода добавьте НачалоМесяца или КонецМесяца.ДОБАВИТЬКДАТЕ(Поле, МЕСЯЦ, -N); для границ — НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА.&ТекущаяДата, если можно подготовить параметр.Вычитание месяца в языке запросов и СКД
Внутри запроса функции встроенного языка недоступны — там свои имена. Отнять месяц помогает ДОБАВИТЬКДАТЕ.
ВЫБРАТЬ
Продажи.Период КАК Период,
ДОБАВИТЬКДАТЕ(Продажи.Период, МЕСЯЦ, -1) КАК ПредыдущийМесяц
ИЗ
РегистрНакопления.Продажи КАК Продажи
Синтаксис — ДОБАВИТЬКДАТЕ(<Дата>, <ВидИнтервала>, <Количество>). Вид интервала пишется без кавычек: МЕСЯЦ, ДЕНЬ, ГОД, КВАРТАЛ, НЕДЕЛЯ, ЧАС, МИНУТА, СЕКУНДА. Отрицательное количество отнимает.
Границы периода в запросе дают НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА:
// первый день прошлого месяца
НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, -1), МЕСЯЦ)
// последний день прошлого месяца
КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, -1), МЕСЯЦ)
В СКД то же выражение можно задать прямо в поле вычисляемого ресурса или в пользовательском поле-выражении — движок СКД использует синтаксис языка запросов, поэтому код из примеров выше переносится без изменений.
Соответствие функций в двух языках — чтобы не путать имена при переносе кода между модулем и запросом:
| Задача | Встроенный язык | Язык запросов и СКД |
|---|---|---|
| Сдвиг на месяцы | ДобавитьМесяц(Д, -N) | ДОБАВИТЬКДАТЕ(Д, МЕСЯЦ, -N) |
| Начало месяца | НачалоМесяца(Д) | НАЧАЛОПЕРИОДА(Д, МЕСЯЦ) |
| Конец месяца | КонецМесяца(Д) | КОНЕЦПЕРИОДА(Д, МЕСЯЦ) |
| Разница в месяцах | считается вручную | РАЗНОСТЬДАТ(Д1, Д2, МЕСЯЦ) |
Если дата минус месяц нужна как параметр отбора всего запроса, посчитайте её во встроенном языке и передайте через УстановитьПараметр("ДатаОт", НачалоМесяца(ДобавитьМесяц(ТекущаяДатаСеанса(), -1))). Это читабельнее, чем городить ДОБАВИТЬКДАТЕ вокруг &ТекущаяДата в каждом условии.
Почему нельзя отнимать месяц секундами и циклами
Дата в 1С хранится как число секунд, поэтому велик соблазн отнять «тридцать дней» вычитанием. Так делать нельзя: месяцы разной длины.
// НЕВЕРНО: 30 суток в секундах
Неправильно = Дата(2026, 3, 31) - 30 * 86400; // получится 01.03.2026, а не февраль
В примере от 31 марта вычли 30 суток и получили 1 марта — тот же месяц, а не предыдущий. Для 31-дневных месяцев сдвиг не попадёт в нужную точку, для февраля промахнётся ещё сильнее.
Второй распространённый приём — вычитать месяц в цикле «по одному». Он тоже лишний: ДобавитьМесяц принимает любое отрицательное число за один вызов.
// Лишний цикл
Дата = ИсходнаяДата;
Для Сч = 1 По 6 Цикл
Дата = ДобавитьМесяц(Дата, -1);
КонецЦикла;
// То же самое одной строкой
Дата = ДобавитьМесяц(ИсходнаяДата, -6);
Вычитание секунд оправдано только для фиксированных интервалов — «минус сутки», «минус час». Для сдвига по календарю (месяц, квартал, год) всегда берите ДобавитьМесяц (год — это -12 месяцев) или ДОБАВИТЬКДАТЕ.
Граничные случаи: пустая дата, минимум и конец месяца
В 1С нет отрицательных дат. Диапазон дат — от 01.01.0001 до 31.12.9999. Пустая дата хранится как 01.01.0001 00:00:00 — это и есть «нулевое» значение реквизита типа «Дата».
Из этого следуют практичные правила:
- 🔹 Проверяйте пустую дату до вычитания.
ДобавитьМесяц('00010101', -1)упрётся в нижнюю границу. Перед сдвигом ставьте проверкуЕсли ЗначениеЗаполнено(Дата) Тогда. - 🔹 Не уходите ниже минимума. Вычитание большого числа месяцев от ранней даты даст ошибку выхода за диапазон, а не «отрицательную» дату.
- 🔹 Помните про перенос на последний день. Для 29, 30 и 31 числа результат может «съехать» на конец более короткого месяца — это не ошибка, а штатное поведение
ДобавитьМесяц.
Если ЗначениеЗаполнено(ДатаНачисления) Тогда
ДатаПрошлая = ДобавитьМесяц(ДатаНачисления, -1);
Иначе
ДатаПрошлая = Дата(1, 1, 1); // оставляем пустой
КонецЕсли;
ТекущаяДата() на сервере в управляемом приложении возвращает дату компьютера сервера и считается устаревшей. В клиент-серверной работе берите ТекущаяДатаСеанса() — она учитывает часовой пояс сеанса.
Практические примеры: отчётность, зарплата, аналитика
Вычитание месяца чаще всего нужно, чтобы взять предыдущий период. Несколько готовых фрагментов под реальные задачи.
Период прошлого месяца для отчёта. Пользователь открыл отчёт — по умолчанию подставляем прошлый месяц целиком:
База = ТекущаяДатаСеанса();
ПериодОтчета.ДатаНачала = НачалоМесяца(ДобавитьМесяц(База, -1));
ПериодОтчета.ДатаОкончания = КонецМесяца(ДобавитьМесяц(База, -1));
Дата месяц назад от даты документа. Например, срок, отсчитанный назад от даты начисления:
КонтрольнаяДата = ДобавитьМесяц(Начисление.Дата, -1);
Сравнение «месяц к месяцу» в запросе. Берём выручку текущего и предыдущего месяца одним запросом через ДОБАВИТЬКДАТЕ в условии соединения — без двух отдельных выборок.
Диапазон за полгода назад. Для анализа динамики:
Конец = КонецМесяца(ТекущаяДатаСеанса());
Начало = НачалоМесяца(ДобавитьМесяц(ТекущаяДатаСеанса(), -5)); // 6 месяцев включительно
Обратите внимание на -5, а не -6: чтобы захватить шесть месяцев вместе с текущим, отступаем на пять назад. Пересчитайте на своих датах, если нужен другой край диапазона.
Расчётный период «месяц назад» в зарплатных расчётах. Когда нужна граница периода, предшествующего текущему месяцу начисления, отступаем от начала месяца начисления:
НачалоТекущего = НачалоМесяца(МесяцНачисления);
НачалоПредыдущего = ДобавитьМесяц(НачалоТекущего, -1);
КонецПредыдущего = КонецМесяца(НачалоПредыдущего);
Такой приём универсален: сначала приводим произвольную дату к началу месяца, затем сдвигаем на нужное число месяцев. Так результат не зависит от того, какое число месяца было в исходной дате, — а это частый источник расхождений, когда документы в периоде датированы по-разному.
Если вычисляете границу периода для отбора в отчёте или для подбора данных, приводите дату к началу или концу месяца явно. Иначе документы, оформленные в первой половине месяца, и документы конца месяца попадут в разные периоды из-за сохранённого в дате времени.
Производительность при работе с датами
На единичных вычислениях скорость неважна. Разница видна в циклах по тысячам строк и в обработках документов.
- ⚡ Выносите постоянные даты из цикла. Если граница периода не меняется, посчитайте
ДобавитьМесяцодин раз доДля … Цикл, а не на каждой итерации. - ⚡ Сдвигайте дату в запросе, а не в коде после выборки.
ДОБАВИТЬКДАТЕвнутри запроса отрабатывает на стороне СУБД по всему набору сразу. Перебирать результат и вызыватьДобавитьМесяцв цикле медленнее. - ⚡ Не пересчитывайте одно и то же.
НачалоМесяца(ДобавитьМесяц(...))в условии отбора считайте один раз в переменную и подставляйте её.
// Хорошо: один расчёт до цикла
ГраницаПериода = НачалоМесяца(ДобавитьМесяц(ТекущаяДатаСеанса(), -1));
Для Каждого Строка Из Таблица Цикл
Если Строка.Дата >= ГраницаПериода Тогда
// ...
КонецЕсли;
КонецЦикла;
Для больших выборок правило простое: календарную арифметику по датам старайтесь делать в запросе, а в коде оставлять только готовые границы.
Сколько месяцев между двумя датами
Обратная задача к вычитанию — посчитать, на сколько месяцев одна дата отстоит от другой. Нужна для стажа, для числа месяцев аренды, для периодов начислений.
В языке запросов для этого есть РАЗНОСТЬДАТ(<Дата1>, <Дата2>, <Тип>). Тип интервала — такой же набор, как у ДОБАВИТЬКДАТЕ:
ВЫБРАТЬ
РАЗНОСТЬДАТ(Договор.ДатаНачала, Договор.ДатаОкончания, МЕСЯЦ) КАК Месяцев
ИЗ
Справочник.Договоры КАК Договор
Во встроенном языке готовой функции «разница в месяцах» нет — считаем через год и месяц каждой даты:
МесяцевВсего = (Год(ДатаКонца) - Год(ДатаНачала)) * 12
+ (Месяц(ДатаКонца) - Месяц(ДатаНачала));
Проверьте логику на примере: с 01.03.2025 по 01.10.2026 формула даёт (2026 - 2025) * 12 + (10 - 3) = 12 + 7 = 19 месяцев. Если важно учитывать неполные месяцы (число второй даты меньше числа первой), добавьте поправку на день после сравнения чисел.
Для стажа и расчётных периодов, где нужны и годы, и месяцы, и дни одновременно, удобнее хранить границы и считать разницу в запросе: РАЗНОСТЬДАТ сразу вернёт целое число интервалов выбранного типа.
Частые вопросы
Чем `ДобавитьМесяц` лучше вычитания секунд?
ДобавитьМесяц сдвигает по календарю и сам обрабатывает конец месяца.Почему 31 марта минус месяц даёт 29 февраля, а не 28 или 31?
Как отнять месяц прямо в запросе или в СКД?
ДОБАВИТЬКДАТЕ(Поле, МЕСЯЦ, -1). Для начала и конца периода — НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА.Как получить весь прошлый месяц для отчёта?
НачалоМесяца(ДобавитьМесяц(ТекущаяДатаСеанса(), -1)) для начала и КонецМесяца(...) для конца. Конец месяца возвращает время 23:59:59.