Арифметика дат в 1С 8.3: что можно прибавлять к дате, а что нельзя

· 10 мин

Содержание

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

Если же задача звучит как «сложить дату и время из двух полей» или «сложить два промежутка времени», решение другое: сначала превратить одну из дат в число секунд, потом прибавить. Всё описанное работает на уровне платформы 8.3 и не зависит от конфигурации.

Почему 1С не складывает две даты

Тип Дата в 1С хранит момент времени: год, месяц, день, часы, минуты, секунды. Диапазон — от 01.01.0001 00:00:00 до 31.12.9999 23:59:59. Часового пояса внутри значения нет.

Сумма двух моментов времени смысла не имеет. «15 марта плюс 20 апреля» — это не дата. Поэтому платформа разрешает только такие операции:

ОперацияРезультатПример
Дата + ЧислоДата, сдвинутая на N секунд'20240131' + 86400 → 01.02.2024
Дата - ЧислоДата раньше на N секунд'20240301' - 1 → 29.02.2024 23:59:59
Дата - ДатаЧисло секунд между ними'20240315' - '20240301' → 1 209 600
Дата + ДатаОшибка—

Единица сдвига — секунда, а не день. Это главная ловушка для тех, кто пришёл из Excel: там A1 + 1 даёт следующий день, а в 1С Дата + 1 даёт плюс одну секунду.

Полезные константы:

  • 🕐 минута — 60;
  • 🕑 час — 3600;
  • 📅 сутки — 86400;
  • 📆 неделя — 604800.

Заведите в общем модуле константы через функции (СекундВСутках() и т.п.) или просто пишите 86400 с комментарием. Выражение Дата + 7 * 86400 читается лучше, чем Дата + 604800.

Как прибавить к дате дни, часы и месяцы

Дни, часы, минуты

Всё, что меньше месяца, прибавляют секундами:

ДатаДокумента = '20240131';

ЧерезСутки   = ДатаДокумента + 86400;          // 01.02.2024 00:00:00
ЧерезНеделю  = ДатаДокумента + 7 * 86400;      // 07.02.2024 00:00:00
ЧерезДваЧаса = ДатаДокумента + 2 * 3600;       // 31.01.2024 02:00:00
ВчераВНачале = НачалоДня(ДатаДокумента - 86400); // 30.01.2024 00:00:00

Перехода на летнее время в типе Дата нет, поэтому сутки всегда равны 86 400 секундам. Сдвиг на день никогда не «съедет» на час.

Месяцы, кварталы и годы

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

Д = '20240131';

ПлюсМесяц   = ДобавитьМесяц(Д, 1);    // 29.02.2024 — дня 31.02 нет
ПлюсКвартал = ДобавитьМесяц(Д, 3);    // 30.04.2024
ПлюсГод     = ДобавитьМесяц(Д, 12);   // 31.01.2025
МинусМесяц  = ДобавитьМесяц(Д, -1);   // 31.12.2023

Если в целевом месяце нет такого дня, функция ставит последний день месяца. Время суток сохраняется.

Не прибавляйте месяц дважды по одному, если важен день. ДобавитьМесяц(ДобавитьМесяц('20240131', 1), 1) даст 29.03.2024: после февраля число «застряло» на 29. А ДобавитьМесяц('20240131', 2) даст 31.03.2024. В графиках платежей всегда считайте сдвиг от исходной даты: ДобавитьМесяц(ДатаНачала, НомерПлатежа).

Гарантированно последний день месяца

Когда нужен именно конец месяца (срок сдачи, закрытие периода ), не полагайтесь на число в исходной дате. Надёжная связка:

КонецСледующегоМесяца = КонецМесяца(ДобавитьМесяц(НачалоМесяца(Д), 1));
// для Д = 15.01.2024 → 29.02.2024 23:59:59

Сначала переходим на 1-е число, сдвигаем, потом берём конец месяца. Так результат не зависит от того, 15-е было в исходной дате или 31-е.

Где на самом деле «складывают две даты»

Чаще всего за этим запросом стоит одна из трёх задач. У каждой своё решение.

1. Дата из одного поля, время из другого

