Почему прибавление секунды к дате 00.00.0000 00:00:00 дает 30.11.0002 00:00:01 в 1С:Предприятии?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке в среде «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. В рамках данной статьи мы подробно разберем, почему возникает эта разница, как платформа хранит временные данные, как на это влияет веб-клиент и как правильно работать с пустыми датами, чтобы избегать досадных ошибок в коде.

Разгадка математики нормализации дат: почему "00.00.0000" становится "30.11.0002"

Многие разработчики при первой встрече с датой 30.11.0002 склонны считать это забавной «пасхалкой», шуткой или багом разработчиков платформы. На самом деле это поведение строго детерминировано и объясняется алгоритмом нормализации дат (так называемым lenient parsing — свободным или «мягким» синтаксическим анализом строк).

Этот алгоритм широко применяется во множестве системных библиотек, написанных на C++, Java или используемых в СУБД. Когда парсер платформы получает некорректную строку, где дни, месяцы или года равны нулю, он не выдает ошибку преобразования типов, а пытается математически скорректировать (нормализовать) переданные значения. Разберем этот пошаговый процесс сдвига времени детально:

  1. Интерпретация года: В григорианском и юлианском календарях отсутствует понятие нулевого года. Сразу перед 1 годом нашей эры (н. э.) шел 1 год до нашей эры (до н. э.). Таким образом, год 0000 математически сдвигается системой на один шаг назад от начала летоисчисления и распознается как 1 год до нашей эры (или условный -1 год).
  2. Интерпретация месяца: Месяца под номером 00 не существует. Система воспринимает его как «на один месяц меньше, чем первый месяц (январь)». В результате математического вычитания одного месяца мы переносимся в декабрь предыдущего года. Год сдвигается еще на одну единицу назад и становится 2-м годом до нашей эры (или условным -2 годом), а месяц становится равным 12.
  3. Интерпретация дня: Дня с номером 00 также не существует. Парсер воспринимает его как «на один день меньше, чем 1-е число месяца». Вычитание одного дня от 1 декабря (полученного на предыдущем шаге) переносит нас на последний день предыдущего месяца. Предыдущим месяцем для декабря является ноябрь, в котором ровно 30 дней. Соответственно, мы получаем число 30, а месяц сдвигается на ноябрь (11).
  4. Результирующая историческая дата: После проведения всех внутренних вычислений мы получаем точную астрономическую дату: 30 ноября 2 года до нашей эры (30.11.-0002).
  5. Специфика вывода в 1С: Платформа 1С:Предприятие при отображении типа Дата во встроенном языке не имеет механизмов для вывода знака «минус» перед годом или явного указания эры «до н. э.». В связи с этим знак минус просто опускается при форматировании и выводе значения. Разработчик видит в отладчике и на экране результат: 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С

Выясним, какое значение является легитимным, правильным и задокументированным представлением пустой даты в экосистеме 1С. Единственная истинно пустая дата в платформе — это **01 января 0001 года 00:00:00**.

Для инициализации пустой даты во встроенном языке ни в коем случае нельзя использовать текстовый разбор некорректных строк типа "00.00.0000". Рассмотрим правильные способы создания пустой даты:

  1. Литерал даты. Это самый быстрый и правильный с точки зрения производительности способ. Запись выполняется в одинарных кавычках в формате YYYYMMDD: ПустаяДата = '00010101'; // или '00010101000000' с указанием времени
  2. Конструктор даты. Использование встроенной функции Дата() с передачей числовых параметров года, месяца и дня: ПустаяДата = Дата(1, 1, 1, 0, 0, 0); // Месяц и день начинаются с единицы!
  3. Проверка заполненности. Вместо прямого сравнения переменной с пустой датой рекомендуется использовать стандартную функцию ЗначениеЗаполнено(), которая корректно определяет пустые значения любых типов, включая даты:
    
    Если Не ЗначениеЗаполнено(ДокументСсылка.Дата) Тогда
        // Обработка ситуации, когда дата не заполнена
    КонецЕсли;
    

Точность хранения времени: миллисекунды и микросекунды в 1С

В ходе дискуссии на форуме разработчики затронули важную тему — точность хранения времени. На сколько мелкие дробные доли секунды способна обрабатывать платформа 1С? Проанализируем возможности типа Дата.

