gifts2017

Как в запросе 1С преобразовать секунды в часы и минуты

Опубликовал Александр Жиличев (alexzhilichev) в раздел Программирование - Практика программирования

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

Есть у меня один клиент. Владеет автосервисом с кучей сервисных постов. Для тех, кто не в курсе, сервисный пост - это оборудованное место, где мастер обслуживает автомобиль. Так вот, захотел однажды этот клиент узнать, сколько времени занимает обслуживание машин на каждом сервисном посту и в целом. Казалось бы, что может быть проще. Есть документ, который фиксирует номер сервисного поста, время поступления авто на сервис и время окончания работ. Бери, пиши несложный запрос, вычисляй разность дат заезда и выезда в секундах, дели на 3600 и получай результат в часах. А не тут то и было. Оказалось, что есть очень много мелких работ, на которые уходит от 2 до 15 минут, и клиенту очень неудобно видеть такие цифры, как, например, 0.1 часа, 0.25 часа и т.п. Ему нужно время в формате ЧЧ:ММ, без дней, чистые часы и минуты. "Хм...", - сказал я и полез почитать умные мысли других людей по этому поводу.

Решение усложнял тот момент, что отчет был написан не на СКД, а использовал известный многим программистам механизм отчета "Список / кросс-таблица", который работает на построителе отчетов. Итак, поиск результатов не принес. Вариантов предлагалось множество, но конкретного рабочего ответа я не получил. Поэтому "ноу-хау" пришлось придумывать самому.

Поскольку мой дорогой клиент хотел видеть время в формате Часы:Минуты, то какое-либо использование даты не подходило банально потому, что в дате не может быть больше 24 часов. Выносить всю кухню из запроса для внешних расчетов тоже очень не хотелось. И я понял, что поможет мне только число с плавающей запятой. Если примем факт, что до запятой мы будем хранить часы, а после запятой будут минуты, то задача очень даже может иметь решение.

Итак, мои "6 шагов к успеху" стали примерно такими:

  1. Представляем результат в виде числа с плавающей запятой.
  2. До запятой храним часы, после - минуты.
  3. Вычисляем часы. 
  4. Вычисляем минуты.
  5. Складываем часы и минуты в единый результат.
  6. При выводе результата используем формат числа, а именно настройку "Разделитель дробной части". 

Наверное понятно, что самые большие трудности были на 3 и 4 шаге. Функции языка запросов для работы с числами и их преобразования достаточно скудный в 1С. Пришлось много анализировать. Конечно, проще всего было вычислить часы:

ВЫБОР
    КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
        ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
    ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
КОНЕЦ

Как мы все знаем, функция запроса ВЫРАЗИТЬ() округляет число, чем мы и воспользуемся. Получаем целое число часов. Оно может быть совпасть с реальным, если системе нечего округлять, или быть больше на единицу, когда система выполнила округление. Если произошло округление, то из полученного числа вычитаем единицу, в противном случае берем неизменный результат функции ВЫРАЗИТЬ().

Логично предположить, что количество минут тоже вычисляется аналогичным способом, только уже используя часть запроса с вычислением часов:

ВЫБОР
    КОГДА (ВЫРАЗИТЬ((СУММА(ДлительностьРабот) - 3600 * ВЫБОР
        КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
            ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
        ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
    КОНЕЦ) / 60 КАК ЧИСЛО(15, 0))) * 60 <= СУММА(ДлительностьРабот) - 3600 * ВЫБОР
        КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
            ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
        ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
    КОНЕЦ
        ТОГДА ВЫРАЗИТЬ((СУММА(ДлительностьРабот) - 3600 * ВЫБОР
            КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
                ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
            ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
        КОНЕЦ) / 60 КАК ЧИСЛО(15, 0))
    ИНАЧЕ (ВЫРАЗИТЬ((СУММА(ДлительностьРабот) - 3600 * ВЫБОР
        КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
            ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
        ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
    КОНЕЦ) / 60 КАК ЧИСЛО(15, 0))) - 1
КОНЕЦ

Затем полученные два числа нужно привести к одному. Для этого объединяем две части путем сложения, но кроме этого, результат второго запроса нужно поделить на 100, чтобы оно перешло в дробную часть. Этот кусок должен размещаться в части вычисления итогов:

ВЫБОР
    КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
        ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
    ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
