Вычитание месяца: функции встроенного языка и запросов 1С

· 9 мин

Содержание

Чтобы вычесть месяц из даты, используйте глобальную функцию ДобавитьМесяц и передайте вторым параметром отрицательное число: ДобавитьМесяц(Дата, -1). Это единственный корректный способ сдвинуть дату ровно на календарный месяц назад — в отличие от вычитания «30 дней», которое ломается на разной длине месяцев.

Функция работает со значением типа Дата, сама учитывает длину месяцев и високосные годы, и возвращает новую дату того же типа. Время суток при этом сохраняется. В запросах роль ДобавитьМесяц играет ДОБАВИТЬКДАТЕ с интервалом МЕСЯЦ — отдельная функция с другим синтаксисом. Эти два инструмента и закрывают почти все задачи: от «дата на месяц раньше» до границ прошлого периода в отчёте.

Функция ДобавитьМесяц во встроенном языке

Синтаксис простой: ДобавитьМесяц(<Дата>, <ЧислоМесяцев>). Первый параметр — исходная дата, второй — целое число месяцев. Положительное число прибавляет месяцы, отрицательное — вычитает.

Исходная = '20240315';
ПрошлыйМесяц = ДобавитьМесяц(Исходная, -1);   // 15.02.2024
ТриМесяцаНазад = ДобавитьМесяц(Исходная, -3); // 15.12.2023

Функция доступна и на клиенте, и на сервере, отдельного импорта не требует. Число месяцев может быть любым целым — вычитание на 14 месяцев назад тоже отработает: ДобавитьМесяц(Дата, -14).

Время суток остаётся прежним. Если в исходной дате было 15.03.2024 18:30:00, то после ДобавитьМесяц(..., -1) получите 15.02.2024 18:30:00. Если нужна чистая дата без времени — оберните результат в НачалоДня.

Для сдвига на год есть аналогичная логика: ДобавитьМесяц(Дата, -12). Отдельной функции «вычесть год» нет, но двенадцать месяцев назад — это ровно год назад, с корректной обработкой 29 февраля.

Важная деталь: функция ничего не меняет в исходной переменной, а возвращает новое значение. Результат нужно присвоить — забытое присваивание (ДобавитьМесяц(Дата, -1); без левой части) ни к чему не приводит, а дата остаётся прежней. Литерал даты записывается в апострофах в формате 'ГГГГММДД' или 'ГГГГММДДччммсс', например '20240315' или '20240315183000'.

Что вернёт функция при вычитании из 31 числа

Здесь кроется главная тонкость. Если после сдвига в целевом месяце нет такого числа, ДобавитьМесяц возвращает последний день этого месяца. Выдумывать несуществующую дату вроде «31 февраля» платформа не станет.

ДобавитьМесяц('20240331', -1);  // 29.02.2024 — февраль високосного года
ДобавитьМесяц('20230331', -1);  // 28.02.2023 — февраль обычного года
ДобавитьМесяц('20240531', -3);  // 29.02.2024 — три месяца назад от 31 мая
ДобавитьМесяц('20240131', -2);  // 30.11.2023 — в ноябре 30 дней

Логика такая: платформа определяет целевой месяц, а затем берёт минимальное из исходного дня месяца и числа дней в целевом месяце. Поэтому 31 марта минус месяц — это 28 или 29 февраля, а не 3 марта.

Високосные годы функция обрабатывает сама по григорианскому календарю. В 2024 году (високосный) февраль закрывается 29-м числом, в 2023 — 28-м. Проверяйте результат именно на граничных числах 29, 30 и 31 — большинство ошибок вылезает там.

Побочный эффект: операция «несимметрична». Если от 31 марта отнять месяц и получить 29 февраля, то обратное прибавление месяца даст 29 марта, а не 31. Это нормальное поведение календарной арифметики, но его стоит держать в голове при расчёте «туда-обратно». Если исходную дату важно восстановить в точности, храните её отдельно, а не вычисляйте обратным сдвигом.

Почему нельзя просто отнять 30 дней

Соблазнительно написать Дата - 30 * 86400 — ведь дата в 1С хранится как число секунд, и 86400 это секунды в сутках. Способ рабочий для «ровно N суток назад», но для месяца он неверен.

Месяцы бывают длиной 28, 29, 30 и 31 день. «Минус 30 дней» от 31 марта даст 1 марта, а не февраль. От 15 июля — 15 июня (случайно совпало), а от 15 мая — 15 апреля. Результат плавает и не совпадает с календарным месяцем.

Сравним подходы на одной дате 31.03.2024:

