При работе с платформой 1С:Предприятие, особенно в клиент-серверном варианте и при использовании SQL Server, разработчики могут столкнуться с неожиданным поведением функции ТекущаяДата(). Иногда на одних рабочих местах эта функция корректно возвращает дату и время с точностью до секунды, а на других — данные записываются без указания временной части, то есть с нулевым временем (00:00:00). Эта проблема может привести к некорректным расчетам и нарушению логики работы конфигурации, особенно если она опирается на последовательность записей или временные интервалы. Давайте подробно разберем, почему возникает такая ситуация, какие факторы на нее влияют, и как мы можем диагностировать и решить эту проблему.
ТекущаяДата() может терять время?Мы столкнулись с ситуацией, когда данные в независимый периодический регистр сведений (РС) должны записываться с детализацией до секунды, но фактически половина записей создается без указания времени. Исходный код для записи поля Период в РС был НоваяЗапись.Период = ТекущаяДата(). Позже мы выяснили, что даже при использовании ДатыДокумента у половины документов время также было нулевым. Причем такая аномалия наблюдается не на всех рабочих местах и только при работе с SQL-базой, на файловой базе ошибок нет.
Одной из наиболее частых причин некорректного поведения с датами и временем являются различия в системных настройках. Мы проанализируем, как региональные установки Windows на клиентских машинах и сервере, а также параметры самого SQL Server, могут влиять на форматирование и хранение даты/времени.
Для того чтобы понять суть проблемы, нам необходимо рассмотреть, как 1С и SQL Server взаимодействуют при хранении данных типа "Дата" или "ДатаВремя".
datetime2(0). Этот тип предпочтительнее устаревшего datetime из-за расширенного диапазона дат и отсутствия ограничения на минимальную дату (01.01.1753). Если в базе используется datetime, это может создавать определенные ограничения или особенности._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 без смещения или с некорректным смещением может привести к ошибке.ТекущаяДата() vs ТекущаяДатаСеанса()В распределенных системах, где пользователи работают в разных часовых поясах, очень важно понимать разницу между функциями получения текущей даты:
ТекущаяДата(): Эта функция определяет текущую (системную) дату и время на том компьютере, где непосредственно выполняется код. Если код выполняется на клиентской машине, будет взято клиентское время.ТекущаяДатаСеанса(): Эта функция получает текущую дату сервера, приведенную к часовому поясу текущего сеанса пользователя. При ее вычислении учитывается часовой пояс сеанса, затем информационной базы, а потом уже сервера. Это обеспечивает единообразие во времени для всех пользователей, независимо от их физического местонахождения и настроек их компьютеров.Мы рекомендуем в клиент-серверных вариантах работы, особенно когда требуется единое время для всех операций, использовать ТекущаяДатаСеанса() вместо ТекущаяДата(). Также важно правильно настроить часовой пояс сеанса в общих настройках программы, так как это влияет на хранение дат всех документов и данных.
Еще одной причиной потери временной части может быть код, выполняющийся в модулях объектов или регистров. Например, если в обработчике события ПриЗаписи модуля регистра сведений или документа присутствует код, который обнуляет время или присваивает дате новое значение без временной части, это может объяснить проблему. Мы выяснили, что в данном случае модуль регистра пуст (сообщение 12), что исключает эту причину, но в других ситуациях это может быть важным фактором.
Если вы столкнулись с подобной проблемой потери времени, мы выполним следующие шаги для диагностики:
Начнем с проверки кода в модулях объектов, которые записывают данные, и в модулях регистров сведений, куда эти данные попадают. Убедимся, что нет явного или неявного кода, который обнуляет временную часть даты. Такой код может выглядеть так:
// Пример кода, который может обнулять время
МояДата = НачалоДня(МояДата);
// Или
МояДата = Дата(Год(МояДата), Месяц(МояДата), День(МояДата));
Для того чтобы точно определить, на каком этапе теряется время и на какой машине это происходит, мы используем журнал регистрации. В ключевых точках кода будем записывать значения функции ТекущаяДата() и поля Период регистра до и после записи. Это поможет нам отследить путь данных и выявить проблемное место.
// В процедуре, где происходит запись в Регистр Сведений
// Получаем текущую дату для сравнения
Перем ТекущаяДатаПриВызове;
ТекущаяДатаПриВызове = ТекущаяДата();
// Создаем новую запись Регистра Сведений
НоваяЗаписьРС = РегистрыСведений.МойРегистр.СоздатьМенеджерЗаписи();
// Присваиваем Период из ТекущаяДата()
НоваяЗаписьРС.Период = ТекущаяДатаПриВызове;
// ... присваиваем другие измерения и ресурсы ...
// Логирование перед записью:
Сообщить("----- Начало логирования -----");
Сообщить("Клиентская ТекущаяДата() на момент вызова: " + Формат(ТекущаяДатаПриВызове, "ДЛФ=ДВ"));
Сообщить("Период РС перед записью: " + Формат(НоваяЗаписьРС.Период, "ДЛФ=ДВ"));
Сообщить("Имя пользователя: " + Пользователи.ТекущийПользователь().Наименование);
Сообщить("Имя компьютера клиента: " + ИмяКомпьютера());
Сообщить("----- Перед записью -----");
Попытка
НоваяЗаписьРС.Записать();
// Логирование после записи:
МенеджерЗаписиПосле = РегистрыСведений.МойРегистр.СоздатьМенеджерЗаписи();
МенеджерЗаписиПосле.Период = НоваяЗаписьРС.Период; // Получаем период, который был записан
// ... присваиваем те же измерения, что и при записи, чтобы найти записанную строку ...
Если МенеджерЗаписиПосле.Прочитать() Тогда
Сообщить("Период РС после записи (прочитан из БД): " + Формат(МенеджерЗаписиПосле.Период, "ДЛФ=ДВ"));
Иначе
Сообщить("Не удалось прочитать только что записанную строку по Периоду: " + Формат(НоваяЗаписьРС.Период, "ДЛФ=ДВ"));
КонецЕсли;
Сообщить("Запись в РС выполнена успешно.");
Исключение
Сообщить("Ошибка при записи в РС: " + ОписаниеОшибки());
КонецПопытки;
Сообщить("----- Окончание логирования -----");
Такое подробное логирование, включающее имя пользователя и имя компьютера, поможет нам точно определить, на какой машине и в какой момент происходит потеря времени.
Мы должны тщательно проверить региональные настройки Windows на всех клиентских машинах, где проявляется проблема, а также на сервере 1С:Предприятия и на сервере SQL. Убедимся, что формат даты и времени установлен единообразно и включает отображение секунд. Также необходимо проверить параметры сортировки (collation) SQL Server и сравнить их с рекомендациями 1С.
Убедимся, что часовые пояса сервера 1С и SQL Server, а также всех клиентских машин, настроены корректно. Несогласованность часовых поясов может приводить к смещению времени или его некорректной интерпретации. Рассмотрите возможность использования ТекущаяДатаСеанса() вместо ТекущаяДата(), чтобы централизовать получение времени от сервера 1С.
В ходе анализа и отладки, автор обнаружил, что истинная причина его проблем с расчетами заключалась не столько в потере времени функцией ТекущаяДата(), сколько в неправильном использовании механизма "среза последних" для регистра сведений в 1С 8.1. Это очень важный момент, демонстрирующий, как одна проблема (потеря времени) может маскировать другую, более глубокую логическую ошибку в конфигурации.
Как мы узнали из сообщения 15, автор использовал регистр сведений как "срез последних" по одному измерению, но не указал второе измерение. В 1С 8.1 (и некоторых схожих версиях) такая конструкция может привести к тому, что "срез последних" возвращает не одно ожидаемое значение, а список значений по этому измерению, если есть несколько записей с одинаковым "первым" измерением и одинаковым (нулевым) временем, но различающихся по "второму" (неиспользуемому в срезе) измерению или другими ресурсами. Вместо получения единственного значения, автор получал набор значений, что требовало дополнительной обработки.
Мы должны помнить: при получении среза последних в 1С, если мы не указываем все необходимые измерения, система может вернуть несколько строк, если они отличаются по непереданным измерениям. Это особенно критично, когда несколько записей имеют одинаковый "Период" (например, из-за потерянного времени), и система не может однозначно определить "последнюю" по вашим критериям.
Чтобы корректно получить одно значение из такого "среза последних", автору все равно пришлось делать дополнительную группировку и выбирать максимальное значение. Это означает, что даже если бы ТекущаяДата() всегда возвращала время, проблема некорректного использования среза последних все равно оставалась бы. Отсутствие времени лишь усугубило ситуацию, делая несколько записей с одинаковым днем (но разным временем) неразличимыми по периоду, и тогда "срез последних" мог выбирать любую из них или возвращать их все, если были другие измерения.
Пример запроса, который мог бы решить проблему автора:
ВЫБРАТЬ
ТаблицаРС.Измерение1,
МАКСИМУМ(ТаблицаРС.Период) КАК Период,
МАКСИМУМ(ТаблицаРС.Ресурс) КАК Ресурс // Или другие необходимые ресурсы, если нужно получить максимальный ресурс для максимального периода
ИЗ
РегистрСведений.ИмяРегистра.СрезПоследних(
&МоментВремени, // Или NULL для текущего момента
Измерение1 = &ЗначениеИзмерения1 // Если нужно фильтровать по первому измерению
) КАК ТаблицаРС
СГРУППИРОВАТЬ ПО
ТаблицаРС.Измерение1
Этот запрос явно группирует по Измерение1 и выбирает максимальный Период для каждой группы, гарантируя, что мы получим одно, действительно последнее значение для каждого уникального Измерения1. Если нам нужно выбрать и соответствующие ресурсы, то это усложняет запрос, так как нужен будет другой механизм получения "сопутствующих" данных к максимальному периоду (например, через вложенный запрос или соединение).
Иногда нам действительно необходимо работать с датами без учета временной части, например, для сравнений "на один и тот же день" или для вывода в отчетах. Мы рассмотрим, как получить и отформатировать дату без времени.
Для того чтобы получить значение даты без временной части, мы можем использовать встроенные функции или операторы.
// Используем функцию НачалоДня()
ДатаБезВремени = НачалоДня(ТекущаяДата()); // Например, 2023-10-27 00:00:00
// Или используем конструктор Даты
ИсходнаяДата = ТекущаяДата();
ДатаБезВремени2 = Дата(Год(ИсходнаяДата), Месяц(ИсходнаяДата), День(ИсходнаяДата)); // Тоже 2023-10-27 00:00:00
Эти методы возвращают значение типа "Дата", у которого временная часть установлена в 00:00:00. Это очень удобно для сравнений, когда нужно учитывать только день, месяц и год.
Если нам нужно только строковое представление даты без времени (например, для отображения в поле ввода или на печати), мы можем использовать функцию Формат() со специальными параметрами.
ИсходнаяДата = ТекущаяДата(); // Например, 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С.