КОНЕЦ + 
ВЫБОР
    КОГДА (ВЫРАЗИТЬ((СУММА(ДлительностьРабот) - 3600 * ВЫБОР
        КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
            ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
        ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
    КОНЕЦ) / 60 КАК ЧИСЛО(15, 0))) * 60 <= СУММА(ДлительностьРабот) - 3600 * ВЫБОР
        КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
            ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
        ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
    КОНЕЦ
        ТОГДА ВЫРАЗИТЬ((СУММА(ДлительностьРабот) - 3600 * ВЫБОР
            КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
                ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
            ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
        КОНЕЦ) / 60 КАК ЧИСЛО(15, 0))
    ИНАЧЕ (ВЫРАЗИТЬ((СУММА(ДлительностьРабот) - 3600 * ВЫБОР
        КОГДА (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) * 3600 <= СУММА(ДлительностьРабот)
            ТОГДА ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))
        ИНАЧЕ (ВЫРАЗИТЬ(СУММА(ДлительностьРабот) / 3600 КАК ЧИСЛО(15, 0))) - 1
    КОНЕЦ) / 60 КАК ЧИСЛО(15, 0))) - 1
КОНЕЦ / 100

И наконец, оформляем результат в коде. Где общий отчет - отчет-объект "Список / кросс-таблица".

ОбщийОтчет.ЗаполнитьПоказатели("ДлительностьРабот", "Длительность работ, Часы:Минуты", Истина, "ЧДЦ=2; ЧРД=:");

И в нашем конечном отчете видим такой результат:

Готовый результат

Наверняка, что-то похожее можно реализовать и для СКД. И скорее всего на СКД это будет даже проще. Моя публикация не претендует на Нобелевскую премию. Я думаю, что кому-нибудь она окажется полезной, поэтому публикую. Всем удачи.

P.S. В комментарии был задан вопрос "На последнем скрине в первом посту 1:00 и 0:01, дали в сумме 1:02. Этому отчету можно верить?". Отчету можно верить. В тексте статьи я забыл добавить, что в отчете может быть некоторая визуальная погрешность, но только из-за того, что остаток секунд, отбрасывается.

P.P.S. Более краткое и красивое решение предложил ildarovich, за что ему большой плюс и огромная благодарность:

РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1, 1, 1), ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, &ДлительностьВСекундах), ЧАС) 
    + МИНУТА(ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, &ДлительностьВСекундах)) / 100

 

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Сергей Галюк (dj_serega) 15.07.16 09:23
Хотя бы с оформлением "поигрались". Непонятно же. А вчитываться не хочется ибо не красиво :)
2. Юрий Былинкин (ardn) 15.07.16 09:59
На последнем скрине в первом посту 1:00 и 0:01, дали в сумме 1:02.
Этому отчету можно верить?
3. marat_n q (marat_n) 15.07.16 10:10
А так не пробовал?

РАЗНОСТЬДАТ(Событие.НачалоСобытия, Событие.ОкончаниеСобытия, ЧАС) КАК ДлительностьЧасов,
РАЗНОСТЬДАТ(Событие.НачалоСобытия, Событие.ОкончаниеСобытия, МИНУТА)
 -РАЗНОСТЬДАТ(Событие.НачалоСобытия, Событие.ОкончаниеСобытия, ЧАС)*60  КАК ДлительностьМин
4. Роберт В е р т и н с к и й (v3rter) 15.07.16 10:25
(2) ardn, видимо на входе отчета даты с секундами и в сумме эти секунды дают больше минуты. Интересно было бы посмотреть при ЧДЦ=4
(3) marat_n, а не будет ситуации, когда округление (до минут) суммы не совпадает с суммой округленных?
5. marat_n q (marat_n) 15.07.16 10:57
(4) v3rter, потестил на разных датах, мой вариант точно не подходит, там специфично разница в часах и минутах считается (не округление).
6. Юрий Былинкин (ardn) 15.07.16 11:31
(4) v3rter,
Как программист я понимаю, откуда взялась лишняя :01
Но для пользователя это непонятно.
7. Александр Жиличев (alexzhilichev) 15.07.16 11:40
(1) dj_serega, спасибо за замечание. Действительно, я не заметил, что из-за табов поехал текст запроса.
8. Александр Жиличев (alexzhilichev) 15.07.16 11:46
(2) ardn, (6) ardn, Да, вы правы. Визуально погрешность есть. Но в целом цифры верные. Я сейчас добавил в статью информацию об этом. Соответственно, заказчика я оповестил, что есть такой нюанс. Думаю и те, кто используют код из статьи, последуют тому же примеру.
9. Дмитрий Соломатин (sdf1979) 15.07.16 13:12
Я обычно, если требуется последующий вывод на форму или в отчет, длительность времени выражаю везде в секундах.
Вывод времени осуществляю с помощью форматирования даты:
Для длительности меньше часа:
ДФ='мм'' мин'''

