Работа с датами в 1С: проверка, периоды и сравнение в запросах

· 9 мин

Содержание

Чтобы проверить дату в 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. В конфигураторе откройте Сервис → Параметры → Запуск 1С:Предприятия → Дополнительные и включите отладку, либо запускайте приложение в режиме отладки.
  2. Поставьте точку останова (клавиша F9) на строке, где формируется дата.
  3. Запустите, дождитесь остановки на точке.

Дальше есть три инструмента:

  • Табло — добавьте в него переменную с датой, и в колонке значения увидите день и время целиком.
  • Вычислить выражение (Shift+F9) — введите выражение, например Формат(ДокументДата, "ДЛФ=DT") или КонецДня(ДатаПо), и сразу получите результат.
  • Всплывающая подсказка — наведите курсор на переменную в остановленном коде, платформа покажет её значение.

В окне «Вычислить выражение» удобно проверять и границы запроса: подставьте НачалоДня(ДатаС) и КонецДня(ДатаПо) до запуска запроса и убедитесь, что время границ 00:00:00 и 23:59:59.

Проверить сам запрос отдельно от кода помогает консоль запросов — внешняя обработка, которую подключают через Файл → Открыть. В ней видно, какие именно значения приходят в параметры &ДатаНачала и &ДатаКонца, и сразу виден результат отбора. Это быстрее, чем гонять всю обработку целиком ради одной выборки.

Если дата формируется в серверном коде (в модуле объекта, общем модуле с флагом «Сервер», фоновом задании), для остановки на точке нужна отладка на сервере. В клиент-серверном варианте включите её в свойствах рабочего процесса или запустите сервер с ключом отладки, а в конфигураторе — Отладка → Подключение → Автоматическое подключение с нужным типом предмета отладки (Фоновое задание, Сеанс). Без этого точки в серверных процедурах не сработают, и будет казаться, что код не выполняется.

Отбор по датам работает верно, если:
Выполнено: 0 / 5

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

Ошибки с датами редко дают явное исключение — чаще отбор молча возвращает не те строки. Самые частые причины:

  • Забыли время в верхней границе. Самая массовая ошибка: Дата <= &ДатаПо, где в параметре '20240115'. Отсекаются почти все документы дня. Лечится через КонецДня.
  • ТекущаяДата() на клиенте. В тонком и веб-клиенте функция недоступна, код не компилируется. Нужна ТекущаяДатаСеанса().
  • Конструктор Дата() и чужой формат строки. Дата("15.01.2024") упадёт — конструктору нужен слитный ГГГГММДДччммсс.
  • Сравнение даты с пустой как с Неопределено. Пустая дата — это '00010101', а не Неопределено; проверка Если Дата = Неопределено всегда ложна.
  • Путаница часовых поясов. В клиент-серверном варианте ТекущаяДата() на сервере возвращает время сервера, а ТекущаяДатаСеанса() учитывает часовой пояс сеанса. При расхождении поясов граница суток «съезжает».
  • Арифметика в днях вместо секунд. Дата + 1 добавляет одну секунду, а не сутки. Для суток складывайте с 86400.
Проверьте себя
Что вернёт КонецДня для значения '20240115143000'?
Как корректно задать верхнюю границу периода в отборе документов?
Чему равна пустая дата в 1С?

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

Почему документ за нужный день не попал в выборку по периоду?
Скорее всего, у документа есть время (например, 14:30), а верхнюю границу периода задали датой без времени — то есть 00:00:00. Пропустите границу через КонецДня(ДатаПо), тогда она станет 23:59:59 и день войдёт целиком.
Как получить только дату без времени из значения с временем?
Примените НачалоДня(Дата) — функция обнулит часы, минуты и секунды, оставив день с временем 00:00:00.
Чем `ТекущаяДата()` отличается от `ТекущаяДатаСеанса()`?
ТекущаяДата() работает на сервере и в толстом клиенте и возвращает время компьютера, где выполняется код. ТекущаяДатаСеанса() доступна и на клиенте и учитывает часовой пояс сеанса. В управляемом приложении на клиенте используйте вторую.
Как сравнить две даты без учёта времени?
Приведите обе к началу суток: НачалоДня(Дата1) = НачалоДня(Дата2). Так сравнение пройдёт по календарному дню, а разное время внутри суток не помешает.
Как в запросе отфильтровать записи с заполненной датой?
Сравните поле с пустой датой: ГДЕ Дата <> ДАТАВРЕМЯ(1, 1, 1). Условие оставит только записи, где дата реально заполнена.