Почему внешне одинаковые даты в 1С не равны при сравнении и как это исправить?

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

Каждый разработчик 1С рано или поздно сталкивается с ситуацией, которая способна вызвать недоумение или даже шок. Представьте сценарий: вы сравниваете две даты в отладчике. Обе отображаются абсолютно идентично — например, 17.12.2018 8:00:32. Обе имеют тип Дата. Но при вычислении логического выражения Дата1 = Дата2 платформа упорно возвращает результат Ложь. А при попытке вычесть одну дату из другой получается не нулевое значение, а дробное число вроде 0,88.

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

Выясняем техническую причину: скрытые миллисекунды

Для начала проанализируем поведение платформы «1С:Предприятие». Согласно официальной документации (Синтакс-Помощнику), значения типа Дата содержат дату григорианского календаря и время с точностью до 0,1 миллисекунды (то есть до 100 микросекунд).

Когда мы получаем дату из стандартного поля ввода или из базы данных, платформа чаще всего возвращает нам значение, округленное до секунд. Однако в процессе математических вычислений, интеграции с внешними системами через REST-сервисы, десериализации JSON/XML или при сложном планировании (например, в посменном планировании в УПП или ERP) даты могут содержать скрытую дробную часть секунды.

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

  1. Разница между СУБД и оперативной памятью: При записи любого объекта в базу данных (будь то MS SQL, PostgreSQL или файловая база) платформа 1С принудительно округляет дату до секунд. Дробная часть просто отбрасывается. Но в оперативной памяти в объекте (например, в табличной части незаписанного или неочищенного документа) дата продолжает храниться с миллисекундами, которые были получены при расчете.
  2. Математические расчеты времени: Если в коде длительность операции прибавляется к дате начала как дробное число секунд (например, время работы станка в часах пересчитали в секунды и прибавили к началу смены), то итоговая дата гарантированно получит дробную часть.
  3. Получение данных запросом: Когда мы пишем запрос к уже записанной табличной части, СУБД возвращает нам дату, округленную до секунд. Но если мы сопоставляем ее со строкой той же табличной части прямо из оперативной памяти (из свойства ДокументОбъект), которая еще не была перечитана из базы данных, мы получаем конфликт сравнения «дата без миллисекунд» против «даты с миллисекундами».

Именно эта ситуация продемонстрирована в исходном вопросе: разность дат дала число 0,88 (что соответствует 880 миллисекундам). Для отладчика и стандартного представления обе даты округляются по правилам форматирования до секунды, но для оператора сравнения = они остаются разными физическими величинами.

Как воспроизвести проблему самостоятельно?

Давайте проведем простой эксперимент во встроенном языке, чтобы наглядно увидеть работу миллисекунд. Посмотрим на следующий пример кода:


ДатаНачала = Дата("20260723120000"); // Ровно 12:00:00
ДатаСМилисекундами = ДатаНачала + 0.350; // Прибавили 350 миллисекунд

Сообщить(ДатаНачала); // Выведет: 23.07.2026 12:00:00
Сообщить(ДатаСМилисекундами); // Выведет: 23.07.2026 12:00:00

Если ДатаНачала = ДатаСМилисекундами Тогда
    Сообщить("Даты равны");
Иначе
    Сообщить("Даты НЕ равны! Разница в секундах: " + (ДатаСМилисекундами - ДатаНачала));
КонецЕсли;

Запустив этот код, мы увидим в окне сообщений, что даты визуально идентичны, но условие равенства не выполняется, а разница составит ровно 0,35. Это доказывает, что платформа полноценно оперирует дробной частью секунд в оперативной памяти.

Теперь разберем практические способы решения этой проблемы. Нам необходимо уметь быстро и эффективно округлять («нормализовать») даты до секунд.

Способ 1. Математическое отсечение миллисекунд (самый быстрый способ)

Поскольку операция вычитания дат в 1С возвращает разницу в секундах в виде числа, мы можем легко получить дробную часть секунды с помощью математической операции взятия остатка от деления (оператор %). Если мы вычтем эту дробную часть из исходной даты, то получим чистую дату без миллисекунд.

Проанализируем этот элегантный метод:


// Очищаем миллисекунды математически
ОкругленнаяДата = ИсходнаяДата - (ИсходнаяДата % 1);

Этот способ работает чрезвычайно быстро, так как не требует вызова сложных системных функций сборки/разборки даты или строковых преобразований. Мы просто забираем остаток от деления на 1 секунду и вычитаем его.