Во встроенном языке 1С:Предприятия тип Дата содержит внутри себя скрытую точность, выходящую за рамки отображаемых секунд. Платформа способна оперировать долями секунды с точностью до **0,1 миллисекунды** (что равняется 100 микросекундам, или 4 знакам после запятой).

Рассмотрим практический пример, чтобы наглядно убедиться в наличии дробной части:


// Зададим базовую дату
БазоваяДата = '20231130120000'; // 30.11.2023 12:00:00

// Прибавим к ней дробное число секунд (6 знаков после запятой)
ДатаПлюс = БазоваяДата + 0.123456;

// Рассчитаем разницу между полученным значением и исходным
Разность = ДатаПлюс - БазоваяДата;

Сообщить("Точное значение разности: " + Разность);
// В окне сообщений мы увидим: "Точное значение разности: 0,1235"

Этот эксперимент наглядно показывает, что платформа округлила прибавленную величину 0.123456 до 4 знаков после запятой — 0.1235. Таким образом, предел точности хранения времени во встроенном языке составляет четыре десятичных знака после запятой.

Однако при работе с базой данных накладываются жесткие ограничения. Рассмотрим их подробнее:

Если перед нами стоит задача точного хронологического упорядочивания документов, созданных в пределах одной секунды, или ведения высокоточных логов, использование типа Дата в базе данных не поможет. В таких сценариях применяются другие подходы:

  1. Использование встроенной системной функции ТекущаяУниверсальнаяДатаВМиллисекундах(). Она возвращает число миллисекунд (в формате типа Число), прошедших с начала нашей эры (01.01.0001 UTC). Это числовое значение можно без потери точности записывать в числовые реквизиты базы данных.
  2. Использование платформенного объекта МоментВремени, который гарантирует уникальный порядок документов в базе данных за счет комбинации даты (с точностью до секунды) и уникальной ссылки на сам документ.

Особенности поведения Веб-клиента и трансляции в JavaScript

В ходе практических тестов разработчики обнаружили удивительное различие. Если на стороне сервера или Тонкого клиента проверка сравнения Дата("00.00.0000") = Дата("30.11.0002") возвращает значение Истина, то на Веб-клиенте аналогичный код возвращает Ложь.

Выясним причину этого расхождения. Веб-клиент 1С работает непосредственно в интернет-браузере. Для выполнения кода на стороне клиента платформа компилирует (транслирует) код встроенного языка 1С в язык JavaScript.

В JavaScript работа с датами организована через стандартный встроенный объект Date. Когда браузерный движок JavaScript (например, V8 в браузерах семейства Chromium) пытается распарсить некорректную текстовую строку вида "00.00.0000 00:00:00", он обрабатывает её по своим правилам:

Данная особенность еще раз подчеркивает важность использования правильных стандартизированных методов работы с типами данных. Попытка использовать недокументированные особенности парсинга строк гарантированно приведет к ошибкам в кроссплатформенном коде при переходе пользователей на работу через веб-браузеры.

Рекомендации по безопасной работе с датами в 1С

Подводя итоги нашего исследования, сформулируем основные правила, которые помогут избежать ошибок при работе с датами:

  1. Никогда не используйте строку "00.00.0000" в коде. Для инициализации пустого значения применяйте только литерал '00010101'.
  2. Проверяйте внешние данные. При импорте информации из XML, JSON или текстовых файлов проверяйте строковые представления дат на соответствие формату перед передачей их в конструктор Дата(). Некорректные строки могут привести к скрытому преобразованию в 30.11.0002 без генерации исключений, что исказит ваши отчеты и аналитику.
  3. Используйте ЗначениеЗаполнено(). Для проверки заполненности полей всегда используйте эту функцию — она наиболее универсальна и устойчива к платформенным различиям сред исполнения.
  4. Для замеров времени используйте Число. Если вам необходима точность выше одной секунды (например, для замеров производительности или сортировки логов), сохраняйте значение ТекущаяУниверсальнаяДатаВМиллисекундах() в реквизиты с типом Число.

Понимание внутренних механизмов работы платформы позволяет писать более надежный, оптимизированный и предсказуемый код, который будет одинаково успешно работать на любых клиентах, операционных системах и СУБД.

← На главную