Арифметика дат в 1С 8.3: что можно прибавлять к дате, а что нельзя
Содержание
Сложить две даты в 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легко даёт дробь, если время в датах не нулевое. Дробные дни потом попадают в табличную часть или в печатную форму.
Скорость расчётов с датами в коде и запросах
Сами операции с датами дешёвые. Медленно становится, когда функции над датами мешают индексу или считаются в цикле там, где хватило бы одного раза.
В запросах
- 🐢 Функция над полем в условии.
ГДЕ НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) = &Деньзаставляет СУБД вычислить выражение для каждой строки, индекс по дате не используется. Пишите диапазон:ГДЕ Док.Дата МЕЖДУ &НачалоДня И &КонецДня. - 🐢 ДОБАВИТЬКДАТЕ от поля в условии.
ГДЕ ДОБАВИТЬКДАТЕ(Док.Дата, ДЕНЬ, 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?
A1 + 1 в 1С — Дата + 86400.Как получить из даты только время?
Дата - НачалоДня(Дата) даст число секунд от полуночи. Чтобы получить значение типа «время», прибавьте их к пустой дате: '00010101' + (Дата - НачалоДня(Дата)).Почему в отчёте разница дат дробная?
НачалоДня() перед вычитанием или округлите результат через Цел().Чем ДОБАВИТЬКДАТЕ с единицей МЕСЯЦ отличается от ДобавитьМесяц?
ДобавитьМесяц(), в тексте запроса — ДОБАВИТЬКДАТЕ(). Проверьте результат на 31-м числе, если от этого зависит срок.Как сравнить даты без учёта времени?
НачалоДня(Д1) = НачалоДня(Д2). В запросе для отбора лучше не сравнение функций, а диапазон МЕЖДУ &НачалоДня И &КонецДня.