Типичный случай: в форме есть поле «Дата» (только дата) и поле «Время» (только время). Время в 1С хранится как дата 01.01.0001 с часами и минутами. Сложить их «в лоб» нельзя, но можно прибавить к дате число секунд от полуночи:

Функция СоединитьДатуИВремя(ДатаДня, Время)
    СекундОтПолуночи = Время - НачалоДня(Время);
    Возврат НачалоДня(ДатаДня) + СекундОтПолуночи;
КонецФункции

// ДатаДня = 15.03.2024, Время = 01.01.0001 09:30:00
// Результат: 15.03.2024 09:30:00

Время - НачалоДня(Время) работает, даже если в поле времени случайно оказалась не нулевая дата. А НачалоДня(ДатаДня) отбрасывает время, если оно было в первом поле.

2. Сумма нескольких промежутков

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

ВсегоСекунд = 0;
Для Каждого Смена Из Смены Цикл
    ВсегоСекунд = ВсегоСекунд + (Смена.Окончание - Смена.Начало);
КонецЦикла;

Смена с 08:30 до 17:15 даёт 31 500 секунд (8 ч 45 мин). Три таких смены — 94 500 секунд, то есть 26 ч 15 мин.

3. Дата плюс срок, заданный числом

Отсрочка платежа 14 дней, гарантия 24 месяца, срок хранения 36 часов. Здесь вторая «дата» на самом деле число, и работают приёмы из предыдущего раздела: ДатаОтгрузки + Отсрочка * 86400 или ДобавитьМесяц(ДатаПродажи, Гарантия).

Если срок хранится в справочнике числом, уточните единицу: дни или месяцы. Путаница «дней» и «месяцев» в одном реквизите — частая причина неверных сроков.

Как посчитать разницу между датами

Разность двух дат — число секунд. Дальше его переводят в нужные единицы.

Дни

ДнейМежду = (НачалоДня(Д2) - НачалоДня(Д1)) / 86400;

НачалоДня() здесь обязателен. Без него разница между 15.03 18:00 и 16.03 09:00 даст 0,625 дня, а не 1 календарный день. Если нужны полные сутки без учёта календаря, используйте Цел((Д2 - Д1) / 86400).

Включать ли оба конца периода — решает задача. Отпуск с 1 по 14 число — это 14 дней, а формула выше даст 13. Для «дней включительно» прибавьте единицу.

Часы и минуты для вывода

Секунды в формат «часы:минуты» удобно переводить так:

Часов = Цел(ВсегоСекунд / 3600);
Минут = Цел((ВсегоСекунд - Часов * 3600) / 60);
Текст = СтрШаблон("%1 ч %2 мин", Формат(Часов, "ЧН=0; ЧГ=0"), Формат(Минут, "ЧН=0"));
// 94500 секунд → "26 ч 15 мин"

Популярный приём Формат('00010101' + ВсегоСекунд, "ДФ='ЧЧ:мм'") работает только для промежутков меньше суток. При 94 500 секундах он покажет 02:15: дата перейдёт на 2 января, а лишние сутки пропадут из вывода.

Месяцы и полные годы

Готовой функции «разность в месяцах» во встроенном языке нет. Считают через год и месяц:

Функция ПолныхМесяцев(Д1, Д2)
    Мес = (Год(Д2) - Год(Д1)) * 12 + Месяц(Д2) - Месяц(Д1);
    Если ДобавитьМесяц(Д1, Мес) > Д2 Тогда
        Мес = Мес - 1;
    КонецЕсли;
    Возврат Мес;
КонецФункции

Проверка через ДобавитьМесяц() отсекает неполный последний месяц. Для 10.01.2024 и 09.03.2024 получится 1, а не 2. Полные годы (стаж, возраст) — это Цел(ПолныхМесяцев(Д1, Д2) / 12).

Сложение и разность дат в запросе 1С

Язык запросов арифметику над датами не поддерживает. Документ.Дата + 86400 в тексте запроса не сработает. Вместо этого две функции.

ДОБАВИТЬКДАТЕ