Способ 2. Использование встроенных функций платформы

Замечательной особенностью платформы 1С является то, что большинство встроенных функций для работы с датами принудительно отбрасывают миллисекунды при возврате значения. Рассмотрим несколько простых трюков:

Использование функции ДобавитьМесяц:

Если мы прибавим к дате 0 месяцев, платформа вернет нам нормализованную дату без дробной части.

ОкругленнаяДата = ДобавитьМесяц(ИсходнаяДата, 0);

Комбинация функций начала периода:

Мы можем использовать функцию НачалоМинуты, которая гарантированно очищает секунды и миллисекунды, а затем прибавить к результату целое количество секунд, полученное из исходной даты.

ОкругленнаяДата = НачалоМинуты(ИсходнаяДата) + Секунда(ИсходнаяДата);

Этот метод абсолютно надежен, хотя и требует чуть больше процессорного времени на выполнение двух функций вместо простой математики.

Способ 3. Сборка даты «с нуля»

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

Разберем по шагам этот алгоритм:


Функция ОкруглитьДатуДоСекунд(Знач ВходнаяДата) Экспорт
    Возврат Дата(
        Год(ВходнаяДата),
        Месяц(ВходнаяДата),
        День(ВходнаяДата),
        Час(ВходнаяДата),
        Минута(ВходнаяДата),
        Секунда(ВходнаяДата)
    );
КонецФункции

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

Способ 4. Приведение дат к секундам в запросах

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

Чтобы гарантировать секундную точность внутри запроса, мы должны использовать функцию НАЧАЛОПЕРИОДА с точностью до секунды. Рассмотрим пример конструкции:


ВЫБРАТЬ
    НАЧАЛОПЕРИОДА(ТаблицаСроков.ДатаОкончанияРасчетная, СЕКУНДА) КАК ДатаБезМиллисекунд,
    ТаблицаСроков.Ссылка КАК ДокументСсылка
ИЗ
    Документ.ПланПроизводства.ЗанятостьРабочихЦентров КАК ТаблицаСроков

Использование НАЧАЛОПЕРИОДА(..., СЕКУНДА) — это стандарт де-факто для любых запросов, где есть риск получить микросекундные сдвиги при расчете временных интервалов.

Чего делать точно не стоит?

В процессе обсуждений на форумах часто предлагают использовать преобразование даты в строку формата ГГГГММДДЧЧММСС через функцию Формат, а затем собирать дату обратно.

Посмотрим на пример такого кода:


// Не рекомендуемый способ!
СтрокаДата = Формат(ИсходнаяДата, "ДФ=ггггММддЧЧммсс");
ОкругленнаяДата = Дата(СтрокаДата);

Почему это плохо? Операции преобразования типов «Дата -> Строка -> Дата» являются чрезвычайно ресурсоемкими. Если вам нужно обработать массив из десятков тысяч строк (например, при анализе занятости рабочих центров или проведении крупных документов), использование строковых функций приведет к резкому падению производительности. Используйте математическое вычитание остатка или функцию ДобавитьМесяц — они работают в разы быстрее.

Рекомендация для работы с методами поиска строк

Если вы используете метод поиска строк в коллекциях, например ТабличнаяЧасть.НайтиСтроки(ПараметрыПоиска), помните: этот метод производит сравнение на строгое равенство всех переданных параметров. Если в параметрах поиска будет лежать дата с миллисекундами, а в табличной части базы данных — дата, округленная до секунды, метод вернет пустой массив.

Перед передачей структуры параметров в метод НайтиСтроки() всегда проверяйте, чтобы дата была нормализована:


ПараметрыПоиска = Новый Структура;
// Обязательно очищаем миллисекунды перед поиском!
ПараметрыПоиска.Вставить("Период", ИскомаяДата - (ИскомаяДата % 1));
ПараметрыПоиска.Вставить("РабочийЦентр", ВыбранныйРЦ);

НайденныеСтроки = ЗанятостьРабочихЦентров.НайтиСтроки(ПараметрыПоиска);

Также хорошей практикой является вызов метода Прочитать() для объекта документа перед выполнением сложных алгоритмов перепланирования. Это гарантирует, что все данные в оперативной памяти будут синхронизированы с базой данных, и все миллисекунды в датах табличных частей будут сброшены до секундной точности СУБД.

← На главную