Программное чтение рабочей даты пользователя в 1С 8.3: БСП, часовые пояса, РИБ
Содержание
Чтобы получить рабочую дату в 1С 8.3 в типовой конфигурации на БСП, вызовите на сервере ОбщегоНазначения.ТекущаяДатаПользователя(). Функция вернёт рабочую дату, если пользователь её задал, а если нет — текущую дату сеанса, приведённую к началу дня.
Частая ошибка — брать ТекущаяДата() и считать, что это и есть рабочая дата. Это просто часы компьютера или сервера. Рабочая дата — пользовательская настройка, и её нужно читать отдельно, иначе документы «из прошлого месяца» будут создаваться сегодняшним числом.
Чем рабочая дата отличается от текущей и где она хранится
Рабочая дата — дата, которую пользователь сам выставил как «сегодня» для своей работы. Бухгалтер закрывает сентябрь в октябре, ставит рабочую дату 30.09 — и новые документы, отчёты, периоды по умолчанию открываются на эту дату.
В конфигурациях на Библиотеке стандартных подсистем (БП 3.0, ЗУП 3.1, УТ 11, КА, ERP) рабочая дата хранится в хранилище общих настроек пользователя. Это не реквизит справочника «Пользователи» и не константа. У каждого пользователя своя дата, и её не видно в обычных списках.
Сравним функции, которые часто путают:
| Функция | Что возвращает | Где работает |
|---|---|---|
ТекущаяДата() | Дату и время компьютера, на котором выполняется код | Клиент и сервер |
ТекущаяДатаСеанса() | Дату и время в часовом поясе сеанса | Сервер, клиент (в новых версиях платформы) |
ТекущаяУниверсальнаяДата() | Время UTC сервера | Сервер |
ОбщегоНазначения.ТекущаяДатаПользователя() | Рабочую дату или начало текущего дня сеанса | Только сервер |
ОбщегоНазначения.РабочаяДатаПользователя() | Только рабочую дату; пустую дату, если она не задана | Только сервер |
- 🕐
ТекущаяДата()на клиенте и на сервере может вернуть разное время: часы рабочей станции и сервера 1С не синхронизированы друг с другом. - 📅
ТекущаяДатаПользователя()время отбрасывает — возвращает начало дня. Для отметок «кто и когда изменил» она не годится. - 🧭
РабочаяДатаПользователя()удобна, когда нужно понять, задал ли пользователь дату вообще.
Если конфигурация самописная и БСП в ней нет, этих функций тоже нет. Тогда рабочую дату придётся хранить самостоятельно — схема ниже, в разделе про регистр сведений.
Как получить рабочую дату на сервере
Общий модуль ОбщегоНазначения в БСП серверный. Вызывать его можно из модуля объекта, модуля менеджера, серверных процедур формы, обработчиков проведения и фоновых заданий.
Базовый вариант — дата для заполнения нового документа:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если Объект.Ссылка.Пустая() Тогда
Объект.Дата = ОбщегоНазначения.ТекущаяДатаПользователя();
КонецЕсли;
КонецПроцедуры
Если нужно отличить «пользователь задал дату» от «даты нет», берите вторую функцию:
РабочаяДата = ОбщегоНазначения.РабочаяДатаПользователя();
Если ЗначениеЗаполнено(РабочаяДата) Тогда
// пользователь работает «в прошлом» или «в будущем»
НачалоПериода = НачалоМесяца(РабочаяДата);
Иначе
НачалоПериода = НачалоМесяца(ТекущаяДатаСеанса());
КонецЕсли;
Обе функции принимают необязательный параметр — имя пользователя ИБ. По умолчанию берётся текущий пользователь. Прочитать дату чужого пользователя можно, но настройки другого пользователя в хранилище доступны только с административными правами.
Установка рабочей даты из кода
Парная процедура — ОбщегоНазначения.УстановитьРабочуюДатуПользователя(НоваяРабочаяДата, ИмяПользователя). Чтобы сбросить рабочую дату и вернуться к текущей, передайте пустую дату '00010101'.
// Поставить рабочую дату на последний день прошлого месяца
ОбщегоНазначения.УстановитьРабочуюДатуПользователя(
КонецМесяца(ДобавитьМесяц(ТекущаяДатаСеанса(), -1)));
// Сбросить рабочую дату
ОбщегоНазначения.УстановитьРабочуюДатуПользователя('00010101');
Модули регламентированного учёта
В обсуждениях встречаются советы брать рабочую дату через общие модули регламентированного учёта. В типовых конфигурациях такие модули решают свои задачи — отчётность, учётную политику, налоги — и рабочую дату в итоге всё равно читают через БСП. Надёжная точка входа одна: ОбщегоНазначения. Чтобы убедиться, откройте модуль в Конфигураторе и найдите функцию через Правка → Найти (Ctrl+F) или глобальный поиск Ctrl+Shift+F по строке ТекущаяДатаПользователя.
Получение рабочей даты на клиенте
На клиенте модуль ОбщегоНазначения недоступен. Попытка вызвать его из процедуры &НаКлиенте даёт ошибку компиляции Переменная не определена (ОбщегоНазначения).
Решение — серверная функция без контекста в модуле формы:
&НаКлиенте
Процедура ЗаполнитьДату(Команда)
Объект.ДатаОтчета = РабочаяДатаНаСервере();
КонецПроцедуры
&НаСервереБезКонтекста
Функция РабочаяДатаНаСервере()
Возврат ОбщегоНазначения.ТекущаяДатаПользователя();
КонецФункции
&НаСервереБезКонтекста не передаёт данные формы на сервер, поэтому вызов лёгкий. Если дата нужна в нескольких формах, вынесите функцию в свой общий модуль с флажками «Сервер» и «Вызов сервера».
Как не дёргать сервер на каждое действие
Рабочая дата меняется редко. Если она нужна в обработчике, который срабатывает часто (ПриИзменении поля, обработка ожидания), прочитайте её один раз в ПриСозданииНаСервере и положите в реквизит формы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
РабочаяДата = ОбщегоНазначения.ТекущаяДатаПользователя();
КонецПроцедуры
Реквизит формы РабочаяДата с типом «Дата» затем читается на клиенте без серверного вызова.
Если пользователь сменит рабочую дату, уже открытые формы этого не увидят. Значение подхватится при следующем открытии формы.
Дата сеанса на клиенте
В БСП есть ОбщегоНазначенияКлиент.ДатаСеанса(). Она возвращает дату и время в часовом поясе сеанса без обращения к серверу, но это не рабочая дата. Подходит для отображения «сейчас» с учётом таймзоны, не для заполнения документов.
Рабочая дата без БСП: собственный регистр сведений
В самописной конфигурации, где БСП нет, рабочую дату проще всего держать в независимом регистре сведений. Пример структуры (объект создаёте сами, в типовых его нет):
- 📁 Регистр сведений
РабочиеДатыПользователей, непериодический, независимый. - 🔑 Измерение
Пользователь— типСправочникСсылка.Пользователи. - 📅 Ресурс
РабочаяДата— тип «Дата», состав «Дата».
Чтение с запасным вариантом:
Функция РабочаяДата(Пользователь) Экспорт
Отбор = Новый Структура("Пользователь", Пользователь);
Значения = РегистрыСведений.РабочиеДатыПользователей.Получить(Отбор);
Если ЗначениеЗаполнено(Значения.РабочаяДата) Тогда
Возврат Значения.РабочаяДата;
КонецЕсли;
Возврат НачалоДня(ТекущаяДатаСеанса());
КонецФункции
Метод Получить() у менеджера регистра возвращает структуру ресурсов. Если записи нет, ресурс в ней будет пустой датой, поэтому проверка ЗначениеЗаполнено обязательна.
Запись:
Менеджер = РегистрыСведений.РабочиеДатыПользователей.СоздатьМенеджерЗаписи();
Менеджер.Пользователь = Пользователь;
Менеджер.РабочаяДата = НоваяДата;
Менеджер.Записать(); // запись с тем же измерением заменит старую
Альтернатива регистру — хранить дату в ХранилищеОбщихНастроек через методы Сохранить и Загрузить со своим ключом объекта. Регистр выигрывает тем, что его видно в списке, по нему можно строить запросы и задавать права.
| Способ | Плюсы | Минусы |
|---|---|---|
БСП, ТекущаяДатаПользователя() | Готово, работает со стандартным интерфейсом | Только в конфигурациях на БСП |
| Свой регистр сведений | Прозрачно, доступно в запросах | Нужно дорабатывать конфигурацию и права |
ХранилищеОбщихНастроек со своим ключом | Без новых объектов метаданных | Не видно в интерфейсе, нельзя выбрать запросом |
Часовые пояса и рабочая дата
Рабочая дата — это день без времени. Часовой пояс на неё не влияет: если бухгалтер поставил 30.09, будет 30.09 и в Москве, и в Новосибирске.
Таймзона влияет на запасной вариант — когда рабочая дата не задана и функция берёт текущую дату сеанса. Вот где возникают сдвиги:
- 🌍 Сервер 1С в Москве, пользователь во Владивостоке. В 02:00 по Владивостоку на сервере ещё вчерашний вечер.
ТекущаяДата()на сервере вернёт вчерашний день, аТекущаяДатаСеанса()— правильный, если часовой пояс сеанса задан. - ⏰ Часовой пояс сеанса по умолчанию берётся из часового пояса информационной базы. Его меняют методом
УстановитьЧасовойПоясСеанса(), а пояс базы —УстановитьЧасовойПоясИнформационнойБазы(). - 🔁 Перевести дату из одного пояса в другой помогает
МестноеВремя(ДатаUTC, ЧасовойПояс)и обратная функцияУниверсальноеВремя().
Проверка, в каком поясе работает сеанс:
Сообщить("Пояс сеанса: " + ЧасовойПоясСеанса());
Сообщить("Пояс базы: " + ПолучитьЧасовойПоясИнформационнойБазы());
Сообщить("Дата сеанса: " + Формат(ТекущаяДатаСеанса(), "ДЛФ=DT"));
Правило для своего кода: везде, где нужна «сегодняшняя» дата для учёта, используйте ТекущаяДатаСеанса(), а не ТекущаяДата(). Тогда филиалы в разных часовых поясах увидят свой день, а не день серверной.
В типовых на БСП часовой пояс базы задаётся в общих настройках программы (раздел Администрирование). После смены пояса проверьте даты новых документов на нескольких рабочих местах.
Рабочая дата в РИБ и фоновых заданиях
Распределённая информационная база
В РИБ узлы обмениваются данными объектов, которые входят в состав плана обмена. Настройки пользователей из хранилища общих настроек в обмен не попадают. Значит, рабочая дата задаётся на каждом узле отдельно: бухгалтер филиала ставит свою дату у себя.
Если дату хранит свой регистр сведений, решите заранее, нужен ли он в обмене:
- 🏢 Включили в состав плана обмена — дата пользователя «переедет» на другой узел, а пользователь с тем же именем там может работать в другом периоде.
- 🔒 Не включили — каждый узел живёт со своими датами. Для рабочей даты это обычно правильный выбор.
Узлы часто стоят в разных часовых поясах. Если рабочая дата не задана, каждый узел возьмёт свою дату сеанса, и документ, созданный в 23:30 в Калининграде, на центральном узле в Москве уже попадёт на следующий день по местному времени. Сама дата документа при этом не меняется — она передаётся как есть.
Фоновые и регламентные задания
Фоновое задание выполняется от имени пользователя, который его запустил. ТекущаяДатаПользователя() внутри вернёт рабочую дату этого пользователя.
Регламентное задание работает от пользователя, указанного в его настройках. Если такой пользователь не указан, рабочей даты у задания по сути нет.
Не полагайтесь на рабочую дату внутри регламентных заданий. Ночная загрузка курсов или закрытие дня должны работать по реальной дате. Если заданию нужна дата конкретного пользователя, передайте её параметром при запуске.
Параметры = Новый Массив;
Параметры.Добавить(ОбщегоНазначения.ТекущаяДатаПользователя());
ФоновыеЗадания.Выполнить("МойМодульСервер.ПересчитатьПериод", Параметры);
Ошибки при получении рабочей даты и как их разобрать
Ещё несколько ловушек, которые не дают ошибки, но портят данные:
- ⚠️ Время в дате документа.
ТекущаяДатаПользователя()возвращает 00:00:00. Если сразу провести несколько документов с одной рабочей датой, их порядок внутри дня определяется не временем, а порядком записи. Для партионного учёта это важно — при необходимости добавляйте время вручную. - ⚠️ Привилегированный режим.
УстановитьПривилегированныйРежим(Истина)не меняет текущего пользователя. Рабочая дата читается у того, кто работает в сеансе, а не у администратора. - ⚠️ Забытая рабочая дата. Пользователь поставил 31.12 для закрытия года и забыл сбросить. Через месяц он создаёт документы декабрём. Полезно выводить предупреждение на начальной странице, если
РабочаяДатаПользователя()заполнена и отстаёт от текущей больше чем на 30 дней.
Пример такой проверки:
РабочаяДата = ОбщегоНазначения.РабочаяДатаПользователя();
Если ЗначениеЗаполнено(РабочаяДата)
И ТекущаяДатаСеанса() - РабочаяДата > 30 * 86400 Тогда
ОбщегоНазначения.СообщитьПользователю(
СтрШаблон("Установлена рабочая дата %1. Проверьте, нужна ли она.",
Формат(РабочаяДата, "ДЛФ=D")));
КонецЕсли;
Разность двух дат в 1С — число секунд, поэтому 30 дней — это 30 * 86400.
Частые вопросы
Видит ли рабочую дату запрос?
Запрос.УстановитьПараметр("Дата", ОбщегоНазначения.ТекущаяДатаПользователя()).Можно ли задать одну рабочую дату всем пользователям сразу?
УстановитьРабочуюДатуПользователя с именем каждого. Нужны права администратора. Обычно так делают только на время закрытия периода.