Почему функция ТекущаяДата() на некоторых компьютерах записывает данные без времени в 1С?

Программист 1С v8.3 (Обычные формы)
← На главную

При работе с платформой 1С:Предприятие, особенно в клиент-серверном варианте и при использовании SQL Server, разработчики могут столкнуться с неожиданным поведением функции ТекущаяДата(). Иногда на одних рабочих местах эта функция корректно возвращает дату и время с точностью до секунды, а на других — данные записываются без указания временной части, то есть с нулевым временем (00:00:00). Эта проблема может привести к некорректным расчетам и нарушению логики работы конфигурации, особенно если она опирается на последовательность записей или временные интервалы. Давайте подробно разберем, почему возникает такая ситуация, какие факторы на нее влияют, и как мы можем диагностировать и решить эту проблему.

Анализ исходной проблемы: почему ТекущаяДата() может терять время?

Мы столкнулись с ситуацией, когда данные в независимый периодический регистр сведений (РС) должны записываться с детализацией до секунды, но фактически половина записей создается без указания времени. Исходный код для записи поля Период в РС был НоваяЗапись.Период = ТекущаяДата(). Позже мы выяснили, что даже при использовании ДатыДокумента у половины документов время также было нулевым. Причем такая аномалия наблюдается не на всех рабочих местах и только при работе с SQL-базой, на файловой базе ошибок нет.

1. Влияние региональных настроек и системного окружения

Одной из наиболее частых причин некорректного поведения с датами и временем являются различия в системных настройках. Мы проанализируем, как региональные установки Windows на клиентских машинах и сервере, а также параметры самого SQL Server, могут влиять на форматирование и хранение даты/времени.

  1. Региональные настройки Windows: Настройки формата даты и времени в Windows могут отличаться на разных компьютерах. Если формат, установленный на клиентской машине или на сервере, каким-либо образом "отсекает" временную часть при взаимодействии с 1С или SQL, это может привести к потере времени. Хотя платформа 1С обычно старается абстрагироваться от таких настроек, в некоторых случаях (особенно при взаимодействии с внешними компонентами или особенностях драйверов) они могут проявиться.
  2. Параметры SQL Server: Проблема, проявляющаяся только на SQL-базе (как мы видим из сообщения 7), сразу наводит нас на мысль о возможном влиянии настроек сервера баз данных. В частности, collation (параметры сортировки) самой базы данных на SQL Server и сервера в целом могут влиять на интерпретацию и хранение строковых представлений дат. Если collation SQL Server отличается от того, который ожидает 1С, это может вызывать ошибки преобразования или потерю данных, включая временную часть. Мы должны убедиться, что используемый collation соответствует рекомендациям 1С для данной версии платформы.
  3. Обновления Windows и служб: Иногда проблема может быть вызвана некорректными обновлениями Windows на сервере, которые могли повлиять на работу служб 1С:Предприятия или SQL Server, особенно при запуске регламентных заданий. Перезагрузка серверов в таких случаях иногда помогает, но это скорее временное решение.

2. Особенности хранения даты и времени в 1С и SQL Server

Для того чтобы понять суть проблемы, нам необходимо рассмотреть, как 1С и SQL Server взаимодействуют при хранении данных типа "Дата" или "ДатаВремя".

  1. Точность хранения в 1С: Платформа 1С хранит дату и время с точностью до секунды. Встроенный язык 1С может работать с точностью до 0,1 миллисекунды, но при сохранении в базу данных эта точность не всегда используется или может быть ограничена возможностями СУБД. В 1С тип "Дата" содержит григорианский календарь (начиная с 01 января 0001 года) и время.
  2. Типы данных в SQL Server: Для хранения даты и времени в SQL Server чаще всего используется тип datetime2(0). Этот тип предпочтительнее устаревшего datetime из-за расширенного диапазона дат и отсутствия ограничения на минимальную дату (01.01.1753). Если в базе используется datetime, это может создавать определенные ограничения или особенности.
  3. "Смещение дат" (_YearOffset): Исторически, SQL Server до определенных версий имел ограничение на хранение дат ранее 1753 года. Чтобы обойти это, 1С применяла механизм "смещения дат": при записи в БД к дате добавлялись две тысячи лет, а при чтении — вычитались. Информация об этом смещении хранится в таблице _YearOffset. Если в 1С пользователь видит 01.01.2000, в БД может храниться 01.01.4000. Важно понимать, что даты в диапазоне с 00:00:00 1 января 1 года по 23:59:59 1 января 1 года в 1С считаются "временем без даты". Попытка записи дат из диапазона с 00:00:00 2 января 1 года по 23:59:59 1 января 1753 года в SQL Server без смещения или с некорректным смещением может привести к ошибке.

