Calcular rápidamente las diferencias de fechas

84

A menudo quiero hacer algunos cálculos rápidos de fechas, como:

  • ¿Cuál es la diferencia entre estas dos fechas?
  • ¿Cuál es la fecha n semanas después de esta otra fecha?

Normalmente abro un calendario y cuento los días, pero creo que debería haber un programa / script que pueda usar para hacer este tipo de cálculos. ¿Alguna sugerencia?

daniel kullmann
fuente
3
Consulte también Herramienta en UNIX para restar fechas para cuando la fecha GNU no está disponible.
Gilles

Respuestas:

107

Las "n semanas después de una fecha" son fáciles con la fecha 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

No conozco una manera simple de calcular la diferencia entre dos fechas, pero puede ajustar una pequeña lógica alrededor de la fecha (1) con una función de shell.

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

Cambie d1y d2si desea el cálculo de la fecha de la otra manera, o sea un poco más elegante para que no importe. Además, en caso de que haya una transición que no sea DST a DST en el intervalo, uno de los días durará solo 23 horas; puede compensar agregando ½ día a la suma.

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

Para un conjunto de herramientas portátiles, pruebe mis propias dateutils . Sus dos ejemplos se reducirían a frases sencillas:

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

o en semanas y días:

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

y

dadd 2011-11-15 21w
=>
  2012-04-10
hroptatyr
fuente
3
+1 sus herramientas oscilan (aunque dateadd -i '%m%d%Y' 01012015 +1dno parece funcionar, simplemente cuelga allí indefinidamente ... funciona si las especificaciones de fecha están separadas por un carácter, cualquier carácter ... ¿alguna idea de lo que está mal?)
don_crissti
2
@don_crissti El analizador no pudo distinguir entre fechas y duraciones solo con números, está arreglado en el maestro actual (d0008f98)
hroptatyr
¿Tiene la distribución binaria de dateutils de Windows?
Mosh
@mosh no, y no tengo las facilidades para probar.
hroptatyr
Tarde para la fiesta aquí, pero dateutils está en cygwin si necesita usar en Windows.
gregb212
30

Un ejemplo de Python para calcular la cantidad de días que he caminado por el planeta:

$ python
>>> from datetime import date as D
>>> print (D.today() - D(1980, 6, 14)).days
11476
Daniel Näslund
fuente
2
En caso de que alguien quiera que esto se comporte como un solo comando, en lugar de escribir un intérprete interactivo: 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
esto funciona para mí python3 -c "desde la fecha de importación de fecha y hora como d; print (d.today () - d (2016, 1, 9))" no se requieren días al final
Kiran K Telukunta
13

Por lo general, prefiero tener la hora / fecha en formato unix utime (número de segundos desde la época, cuando comenzaron los setenta, UTC). De esa forma, siempre se reduce a una simple sustracción o adición de segundos.

El problema suele ser transformar una fecha / hora en este formato.

En un entorno de shell / script puede obtenerlo. date '+%s' Al momento de escribir, la hora actual es 1321358027.

Para comparar con 2011-11-04 (mi cumpleaños) date '+%s' -d 2011-11-04, cediendo 1320361200. Restar: expr 1321358027 - 1320361200da 996827segundos, que es expr 996827 / 86400= hace 11 días.

La conversión de utime (formato 1320361200) a una fecha es muy simple, por ejemplo, en C, PHP o perl, usando bibliotecas estándar. Con GNU date, el -dargumento puede anteponerse @para indicar el formato "Segundos desde la época".

MattBianco
fuente
77
Con la fecha GNU, date -d @1234567890convierte de segundos desde la época a cualquier formato de fecha que especifique.
Gilles
1
@Gilles: eso es brillante. No pude encontrar eso en la página de manual. ¿Dónde aprendiste eso?
MattBianco
1
@MattBianco, ver info date, sobre todo los segundos desde la época de nodo: “Si preceden a un número con '@', esto representa un sello de tiempo interno como un recuento de segundos.”
manatwork
8

Si una herramienta gráfica está bien para usted, lo recomiendo qalculate(una calculadora con énfasis en las conversiones de unidades, viene con una interfaz GTK y KDE, IIRC). Ahí puedes decir, por ejemplo

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

para obtener el número de días (140, ya que 1900 no fue un año bisiesto) entre las fechas, pero, por supuesto, también puede hacer lo mismo por momentos:

17:12:45 − 08:45:12

produce 8.4591667horas o, si se establece la salida de formato de tiempo, 8:27:33.

quazgar
fuente
3
Eso es genial, y más aún porque Qalculate hace tener una CLI. Intenta qalcentonces help days.
Sparhawk
qcalcejemplo: qalc -t 'days(1900-05-21, 1900-01-01)'-> 140
sierrasdetandil
8