Синтаксис: ДОБАВИТЬКДАТЕ(Дата, Единица, Количество). Единицы: СЕКУНДА, МИНУТА, ЧАС, ДЕНЬ, НЕДЕЛЯ, МЕСЯЦ, КВАРТАЛ, ГОД, ДЕКАДА, ПОЛУГОДИЕ.

ВЫБРАТЬ
    Счет.Ссылка КАК Счет,
    Счет.Дата КАК ДатаСчета,
    ДОБАВИТЬКДАТЕ(Счет.Дата, ДЕНЬ, 10) КАК СрокОплаты,
    ДОБАВИТЬКДАТЕ(НАЧАЛОПЕРИОДА(Счет.Дата, МЕСЯЦ), МЕСЯЦ, 1) КАК ПервоеЧислоСледМесяца
ИЗ
    Документ.СчетНаОплатуПокупателю КАК Счет
ГДЕ
    Счет.Дата МЕЖДУ &НачалоПериода И &КонецПериода

Количество может быть не только константой, но и полем: ДОБАВИТЬКДАТЕ(Т.Дата, ДЕНЬ, Т.ДнейОтсрочки).

РАЗНОСТЬДАТ

Синтаксис: РАЗНОСТЬДАТ(Дата1, Дата2, Единица). Единицы: СЕКУНДА, МИНУТА, ЧАС, ДЕНЬ, МЕСЯЦ, КВАРТАЛ, ГОД. Результат положительный, если Дата2 позже Дата1.

ВЫБРАТЬ
    Счет.Ссылка,
    РАЗНОСТЬДАТ(Счет.Дата, &ДатаОтчета, ДЕНЬ) КАК ДнейСДатыСчета
ИЗ
    Документ.СчетНаОплатуПокупателю КАК Счет

Для крупных единиц функция считает границы периодов, а не полные промежутки. РАЗНОСТЬДАТ(31.01.2024, 01.02.2024, МЕСЯЦ) вернёт 1, хотя прошёл один день. Если нужны полные месяцы, вычисляйте их во встроенном языке функцией из предыдущего раздела.

Соединение даты и времени в запросе

Аналог функции СоединитьДатуИВремя() для запроса:

ДОБАВИТЬКДАТЕ(
    НАЧАЛОПЕРИОДА(Т.ДатаДня, ДЕНЬ),
    СЕКУНДА,
    ЧАС(Т.Время) * 3600 + МИНУТА(Т.Время) * 60 + СЕКУНДА(Т.Время))

Ошибки, которые дают неверный результат без сообщения

Платформа ругается только на Дата + Дата. Остальные ошибки проходят тихо, и расчёт просто врёт.

  • ❌ Прибавили дни как единицы. Дата + 5 — это плюс 5 секунд. Документ с «отсрочкой 5 дней» получит срок в ту же минуту.
  • ❌ Пустая дата в расчёте. Незаполненный реквизит равен 01.01.0001. Разность с ним даёт около 63,8 млрд секунд — «срок просрочки» в 2 000 лет. Перед расчётом проверяйте ЗначениеЗаполнено(Дата).
  • ❌ Конец периода без времени. Параметр &КонецПериода = '20240331' означает 31.03.2024 00:00:00. Все документы, введённые 31 марта днём, не попадут в выборку. Передавайте КонецДня(ДатаОкончания).
  • ❌ Текущая дата не того компьютера. ТекущаяДата() возвращает время компьютера, где выполняется код: на клиенте одно, на сервере другое. Для документов и расчётов берите ТекущаяДатаСеанса() — она учитывает часовой пояс сеанса.
  • ❌ Выход за диапазон. Сдвиг, который уводит дату раньше 0001 года или позже 9999-го, вызовет ошибку. Такое случается, когда к пустой дате вычитают срок.
  • ❌ Деление без округления. (Д2 - Д1) / 86400 легко даёт дробь, если время в датах не нулевое. Дробные дни потом попадают в табличную часть или в печатную форму.
Проверьте себя
Что вернёт выражение '20240131' + 1 во встроенном языке?
Что вернёт ДобавитьМесяц('20240131', 1)?
Как в запросе прибавить к дате документа 10 дней?

Скорость расчётов с датами в коде и запросах

