Como posso calcular e formatar a duração de uma data usando ferramentas GNU, não uma data de resultado?

Como posso calcular e formatar a duração de uma data usando ferramentas GNU, não uma data de resultado?

Todos os toneladas de artigos que encontrei até agora pareciam estar focados na obtenção de uma data resultante, o que ainda é útil, mas não o que desejo neste caso.

Link de exemplo:Unix e Linux SE - Calcule rapidamente diferenças de datas.

Com datediffessas outras perguntas e respostas, isso é mais o que eu quero, referindo-se a um resultado de duração de data/hora, e basicamente usa matemática de carimbo de data/hora Unix com as conversões anteriores, mas quero minha granularidade até o segundo, e é por isso que não divida por 86.400.

Com a duração de segundos disponível, agora quero formatá-lo para algo como usar o datecomando GNU desta forma:date -d "@69600" "+ %Y years %m months %e days %H:%M:%S"

Sei que tudo é essencialmente uma duração de data/hora da época Unix, mas agora tenho o problema com os números de dia, mês e ano não começando em 0, o que não representaria corretamente o valor de duração que desejo.

Eu poderia cortar esse formato e aplicar mais exprcomandos para subtrair essencialmente 1 ou 1970 de cada valor, mas estou me perguntando se existe alguma outra maneira mais fácil e simples de lidar com o cálculo de data/hora para obter um resultado de duração além do encadeamento matemático e etapas de formatação juntas. Talvez haja alguma outra opção no GNU dateda qual eu possa aproveitar, ou outra ferramenta que aceite 2 argumentos de data/hora e me dê imediatamente o resultado que estou procurando.

Tê-lo disponível no Homebrew para Macs seria uma vantagem :).

Link matemático de data simples:Walker News - Aritmética de datas em scripts Shell do Linux

Links de formatação de data aleatória:

Responder1

Comdatautilsdedatediff(não GNU, desculpe), (anteriormente ddiff, dateutils.ddiffno Debian):

$ dateutils.ddiff -f '%Y years, %m months, %d days, %H:%0M:%0S' \
    '2012-01-23 15:23:01' '2017-06-01 09:24:00'
5 years, 4 months, 8 days, 18:00:59

(datas consideradas como UTC, adicione algo como --from-zone=Europe/Londonpara as datas serem consideradas como hora local no fuso horário correspondente. --from-zone=localtimepode funcionar para o fuso horário padrão no sistema; --from-zone="${TZ#:}"funcionaria contanto que $TZespecificasse o caminho de um arquivo de fuso horário IANA (como TZ=:Europe/London, não TZ=GMT0BST,M3.5.0/1:00:00,M10.5.0/2:00:00especificação TZ estilo POSIX)).

Comast-abertodate, então ainda não com as ferramentas GNU, desculpe (se estiver usando ksh93como shell, isso datepode estar disponível como embutido), você pode usar date -Epara obter a diferença entre duas datas em um formato que fornece 2 números com unidades:

$ date -E 1970-01-01 2017-06-01
47Y04M
$ date -E 2017-01-01 2017-06-01
4M29d
$ date -E 12:12:01 23:01:43
10h49m

Observe que qualquer coisa acimahoraé ambíguo, pois os dias têm um número variável de horas em localidades com horário de verão (23, 24 ou 25) e os meses e anos têm um número variável de dias, portanto, pode não fazer muito sentido ter mais precisão do que as duas unidades acima.

Por exemplo, há exatamente um ano entre 2015-01-01 00:00:00 e 2016-01-01 00:00:00 ou entre 2016-01-01 00:00:00 e 2017-01-01 00: 00:00, mas não é a mesma duração (365*24 horas em um caso, 366*24 horas no outro)

Há exatamente um dia entre 24/03/2017 às 12:00:00 e 25/03/2017 às 12:00:00 ou entre 25/03/2017 às 12:00 e 26/03/2017 às 12:00, mas quando esse é o horário local dos países europeus (que mudaram para o horário de verão em 26/03/2017), issoum diaé de 24 horas em um caso e 23 horas no outro.

Por outras palavras, uma duração que mencione anos, meses, semanas ou dias só tem sentido quando associada a um dos limites a que se destina (e ao fuso horário). Portanto, você não pode converter um número de segundos para essa duração sem saber a que horas (de ou até) ela deve ser aplicada (a menos que você queira usar definições aproximadas de dia (24 horas), mês (30*24 horas) ou ano (365*24 horas)).

Até certo ponto, mesmo uma duração em segundos é ambígua. Para simplificar, os segundos na época do Unix são definidos como a 86.400ª parte de um determinado dia terrestre 1 . Esses segundos estão ficando cada vez mais longos à medida que a Terra gira.

