Formatar e exibir um intervalo com segundos fracionários no shell

4

Eu tenho um intervalo, em segundos com um decimal, que gostaria de exibir em formato legível por humanos (H: MM: SS.SSS). Por exemplo, 16633.284 deve ser exibido como 4: 37: 13.284. Todos estes tempos são menos de 24 horas, mas se eles terminassem ainda seria apenas horas.

Mais alguns exemplos:

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

Note que é de largura fixa por menos de 10 horas. Claro, isso é facilmente feito com printf .

Estou postando minha solução como resposta, mas parece que deve haver uma maneira melhor de fazer isso, então estou perguntando: quais são os outros caminhos? Estou aberto a formas que são bashisms, zshisms, etc.

Observação: isso está relacionado a Exibição de segundos como dias / horas / mins / segundos? mas essas abordagens não funcionam porque a aritmética no bash é somente inteiro.

    
por derobert 20.05.2017 / 07:18

5 respostas

1

Por durações de menos de 24 horas, em ksh93 , você pode fazer:

$ TZ=UTC0 printf '%(%T.%3N)T\n' '#86399.99'
23:59:59.990

Possivelmente, fazer o cálculo manualmente é relativamente fácil. E awk com suas habilidades de matemática e formatação soa como a escolha mais óbvia:

$ awk -v t=123456789.123456 'BEGIN{
    printf "%d:%02d:%06.3f\n", t/3600, (t/60)%60, t%60}'
34293:33:09.123

Assim como sua abordagem printf , você pode ter alguns resultados inesperados devido ao arredondamento:

$ awk -v t=59.9999 'BEGIN{
   printf "%d:%02d:%06.3f\n", t/3600, (t/60)%60, t%60}'
0:00:60.000

Então, você pode querer fazer:

$ awk -v t=599.9999 '
   BEGIN{t=int(t*1000);
   printf "%d:%02d:%02d.%03d\n", t/3600000, t/60000%60, t/1000%60, t%1000}'
0:09:59.999
    
por 20.05.2017 / 23:05
7

A parte fracionária não contribui com 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 parecido com isto:

#!/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 sh (exceto alguns antigos pré-POSIX).

    
por 20.05.2017 / 09:10
3

Minha abordagem é essa, 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")

Espera-se que o printf seja bastante familiar, mas se não, %i significa um inteiro. %02i significa preencher o inteiro com um 0 à esquerda se for um dígito único. %06.3f é o mais estranho, mas significa 3 dígitos após o ponto decimal e é preenchido com 0s iniciais se o comprimento total for menor que 6 (incluindo o ponto decimal).

A substituição $(…) intencionalmente não citada fornece esses três parâmetros, usando dc :

Primeiro, empurre $seconds (expandido, de modo que o número real) para a pilha; duplicá-lo; em seguida, divida (com truncado) por 3600 para obter horas. Estale e imprima isso, empurre o espaço e imprima-o ( [ ] n ).

Isso deixa apenas os segundos na pilha. Duplique, divida por 60 (novamente truncando). Isso dá minutos; Leve o mod 60 para jogar fora as horas. Imprima-o novamente com o espaço. Deixando apenas os segundos na pilha.

Finalmente, mod 60, que dá apenas o segundo campo - e, em dc, o módulo está perfeitamente satisfeito com decimais e preserva a precisão. f , em seguida, imprime a pilha mais uma nova linha.

    
por 20.05.2017 / 07:18
3

Você pode facilmente fazê-lo de forma simples . Você pode tornar o código um pouco mais curto com a data GNU (espere que você não se importe em arredondar os milissegundos para baixo, arredondando para o mais próximo seria um pouco mais):

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

Invocamos dc e colocamos o número de segundos em sua pilha e executamos as compuatações (secs- > HH / MM / SS.MSEC), formamos e exibimos os 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
    
por 22.05.2017 / 07:25