Calcular rapidamente diferenças de data

84

Muitas vezes, quero fazer alguns cálculos rápidos de data, como:

  • Qual é a diferença entre essas duas datas?
  • Qual é a data n semanas após esta outra data?

Normalmente, abro um calendário e conto os dias, mas acho que deve haver um programa / script que eu possa usar para fazer esse tipo de cálculo. Alguma sugestão?

daniel kullmann
fonte
3
Consulte também Ferramenta no UNIX para subtrair datas para quando a data GNU não estiver disponível.
Gilles

Respostas:

107

As "n semanas após uma data" são fáceis com a data GNU (1):

$ date -d 'now + 3 weeks'
Tue Dec  6 23:58:04 EST 2011
$ date -d 'Aug 4 + 3 weeks'
Thu Aug 25 00:00:00 EST 2011
$ date -d 'Jan 1 1982 + 11 weeks'
Fri Mar 19 00:00:00 EST 1982

Não conheço uma maneira simples de calcular a diferença entre duas datas, mas você pode envolver um pouco de lógica em torno do date (1) com uma função shell.

datediff() {
    d1=$(date -d "$1" +%s)
    d2=$(date -d "$2" +%s)
    echo $(( (d1 - d2) / 86400 )) days
}
$ datediff '1 Nov' '1 Aug'
91 days

Troque d1e d2se você quiser o cálculo da data de outra maneira, ou fique um pouco mais sofisticado para que isso não importe. Além disso, no caso de uma transição não DST para DST no intervalo, um dos dias terá apenas 23 horas; você pode compensar adicionando ½ dia à soma.

echo $(( (((d1-d2) > 0 ? (d1-d2) : (d2-d1)) + 43200) / 86400 )) days
camh
fonte
41

Para um conjunto de ferramentas portáteis, tente meus próprios dateutils . Seus dois exemplos se resumem a one-liners:

ddiff 2011-11-15 2012-04-11
=>
  148

ou em semanas e dias:

ddiff 2011-11-15 2012-04-11 -f '%w %d'
=>
  21 1

e

dadd 2011-11-15 21w
=>
  2012-04-10
hroptatyr
fonte
3
Marque suas ferramentas com +1 (apesar de dateadd -i '%m%d%Y' 01012015 +1dnão parecer funcionar, ele fica suspenso indefinidamente ... funciona se as especificações de data forem separadas por um caractere, qualquer caractere ... alguma idéia do que há de errado?)
don_crissti
2
@don_crissti O analisador não podia distinguir entre os numerais em apenas datas e durações, ele é fixo no mestre atual (d0008f98)
hroptatyr
Você tem a distribuição binária de dateutils da janela?
mosh
@mosh não, e não tenho condições de tentar.
Hroptatyr
Chegou atrasado para a festa aqui, mas o dateutils está no cygwin se você precisar usar no Windows.
gregb212 5/03
30

Um exemplo de python para calcular o número de dias em que andei no planeta:

$ python
>>> from datetime import date as D
>>> print (D.today() - D(1980, 6, 14)).days
11476
Daniel Näslund
fonte
2
Apenas no caso de alguém quiser isso para se comportam como um único comando, em vez de digitar um intérprete interativo: ychaouche@ychaouche-PC ~ $ python -c "from datetime import date as d; print (d.today() - d(1980, 6, 14)).days" 12813 ychaouche@ychaouche-PC ~ $
ychaouche
1
isso funciona para mim python3 -c "a partir da data de importação data e hora como d; print (d.today () - d (2016, 1, 9))" dias no final não é necessária
Kiran K Telukunta
13

Eu geralmente prefiro ter a hora / data no formato unix utime (número de segundos desde a época, quando os anos setenta começaram, UTC). Dessa forma, sempre se resume a subtração simples ou adição de segundos.

O problema geralmente é transformar uma data / hora nesse formato.

Em um ambiente / script de shell, você pode obtê-lo date '+%s' No momento da escrita, a hora atual é 1321358027.

Para comparar com 2011-11-04 (meu aniversário) date '+%s' -d 2011-11-04,, produzindo 1320361200. Subtrair: expr 1321358027 - 1320361200fornece 996827segundos, ou seja, há expr 996827 / 8640011 dias.

A conversão do utime (formato 1320361200) para uma data é muito simples, por exemplo, C, PHP ou perl, usando bibliotecas padrão. Com o GNU date, o -dargumento pode ser anexado @para indicar o formato "Segundos desde a época".

MattBianco
fonte
7
Com a data GNU, date -d @1234567890converte de segundos desde a época para qualquer formato de data que você especificar.
Gilles
1
@ Gilles: isso é brilhante. Não foi possível encontrar isso na página de manual. Onde você aprendeu aquilo?
Matt Bianco
1
@MattBianco, ver info date, especialmente os segundos desde a época nó: “Se você preceder um número com '@', representa um selo de tempo interna como uma contagem de segundos.”
manatwork
8