СпособКодРезультатКорректно?
ДобавитьМесяцДобавитьМесяц('20240331', -1)29.02.2024✅ календарный месяц
Минус 30 дней'20240331' - 30*8640001.03.2024❌ не тот месяц
Минус 31 день'20240331' - 31*8640029.02.2024❌ совпало случайно

В таблице «минус 31 день» совпало с правильным ответом чисто арифметически: от 31 марта до 29 февраля ровно 31 день именно в 2024 году. В невисокосном 2023-м те же 31*86400 дадут уже 28 февраля только случайно, а в других месяцах разъедутся. Опираться на такое совпадение нельзя.

Вывод один: вычитание секунд годится только для суток, часов и минут. Для месяцев, кварталов и лет — всегда ДобавитьМесяц.

Что именно нужно получить?

Как вычесть месяц в запросе 1С

В языке запросов своя функция — ДОБАВИТЬКДАТЕ. Синтаксис: ДОБАВИТЬКДАТЕ(<Дата>, <ТипИнтервала>, <Количество>). Тип интервала для месяца — МЕСЯЦ, количество отрицательное для вычитания.

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

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

Частый сценарий — отобрать данные за предыдущий месяц относительно параметра отчёта. В условии удобно комбинировать ДОБАВИТЬКДАТЕ с границами периода:

ГДЕ
    Продажи.Период >= НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, -1), МЕСЯЦ)
    И Продажи.Период <= КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&Дата, МЕСЯЦ, -1), МЕСЯЦ)

Эти же функции доступны в выражениях системы компоновки данных — например, чтобы вычислить дату начала по умолчанию для параметра отчёта. Но если месяц вычитается от параметра, который пользователь задаёт в форме, надёжнее подготовить значение в коде через ДобавитьМесяц и передать его готовым: читается яснее, чем длинное выражение внутри запроса.

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

Границы периода: начало и конец прошлого месяца

Чаще всего «вычесть месяц» нужно не ради одной даты, а чтобы получить диапазон прошлого месяца для отчёта или регистра. Для этого есть пара функций встроенного языка: НачалоМесяца и КонецМесяца.

Дата = ТекущаяДатаСеанса();
НачалоПрошлого = НачалоМесяца(ДобавитьМесяц(Дата, -1)); // 01.09.2026 00:00:00
КонецПрошлого  = КонецМесяца(ДобавитьМесяц(Дата, -1));  // 30.09.2026 23:59:59

Порядок важен: сначала сдвигаем дату на месяц назад, затем берём её начало или конец. НачалоМесяца возвращает первый день месяца с временем 00:00:00, КонецМесяца — последний день с временем 23:59:59. Длину месяца функции определяют сами, поэтому про «30 или 31» думать не нужно.

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

Для построения отчёта «месяц к месяцу» сдвигайте границы, а не отдельные даты продаж. Тогда короткий и длинный месяцы сравниваются корректно.

Готовая функция для периода прошлого месяца

Чтобы не повторять связку «сдвиг плюс границы» в каждом отчёте, вынесите её в отдельную функцию. Вот рабочий вариант, который возвращает структуру с началом и концом прошлого месяца относительно переданной даты (по умолчанию — текущей):

Функция ПериодПрошлогоМесяца(ОтносительноДаты = Неопределено)
    Если ОтносительноДаты = Неопределено Тогда
        ОтносительноДаты = ТекущаяДатаСеанса();
    КонецЕсли;

    СдвинутаяДата = ДобавитьМесяц(ОтносительноДаты, -1);

    Результат = Новый Структура;
    Результат.Вставить("Начало", НачалоМесяца(СдвинутаяДата));
    Результат.Вставить("Конец",  КонецМесяца(СдвинутаяДата));
    Возврат Результат;
КонецФункции

Вызов и подстановка в параметры запроса:

Период = ПериодПрошлогоМесяца();
Запрос.УстановитьПараметр("НачалоПериода", Период.Начало);
Запрос.УстановитьПараметр("КонецПериода",  Период.Конец);

Функция самодостаточна: она не зависит от текущей даты жёстко, поэтому её удобно тестировать, передавая конкретные даты граничных месяцев. Передали '20240331' — получите период с 1 по 29 февраля; передали '20240115' — с 1 по 31 декабря предыдущего года. По тому же шаблону строятся функции для прошлого квартала (ДобавитьМесяц(..., -3) плюс НачалоКвартала/КонецКвартала) и прошлого года.

Несколько месяцев, кварталы и часовой пояс

