При разработке в среде «1С:Предприятие» программисты периодически сталкиваются с неочевидным поведением платформы при работе со значениями типа Дата. Одним из ярких примеров является разница прибавления времени к различным «пустым» датам. Например, если мы попытаемся прибавить одну секунду к дате вида "01.01.0001 00:00:00", мы получим вполне ожидаемый результат: 01.01.0001 00:00:01. Однако, если мы выполним аналогичную операцию прибавления секунды к дате, заданной строкой "00.00.0000 00:00:00", платформа вернет нам неожиданное значение — 30.11.0002 00:00:01. В рамках данной статьи мы подробно разберем, почему возникает эта разница, как платформа хранит временные данные, как на это влияет веб-клиент и как правильно работать с пустыми датами, чтобы избегать досадных ошибок в коде.
Многие разработчики при первой встрече с датой 30.11.0002 склонны считать это забавной «пасхалкой», шуткой или багом разработчиков платформы. На самом деле это поведение строго детерминировано и объясняется алгоритмом нормализации дат (так называемым lenient parsing — свободным или «мягким» синтаксическим анализом строк).
Этот алгоритм широко применяется во множестве системных библиотек, написанных на C++, Java или используемых в СУБД. Когда парсер платформы получает некорректную строку, где дни, месяцы или года равны нулю, он не выдает ошибку преобразования типов, а пытается математически скорректировать (нормализовать) переданные значения. Разберем этот пошаговый процесс сдвига времени детально:
0000 математически сдвигается системой на один шаг назад от начала летоисчисления и распознается как 1 год до нашей эры (или условный -1 год).00 не существует. Система воспринимает его как «на один месяц меньше, чем первый месяц (январь)». В результате математического вычитания одного месяца мы переносимся в декабрь предыдущего года. Год сдвигается еще на одну единицу назад и становится 2-м годом до нашей эры (или условным -2 годом), а месяц становится равным 12.00 также не существует. Парсер воспринимает его как «на один день меньше, чем 1-е число месяца». Вычитание одного дня от 1 декабря (полученного на предыдущем шаге) переносит нас на последний день предыдущего месяца. Предыдущим месяцем для декабря является ноябрь, в котором ровно 30 дней. Соответственно, мы получаем число 30, а месяц сдвигается на ноябрь (11).Дата во встроенном языке не имеет механизмов для вывода знака «минус» перед годом или явного указания эры «до н. э.». В связи с этим знак минус просто опускается при форматировании и выводе значения. Разработчик видит в отладчике и на экране результат: 30.11.0002 00:00:00.Проанализируем эту гипотезу с помощью простого эксперимента во встроенном языке 1С. Выполним сравнение этих двух представлений даты:
// Сравним результат парсинга некорректной строки с полученной датой
РезультатСравнения = (Дата("00.00.0000 00:00:00") = Дата("30.11.0002 00:00:00"));
Сообщить("Равны ли даты? " + ?(РезультатСравнения, "Да", "Нет"));
// Результат выполнения: "Равны ли даты? Да"
Как мы видим, обе записи указывают на одну и ту же физическую точку на временной шкале платформы. Следовательно, прибавив к дате "00.00.0000 00:00:00" одну секунду, мы закономерно получаем 30.11.0002 00:00:01.
Выясним, какое значение является легитимным, правильным и задокументированным представлением пустой даты в экосистеме 1С. Единственная истинно пустая дата в платформе — это **01 января 0001 года 00:00:00**.
Для инициализации пустой даты во встроенном языке ни в коем случае нельзя использовать текстовый разбор некорректных строк типа "00.00.0000". Рассмотрим правильные способы создания пустой даты:
ПустаяДата = '00010101'; // или '00010101000000' с указанием времени
Дата() с передачей числовых параметров года, месяца и дня:
ПустаяДата = Дата(1, 1, 1, 0, 0, 0); // Месяц и день начинаются с единицы!
ЗначениеЗаполнено(), которая корректно определяет пустые значения любых типов, включая даты:
Если Не ЗначениеЗаполнено(ДокументСсылка.Дата) Тогда
// Обработка ситуации, когда дата не заполнена
КонецЕсли;
В ходе дискуссии на форуме разработчики затронули важную тему — точность хранения времени. На сколько мелкие дробные доли секунды способна обрабатывать платформа 1С? Проанализируем возможности типа Дата.
Во встроенном языке 1С:Предприятия тип Дата содержит внутри себя скрытую точность, выходящую за рамки отображаемых секунд. Платформа способна оперировать долями секунды с точностью до **0,1 миллисекунды** (что равняется 100 микросекундам, или 4 знакам после запятой).
Рассмотрим практический пример, чтобы наглядно убедиться в наличии дробной части:
// Зададим базовую дату
БазоваяДата = '20231130120000'; // 30.11.2023 12:00:00
// Прибавим к ней дробное число секунд (6 знаков после запятой)
ДатаПлюс = БазоваяДата + 0.123456;
// Рассчитаем разницу между полученным значением и исходным
Разность = ДатаПлюс - БазоваяДата;
Сообщить("Точное значение разности: " + Разность);
// В окне сообщений мы увидим: "Точное значение разности: 0,1235"
Этот эксперимент наглядно показывает, что платформа округлила прибавленную величину 0.123456 до 4 знаков после запятой — 0.1235. Таким образом, предел точности хранения времени во встроенном языке составляет четыре десятичных знака после запятой.
Однако при работе с базой данных накладываются жесткие ограничения. Рассмотрим их подробнее:
Дата сохраняются базой данных с округлением до целых секунд. Вся дробная часть при записи в базу данных безвозвратно теряется.Если перед нами стоит задача точного хронологического упорядочивания документов, созданных в пределах одной секунды, или ведения высокоточных логов, использование типа Дата в базе данных не поможет. В таких сценариях применяются другие подходы:
ТекущаяУниверсальнаяДатаВМиллисекундах(). Она возвращает число миллисекунд (в формате типа Число), прошедших с начала нашей эры (01.01.0001 UTC). Это числовое значение можно без потери точности записывать в числовые реквизиты базы данных.МоментВремени, который гарантирует уникальный порядок документов в базе данных за счет комбинации даты (с точностью до секунды) и уникальной ссылки на сам документ.В ходе практических тестов разработчики обнаружили удивительное различие. Если на стороне сервера или Тонкого клиента проверка сравнения Дата("00.00.0000") = Дата("30.11.0002") возвращает значение Истина, то на Веб-клиенте аналогичный код возвращает Ложь.
Выясним причину этого расхождения. Веб-клиент 1С работает непосредственно в интернет-браузере. Для выполнения кода на стороне клиента платформа компилирует (транслирует) код встроенного языка 1С в язык JavaScript.
В JavaScript работа с датами организована через стандартный встроенный объект Date. Когда браузерный движок JavaScript (например, V8 в браузерах семейства Chromium) пытается распарсить некорректную текстовую строку вида "00.00.0000 00:00:00", он обрабатывает её по своим правилам:
Invalid Date, либо интерпретирует её как дефолтную дату (например, 01 января 2000 года).Данная особенность еще раз подчеркивает важность использования правильных стандартизированных методов работы с типами данных. Попытка использовать недокументированные особенности парсинга строк гарантированно приведет к ошибкам в кроссплатформенном коде при переходе пользователей на работу через веб-браузеры.
Подводя итоги нашего исследования, сформулируем основные правила, которые помогут избежать ошибок при работе с датами:
'00010101'.Дата(). Некорректные строки могут привести к скрытому преобразованию в 30.11.0002 без генерации исключений, что исказит ваши отчеты и аналитику.ТекущаяУниверсальнаяДатаВМиллисекундах() в реквизиты с типом Число.Понимание внутренних механизмов работы платформы позволяет писать более надежный, оптимизированный и предсказуемый код, который будет одинаково успешно работать на любых клиентах, операционных системах и СУБД.