3. Роль серверного и клиентского времени: ТекущаяДата() vs ТекущаяДатаСеанса()

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

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

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

4. Влияние модулей объектов и регистров

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

Методы отладки и диагностики проблемы

Если вы столкнулись с подобной проблемой потери времени, мы выполним следующие шаги для диагностики:

  1. Проверка модулей объектов и регистров:

    Начнем с проверки кода в модулях объектов, которые записывают данные, и в модулях регистров сведений, куда эти данные попадают. Убедимся, что нет явного или неявного кода, который обнуляет временную часть даты. Такой код может выглядеть так:

    
    // Пример кода, который может обнулять время
    МояДата = НачалоДня(МояДата); 
    // Или
    МояДата = Дата(Год(МояДата), Месяц(МояДата), День(МояДата));
    
  2. Логирование значений даты/времени:

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

    
    // В процедуре, где происходит запись в Регистр Сведений
            
    // Получаем текущую дату для сравнения
    Перем ТекущаяДатаПриВызове;
    ТекущаяДатаПриВызове = ТекущаяДата();
            
    // Создаем новую запись Регистра Сведений
    НоваяЗаписьРС = РегистрыСведений.МойРегистр.СоздатьМенеджерЗаписи();
    // Присваиваем Период из ТекущаяДата()
    НоваяЗаписьРС.Период = ТекущаяДатаПриВызове;
    // ... присваиваем другие измерения и ресурсы ...
            
    // Логирование перед записью:
    Сообщить("----- Начало логирования -----");
    Сообщить("Клиентская ТекущаяДата() на момент вызова: " + Формат(ТекущаяДатаПриВызове, "ДЛФ=ДВ"));
    Сообщить("Период РС перед записью: " + Формат(НоваяЗаписьРС.Период, "ДЛФ=ДВ"));
    Сообщить("Имя пользователя: " + Пользователи.ТекущийПользователь().Наименование);
    Сообщить("Имя компьютера клиента: " + ИмяКомпьютера());
    Сообщить("----- Перед записью -----");
            
    Попытка
        НоваяЗаписьРС.Записать();
        // Логирование после записи:
        МенеджерЗаписиПосле = РегистрыСведений.МойРегистр.СоздатьМенеджерЗаписи();
        МенеджерЗаписиПосле.Период = НоваяЗаписьРС.Период; // Получаем период, который был записан
        // ... присваиваем те же измерения, что и при записи, чтобы найти записанную строку ...
        Если МенеджерЗаписиПосле.Прочитать() Тогда
            Сообщить("Период РС после записи (прочитан из БД): " + Формат(МенеджерЗаписиПосле.Период, "ДЛФ=ДВ"));
        Иначе
            Сообщить("Не удалось прочитать только что записанную строку по Периоду: " + Формат(НоваяЗаписьРС.Период, "ДЛФ=ДВ"));
        КонецЕсли;
        Сообщить("Запись в РС выполнена успешно.");
    Исключение
        Сообщить("Ошибка при записи в РС: " + ОписаниеОшибки());
    КонецПопытки;
    Сообщить("----- Окончание логирования -----");
    

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

  3. Проверка региональных настроек:

    Мы должны тщательно проверить региональные настройки Windows на всех клиентских машинах, где проявляется проблема, а также на сервере 1С:Предприятия и на сервере SQL. Убедимся, что формат даты и времени установлен единообразно и включает отображение секунд. Также необходимо проверить параметры сортировки (collation) SQL Server и сравнить их с рекомендациями 1С.

  4. Проверка часовых поясов:

    Убедимся, что часовые пояса сервера 1С и SQL Server, а также всех клиентских машин, настроены корректно. Несогласованность часовых поясов может приводить к смещению времени или его некорректной интерпретации. Рассмотрите возможность использования ТекущаяДатаСеанса() вместо ТекущаяДата(), чтобы централизовать получение времени от сервера 1С.

Конкретное решение проблемы автора: особенности работы "Среза последних" в 1С 8.1

В ходе анализа и отладки, автор обнаружил, что истинная причина его проблем с расчетами заключалась не столько в потере времени функцией ТекущаяДата(), сколько в неправильном использовании механизма "среза последних" для регистра сведений в 1С 8.1. Это очень важный момент, демонстрирующий, как одна проблема (потеря времени) может маскировать другую, более глубокую логическую ошибку в конфигурации.

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

Мы должны помнить: при получении среза последних в 1С, если мы не указываем все необходимые измерения, система может вернуть несколько строк, если они отличаются по непереданным измерениям. Это особенно критично, когда несколько записей имеют одинаковый "Период" (например, из-за потерянного времени), и система не может однозначно определить "последнюю" по вашим критериям.

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

Пример запроса, который мог бы решить проблему автора:


ВЫБРАТЬ
    ТаблицаРС.Измерение1,
    МАКСИМУМ(ТаблицаРС.Период) КАК Период,
    МАКСИМУМ(ТаблицаРС.Ресурс) КАК Ресурс // Или другие необходимые ресурсы, если нужно получить максимальный ресурс для максимального периода
ИЗ
    РегистрСведений.ИмяРегистра.СрезПоследних(
        &МоментВремени, // Или NULL для текущего момента
        Измерение1 = &ЗначениеИзмерения1 // Если нужно фильтровать по первому измерению
    ) КАК ТаблицаРС
СГРУППИРОВАТЬ ПО
    ТаблицаРС.Измерение1

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

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

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

  1. Обнуление временной части значения:

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

    
    // Используем функцию НачалоДня()
    ДатаБезВремени = НачалоДня(ТекущаяДата()); // Например, 2023-10-27 00:00:00
            
    // Или используем конструктор Даты
    ИсходнаяДата = ТекущаяДата();
    ДатаБезВремени2 = Дата(Год(ИсходнаяДата), Месяц(ИсходнаяДата), День(ИсходнаяДата)); // Тоже 2023-10-27 00:00:00
    

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

  2. Форматирование для вывода:

    Если нам нужно только строковое представление даты без времени (например, для отображения в поле ввода или на печати), мы можем использовать функцию Формат() со специальными параметрами.

    
    ИсходнаяДата = ТекущаяДата(); // Например, 2023-10-27 14:35:10
            
    // Форматирование только даты (без времени)
    СтрокаДаты = Формат(ИсходнаяДата, "ДЛФ=D"); // Результат: "27.10.2023" (или другой формат в зависимости от региональных настроек)
            
    // Форматирование с указанием конкретного формата
    СтрокаДаты2 = Формат(ИсходнаяДата, "ДФ=dd.MM.yyyy"); // Результат: "27.10.2023"
            
    // Форматирование только времени
    СтрокаВремени = Формат(ИсходнаяДата, "ДЛФ=T"); // Результат: "14:35:10"
    

    Обратите внимание, что даже если значение даты содержит время 00:00:00, 1С по умолчанию при преобразовании к строке все равно может добавлять временную часть (00:00:00), если не указан соответствующий формат. Функция Формат() дает нам полный контроль над представлением даты.

Заключение

Проблема потери временной части у функции ТекущаяДата() в 1С, особенно на SQL Server и в условиях работы на разных машинах, является комплексной и может быть вызвана множеством факторов: от региональных настроек Windows и SQL Server, особенностей хранения данных в СУБД, до специфики работы самой платформы 1С и логических ошибок в конфигурации. Случай, описанный в теме, ярко демонстрирует, как кажущаяся проблема с системной функцией (ТекущаяДата()) может оказаться симптомом более глубокой ошибки в логике работы со срезом последних в старых версиях 1С.

Мы рекомендуем всегда проводить тщательную диагностику с использованием логирования, проверять все уровни системы (клиент, сервер 1С, SQL Server), а также внимательно анализировать логику работы с данными в конфигурации. Понимание различий между ТекущаяДата() и ТекущаяДатаСеанса(), а также особенностей работы с периодическими регистрами и "срезами последних" в разных версиях платформы, поможет нам избегать подобных ошибок и строить надежные и корректно работающие системы 1С.

← На главную