Работа со временем в 1С: получаем, форматируем и выводим часы

· 9 мин

Содержание

Текущее время в 1С 8.3 даёт функция ТекущаяДата(), а в клиент-серверной базе — ТекущаяДатаСеанса(). Отдельного типа «время» в платформе нет: часы, минуты и секунды хранятся внутри типа Дата вместе с календарной датой. Чтобы показать только время, значение прогоняют через функцию Формат с шаблоном ДЛФ=T — получится строка вида 14:35:07.

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

Тип «Дата» и как получить текущий момент

В 1С любая дата — это дата и время одновременно, с точностью до секунды. Даже когда вы видите в поле только 01.01.2024, внутри хранится ещё и время суток (по умолчанию 00:00:00). Поэтому «сделать часы» — значит правильно вытащить из Дата его временную часть и отформатировать.

Получить текущий момент можно двумя функциями:

  • ТекущаяДата() — дата и время компьютера, на котором исполняется код. На сервере это время сервера, на клиенте — время рабочей станции пользователя.
  • ТекущаяДатаСеанса() — момент с поправкой на часовой пояс сеанса. В клиент-серверной базе это основной способ: он не зависит от того, в каком поясе стоит сервер.
// Время компьютера, где выполняется код
ТекущийМомент = ТекущаяДата();

// Время с учётом часового пояса сеанса — предпочтительно в бизнес-логике
ТекущийМомент = ТекущаяДатаСеанса();

Для документов, движений и любых проводок по времени берите ТекущаяДатаСеанса(). Тогда сотрудник в Калининграде и сервер во Франкфурте увидят один и тот же «местный» момент, а не разницу в два часа.

Собрать дату из частей умеет конструктор Дата. Формат — год, месяц, день, час, минута, секунда:

// 1 января, 9 часов 30 минут ровно
Момент = Дата(2024, 1, 1, 9, 30, 0);

Пустая дата записывается как Дата(1, 1, 1) или литералом '00010101'. Это нулевое значение, с которым удобно сравнивать незаполненные реквизиты.

Как получить отдельно часы, минуты и секунды

Разобрать момент на составляющие помогают три функции — каждая принимает Дата и возвращает число:

Момент  = ТекущаяДатаСеанса();
Часы    = Час(Момент);     // 0..23
Минуты  = Минута(Момент);  // 0..59
Секунды = Секунда(Момент); // 0..59

Есть и обратная задача — отбросить время и оставить только дату. Для этого служит НачалоДня, а КонецДня даёт последнюю секунду суток:

НачалоСуток = НачалоДня(ТекущаяДатаСеанса());  // сегодня, 00:00:00
КонецСуток  = КонецДня(ТекущаяДатаСеанса());   // сегодня, 23:59:59

Отдельный приём, который экономит строчки кода: разность двух дат в 1С — это число секунд между ними. Отсюда — сколько секунд прошло с начала суток:

СекундСНачалаСуток = ТекущаяДатаСеанса() - НачалоДня(ТекущаяДатаСеанса());
ЧасовСНачалаСуток  = СекундСНачалаСуток / 3600;

И наоборот — прибавление числа к дате сдвигает её на столько же секунд. Момент + 3600 — это тот же момент плюс час, Момент - 60 — минус минута. Никаких отдельных функций «добавить час» не нужно.

Для «обрезки» момента до круглого значения есть пара функций, симметричных НачалоДня:

НачалоТекущегоЧаса = НачалоЧаса(ТекущаяДатаСеанса());   // 14:00:00
НачалоТекущейМинуты = НачалоМинуты(ТекущаяДатаСеанса()); // 14:35:00

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

Момент    = ТекущаяДатаСеанса();
Секунды   = Момент - НачалоДня(Момент);           // секунд с начала суток
Округлено = Цел(Секунды / 900) * 900;              // 900 сек = 15 минут
РезультатМомент = НачалоДня(Момент) + Округлено;

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

Форматирование времени: функция Формат

Превратить момент в читаемую строку умеет Формат. Два рабочих способа — готовый локальный формат и собственный шаблон.

Локальный формат ДЛФ использует настройки региона и не требует помнить порядок букв:

ТолькоВремя = Формат(ТекущаяДатаСеанса(), "ДЛФ=T");   // 14:35:07
ДатаИВремя  = Формат(ТекущаяДатаСеанса(), "ДЛФ=DT");  // 01.01.2024 14:35:07
ТолькоДата  = Формат(ТекущаяДатаСеанса(), "ДЛФ=D");   // 01.01.2024

Собственный шаблон ДФ задаёт порядок и разделители вручную. Буквы шаблона:

БукваЧто выводитПример
Ч / ЧЧЧас в 24-часовом формате9 / 09
ч / ччЧас в 12-часовом формате9 / 09
м / ммМинуты5 / 05
с / ссСекунды7 / 07
ттAM / PMAM
ЧасыМинуты = Формат(ТекущаяДатаСеанса(), "ДФ='ЧЧ:мм'");      // 14:35
Полное     = Формат(ТекущаяДатаСеанса(), "ДФ='ЧЧ:мм:сс'");   // 14:35:07

Главная ловушка форматной строки: месяц — это заглавная М, а минута — строчная м. Напишете "ДФ='ЧЧ:ММ'" — и вместо минут получите номер месяца. Одно внимание к регистру экономит разбор «почему часы показывают 14:01 в январе».

Двойная буква (ЧЧ, мм, сс) добавляет ведущий ноль, одинарная — нет. Для табло и печати почти всегда нужен двузначный вид.

Вывод времени в печатную форму

В печатной форме время обычно ставят в шапку — «Дата и время печати» или «Отпечатано». Механика одинаковая для любого макета табличного документа.

  1. Откройте макет печатной формы в конфигураторе или через расширение конфигурации (если дорабатываете типовую, не снимая её с поддержки).
  2. В нужной ячейке макета задайте свойство Заполнение → Параметр и впишите имя, например ВремяПечати.
  3. В модуле, который формирует печать, заполните этот параметр отформатированным значением.
Макет   = Документы.РасходнаяНакладная.ПолучитьМакет("Печать");
Область = Макет.ПолучитьОбласть("Шапка");

Область.Параметры.ВремяПечати = Формат(ТекущаяДатаСеанса(), "ДЛФ=DT");

ТабДокумент.Вывести(Область);

Если нужно, чтобы время в документе не «плыло» при каждом открытии, сохраните момент печати в реквизит документа при первом формировании и выводите уже сохранённое значение. Тогда повторная печать покажет то же время, что и первый экземпляр.

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

Живые часы на управляемой форме

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

Порядок настройки:

  1. Добавьте реквизит формы Часы типа Строка и выведите его на форму как поле или надпись.
  2. В модуле формы создайте процедуру, которая пишет в этот реквизит текущее время.
  3. Запустите её по таймеру функцией ПодключитьОбработчикОжидания.
&НаКлиенте
Процедура ПриОткрытии(Отказ)
    ОбновитьЧасы();
    // Второй аргумент — интервал в секундах
    ПодключитьОбработчикОжидания("ОбновитьЧасы", 1);
КонецПроцедуры

&НаКлиенте
Процедура ОбновитьЧасы()
    Часы = Формат(ТекущаяДата(), "ДФ='ЧЧ:мм:сс'");
КонецПроцедуры

Здесь намеренно взят ТекущаяДата(): часы на экране показывают локальное время пользователя, и это правильно — человек сверяется со своими настенными часами, а не с сервером.

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

Проверьте себя
Какой буквой в шаблоне `ДФ` задаются минуты?
Какая функция вернёт час в диапазоне 0–23?
Чем в клиент-серверной базе лучше брать текущий момент для бизнес-логики?

Часовые пояса и перевод времени

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

  • ЧасовойПоясСеанса() — возвращает часовой пояс текущего сеанса строкой (например, Europe/Moscow).
  • МестноеВремя(УниверсальнаяДата, ЧасовойПояс) — переводит момент из универсального времени (UTC) в местное. Если пояс не указать, берётся пояс сеанса.
  • УниверсальноеВремя(МестнаяДата, ЧасовойПояс) — обратный перевод, из местного в UTC.
ЧП = ЧасовойПоясСеанса();

// Из UTC в местное время сеанса
МестноеЗначение = МестноеВремя(МоментUTC);