Se uma ferramenta gráfica é boa para você, eu recomendo qalculate(uma calculadora com ênfase em conversões de unidades, ela vem com uma interface GTK e KDE, IIRC). Lá você pode dizer, por exemplo

days(1900-05-21, 1900-01-01)

para obter o número de dias (140, desde 1900 não foi um ano bissexto) entre as datas, mas é claro que você também pode fazer o mesmo por vezes:

17:12:45 − 08:45:12

gera 8.4591667horas ou, se você definir a saída para formatar a hora 8:27:33,.

quazgar
fonte
3
Isso é ótimo, e ainda mais porque qalculate faz ter um CLI. Tente qalcentão help days.
Sparhawk
qcalcexemplo: qalc -t 'days(1900-05-21, 1900-01-01)'-> 140
sierrasdetandil 25/03
8

Isso surgiu ao usar date -d "$death_date - $y years - $m months - $d days"para obter uma data de nascimento (para genealogia). Esse comando está errado. Meses não têm a mesma duração, então (date + offset) - offset != date. As idades, em ano / mês / dia, são medidas que avançam a partir da data de nascimento.

$ date --utc -d 'mar 28 1867 +72years +11months +2days'
Fri Mar  1 00:00:00 UTC 1940

$ date --utc -d 'mar 1 1940 -72years -11months -2days'
Sat Mar 30 00:00:00 UTC 1867
# (2 days later than our starting point)

A data fornece a saída correta nos dois casos, mas no segundo caso, você estava fazendo a pergunta errada. É importante QUAIS 11 meses do ano a cobertura +/- 11, antes de adicionar / subtrair dias. Por exemplo:

$ date --utc -d 'mar 31 1939  -1month'
Fri Mar  3 00:00:00 UTC 1939
$ date --utc -d 'mar 31 1940  -1month' # leap year
Sat Mar  2 00:00:00 UTC 1940
$ date --utc -d 'jan 31 1940  +1month' # leap year
Sat Mar  2 00:00:00 UTC 1940

Para subtrair a operação inversa da adição, a ordem das operações teria que ser revertida. Adicionar adiciona anos, ENTÃO meses, ENTÃO dias. Se subtrair usasse a ordem oposta, você retornaria ao seu ponto de partida. Não, se não, se o deslocamento dos dias ultrapassar o limite do mês em um mês diferente.

Se você precisar trabalhar de trás para frente a partir de uma data e idade de término, poderá fazê-lo com várias invocações de date. Primeiro subtraia os dias, depois os meses e depois os anos. (Não acho seguro combinar os anos e os meses em uma única dateinvocação, por causa dos anos bissextos que alteram a duração de fevereiro.)

Peter Cordes
fonte
3

Eu freqüentemente uso SQL para cálculos de datas. Por exemplo , MySQL , PostgreSQL ou SQLite :

bash-4.2$ mysql <<< "select datediff(current_date,'1980-06-14')"
datediff(current_date,'1980-06-14')
11477

bash-4.2$ psql <<< "select current_date-'1980-06-14'"
 ?column? 
----------
    11477
(1 row)

bash-4.2$ sqlite2 <<< "select julianday('now')-julianday('1980-06-14');"
11477.3524537035

Outras vezes, sinto vontade de usar o JavaScript. Por exemplo , SpiderMonkey , WebKit , Seed ou Node.js :

bash-4.2$ js -e 'print((new Date()-new Date(1980,5,14))/1000/60/60/24)'
11477.477526192131

bash-4.2$ jsc-1 -e 'print((new Date()-new Date(1980,5,14))/1000/60/60/24)'
11477.47757960648

bash-4.2$ seed -e '(new Date()-new Date(1980,5,14))/1000/60/60/24'
11477.4776318287

bash-4.2$ node -pe '(new Date()-new Date(1980,5,14))/1000/60/60/24'
11624.520061481482

(Cuidado ao passar o mês para o Dateconstrutor do objeto JavaScript . Começa com 0.)

homem a trabalhar
fonte
3

Outra maneira de calcular a diferença entre duas datas do mesmo ano civil, você pode usar isso:

date_difference.sh
1  #!/bin/bash
2  DATEfirstnum=`date -d "2014/5/14" +"%j"`
3  DATElastnum=`date -d "12/31/14" +"%j"`
4  DAYSdif=$(($DATElastnum - $DATEfirstnum))
5  echo "$DAYSdif"
  • A linha 1 declara para o shell que intérprete usar.
  • A linha 2 atribui o valor de out dateà variável DATEfirstnum. O -dsinalizador exibe a sequência em um formato de hora, neste caso, em 14 de maio de 2014 e +"%j"informa datepara formatar a saída apenas para o dia do ano (1-365).
  • A linha 3 é igual à linha 2, mas com uma data e formato diferentes para a sequência, 31 de dezembro de 2014.
  • A linha 4 atribui o valor DAYSdifà diferença dos dois dias.
  • A linha 5 exibe o valor de DAYSdif.

