Форматировать и отображать интервал с дробными частями секунд в оболочке

Форматировать и отображать интервал с дробными частями секунд в оболочке

У меня есть интервал в секундах с десятичной точкой, который я хотел бы отобразить в удобном для восприятия человеком формате (Ч:ММ:СС.ССС). Например, 16633.284 должно быть отображено как 4:37:13.284. Все эти времена меньше 24 часов, но если бы они были больше, это все равно были бы просто часы.

Еще несколько примеров:

   0      → 0:00:00.000
  60.394  → 0:01:00.394
8944.77   → 2:29:04.770

Обратите внимание, что он имеет фиксированную ширину менее 10 часов. Конечно, это довольно легко сделать с помощью printf.

Я публикую свое решение как ответ, но мне кажется, что должен быть лучший способ сделать это, поэтому я спрашиваю: какие есть другие способы? Я открыт для способов, которые являются башизмами, зшизмами и т. д.

Примечание: Это связано сОтображение секунд в формате дней/часов/минут/секунд?Но все эти подходы не работают, поскольку арифметика в bash работает только с целыми числами.

решение1

Дробная часть не вносит никакого вклада в количество часов, минут или секунд, поэтому вы можете просто оставить ее в стороне, выполнить вычисления без нее и добавить ее обратно в конце. Что-то вроде этого:

#!/bin/sh
seconds=16633.284
f=${seconds##*.}
if [ "$f" = "$seconds" ]; then f=0; fi
t=${seconds%.*}
s=$((t%60))
m=$((t/60%60))
h=$((t/60/60))
printf '%d:%02d:%06.3f\n' $h $m $s.$f

Выход:

   0     → 0:00:00.000
  33.    → 0:00:33.000
    .21  → 0:00:00.210
  60.394 → 0:01:00.394
8944.77  → 2:29:04.770

Это работает в любой sh-подобной оболочке (за исключением некоторых древних, до POSIX-версий).

решение2

Мой подход заключается в следующем: dcдля расчетов я использую:

seconds=16633.284
printf '%i:%02i:%06.3f\n' \
    $(dc -e "$seconds d 3600 / n [ ] n d 60 / 60 % n [ ] n 60 % f")

Надеюсь, printf вам знаком, но если нет, %iто означает целое число. %02iозначает дополнить целое число ведущим нулем, если оно состоит из одной цифры. %06.3f— самый странный вариант, но он означает 3 цифры после десятичной точки и дополнить ведущими нулями, если общая длина меньше 6 (включая десятичную точку).

Намеренно не заключенная в кавычки $(…)замена дает эти три параметра, используя dc:

Сначала поместите $seconds(расширенное, то есть фактическое число) в стек; продублируйте его; затем разделите (с усечением) на 3600, чтобы получить часы. Извлеките и распечатайте это, затем поместите пробел и распечатайте это ( [ ] n).

Это оставляет только секунды в стеке. Дублируем его, делим на 60 (снова усекаем). Это дает минуты; берем mod 60, чтобы отбросить часы. Распечатываем его, снова с пробелом. Оставляем только секунды в стеке.

Наконец, mod 60, который дает только поле секунд, а в dc modulus прекрасно работает с десятичными знаками и сохраняет точность. fЗатем выводит стек и новую строку.

решение3

Вы можете легко сделать это на простом языке sh. Вы можете сделать код немного короче с помощью GNU date (надеюсь, вы не против округления миллисекунд в меньшую сторону, округление до ближайшего целого будет немного длиннее):

hours=$((${seconds%.*} / 3600))
min_s_nano=$(TZ=GMT date -d @$seconds +%M:%S.%N)
time_string=$hours:${min_s_nano%??????}

решение4

Мы вызываем dcи помещаем количество секунд в его стек и выполняем вычисления (сек->ЧЧ/ММ/СС.МСЕК), форматируем и отображаем результаты.

seconds=16633.284

dc <<DC
# rearrange stack for number < 1hr
[r ldx q]sb

# rearrange stack for number < 1min
[rd ldxq]sc

# min++, sec-=60   hrs++, min-=60
[r1+r 60-d 60!>a]sa

# display result as h:mm:ss.sss
[n 58an 2lpx 58an 2lpx 46an 3lpx 10an]sd

# perform the conversion of seconds -> hours, minutes, seconds
[d60 >c lax r0rd60 >b lax r ldx]si

[
d1000* 1000% 1/r   # fractional portion
 1000* 1000/ 1/0r  # integer    portion
lix
]sh

# left zero-pad a number
[lk1+d sk 0n le >g ]sg
[sedZd sk    le >gn]sp

# setup the number on the stack and begin computation
$seconds lhx
DC

Полученные результаты

0                 -->  0:00:00.000
60.394            -->  0:01:00.394
8944.77           -->  2:29:04.770
86399.99          -->  23:59:59.990
59.9999           -->  0:00:59.999
599.9999          -->  0:09:59.999
16633.284         -->  4:37:13.284
33                -->  0:00:33.000
.21               -->  0:00:00.210
123456789.123456  -->  34293:33:09.123

Связанный контент