Работа с датами в 1С: проверка, периоды и сравнение в запросах
Содержание
Чтобы проверить дату в 1С, первым делом вспомните: тип Дата всегда хранит и календарный день, и время до секунды. Пустая дата — это не Неопределено, а конкретное значение '00010101000000' (1 января 1-го года, полночь). Из-за этой пары «день + время» ломается половина отборов по периоду: документ за 15-е число с временем 14:30 не попадает в выборку, если верхнюю границу задали как '20240115'.
Разберём типизацию даты, функции НачалоПериода и КонецПериода, корректное сравнение дат в запросах и приёмы проверки значения в отладчике. Примеры даны для платформы 8.3, интерфейс «Такси».
Что хранит тип Дата и как задать значение в коде
Значение типа Дата — это момент времени: год, месяц, день, час, минута, секунда. Отдельного типа «только дата» или «только время» в языке нет. Если время не нужно, у даты просто стоит 00:00:00.
Задать дату в модуле можно тремя способами:
- Литерал в одинарных кавычках —
'20240115'(только день) или'20240115143000'(день и время). Формат жёсткий:ГГГГММДДччммсс. - Функция-конструктор —
Дата(2024, 1, 15)илиДата(2024, 1, 15, 14, 30, 0). Удобна, когда год, месяц и день лежат в переменных. - Текущий момент — на сервере и в толстом клиенте это
ТекущаяДата(), в тонком и веб-клиенте —ТекущаяДатаСеанса(). На клиенте управляемого приложенияТекущаяДата()недоступна — вместо неё подставляйтеТекущаяДатаСеанса(), иначе получите ошибку на этапе компиляции.
Проверить, что в переменной действительно дата, а не строка или число, помогает ТипЗнч:
Если ТипЗнч(Значение) = Тип("Дата") Тогда
Сообщить("Это дата: " + Формат(Значение, "ДЛФ=DT"));
КонецЕсли;
Формат "ДЛФ=DT" выводит дату и время в локальном представлении. Для вывода одной лишь даты используйте "ДЛФ=D", только времени — "ДЛФ=T".
Склеивать дату со строкой через + без Формат можно, но вы получите техническое представление с секундами. Для читаемого текста всегда оборачивайте дату в Формат(Дата, "ДЛФ=D").
Функции НачалоПериода и КонецПериода
Это пара функций, которая «срезает» дату до границы периода. НачалоПериода обнуляет младшие составляющие, КонецПериода выставляет их в максимум.
Второй параметр — вид периода. Доступны значения: Минута, Час, День, Неделя, Декада, Месяц, Квартал, Полугодие, Год.
Дата = '20240115143000'; // 15 января 2024, 14:30:00
НачалоПериода(Дата, ВидПериода.День); // 15.01.2024 00:00:00
КонецПериода(Дата, ВидПериода.День); // 15.01.2024 23:59:59
НачалоПериода(Дата, ВидПериода.Месяц); // 01.01.2024 00:00:00
КонецПериода(Дата, ВидПериода.Месяц); // 31.01.2024 23:59:59
НачалоПериода(Дата, ВидПериода.Квартал); // 01.01.2024 00:00:00
КонецПериода(Дата, ВидПериода.Год); // 31.12.2024 23:59:59
У тех же операций есть короткие синонимы: НачалоДня, КонецДня, НачалоМесяца, КонецМесяца, НачалоКвартала, КонецКвартала, НачалоГода, КонецГода, НачалоНедели, КонецНедели, НачалоМинуты, КонецМинуты, НачалоЧаса, КонецЧаса. Они читаются лучше: КонецМесяца(Дата) понятнее, чем КонецПериода(Дата, ВидПериода.Месяц).
Два момента, на которых часто спотыкаются:
- Неделя начинается с понедельника.
НачалоНедели(Дата)вернёт понедельник той недели, в которую попадает дата, аКонецНедели— воскресенье. Это не настраивается. Декада— это десятидневка, а не декабрь.НачалоПериода(Дата, ВидПериода.Декада)для 15 января даст 11 января (вторая декада месяца: 1–10, 11–20, 21–конец).
Ключевая деталь: КонецДня возвращает время 23:59:59, а не 00:00:00 следующих суток. Это важно для верхней границы отбора — к ней ниже вернёмся.
Сдвинуть дату на нужный шаг помогает ДобавитьМесяц(Дата, Количество) (количество может быть отрицательным — тогда сдвиг назад). Для произвольного шага в днях или секундах складывайте дату с числом секунд:
ЗавтраВэтоЖеВремя = ТекущаяДатаСеанса() + 86400; // +1 сутки
ТриДняНазад = ТекущаяДатаСеанса() - 3 * 86400; // -3 суток
Разность двух дат тоже число секунд: (ДатаКонца - ДатаНачала) / 86400 даёт количество суток между ними.
Проверить, попадает ли дата в период, прямо в модуле можно сравнением с рассчитанными границами:
НачалоТекМесяца = НачалоМесяца(ТекущаяДатаСеанса());
КонецТекМесяца = КонецМесяца(ТекущаяДатаСеанса());
Если ДокументДата >= НачалоТекМесяца И ДокументДата <= КонецТекМесяца Тогда
Сообщить("Документ относится к текущему месяцу");
КонецЕсли;
Вывести сам период одной строкой помогает ПредставлениеПериода(НачалоПериода, КонецПериода) — функция сама подберёт формулировку вида «Январь 2024 г.» или «1 квартал 2024 г.», если границы совпадают с календарным периодом.
Как правильно сравнивать даты в запросах
В языке запросов для дат есть свои функции: НАЧАЛОПЕРИОДА, КОНЕЦПЕРИОДА, ДОБАВИТЬКДАТЕ, РАЗНОСТЬДАТ, а также ГОД, КВАРТАЛ, МЕСЯЦ, ДЕНЬ, ЧАС, МИНУТА, СЕКУНДА, НЕДЕЛЯГОДА, ДЕНЬГОДА. Пустую дату в запросе задают через ДАТАВРЕМЯ(1, 1, 1).
Самая частая задача — отобрать документы за период. Параметры передают из модуля, предварительно подготовив границы:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка, Дата, Сумма
|ИЗ Документ.РеализацияТоваровУслуг
|ГДЕ Дата МЕЖДУ &ДатаНачала И &ДатаКонца";
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаС));
Запрос.УстановитьПараметр("ДатаКонца", КонецДня(ДатаПо));
Верхнюю границу периода обязательно пропускайте через КонецДня. Если передать ДатаПо как '20240115' (время 00:00:00), оператор МЕЖДУ отсечёт все документы этого дня с ненулевым временем — а их почти все. КонецДня поднимает границу до 23:59:59, и день входит целиком.
Есть и второй надёжный приём — сравнение строгим «меньше» с началом следующего дня:
ГДЕ Дата >= &ДатаНачала И Дата < &ДатаКонца
где ДатаКонца = НачалоДня(ДатаПо) + 86400. Такой вариант устойчивее, если в данных встречается время вплоть до 23:59:59 — граница < начало завтра не пропустит ни одной секунды.
Оба подхода к верхней границе дают один результат, но ведут себя по-разному на граничной секунде:
| Способ | Как задать границу | Когда предпочесть |
|---|---|---|
МЕЖДУ … И + КонецДня | КонецДня(ДатаПо) → 23:59:59 | Привычно, читается «с и по». Включает весь день |
Строгое < + начало завтра | НачалоДня(ДатаПо) + 86400 | Надёжнее для регистров с долями секунды и временем движений |
Разницу между датами в запросе считает РАЗНОСТЬДАТ, сдвиг даты — ДОБАВИТЬКДАТЕ. Например, отобрать документы старше 30 дней от текущей даты:
ВЫБРАТЬ Ссылка, Дата
ИЗ Документ.РеализацияТоваровУслуг
ГДЕ РАЗНОСТЬДАТ(Дата, &ТекущаяДата, ДЕНЬ) > 30
Третий параметр РАЗНОСТЬДАТ — единица измерения (СЕКУНДА, МИНУТА, ЧАС, ДЕНЬ, МЕСЯЦ, КВАРТАЛ, ГОД). А ДОБАВИТЬКДАТЕ(&Дата, ДЕНЬ, -30) вернёт дату на 30 дней раньше — удобно для подвижной границы «за последний месяц».
Группировать по периодам прямо в запросе удобно функцией НАЧАЛОПЕРИОДА:
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ) КАК Месяц,
СУММА(Сумма) КАК Оборот
ИЗ Документ.РеализацияТоваровУслуг
СГРУППИРОВАТЬ ПО НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ)
ДАТАВРЕМЯ и НАЧАЛОПЕРИОДА в запросе пишутся по-русски и без кавычек у вида периода: НАЧАЛОПЕРИОДА(Дата, КВАРТАЛ), а не ВидПериода.Квартал. Это распространённая ошибка при переносе кода из модуля в запрос.
Пустая дата и как её поймать
Пустая дата — '00010101000000'. Она не равна Неопределено и не вызывает ошибку при сравнении, поэтому забытая проверка тихо портит логику: расчёт срока от пустой даты даст разницу в пару тысяч лет.
Самый универсальный способ — функция ЗначениеЗаполнено. Для даты она возвращает Ложь, если дата пустая:
Если Не ЗначениеЗаполнено(ДокументДата) Тогда
ВызватьИсключение "Не указана дата документа";
КонецЕсли;
Прямое сравнение с литералом тоже работает и иногда нагляднее:
Если ДокументДата = '00010101' Тогда
// дата не заполнена
КонецЕсли;
В запросе пустую дату отлавливают сравнением с ДАТАВРЕМЯ(1, 1, 1):
ГДЕ Дата <> ДАТАВРЕМЯ(1, 1, 1)
Отдельная история — «некорректная» дата, то есть дата как строка из внешнего источника (выгрузка, обмен, файл). Прежде чем работать с ней как с датой, преобразуйте и проверьте результат:
ЗначениеДаты = '00010101';
Попытка
ЗначениеДаты = Дата(СтрокаИзФайла); // ждёт формат ГГГГММДДччммсс
Исключение
Сообщить("Не удалось разобрать дату: " + СтрокаИзФайла);
КонецПопытки;
Если формат строки отличается (например, 15.01.2024), разберите её вручную через Сред и соберите Дата(Год, Месяц, День) — конструктор Дата() понимает только слитный формат ГГГГММДДччммсс.
Как посмотреть значение даты в отладчике и консоли запросов
Когда отбор работает не так, как ждёшь, причина почти всегда во времени внутри даты — его не видно в типовых формах, где показывают только день. Посмотреть реальное значение помогает отладчик.
Чтобы отладка заработала:
- В конфигураторе откройте
Сервис → Параметры → Запуск 1С:Предприятия → Дополнительныеи включите отладку, либо запускайте приложение в режиме отладки. - Поставьте точку останова (клавиша
F9) на строке, где формируется дата. - Запустите, дождитесь остановки на точке.
Дальше есть три инструмента:
- Табло — добавьте в него переменную с датой, и в колонке значения увидите день и время целиком.
- Вычислить выражение (
Shift+F9) — введите выражение, напримерФормат(ДокументДата, "ДЛФ=DT")илиКонецДня(ДатаПо), и сразу получите результат. - Всплывающая подсказка — наведите курсор на переменную в остановленном коде, платформа покажет её значение.
В окне «Вычислить выражение» удобно проверять и границы запроса: подставьте НачалоДня(ДатаС) и КонецДня(ДатаПо) до запуска запроса и убедитесь, что время границ 00:00:00 и 23:59:59.
Проверить сам запрос отдельно от кода помогает консоль запросов — внешняя обработка, которую подключают через Файл → Открыть. В ней видно, какие именно значения приходят в параметры &ДатаНачала и &ДатаКонца, и сразу виден результат отбора. Это быстрее, чем гонять всю обработку целиком ради одной выборки.
Если дата формируется в серверном коде (в модуле объекта, общем модуле с флагом «Сервер», фоновом задании), для остановки на точке нужна отладка на сервере. В клиент-серверном варианте включите её в свойствах рабочего процесса или запустите сервер с ключом отладки, а в конфигураторе — Отладка → Подключение → Автоматическое подключение с нужным типом предмета отладки (Фоновое задание, Сеанс). Без этого точки в серверных процедурах не сработают, и будет казаться, что код не выполняется.
Типичные ошибки при работе с датами
Ошибки с датами редко дают явное исключение — чаще отбор молча возвращает не те строки. Самые частые причины:
- Забыли время в верхней границе. Самая массовая ошибка:
Дата <= &ДатаПо, где в параметре'20240115'. Отсекаются почти все документы дня. Лечится черезКонецДня. ТекущаяДата()на клиенте. В тонком и веб-клиенте функция недоступна, код не компилируется. НужнаТекущаяДатаСеанса().- Конструктор
Дата()и чужой формат строки.Дата("15.01.2024")упадёт — конструктору нужен слитныйГГГГММДДччммсс. - Сравнение даты с пустой как с
Неопределено. Пустая дата — это'00010101', а неНеопределено; проверкаЕсли Дата = Неопределеновсегда ложна. - Путаница часовых поясов. В клиент-серверном варианте
ТекущаяДата()на сервере возвращает время сервера, аТекущаяДатаСеанса()учитывает часовой пояс сеанса. При расхождении поясов граница суток «съезжает». - Арифметика в днях вместо секунд.
Дата + 1добавляет одну секунду, а не сутки. Для суток складывайте с86400.
Частые вопросы
Почему документ за нужный день не попал в выборку по периоду?
14:30), а верхнюю границу периода задали датой без времени — то есть 00:00:00. Пропустите границу через КонецДня(ДатаПо), тогда она станет 23:59:59 и день войдёт целиком.Как получить только дату без времени из значения с временем?
НачалоДня(Дата) — функция обнулит часы, минуты и секунды, оставив день с временем 00:00:00.Чем `ТекущаяДата()` отличается от `ТекущаяДатаСеанса()`?
ТекущаяДата() работает на сервере и в толстом клиенте и возвращает время компьютера, где выполняется код. ТекущаяДатаСеанса() доступна и на клиенте и учитывает часовой пояс сеанса. В управляемом приложении на клиенте используйте вторую.Как сравнить две даты без учёта времени?
НачалоДня(Дата1) = НачалоДня(Дата2). Так сравнение пройдёт по календарному дню, а разное время внутри суток не помешает.Как в запросе отфильтровать записи с заполненной датой?
ГДЕ Дата <> ДАТАВРЕМЯ(1, 1, 1). Условие оставит только записи, где дата реально заполнена.