В практике разработчика 1С часто встречается ситуация, когда в базе данных хранятся значения с типом «Дата», где заполнено только время, а сама дата установлена по умолчанию — 01.01.0001. Это происходит, если в свойствах реквизита выбран состав даты «Время». При попытке использовать такие данные в запросах для вычисления разницы дат или при выводе в отчеты могут возникать ошибки или некорректные результаты. В этой статье мы подробно разберем, как правильно перенести такие значения в текущий год, почему математический подход «в лоб» часто подводит и какие нюансы СУБД нужно учитывать.
Рассмотрим ситуацию, с которой столкнулся автор темы. Имеется дата 01.01.0001 23:58:22. Стоит задача: программно заменить 0001 год на 2019-й, сохранив при этом часы, минуты и секунды. Автор решил использовать функцию ДобавитьМесяц, вычислив общее количество месяцев от начала летоисчисления до текущей даты.
Логика кода выглядела примерно так:
ГодТекущейДаты = Год(ТекущаяДата()); // Допустим, 2019
КоличествоМесяцев = ГодТекущейДаты * 12;
// Для 2019 года это 24228 месяцев
НоваяДата = ДобавитьМесяц('00010101', КоличествоМесяцев);
В чем заключается ошибка? Проанализируем ситуацию: в григорианском календаре, используемом в 1С, отсутствует «нулевой» год. Летоисчисление начинается сразу с 1 года. Когда мы прибавляем 2019 лет (в виде месяцев) к 1-му году, мы фактически получаем 2020-й год (1 + 2019 = 2020).
Именно поэтому при попытке «добавить 2019 лет» автор получил дату 2020 года. Чтобы получить корректный результат через ДобавитьМесяц, необходимо добавлять разницу: (НужныйГод - 1) * 12.
Вместо сложных математических вычислений с месяцами, которые могут привести к ошибкам в високосные годы или при переходе месяцев, лучше использовать прямой конструктор функции Дата(). Это позволяет «собрать» новую дату из частей старой.
Разберем по шагам, как это сделать:
Пример кода:
Для Каждого Запись Из ЭтотОбъект Цикл
СтараяДата = Запись.Заезд;
// Проверяем, что год действительно "пустой" (0001)
Если Год(СтараяДата) = 1 Тогда
ТекущийГод = Год(ТекущаяДата());
// Собираем новую дату, сохраняя месяц, день и время
Запись.Заезд = Дата(ТекущийГод, Месяц(СтараяДата), День(СтараяДата),
Час(СтараяДата), Минута(СтараяДата), Секунда(СтараяДата));
КонецЕсли;
КонецЦикла;
Этот метод исключает любые ошибки смещения и работает максимально прозрачно для других разработчиков.
Если по каким-то причинам вы предпочитаете использовать функцию ДобавитьМесяц, расчет должен учитывать точку отсчета. Посмотрим на правильную формулу:
НужныйГод = 2019;
// Вычитаем 1, так как один год уже есть в дате '00010101'
МесяцевДляДобавления = (НужныйГод - 1) * 12;
Запись.Заезд = ДобавитьМесяц(Запись.Заезд, МесяцевДляДобавления);
Важно: Помните, что если в исходной дате Заезд был не январь, а, например, февраль, то прибавление полного количества месяцев текущего года также может сдвинуть результат. Поэтому метод с конструктором Дата() остается приоритетным.
Автор упомянул, что запрос выдает ошибку при расчете разности дат. Выясним причину. Большинство инсталляций 1С работают на базе MS SQL Server. У типа данных DATETIME в SQL Server есть ограничение: минимально допустимая дата — 1 января 1753 года.
Когда 1С выполняет функцию запроса РАЗНОСТЬДАТ, СУБД пытается обработать дату 0001 года. Если результат или промежуточное вычисление выходит за пределы лимитов SQL, возникает ошибка «SQL Server Error: Arithmetic overflow error».
Как этого избежать в запросах? Проанализируем пример безопасного кода в запросе:
ВЫБРАТЬ
ВЫБОР
КОГДА Т.ДатаЗаезда < ДАТАВРЕМЯ(1753, 1, 1)
ТОГДА ДАТАВРЕМЯ(2019, 1, 1) // Подменяем на минимально адекватную или нужную
ИНАЧЕ Т.ДатаЗаезда
КОНЕЦ КАК БезопаснаяДата
ИЗ
РегистрСведений.Заезды КАК Т
Стоит обратить внимание на важную техническую особенность платформы 1С. Если реквизит имеет тип «Дата», но в нем хранится только время (например, 23:58:22), то фактически в базе записано 01.01.0001 23:58:22.
Многие начинающие программисты используют проверку ЗначениеЗаполнено(Дата). Будьте осторожны:
01.01.0001 00:00:00 функция вернет Ложь.01.01.0001 00:00:01 (есть хотя бы одна секунда) функция вернет Истина.Поэтому, если ваша задача — найти «пустые» даты, в которых заполнено только время, всегда проверяйте год: Если Год(ПроверяемаяДата) < 2000 Тогда....
При манипуляциях с датами, особенно в серверном коде или при работе в разных часовых поясах, мы рекомендуем использовать ТекущаяДатаСеанса() вместо ТекущаяДата(). Это гарантирует, что «текущий год» будет определен в соответствии с настройками пользователя, а не по часам сервера, который может находиться в другом регионе. Это критично для систем, работающих с филиалами по всей стране.
Подведем итог: работа с датами начала летоисчисления требует аккуратности из-за отсутствия нулевого года и ограничений SQL-сервера. Используйте конструктор Дата() для безопасной замены года и всегда проверяйте входящие данные на наличие времени перед выполнением арифметических операций.