// Из местного в универсальное
МоментUTC = УниверсальноеВремя(МестноеЗначение, ЧП);

Правило простое: храните и сравнивайте моменты в одном поясе. Если часть данных приходит из внешней системы в UTC, переведите их в местное время сразу на входе, а не при каждом выводе на экран. Тогда отчёты и отборы по дате не будут расходиться на величину смещения.

Типичный сценарий — заказ, оформленный в московском офисе и просматриваемый из филиала в другом поясе. Если момент сохранён в поясе сеанса через ТекущаяДатаСеанса(), оба пользователя увидят одно и то же время создания. А вот смешение: сохранили по ТекущаяДата() на сервере, а фильтруете по дате сеанса — приведёт к тому, что документ «за сегодня» то попадает в отбор, то выпадает из него на границе суток.

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

Запуск по расписанию и измерение интервалов

Когда код должен сработать не по действию пользователя, а в заданное время, используют регламентные задания. Это отдельный объект метаданных «Регламентное задание», у которого есть расписание. Увидеть список запусков можно через Главное меню → Все функции → Стандартные → Регламентные и фоновые задания (пункт «Все функции» включается в настройках программы).

Коротко о механике:

  • В клиент-серверной базе регламентные задания выполняет сервер автоматически, по расписанию, без открытого сеанса пользователя.
  • В файловой базе заданиям нужен открытый сеанс или отдельный выделенный сеанс, который их обслуживает, — сам по себе сервер в файловом режиме задания не крутит.
  • Запустить обработку немедленно, в фоне, можно через менеджер фоновых заданий из кода.

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

// Запуск метода общего модуля в фоне
Параметры = Новый Массив;
Параметры.Добавить(НачалоДня(ТекущаяДатаСеанса()));

ФоновоеЗадание = ФоновыеЗадания.Выполнить(
    "МойОбщийМодуль.ПересчитатьЗаПериод",
    Параметры);

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

Расписание удобнее всего настроить в диалоге расписания (вкладки «Общие», «Дневное», «Недельное», «Месячное») — там задаются время начала, периодичность и дни. В коде расписание — это объект:

Расписание = Новый РасписаниеРегламентногоЗадания;
Расписание.ВремяНачала = Дата(1, 1, 1, 9, 0, 0); // старт в 09:00

Остальные свойства расписания многочисленны — точные имена удобнее подсмотреть в синтакс-помощнике, чтобы не ошибиться с периодичностью.

Отдельная, но близкая задача — замерить, сколько работал участок кода. Для этого есть функция, возвращающая число миллисекунд:

Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();

// ... измеряемый код ...

Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить("Выполнено за " + Длительность + " мс");

Эта функция не зависит от часового пояса и не «прыгает» при переводе часов, поэтому для измерения длительности она точнее, чем разность обычных дат.

Частые вопросы

Как вывести только время, без даты?
Прогоните момент через Формат(Значение, "ДЛФ=T") или задайте свой шаблон Формат(Значение, "ДФ='ЧЧ:мм:сс'"). Само значение остаётся типом Дата с календарной частью — меняется только строковое представление.
Почему часы показывают неправильный месяц вместо минут?
Почти всегда перепутан регистр в шаблоне: заглавная М — это месяц, строчная м — минуты. Исправьте "ДФ='ЧЧ:ММ'" на "ДФ='ЧЧ:мм'".
Чем `ТекущаяДата()` отличается от `ТекущаяДатаСеанса()`?
ТекущаяДата() берёт время компьютера, где исполняется код (сервер или клиент). ТекущаяДатаСеанса() учитывает часовой пояс сеанса — это надёжнее в клиент-серверной базе, где сервер может стоять в другом регионе.
Можно ли хранить в реквизите только время, без даты?
Отдельного типа «время» в 1С нет. Используйте реквизит типа Дата с составом «Время» — платформа покажет в нём только часы и минуты, а дату подставит нулевую.
Как сделать так, чтобы часы на форме обновлялись сами?
Заведите реквизит формы под строку и вызывайте процедуру обновления через ПодключитьОбработчикОжидания("ИмяПроцедуры", 1) с интервалом в одну секунду. Процедура должна быть клиентской.