Несколько месяцев. Передавайте нужное число вторым параметром: ДобавитьМесяц(Дата, -6) — полгода назад, ДобавитьМесяц(Дата, -12) — год назад. Складывать несколько вызовов подряд не нужно, функция принимает любое целое за один раз. Квартал назад — это ДобавитьМесяц(Дата, -3); отдельной «вычесть квартал» во встроенном языке нет, но три месяца дают тот же результат.

Кварталы и годы. Квартал назад — ДобавитьМесяц(Дата, -3), год назад — ДобавитьМесяц(Дата, -12). Если нужна граница прошлого квартала, оборачивайте сдвинутую дату в НачалоКвартала и КонецКвартала — так же, как с месяцем. В языке запросов для этого есть интервал КВАРТАЛ: ДОБАВИТЬКДАТЕ(Дата, КВАРТАЛ, -1).

Сколько месяцев между датами. Обратная задача решается функцией РазностьДат (появилась в платформе 8.3.10): РазностьДат(ДатаНачала, ДатаОкончания, "Месяц") вернёт число месяцев между датами. Учтите, что считаются именно полные интервалы: с 15 января по 10 февраля функция вернёт 0, а не 1, потому что полный месяц ещё не прошёл. На более старых платформах календарную разницу по номеру месяца считают вручную через Год() и Месяц(): (Год(Д2) - Год(Д1)) * 12 + (Месяц(Д2) - Месяц(Д1)) — этот способ даст 1 и не зависит от дня. Выбирайте формулу под задачу: «полные месяцы» или «разница по календарю». В языке запросов аналог — РАЗНОСТЬДАТ(Д1, Д2, МЕСЯЦ).

Часовой пояс. На результат ДобавитьМесяц часовой пояс не влияет. Тип Дата в 1С — это момент без привязки к поясу, и арифметика над ним идёт по самому значению. Пояс важен только при получении текущего момента: ТекущаяДатаСеанса() отдаёт время с учётом часового пояса сеанса, а устаревшая ТекущаяДата() — время сервера. Поэтому берите точку отсчёта через ТекущаяДатаСеанса(), а дальше вычитайте месяц спокойно — сдвиг от пояса не зависит.

Типичные ошибки при работе с датами

Ошибки на датах редко падают с исключением — чаще просто тихо дают неверный период в отчёте. Поэтому их и не замечают сразу.

  • 📅 Вычитают 30 дней вместо месяца. Самая частая. Даёт разъезд на граничных числах и в феврале.
  • 🔁 Прибавляют месяц, ожидая вернуть исходную дату. После 31.03 → 29.02 обратный сдвиг даст 29.03, а не 31.03. Арифметика несимметрична.
  • ⏰ Забывают про время суток. ДобавитьМесяц сохраняет часы и минуты. Для сравнения «по дате» оборачивайте в НачалоДня, иначе 18:30 не попадёт в условие <= КонецДня.
  • 🧩 Путают функции кода и запроса. ДобавитьМесяц в тексте запроса не скомпилируется, ДОБАВИТЬКДАТЕ в модуле — тоже.
  • 📊 Строят период из отдельной даты. Для диапазона прошлого месяца нужны НачалоМесяца и КонецМесяца от сдвинутой даты, а не одна дата.
Проверка перед релизом кода с вычитанием месяца
Выполнено: 0 / 5

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

Можно ли вычесть месяц, просто отняв 30 дней?
Нет. Месяцы разной длины, поэтому «минус 30 дней» совпадёт с календарным месяцем только случайно. Для месяца всегда используйте ДобавитьМесяц(Дата, -1), а вычитание секунд оставьте для суток и часов.
Что вернёт ДобавитьМесяц, если вычесть месяц из 31 марта?
Последний день февраля: 29-е в високосный год, 28-е в обычный. Платформа не создаёт несуществующую дату и подставляет последнее число целевого месяца.
Как вычесть месяц в запросе, а не в модуле?
Функцией ДОБАВИТЬКДАТЕ(Дата, МЕСЯЦ, -1). Она работает только в тексте запроса; ДобавитьМесяц — только в коде модуля.
Влияет ли часовой пояс на вычитание месяца?
Нет. Тип Дата хранит момент без пояса, и сдвиг идёт по значению. Пояс учитывается лишь при получении текущего времени через ТекущаяДатаСеанса().
Как получить начало и конец прошлого месяца?
Сначала сдвиньте дату: ДобавитьМесяц(Дата, -1). Затем возьмите НачалоМесяца(...) для первого дня и КонецМесяца(...) для последнего. Длину месяца функции рассчитают сами.