
Tenho um intervalo, em segundos com decimal, que gostaria de exibir em formato legível (H:MM:SS.SSS). Por exemplo, 16633.284 deve ser exibido como 4:37:13.284. Todos esses tempos são inferiores a 24 horas, mas se acabassem, ainda seriam apenas algumas horas.
Mais alguns exemplos:
0 → 0:00:00.000
60.394 → 0:01:00.394
8944.77 → 2:29:04.770
Observe que tem largura fixa por menos de 10 horas. Claro, isso é feito facilmente com printf
.
Estou postando minha solução como resposta, mas parece que deve haver uma maneira melhor de fazer isso, então pergunto: quais são as outras maneiras? Estou aberto a formas que são bashisms, zshisms, etc.
Nota: Isto está relacionado comExibindo segundos como dias/horas/minutos/segundos?mas todas essas abordagens não funcionam porque a aritmética no bash é apenas com números inteiros.
Responder1
A parte fracionária não contribui em nada para o número de horas, minutos ou segundos, então você pode simplesmente deixá-la de lado, fazer os cálculos sem ela e adicioná-la novamente no final. Algo assim:
#!/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
Saída:
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
Isso funciona em qualquer shell do tipo sh (exceto alguns antigos pré-POSIX).
Responder2
Minha abordagem é esta, usando dc
para fazer os cálculos:
seconds=16633.284
printf '%i:%02i:%06.3f\n' \
$(dc -e "$seconds d 3600 / n [ ] n d 60 / 60 % n [ ] n 60 % f")
Esperamos que printf seja bastante familiar, mas se não, %i
significa um número inteiro. %02i
significa preencher o número inteiro com um 0 à esquerda se for um dígito único. %06.3f
é o mais estranho, mas significa 3 dígitos após a vírgula decimal e preencher com 0s iniciais se o comprimento total for menor que 6 (incluindo a vírgula decimal).
A substituição intencionalmente não citada $(…)
fornece esses três parâmetros, usando dc
:
Primeiro, empurre $seconds
(expandido, portanto o número real) para a pilha; duplicá-lo; em seguida, divida (com truncado) por 3600 para obter horas. Abra e imprima isso, pressione espaço e imprima ( [ ] n
).
Isso deixa apenas os segundos na pilha. Duplique, divida por 60 (novamente truncando). Isso dá minutos; pegue o mod 60 para jogar fora as horas. Imprima, novamente com o espaço. Deixando apenas os segundos na pilha.
Finalmente, mod 60 que fornece apenas o campo dos segundos - e em CC, o módulo fica perfeitamente satisfeito com decimais e preserva a precisão. f
em seguida, imprime a pilha mais uma nova linha.
Responder3
Você pode fazer isso facilmente em sh simples. Você pode tornar o código um pouco mais curto com a data GNU (espero que não se importe em arredondar os milissegundos para baixo, o arredondamento para o mais próximo seria um pouco mais longo):
hours=$((${seconds%.*} / 3600))
min_s_nano=$(TZ=GMT date -d @$seconds +%M:%S.%N)
time_string=$hours:${min_s_nano%??????}
Responder4
Invocamos dc
e colocamos o número de segundos em sua pilha e realizamos os cálculos (secs->HH/MM/SS.MSEC), formatação e exibição de resultados.
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
Resultados
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