
У меня есть интервал в секундах с десятичной точкой, который я хотел бы отобразить в удобном для восприятия человеком формате (Ч:ММ:СС.ССС). Например, 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