Esto surgió cuando se usa date -d "$death_date - $y years - $m months - $d days"para obtener una fecha de nacimiento (para genealogía). Ese comando es INCORRECTO. Meses no son todos de la misma longitud, por lo que (date + offset) - offset != date. Las edades, en año / mes / día, son medidas que van desde la fecha de nacimiento.

$ 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)

La fecha da el resultado correcto en ambos casos, pero en el segundo caso estaba haciendo la pregunta incorrecta. Importa QUÉ 11 meses del año cubren +/- 11, antes de sumar / restar días. Por ejemplo:

$ 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 que la resta sea la operación inversa de sumar, el orden de las operaciones debería invertirse. Agregar agrega años, ENTONCES meses, ENTONCES días. Si restar usa el orden opuesto, entonces volverías a tu punto de partida. No lo hace, por lo que no lo hace, si el desplazamiento de días cruza el límite de un mes en un mes de duración diferente.

Si necesita trabajar hacia atrás desde una fecha y edad de finalización, puede hacerlo con múltiples invocaciones de date. Primero reste los días, luego los meses, luego los años. (No creo que sea seguro combinar los años y los meses en una sola dateinvocación, debido a que los años bisiestos alteran la duración de febrero).

Peter Cordes
fuente
4

Otra forma de calcular la diferencia entre dos fechas del mismo año calendario podría usar esto:

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"
  • La línea 1 declara al intérprete qué intérprete usar.
  • La línea 2 asigna el valor desde fuera datea la variable DATEfirstnum. El -dindicador muestra la cadena en un formato de hora en este caso el 14 de mayo de 2014 y +"%j"le indica dateque formatee la salida solo el día del año (1-365).
  • La línea 3 es la misma que la línea 2 pero con una fecha diferente y un formato diferente para la cadena, 31 de diciembre de 2014.
  • La línea 4 asigna el valor DAYSdifa la diferencia de los dos días.
  • La línea 5 muestra el valor de DAYSdif.

Esto funciona con la versión GNU de date, pero no con la versión PC-BSD / FreeBSD. Lo instalé coreutilsdesde el árbol de puertos y usé el comando en su /usr/local/bin/gdatelugar.

Justin Holcomb
fuente
1
Este script no se ejecutará. Hay un error tipográfico en la última línea y espacios alrededor de las asignaciones de variables, por lo que bash intenta ejecutar un programa llamado DATEfirst name con dos argumentos. Pruebe esto: DATEfirstnum=$(date -d "$1" +%s) DATElastnum=$(date -d "$2" +%s) Además, este script no podrá calcular la diferencia entre dos años diferentes. +%jse refiere al día del año (001..366), por lo que las ./date_difference.sh 12/31/2001 12/30/2014salidas -1. Como han señalado otras respuestas, debe convertir ambas fechas en segundos desde 1970-01-01 00:00:00 UTC.
Seis
No necesita $una expresión aritmética interna: $((DATElastnum - DATEfirstnum))también funcionará.
Ruslan
3

Con frecuencia uso SQL para cálculos de fechas. Por ejemplo MySQL , PostgreSQL o 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

Otras veces me siento de humor para JavaScript. Por ejemplo SpiderMonkey , WebKit , Seed o 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

(Tenga cuidado al pasar el mes al Dateconstructor del objeto JavaScript . Comienza con 0.)

hombre trabajando
fuente
3

Con la ayuda de las soluciones de dannas, esto se puede hacer en una línea con el siguiente código:

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

(Funciona tanto en Python 2.xy Python 3.)

Kiran K Telukunta
fuente
1
Puedes editar tu publicación en lugar de comentar
Stephen Rauch
3

datey bash puede hacer diferencias de fecha (se muestran las opciones de OS X). Coloque la última fecha primero.

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

También hay cálculos de tiempo de la unidad GNU combinados con la fecha 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 son unidades en Linux, gdate es fecha)

Larry
fuente
1

dateiff.sh en 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
usuario178063
fuente
1

La respuesta de Camh se ocupa de la mayor parte, pero podemos mejorar para hacer frente al redondeo, zonas horarias, etc. Además, obtenemos algo de precisión adicional y la capacidad de elegir nuestras 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"))
}

-ddice dateque estamos proporcionando una fecha y hora para convertir. +%s.%Ncambia la fecha y hora a segundos.nanosegundos desde 1970-01-01 00:00:00 UTC. bccalcula la diferencia entre los dos números, y el factor de división nos da las diferentes unidades. printfagrega un 0 antes del decimal si es necesario ( bcno lo hace) y asegura que el redondeo vaya al más cercano ( bcsolo se trunca). También podrías usar awk.

Ahora ejecutemos esto con un caso de prueba funky,

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

Tenga en cuenta que dado que la duración de un mes o año no siempre es la misma, no hay una sola respuesta "correcta" allí, aunque hoy en día se podría usar 365.24 días como una aproximación razonable.

TTT
fuente
0

Puede usar la biblioteca awk Velour :

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

O:

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

Resultado:

7
Steven Penny
fuente