Работа со временем в 1С: получаем, форматируем и выводим часы
Содержание
Текущее время в 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 / PM | AM |
ЧасыМинуты = Формат(ТекущаяДатаСеанса(), "ДФ='ЧЧ:мм'"); // 14:35
Полное = Формат(ТекущаяДатаСеанса(), "ДФ='ЧЧ:мм:сс'"); // 14:35:07
Главная ловушка форматной строки: месяц — это заглавная М, а минута — строчная м. Напишете "ДФ='ЧЧ:ММ'" — и вместо минут получите номер месяца. Одно внимание к регистру экономит разбор «почему часы показывают 14:01 в январе».
Двойная буква (ЧЧ, мм, сс) добавляет ведущий ноль, одинарная — нет. Для табло и печати почти всегда нужен двузначный вид.
Вывод времени в печатную форму
В печатной форме время обычно ставят в шапку — «Дата и время печати» или «Отпечатано». Механика одинаковая для любого макета табличного документа.
- Откройте макет печатной формы в конфигураторе или через расширение конфигурации (если дорабатываете типовую, не снимая её с поддержки).
- В нужной ячейке макета задайте свойство
Заполнение→Параметри впишите имя, напримерВремяПечати. - В модуле, который формирует печать, заполните этот параметр отформатированным значением.
Макет = Документы.РасходнаяНакладная.ПолучитьМакет("Печать");
Область = Макет.ПолучитьОбласть("Шапка");
Область.Параметры.ВремяПечати = Формат(ТекущаяДатаСеанса(), "ДЛФ=DT");
ТабДокумент.Вывести(Область);
Если нужно, чтобы время в документе не «плыло» при каждом открытии, сохраните момент печати в реквизит документа при первом формировании и выводите уже сохранённое значение. Тогда повторная печать покажет то же время, что и первый экземпляр.
Для доработки макета типовой конфигурации используйте расширение конфигурации — оно хранит ваш макет отдельно и переживает обновление. Это и есть главный приём, который отличает аккуратную доработку от «правки на поддержке».
Живые часы на управляемой форме
Чтобы на форме тикали часы, которые обновляются сами, нужен реквизит формы под строку времени и обработчик ожидания — клиентская процедура, которую платформа вызывает через заданный интервал.
Порядок настройки:
- Добавьте реквизит формы
ЧасытипаСтрокаи выведите его на форму как поле или надпись. - В модуле формы создайте процедуру, которая пишет в этот реквизит текущее время.
- Запустите её по таймеру функцией
ПодключитьОбработчикОжидания.
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ОбновитьЧасы();
// Второй аргумент — интервал в секундах
ПодключитьОбработчикОжидания("ОбновитьЧасы", 1);
КонецПроцедуры
&НаКлиенте
Процедура ОбновитьЧасы()
Часы = Формат(ТекущаяДата(), "ДФ='ЧЧ:мм:сс'");
КонецПроцедуры
Здесь намеренно взят ТекущаяДата(): часы на экране показывают локальное время пользователя, и это правильно — человек сверяется со своими настенными часами, а не с сервером.
Остановить таймер, когда он больше не нужен (например, при закрытии формы), можно парной функцией ОтключитьОбработчикОжидания("ОбновитьЧасы"). Обработчик ожидания работает только на клиенте и только пока форма открыта, так что держать на нём тяжёлую логику не стоит — лёгкое обновление надписи идеально.
Часовые пояса и перевод времени
Когда база обслуживает пользователей из разных регионов, время приходится переводить между поясами. В платформе за это отвечают три функции.
ЧасовойПоясСеанса()— возвращает часовой пояс текущего сеанса строкой (например,Europe/Moscow).МестноеВремя(УниверсальнаяДата, ЧасовойПояс)— переводит момент из универсального времени (UTC) в местное. Если пояс не указать, берётся пояс сеанса.УниверсальноеВремя(МестнаяДата, ЧасовойПояс)— обратный перевод, из местного в UTC.
ЧП = ЧасовойПоясСеанса();
// Из UTC в местное время сеанса
МестноеЗначение = МестноеВремя(МоментUTC);
// Из местного в универсальное
МоментUTC = УниверсальноеВремя(МестноеЗначение, ЧП);
Правило простое: храните и сравнивайте моменты в одном поясе. Если часть данных приходит из внешней системы в UTC, переведите их в местное время сразу на входе, а не при каждом выводе на экран. Тогда отчёты и отборы по дате не будут расходиться на величину смещения.
Типичный сценарий — заказ, оформленный в московском офисе и просматриваемый из филиала в другом поясе. Если момент сохранён в поясе сеанса через ТекущаяДатаСеанса(), оба пользователя увидят одно и то же время создания. А вот смешение: сохранили по ТекущаяДата() на сервере, а фильтруете по дате сеанса — приведёт к тому, что документ «за сегодня» то попадает в отбор, то выпадает из него на границе суток.
ТекущаяДата() на сервере вернёт время сервера в его собственном поясе. Если сервер стоит в другом регионе, а вы сравниваете это значение с датами документов, разница в несколько часов даст плавающие ошибки в отборах. Для таких сравнений берите ТекущаяДатаСеанса().
Запуск по расписанию и измерение интервалов
Когда код должен сработать не по действию пользователя, а в заданное время, используют регламентные задания. Это отдельный объект метаданных «Регламентное задание», у которого есть расписание. Увидеть список запусков можно через Главное меню → Все функции → Стандартные → Регламентные и фоновые задания (пункт «Все функции» включается в настройках программы).
Коротко о механике:
- В клиент-серверной базе регламентные задания выполняет сервер автоматически, по расписанию, без открытого сеанса пользователя.
- В файловой базе заданиям нужен открытый сеанс или отдельный выделенный сеанс, который их обслуживает, — сам по себе сервер в файловом режиме задания не крутит.
- Запустить обработку немедленно, в фоне, можно через менеджер фоновых заданий из кода.
Фоновое задание отличается от регламентного тем, что оно разовое: вы запускаете его сами, а не по расписанию. Это удобно, когда долгий расчёт не должен держать пользователя у заблокированной формы:
// Запуск метода общего модуля в фоне
Параметры = Новый Массив;
Параметры.Добавить(НачалоДня(ТекущаяДатаСеанса()));
ФоновоеЗадание = ФоновыеЗадания.Выполнить(
"МойОбщийМодуль.ПересчитатьЗаПериод",
Параметры);
Метод, который вызывается в фоне, должен быть экспортным и находиться в общем модуле с флагом сервера. Проверить, завершилось ли задание, позволяет его метод ожидания или повторное обращение к менеджеру заданий по идентификатору.
Расписание удобнее всего настроить в диалоге расписания (вкладки «Общие», «Дневное», «Недельное», «Месячное») — там задаются время начала, периодичность и дни. В коде расписание — это объект:
Расписание = Новый РасписаниеРегламентногоЗадания;
Расписание.ВремяНачала = Дата(1, 1, 1, 9, 0, 0); // старт в 09:00
Остальные свойства расписания многочисленны — точные имена удобнее подсмотреть в синтакс-помощнике, чтобы не ошибиться с периодичностью.
Отдельная, но близкая задача — замерить, сколько работал участок кода. Для этого есть функция, возвращающая число миллисекунд:
Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
// ... измеряемый код ...
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить("Выполнено за " + Длительность + " мс");
Эта функция не зависит от часового пояса и не «прыгает» при переводе часов, поэтому для измерения длительности она точнее, чем разность обычных дат.
Частые вопросы
Как вывести только время, без даты?
Формат(Значение, "ДЛФ=T") или задайте свой шаблон Формат(Значение, "ДФ='ЧЧ:мм:сс'"). Само значение остаётся типом Дата с календарной частью — меняется только строковое представление.Почему часы показывают неправильный месяц вместо минут?
М — это месяц, строчная м — минуты. Исправьте "ДФ='ЧЧ:ММ'" на "ДФ='ЧЧ:мм'".Чем `ТекущаяДата()` отличается от `ТекущаяДатаСеанса()`?
ТекущаяДата() берёт время компьютера, где исполняется код (сервер или клиент). ТекущаяДатаСеанса() учитывает часовой пояс сеанса — это надёжнее в клиент-серверной базе, где сервер может стоять в другом регионе.Можно ли хранить в реквизите только время, без даты?
Дата с составом «Время» — платформа покажет в нём только часы и минуты, а дату подставит нулевую.Как сделать так, чтобы часы на форме обновлялись сами?
ПодключитьОбработчикОжидания("ИмяПроцедуры", 1) с интервалом в одну секунду. Процедура должна быть клиентской.