Isso funciona com a versão GNU date, mas não na versão PC-BSD / FreeBSD. Eu instalei a coreutilspartir da árvore de portas e usei o comando /usr/local/bin/gdate.

Justin Holcomb
fonte
1
Este script não será executado. Há um erro de digitação na última linha e espaços em torno das atribuições das variáveis; portanto, o bash está tentando executar um programa chamado DATEfirst name com dois argumentos. Tente isto: DATEfirstnum=$(date -d "$1" +%s) DATElastnum=$(date -d "$2" +%s) Além disso, este script não poderá calcular a diferença entre dois anos diferentes. +%jrefere-se ao dia do ano (001..366), portanto ./date_difference.sh 12/31/2001 12/30/2014gera -1. Como outras respostas observaram, você precisa converter as duas datas em segundos desde 1970-01-01 00:00:00 UTC.
Seis
Você não precisa de uma $expressão aritmética interna: $((DATElastnum - DATEfirstnum))também funcionará.
Ruslan
3

Com a ajuda das soluções dannas, isso pode ser feito em uma linha com o seguinte código:

python -c "from datetime import date as d; print(d.today() - d(2016, 7, 26))"

(Funciona no Python 2.xe no Python 3.)

Kiran K Telukunta
fonte
1
Você pode editar sua postagem em vez de comentar
Stephen Rauch
3

datee o bash podem fazer diferenças de data (opções do OS X mostradas). Coloque a última data primeiro.

echo $((($(date -jf%D "04/03/16" +%s) - $(date -jf%D "03/02/16" +%s)) / 86400))
# 31
Dave
fonte
1

Há também cálculos de tempo da unidade GNU combinados com a data GNU:

$ gunits $(gdate +%s)sec-$(gdate +%s -d -1234day)sec 'yr;mo;d;hr;min;s'
        3 yr + 4 mo + 16 d + 12 hr + 37 min + 26.751072 s
$ gunits $(gdate +%s -d '2015-1-2 3:45:00')sec-$(gdate +%s -d '2013-5-6 7:43:21')sec 'yr;mo;d;hr;min;s'
        1 yr + 7 mo + 27 d + 13 hr + 49 min + 26.206759 s

(gunits são unidades no Linux, gdate é data)

Larry
fonte
1

datediff.sh no github: gist

#!/bin/bash
#Simplest calculator two dates difference. By default in days

# Usage:
# ./datediff.sh first_date second_date [-(s|m|h|d) | --(seconds|minutes|hours|days)]

first_date=$(date -d "$1" "+%s")
second_date=$(date -d "$2" "+%s")

case "$3" in
"--seconds" | "-s") period=1;;
"--minutes" | "-m") period=60;;
"--hours" | "-h") period=$((60*60));;
"--days" | "-d" | "") period=$((60*60*24));;
esac

datediff=$(( ($first_date - $second_date)/($period) ))
echo $datediff
user178063
fonte
1

A resposta de camh cuida da maior parte, mas podemos melhorar para lidar com arredondamentos, fusos horários etc., além de obtermos uma precisão extra e a capacidade de escolher nossas unidades:

datediff() {
#convert dates to decimal seconds since 1970-01-01 00:00:00 UTC
date1seconds=$(date +%s.%N -d "$date1")
date2seconds=$(date +%s.%N -d "$date2")

#Calculate time difference in various time units
timeseconds=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)"))
timeminutes=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/60"))
  timehours=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/3600"))
   timedays=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/86400"))
  timeweeks=$(printf "%0.8f\n" $(bc <<<"scale=9; ($date2sec-$date1sec)/604800"))
}

-dinforma dateque estamos fornecendo uma data e hora para a conversão. +%s.%Naltera a data e hora para seconds.nanosegundos desde 1970-01-01 00:00:00 UTC. bccalcula a diferença entre os dois números e o fator de divisão nos leva a diferentes unidades. printfadiciona um 0 antes da casa decimal, se necessário ( bcnão) e garante que o arredondamento vá para o mais próximo ( bcapenas trunca). Você também pode usar awk.

Agora vamos executar isso com um caso de teste descolado,

date1='Tue Jul  9 10:18:04.031 PST 2020'
date2='Wed May  8 15:19:34.447 CDT 2019'
datediff "$date1" "$date2"

echo $timeseconds seconds
-36971909.584000000 seconds

echo $timeminutes minutes
-616198.493066667 minutes

echo $timehours hours
-10269.974884444 hours

echo $timedays days
-427.915620185 days

echo $timeweeks weeks
-61.130802884 weeks

Observe que, como a duração de um mês ou ano nem sempre é a mesma, não existe uma única resposta "correta", embora se possa usar 365,24 dias como uma aproximação razoável atualmente.

TTT
fonte
0

Você pode usar a biblioteca awk Velour :

velour -n 'print t_secday(t_utc("2017-4-12") - t_utc("2017-4-5"))'

Ou:

velour -n 'print t_secday(t_utc(ARGV[1]) - t_utc(ARGV[2]))' 2017-4-12 2017-4-5

Resultado:

7
Steven Penny
fonte