Сами операции с датами дешёвые. Медленно становится, когда функции над датами мешают индексу или считаются в цикле там, где хватило бы одного раза.

В запросах

  • 🐢 Функция над полем в условии. ГДЕ НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) = &День заставляет СУБД вычислить выражение для каждой строки, индекс по дате не используется. Пишите диапазон: ГДЕ Док.Дата МЕЖДУ &НачалоДня И &КонецДня.
  • 🐢 ДОБАВИТЬКДАТЕ от поля в условии. ГДЕ ДОБАВИТЬКДАТЕ(Док.Дата, ДЕНЬ, 30) < &Дата — тот же эффект. Перенесите сдвиг на параметр: ГДЕ Док.Дата < &ДатаМинус30, где параметр посчитан в коде как Дата - 30 * 86400.
  • ✅ Параметры виртуальных таблиц. Границы периода передавайте в параметры Остатки(&Дата) и Обороты(&Начало, &Конец), а не фильтруйте результат через ГДЕ.

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

  • ✅ Текущую дату получайте один раз перед циклом и храните в переменной. Иначе на обработке, которая идёт через полночь, часть строк посчитается по одному дню, часть — по другому.
  • ✅ Вызовы сервера ради расчёта даты не нужны. ДобавитьМесяц(), НачалоДня(), КонецМесяца() работают и на клиенте.
  • ✅ Рабочие дни по производственному календарю считайте в запросе к регистру календаря или через общий модуль КалендарныеГрафики в конфигурациях на БСП, а не перебором дат с проверкой каждого дня.

Если нужна дата через N рабочих дней

Прибавление N * 86400 даёт календарные дни. Для сроков «через 5 рабочих дней» этого мало: выходные и праздники надо пропустить.

В типовых конфигурациях на БСП (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) есть производственный календарь и общий модуль КалендарныеГрафики. Откройте его в конфигураторе и найдите функцию, которая возвращает дату по календарю от заданной даты: там описаны параметры и примеры вызова. Это надёжнее самописного цикла — праздники и переносы берутся из обновляемого календаря.

Если БСП нет, простой вариант для пятидневки без праздников:

Функция ПлюсРабочихДней(ДатаНачала, Дней)
    Результат = НачалоДня(ДатаНачала);
    Осталось = Дней;
    Пока Осталось > 0 Цикл
        Результат = Результат + 86400;
        Если ДеньНедели(Результат) < 6 Тогда // 1 — понедельник, 6 и 7 — выходные
            Осталось = Осталось - 1;
        КонецЕсли;
    КонецЦикла;
    Возврат Результат;
КонецФункции

// пятница 15.03.2024 + 3 рабочих дня → среда 20.03.2024

Праздники такая функция не учитывает. Для реальных сроков оплаты и сдачи отчётов используйте календарь из базы.

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

Можно ли в 1С сложить две даты так, как в Excel?
Нет. В Excel дата — это число дней, поэтому даты складываются как числа. В 1С дата — отдельный тип, к нему прибавляют только число секунд. Аналог Excel-формулы A1 + 1 в 1С — Дата + 86400.
Как получить из даты только время?
Вычтите начало дня: Дата - НачалоДня(Дата) даст число секунд от полуночи. Чтобы получить значение типа «время», прибавьте их к пустой дате: '00010101' + (Дата - НачалоДня(Дата)).
Почему в отчёте разница дат дробная?
Даты содержат время. Разница между 10:00 и 22:00 соседних дней — 1,5 суток в долях. Приведите обе даты к НачалоДня() перед вычитанием или округлите результат через Цел().
Чем ДОБАВИТЬКДАТЕ с единицей МЕСЯЦ отличается от ДобавитьМесяц?
Это одна задача на разных уровнях: первое — функция языка запросов, второе — встроенного языка. В коде используйте ДобавитьМесяц(), в тексте запроса — ДОБАВИТЬКДАТЕ(). Проверьте результат на 31-м числе, если от этого зависит срок.
Как сравнить даты без учёта времени?
Сравнивайте начала дней: НачалоДня(Д1) = НачалоДня(Д2). В запросе для отбора лучше не сравнение функций, а диапазон МЕЖДУ &НачалоДня И &КонецДня.