Então, algo como (com GNU date):

d1='2016-01-01 00:00:00' d2='2017-01-01 00:00:00'
eval "$(date -d "@$(($(date -d "$d2" +%s) - $(date -d "$d1" +%s)))" +'
  delta="$((%-Y - 1970)) years, $((%-m - 1)) months, $((%-d - 1)) days, %T"')"
echo "$delta"

Ou em fish:

date -d@(expr (date -d $d2 +%s) - (date -d $d1 +%s)) +'%Y %-m %-d %T' | \
  awk '{printf "%s years, %s months, %s days, %s\n", $1-1970, $2-1, $3-1, $4, $5}'

Geralmente, não forneceria algo correto, pois o delta de tempo é aplicado a 1970, em vez da data de início real de 2016.

Por exemplo, acima, isso me dá (em um fuso horário Europa/Londres):

1 years, 0 months, 1 days, 01:00:00

em vez de

1 years, 0 months, 0 days, 00:00:00

(isso ainda pode ser uma aproximação boa o suficiente para você).


1 Tecnicamente, embora um dia tenha 86.400 segundos Unix, os sistemas conectados em rede normalmente sincronizam seu relógio com relógios atômicos usando segundos SI. Quando um relógio atômico marca 12h00min00.00, esses sistemas Unix também marcam 12h00min00.00. A exceção ocorre apenas na introdução de segundos bissextos, onde há um segundo Unix que dura 2 segundos ou alguns segundos que duram um pouco mais para manchar aquele segundo extra em um período mais longo.

Assim, é possível saber a duração exata (em termos de segundos atômicos) entre dois carimbos de data e hora Unix (emitidos por sistemas sincronizados com o relógio atômico): obtenha a diferença do tempo da época Unix entre os dois carimbos de data e hora e some o número de saltos segundos que foram adicionados entre eles.

datedifftambém tem um -f %rSpara obter o número derealsegundos entre duas datas:

$ dateutils.ddiff -f %S '1990-01-01 00:00:00' '2010-01-01 00:00:00'
631152000
$ dateutils.ddiff -f %rS '1990-01-01 00:00:00' '2010-01-01 00:00:00'
631152009

A diferença refere-se aos 9 segundos bissextos que foram adicionados entre 1990 e 2010.

Agora, esse número derealsegundos não vai ajudá-lo a calcular o número de dias, pois os dias não têm um número constante derealsegundos. Há exatamente 20 anos entre essas 2 datas e esses 9 segundos bissextos atrapalham para chegar a esse valor. Observe também que ddiff%rSfunciona quando não combinado com outros especificadores de formato. Além disso, os segundos bissextos futuros não são conhecidos com muita antecedência, mas também as informações sobre os segundos bissextos recentes podem não estar disponíveis. Por exemplo, meu sistema não sabe sobre aqueles posteriores a 01/07/2012.

Responder2

Ok, digamos que você tenha duas datestrings compatíveis definindo dois pontos no tempo entre os quais deseja calcular a duração:

echo $(($(date +%s -d "3 days ago")-$(date +%s -d "4 weeks 2 hours 1 minutes 33 seconds ago"))) | sed 's/-//'

Mas digamos ainda que você deseja mais legível:

convertsecs() {
    hours=$(($1/3600))
    minutes=$(($1%3600/60))
    seconds=$(($1%60))
}
totalduration="$(($(date +%s -d "3 days ago")-$(date +%s -d "4 weeks 2 hours 1 minutes 33 seconds ago"))) | sed 's/-//'"
convertsecs "$totalduration"
echo "Duration was ${hours} hours, ${minutes} minutes, and ${seconds} seconds."

Responder3

O tempo é uma coisa complicada e é importante gerenciar suas expectativas ao calculá-lo. Não acredito que seja tecnicamente possível fazer o que você pede com precisão e precisão no caso geral, uma vez que um determinado número de segundos pode corresponder a um número diferente de minutos, uma vez que você considera coisas como segundos bissextos, que por si só não ocorrem numa base totalmente previsível.

Dito de outra forma, embora digamos que um dia tem 24 horas, nãotododia é exatamente 24 horas, então se você quiser saber quantos dias mais minutos mais segundos durou uma determinada duração, você precisa ancorar essa duração em algum lugar em tempo real para saber se houve ou não um segundo bissexto ali, ou mesmo que o dia 29 de abril tenha existido ou não em algum momento.

Ainda mais exótico, nem todos os meses têm o mesmo número de dias.

Se isso não for uma preocupação, basta contar os segundos através da aritmética básica; use datepara converter para segundos desde a época, divida por 86.400 para obter dias, o restante por 3.600 para obter horas, o restante para obter minutos e assim por diante.

informação relacionada