Для длительности меньше суток
ДФ='ЧЧ''ч ''мм''мин'''

Для длительности меньше месяца
ДФ='дд''сут ''ЧЧ''ч ''мм''мин'''

Данный прием хорошо работает, если длительность периода не превышает 31 день.
10. Сергей (ildarovich) 15.07.16 13:57
Все же есть гораздо более простое выражение для решения той же задачи:
РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1, 1, 1), ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, &ДлительностьВСекундах), ЧАС) 
	+ МИНУТА(ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, &ДлительностьВСекундах)) / 100

Вот запрос для проверки:
ВЫБРАТЬ
	&ДлительностьВСекундах,
	РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1, 1, 1), ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, &ДлительностьВСекундах), ЧАС) 
	+ МИНУТА(ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, &ДлительностьВСекундах)) / 100 КАК ДробноеЧисло
Хитрость в том, что секунды добавляются к началу времен, чтобы минуты и часы были "целыми".
Посмотрите еще Выразить число как строку и дату как строку в запросе. так как этот прием оттуда.
kare; serg_infostart; h00k; BigClock; Irwin; Yashazz; Dach; ZOMI; kuzyara; METAL; dgolovanov; K_A_O; NeviD; alexzhilichev; bulpi; +15 Ответить 2
11. Александр Жиличев (alexzhilichev) 15.07.16 14:38
(10) ildarovich, Действительно, это даже лучше того, что я сделал. Спасибо, если не возражаете, добавлю в тело статьи.
12. Антон Рощин (wolfsoft) 20.07.16 07:49
А так не проще?

ДобавитьКДате(ДатаВремя(1, 1, 1), Секунда, РАЗНОСТЬДАТ(<Дата1>, <Дата2>, Секунда))

Плюс формат вывода значения в ячейке "Ч:мм".
13. Серега Сергейич (serega33) 20.07.16 09:18
не понял весь дзен этой статьи, ну провел он посту 40 минут, ну 30 минут еще дополнительных работ, ну зафиксировали 70 минут и показали в отчете 1:10, хоть на скд, хоть просто выводом в табл. документ...
14. Александр Жиличев (alexzhilichev) 20.07.16 15:46
(12) wolfsoft, не проще. Часов у вас может быть больше 24. А ДобавитьКДате() будет в дни их преобразовывать. Представьте. 10 постов по 12 рабочих часов каждый. Общее время 120 часов. Это в сутки норма времени на все посты. Сами понимаете, что формат даты такого не даст сделать.
15. Яков Коган (Yashazz) 22.07.16 13:03
Во, наконец-то пришёл Ильдарович и показал, как надо)
16. Паш Каз (ka3a4ok) 23.07.16 22:59
хм... я бы сделал так:

ВЫБРАТЬ
ВЫРАЗИТЬ(ВЫРАЗИТЬ(&КоличествоСекунд / 60 КАК Число(15,0)) / 60 - 0.5 КАК Число(15, 0)) + (ВЫРАЗИТЬ(&КоличествоСекунд / 60 КАК Число(15,0)) / 60 - ВЫРАЗИТЬ(ВЫРАЗИТЬ(&КоличествоСекунд / 60 КАК Число(15,0)) / 60 - 0.5 КАК Число(15, 0))) / 100 * 60 КАК ЧасыМинуты


аналогичный код:

КолМин = Окр(КоличествоСекунд / 60);
ЧасыМинуты = Цел(КолМин / 60) + (КолМин / 60 - Цел(КолМин / 60)) / 100 * 60;

17. Максим Кузнецов (Makushimo) 25.07.16 06:34
(10) ildarovich,
Вот тут
МИНУТА(ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, &ДлительностьВСекундах))


если ДлительностьВСекундах будет 119, тогда результат будет 1 минута.
Чисто технически это верно, но для клиента это 2 минуты.
Ну и если он сложит несколько таких строк на пальцах или в отчете, тогда потери будут несколько минут.
Или я не прав?
18. Сергей (ildarovich) 25.07.16 11:14
(17) Makushimo, правы, но тут все зависит от целей. Если это важно, то можно: 1) выводить время с секундами; 2) получать минуты по принципам округления (в формулу к секундам + 30 добавить); 3) складывать исходные секунды, чтобы не набегала ошибка.
Но наличие часов в результате как бы намекает, что среднее время выполнения работ - это не минуты, поэтому относительная погрешность при отбрасывании не полных минут будет невелика.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа