Пустая дата в 1С 8.3: как её распознать в коде, запросе и при обмене
Содержание
Чтобы проверить дату на пустое значение в 1С, вызовите ЗначениеЗаполнено(Дата): для пустой даты функция вернёт Ложь. Пустая дата в 1С:Предприятии 8 — это не Неопределено и не NULL, а конкретное значение 01.01.0001 00:00:00, то есть Дата(1, 1, 1).
Частая ошибка — сравнивать дату с пустой строкой или проверять её на Неопределено. Такой код либо падает, либо молча пропускает незаполненные поля. Ниже — способы для встроенного языка и языка запросов, разбор ошибок и ловушек, на которых спотыкаются даже опытные разработчики. Всё сказанное относится к платформе 8.3 в целом и не зависит от конфигурации.
Как устроена пустая дата в платформе 1С
Тип Дата в 1С хранит год, месяц, день, часы, минуты и секунды. Допустимый диапазон — от 1 января 0001 года до 31 декабря 9999 года. Миллисекунд в самом значении нет.
У типа Дата нет отдельного «пустого» состояния. Значением по умолчанию считается минимальная дата диапазона:
- 📅 в коде —
Дата(1, 1, 1)или литерал'00010101'; - 📅 в запросе —
ДАТАВРЕМЯ(1, 1, 1); - 📅 в XML и JSON — строка
0001-01-01T00:00:00; - 📅 на форме — пустое поле ввода без цифр.
Отсюда главное правило. Новый реквизит типа Дата у документа или справочника сразу содержит 01.01.0001. Переменная, объявленная через Перем и ничем не заполненная, содержит Неопределено. Это разные случаи, и проверять их нужно по-разному.
| Что проверяем | Значение «пусто» | Чем проверять |
|---|---|---|
| Реквизит объекта типа Дата | Дата(1, 1, 1) | ЗначениеЗаполнено() или сравнение |
| Переменная без присвоения | Неопределено | ЗначениеЗаполнено() или = Неопределено |
| Поле из левого соединения в запросе | NULL | ЕСТЬ NULL или ЕСТЬNULL() |
| Реквизит составного типа | Неопределено или пустое значение любого из типов | ЗначениеЗаполнено() + ТипЗнч() |
Проверка даты на пустоту во встроенном языке
Есть три рабочих способа. Все они дают одинаковый результат для реквизита строго типа Дата.
Способ 1. ЗначениеЗаполнено()
Если Не ЗначениеЗаполнено(Объект.ДатаОплаты) Тогда
Сообщить("Не указана дата оплаты");
КонецЕсли;
Функция возвращает Ложь и для Дата(1, 1, 1), и для Неопределено, и для NULL. Поэтому это самый безопасный вариант, когда тип значения заранее не гарантирован. Функция доступна и на сервере, и на клиенте.
Способ 2. Сравнение с Дата(1, 1, 1)
Если Объект.ДатаОплаты = Дата(1, 1, 1) Тогда
// дата пустая
КонецЕсли;
Код читается однозначно: видно, что речь именно о дате. Если в переменной окажется Неопределено, условие вернёт Ложь, и пустое значение проскочит. Ошибки не будет, но проверка не сработает.
Способ 3. Сравнение с литералом ‘00010101’
Если Объект.ДатаОплаты = '00010101' Тогда
// дата пустая
КонецЕсли;
Литерал даты пишется в одинарных кавычках в формате ГГГГММДД или ГГГГММДДччммсс. Работает так же, как способ 2. Минус один: литерал легко перепутать со строкой при чтении чужого кода.
Если в модуле дата сравнивается с пустой много раз, вынесите её в переменную в начале процедуры: ПустаяДата = Дата(1, 1, 1);. Код станет короче, а смысл не потеряется.
Когда тип значения неизвестен
Реквизит составного типа или значение из коллекции может оказаться чем угодно. Тогда сначала проверьте тип, затем заполненность:
Если ТипЗнч(Значение) = Тип("Дата") И ЗначениеЗаполнено(Значение) Тогда
// это настоящая непустая дата
КонецЕсли;
Без проверки типа ЗначениеЗаполнено() вернёт Истина для любой непустой строки или числа, и код примет их за дату.
Пустая дата в языке запросов 1С
В запросе литерала '00010101' нет. Пустую дату задают функцией ДАТАВРЕМЯ(1, 1, 1) или передают параметром.
Отбор документов с незаполненной датой
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка,
| Док.Номер
|ИЗ
| Документ.СчетНаОплатуПокупателю КАК Док
|ГДЕ
| Док.СрокОплаты = ДАТАВРЕМЯ(1, 1, 1)";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить("Без срока оплаты: " + Выборка.Номер);
КонецЦикла;
Имя документа и реквизита замените на свои: у разных конфигураций они отличаются. Вариант с параметром делает то же самое:
Запрос.Текст = "... ГДЕ Док.СрокОплаты = &ПустаяДата";
Запрос.УстановитьПараметр("ПустаяДата", Дата(1, 1, 1));
NULL после левого соединения
Если поле приходит из правой таблицы ЛЕВОГО СОЕДИНЕНИЯ и пары не нашлось, в нём будет NULL, а не пустая дата. Условие = ДАТАВРЕМЯ(1, 1, 1) такие строки не поймает.
Решения два:
- Проверить оба состояния явно:
ГДЕ Т.Дата ЕСТЬ NULL ИЛИ Т.Дата = ДАТАВРЕМЯ(1, 1, 1). - Привести
NULLк пустой дате в секции выборки:ЕСТЬNULL(Т.Дата, ДАТАВРЕМЯ(1, 1, 1)) КАК Дата.
Второй вариант удобен, когда результат дальше обрабатывается кодом: в выборку попадёт настоящая дата, и ЗначениеЗаполнено() отработает предсказуемо.
Поля составного типа
Если реквизит может хранить и дату, и, например, строку, сравнение с ДАТАВРЕМЯ(1, 1, 1) вернёт строки только с пустой датой. Пустые строки и Неопределено останутся за бортом. Для таких полей добавьте проверку типа: ТИПЗНАЧЕНИЯ(Т.Поле) = ТИП(ДАТА).
Ошибки при работе с пустыми датами
Код с датами редко падает на проверке заполненности. Падает он рядом: при сравнении, преобразовании и арифметике.
Значение < ТекущаяДатаСеанса() слева оказалось Неопределено или ссылка. Добавьте перед сравнением ТипЗнч(Значение) = Тип("Дата") либо ЗначениеЗаполнено(Значение).Дата() передали пустую строку или строку в неверном формате. Проверьте строку до вызова или используйте ПривестиЗначение (пример ниже).Дата(1, 1, 1) любое условие «дата меньше» истинно. Исключите пустые даты отдельным условием.01.01.0001 выходит величина больше 60 миллиардов. Проверяйте заполненность перед вычитанием.Безопасное преобразование строки в дату
Когда дата приходит из файла или внешней системы, строка может быть пустой или кривой. Вместо Дата(Строка) с обёрткой Попытка используйте описание типов:
ОписаниеДаты = Новый ОписаниеТипов("Дата");
Результат = ОписаниеДаты.ПривестиЗначение(ВходящееЗначение);
Если Не ЗначениеЗаполнено(Результат) Тогда
// значение не распознано как дата
КонецЕсли;
ПривестиЗначение не вызывает исключение: если значение не приводится к дате, метод вернёт значение по умолчанию, то есть Дата(1, 1, 1). Проверка заполненности сразу покажет, что разобрать дату не удалось.
Пустая дата и проверка заполнения на форме
Если у реквизита в конфигураторе свойство «Проверка заполнения» установлено в «Выдавать ошибку», платформа сама считает 01.01.0001 незаполненным значением и не даст записать объект. Писать для этого код в ОбработкаПроверкиЗаполнения не нужно. Свой код там нужен, когда обязательность зависит от условий — например, дата оплаты обязательна только для одного вида операции.
Своя функция ПустаяДата(): зачем и как написать
В глобальном контексте платформы функции ПустаяДата() нет. Если вы встретили её в коде, это функция общего модуля конкретной конфигурации или доработки. Вызов в чужой базе без этого модуля закончится ошибкой «Процедура или функция с указанным именем не определена».
Своя функция оправдана, когда команде важна единая запись и читаемость. Создайте общий модуль с флажками «Клиент», «Сервер» и «Внешнее соединение» (или используйте существующий клиент-серверный модуль) и добавьте:
// Возвращает пустое значение типа Дата.
Функция ПустаяДата() Экспорт
Возврат Дата(1, 1, 1);
КонецФункции
// Истина, если значение - дата и она заполнена.
Функция ДатаЗаполнена(Значение) Экспорт
Возврат ТипЗнч(Значение) = Тип("Дата") И ЗначениеЗаполнено(Значение);
КонецФункции
Вызов выглядит так: Если Не РаботаСДатамиКлиентСервер.ДатаЗаполнена(Объект.СрокОплаты) Тогда. Имя модуля подберите под свои соглашения.
Не дорабатывайте типовые общие модули напрямую: при обновлении конфигурации правки придётся переносить вручную. Новую функцию разместите в своём модуле или в расширении конфигурации.
Какой способ проверки быстрее
На практике разница между способами в коде не влияет на скорость прикладной задачи. Проверка заполненности — операция в памяти, которая занимает ничтожную долю времени по сравнению с чтением объекта из базы.
| Способ | Плюсы | Минусы |
|---|---|---|
ЗначениеЗаполнено() | Ловит Неопределено и NULL, короткая запись | Не проверяет тип |
= Дата(1, 1, 1) | Явно показывает, что это дата | Пропускает Неопределено |
= '00010101' | Литерал, без вызова функции | Хуже читается |
| Своя функция модуля | Единый стиль в команде | Лишний вызов, нужна в каждой базе |
Если хотите убедиться сами, замерьте на своём железе:
Значение = Дата(1, 1, 1);
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Сч = 1 По 1000000 Цикл
Пусто = Не ЗначениеЗаполнено(Значение);
КонецЦикла;
Сообщить("ЗначениеЗаполнено: " + Формат(ТекущаяУниверсальнаяДатаВМиллисекундах() - Старт, "ЧГ=0") + " мс");
Повторите цикл с Значение = Дата(1, 1, 1) и Значение = '00010101' в условии и сравните числа.
Где скорость действительно важна — в запросе
В запросе не оборачивайте поле в функции внутри условия ГДЕ. Условие Док.СрокОплаты = ДАТАВРЕМЯ(1, 1, 1) СУБД может выполнить по индексу, если он есть. А ЕСТЬNULL(Док.СрокОплаты, ДАТАВРЕМЯ(1, 1, 1)) = ДАТАВРЕМЯ(1, 1, 1) или НАЧАЛОПЕРИОДА(Док.СрокОплаты, ДЕНЬ) = ... заставляют вычислять выражение для каждой строки.
Правило простое:
- ✅ сравнивайте поле напрямую с
ДАТАВРЕМЯ(...)или параметром; - ✅
ЕСТЬNULLприменяйте в секцииВЫБРАТЬ, а вГДЕпишитеЕСТЬ NULL ИЛИ = ...; - ❌ не пересчитывайте поле функциями ради сравнения;
- ❌ не выбирайте все строки, чтобы отфильтровать пустые даты циклом в коде.
Для отбора по реквизиту, по которому часто ищут, в конфигураторе можно включить у реквизита свойство «Индексировать». Это меняет структуру базы, поэтому делается через обновление конфигурации в согласованное окно.
Неочевидные случаи: время, SQL и обмены
Реквизит «только время»: полночь равна пустоте
У реквизита с составом даты «Время» значение хранится как 01.01.0001 ЧЧ:ММ:СС. Если пользователь ввёл 00:00:00, получится ровно Дата(1, 1, 1). Для платформы это пустое значение.
Последствия:
- ⏰
ЗначениеЗаполнено()вернётЛожьдля времени начала смены «00:00»; - ⏰ проверка заполнения на форме не даст записать объект с полуночью.
Если полночь — законное значение, не делайте такой реквизит обязательным через свойство. Храните признак «время указано» отдельным булевым реквизитом или проверяйте заполненность своим кодом.
Прямой SQL-запрос к базе на MS SQL Server
Тип datetime в MS SQL Server не поддерживает даты раньше 1753 года. Поэтому при создании базы 1С на этой СУБД обычно задают смещение дат 2000 лет. С таким смещением пустая дата 1С в таблицах сервера лежит как 2001-01-01 00:00:00, а все остальные даты сдвинуты на 2000 лет вперёд.
Если вы строите отчёт BI или выгрузку прямым SQL, учитывайте смещение: условие = '0001-01-01' ничего не найдёт. Смещение указывается при создании информационной базы и в её параметрах в консоли кластера.
Обмен через XML и JSON
При сериализации пустая дата превращается в 0001-01-01T00:00:00. Принимающая система не на 1С часто не умеет такую дату разобрать или подставляет свою «нулевую» дату. Договоритесь о формате заранее: либо не выгружать незаполненные даты вовсе, либо писать пустую строку и при загрузке приводить её через ПривестиЗначение.
Частые вопросы
Пустая дата и NULL в 1С — одно и то же?
01.01.0001. NULL появляется только в результатах запроса, например в поле правой таблицы левого соединения без пары.Почему поле на форме пустое, а в коде дата 01.01.0001?
Дата(1, 1, 1).Как очистить дату в коде?
Объект.ДатаОплаты = Дата(1, 1, 1);. Присваивать Неопределено реквизиту типа Дата не стоит: при записи значение приведётся к типу, а до записи код может повести себя непредсказуемо.Как задать пустую дату в настройках отчёта на СКД?
ДАТАВРЕМЯ(1, 1, 1).ЗначениеЗаполнено() считает дату 02.01.0001 пустой?
01.01.0001 00:00:00. Любая другая дата, даже на секунду позже, заполнена.