¿Cómo encontrar la diferencia de días entre dos fechas?

82

A = "2002-20-10"
B = "2003-22-11"

¿Cómo encontrar la diferencia de días entre dos fechas?

Árbol
fuente
5
Como se explica a continuación, pero sus fechas están al revés: deben ser aaaa-mm-dd
Peter Flynn
Muchas respuestas usan la opción -d de (GNU-) date. Quiero agregar que esto NO es parte de la fecha POSIX, por lo tanto, es menos portátil. Siempre que el OP no funcione en distribuciones Unix como Solaris, sino solo en "Linux común", debería ser bueno. Una etiqueta respectiva ayudaría en mi humilde opinión.
Cadoiz

Respuestas:

33

Si tiene GNU date, permite imprimir la representación de una fecha arbitraria ( -dopción). En este caso, convierta las fechas a segundos desde EPOCH, reste y divida por 24 * 3600.

¿O necesitas una forma portátil?


fuente
1
@Tree: Creo que no hay una forma portátil de restar fechas antes de implementarlo usted mismo; no es tan difícil, lo único que interesa son los años bisiestos. Pero hoy en día todo el mundo quiere restar fechas, como parece: Eche un vistazo a unix.stackexchange.com/questions/1825/… :)
14
Por el bien de lazyweb, creo que esto es lo que quiso decir el usuario332325:echo "( `date -d $B +%s` - `date -d $A +%s`) / (24*3600)" | bc -l
Pablo Mendes
2
¿Qué significa portátil en este contexto?
htellez
@htellez algo que funciona en múltiples plataformas (Windows, Linux, etc.)
Sumudu
Para bash one liner basado en esta respuesta, consulte: stackoverflow.com/a/49728059/1485527
jschnasse
64

El método bash: convierta las fechas en el formato% y% m% d y luego puede hacerlo directamente desde la línea de comando:

echo $(( ($(date --date="031122" +%s) - $(date --date="021020" +%s) )/(60*60*24) ))
techjacker
fuente
9
Esto no necesariamente necesita que las fechas estén en %y%m%dformato. Por ejemplo, gnu date aceptará el %Y-%m-%dformato que utilizó el OP. En general, ISO-8601 es una buena opción (y el formato de OP es uno de esos formatos). Es mejor evitar los formatos con años de 2 dígitos.
mc0e
2
Por la diferencia de hoy , uno puede querer usar days_diff=$(( (`date -d $B +%s` - `date -d "00:00" +%s`) / (24*3600) )). tenga en cuenta que $days_diffserá un número entero (es decir, sin decimales)
Julien
1
Esto depende de la variante de dateinstalado. Funciona para GNU date. No funciona con POSIX date. Probablemente esto no sea un problema para la mayoría de los lectores, pero vale la pena señalarlo.
mc0e
Si realmente desea la portabilidad POSIX, consulte esto: unix.stackexchange.com/a/7220/43835
mc0e
1
Solo para combatir la confusión: el formato de OP no lo es %Y-%m-%d, hasta que lo presentamos August 2.0y October 2.0el formato de OP es %Y-%d-%m. Relevante xkcd: xkcd.com/1179
confetti
22

tl; dr

date_diff=$(( ($(date -d "2015-03-11 UTC" +%s) - $(date -d "2015-03-05 UTC" +%s)) / (60*60*24) ))

¡Cuidado! Muchas de las soluciones bash aquí están divididas para rangos de fechas que abarcan la fecha en que comienza el horario de verano (cuando corresponda). Esto se debe a que la construcción $ ((matemáticas)) realiza una operación de 'piso' / truncamiento en el valor resultante, devolviendo solo el número entero. Déjame ilustrarte:

El horario de verano comenzó el 8 de marzo de este año en los EE. UU., Así que usemos un rango de fechas que abarque eso:

start_ts=$(date -d "2015-03-05" '+%s')
end_ts=$(date -d "2015-03-11" '+%s')

Veamos qué obtenemos con el doble paréntesis:

echo $(( ( end_ts - start_ts )/(60*60*24) ))

Devuelve '5'.

Hacer esto usando 'bc' con más precisión nos da un resultado diferente:

echo "scale=2; ( $end_ts - $start_ts )/(60*60*24)" | bc

Devuelve '5,95'; el 0,05 que falta es la hora perdida del cambio de horario de verano.

Entonces, ¿cómo se debe hacer esto correctamente?
Sugeriría usar esto en su lugar:

printf "%.0f" $(echo "scale=2; ( $end_ts - $start_ts )/(60*60*24)" | bc)

Aquí, 'printf' redondea el resultado más preciso calculado por 'bc', lo que nos da el rango de fechas correcto de '6'.

Editar: resaltando la respuesta en un comentario de @ hank-schultz a continuación, que he estado usando últimamente:

date_diff=$(( ($(date -d "2015-03-11 UTC" +%s) - $(date -d "2015-03-05 UTC" +%s) )/(60*60*24) ))

Esto también debería ser un segundo intercalar seguro siempre que reste siempre la fecha anterior de la última, ya que los segundos intercalares solo aumentarán la diferencia: el truncamiento se redondea efectivamente al resultado correcto.

evan_b
fuente
6
Si, en cambio, especifica la zona horaria tanto para el inicio como para el final (y se asegura de que sean iguales), entonces tampoco tendrá este problema, así:, echo $(( ($(date --date="2015-03-11 UTC" +%s) - $(date --date="2015-03-05 UTC" +%s) )/(60*60*24) ))que devuelve 6, en lugar de 5.
Hank Schultz
1
Ah, uno de los que respondieron pensó en las inexactitudes de la solución básica repetida por otros en variantes. ¡Pulgares hacia arriba! ¿Qué tal los segundos intercalares? ¿Es seguro para un segundo intercalar? Hay precedentes de software que devuelve un resultado de un día si se ejecuta en determinados momentos, problemas de referencia asociados con el segundo intercalar .
Stéphane Gourichon
Vaya, el cálculo "incorrecto" que da como ejemplo devuelve correctamente 6 aquí (Ubuntu 15.04 AMD64, fecha GNU versión 8.23). ¿Quizás su ejemplo depende de la zona horaria? Mi zona horaria es "Europa / París".
Stéphane Gourichon
El mismo buen resultado para el cálculo "incorrecto" con GNU date 2.0 en MSYS, misma zona horaria.
Stéphane Gourichon
1
@ StéphaneGourichon, el cambio de horario de verano de su zona horaria parece haber ocurrido el 29 de marzo de 2015, así que pruebe con un rango de fechas que lo incluya.
evan_b
20

Y en pitón

$python -c "from datetime import date; print (date(2003,11,22)-date(2002,10,20)).days"
398
Fred Laurent
fuente
2
@mouviciel no realmente. Python no siempre está presente.
mc0e
1
@ mc0e en ese contexto, nada está siempre presente
Sumudu
5
@Anub es el OP especificado en las etiquetas como bashy shell, así que creo que podemos asumir que estamos hablando de un sistema * nix. Dentro de tales sistemas, echoy dateestán presentes de manera confiable, pero Python no lo está.
mc0e
2
@ mc0e sí, eso tiene sentido. Aunque python2 está disponible en la mayoría de los sistemas * nix, cuando se trata de dispositivos de gama baja (embebidos, etc.) tendremos que sobrevivir solo con herramientas primarias.
Sumudu
11

Esto funciona para mi:

A="2002-10-20"
B="2003-11-22"
echo $(( ($(date -d $B +%s) - $(date -d $A +%s)) / 86400 )) days

Huellas dactilares

398 days

¿Qué está pasando?

  1. Proporcione una cadena de tiempo válida en A y B
  2. Úselo date -dpara manejar cadenas de tiempo
  3. Úselo date %spara convertir cadenas de tiempo a segundos desde 1970 (unix epoche)
  4. Utilice la expansión del parámetro bash para restar segundos
  5. dividir por segundos por día (86400 = 60 * 60 * 24) para obtener la diferencia como días
  6. ! ¡DST no se tiene en cuenta! ¡Vea esta respuesta en unix.stackexchange !
jschnasse
fuente
7

Si la opción -d funciona en su sistema, aquí hay otra forma de hacerlo. Hay una advertencia de que no daría cuenta de los años bisiestos, ya que he considerado 365 días al año.

date1yrs=`date -d "20100209" +%Y`
date1days=`date -d "20100209" +%j`
date2yrs=`date +%Y`
date2days=`date +%j`
diffyr=`expr $date2yrs - $date1yrs`
diffyr2days=`expr $diffyr \* 365`
diffdays=`expr $date2days - $date1days`
echo `expr $diffyr2days + $diffdays`
Ruchi
fuente
7

Aquí está la versión de MAC OS X para su conveniencia.

$ A="2002-20-10"; B="2003-22-11";
$ echo $(((`date -jf %Y-%d-%m $B +%s` - `date -jf %Y-%d-%m $A +%s`)/86400))

nJoy!

nickl-
fuente
1
-bash: (-) / 86400: error de sintaxis: operando esperado (el token de error es ") / 86400")
cabalgata
1
@cavalcade - eso es porque no lo ha hechoA="2002-20-10"; B="2003-22-11"
RAM237
Gracias, a pesar de que funcione para este caso particular, se recomienda usar echo $(((`date -jf "%Y-%d-%m" "$B" +%s` - `date -jf "%Y-%d-%m" "$A" +%s`)/86400)), es decir, envolver ambos formatos y variables en comillas dobles, de lo contrario puede fallar en diferente formato de fecha (por ejemplo, %b %d, %Y/ Jul 5, 2017)
RAM237
@ RAM237 Eso es lo que obtengo por no prestar atención headmack Bien visto, gracias
cabalgata
5

Incluso si no tiene la fecha de GNU, probablemente tenga Perl instalado:

use Time::Local;
sub to_epoch {
  my ($t) = @_; 
  my ($y, $d, $m) = ($t =~ /(\d{4})-(\d{2})-(\d{2})/);
  return timelocal(0, 0, 0, $d+0, $m-1, $y-1900);
}
sub diff_days {
  my ($t1, $t2) = @_; 
  return (abs(to_epoch($t2) - to_epoch($t1))) / 86400;
}
print diff_days("2002-20-10", "2003-22-11"), "\n";

Esto regresa 398.041666666667: 398 días y una hora debido al horario de verano.


La pregunta volvió a aparecer en mi feed. Aquí hay un método más conciso usando un módulo incluido de Perl

days=$(perl -MDateTime -le '
    sub parse_date { 
        @f = split /-/, shift;
        return DateTime->new(year=>$f[0], month=>$f[2], day=>$f[1]); 
    }
    print parse_date(shift)->delta_days(parse_date(shift))->in_units("days");
' $A $B)
echo $days   # => 398
Glenn Jackman
fuente
2

Este es el más simple que logré trabajar en centos 7:

OLDDATE="2018-12-31"
TODAY=$(date -d $(date +%Y-%m-%d) '+%s')
LINUXDATE=$(date -d "$OLDDATE" '+%s')
DIFFDAYS=$(( ($TODAY - $LINUXDATE) / (60*60*24) ))

echo $DIFFDAYS
Gotxi
fuente
2

Aquí está mi enfoque de trabajo usando zsh. Probado en OSX:

# Calculation dates
## A random old date
START_DATE="2015-11-15"
## Today's date
TODAY=$(date +%Y-%m-%d)

# Import zsh date mod
zmodload zsh/datetime

# Calculate number of days
DIFF=$(( ( $(strftime -r %Y-%m-%d $TODAY) - $(strftime -r %Y-%m-%d $START_DATE) ) / 86400 ))
echo "Your value: " $DIFF

Resultado:

Your value:  1577

Básicamente, usamos strftimela -rfunción reverse ( ) para transformar nuestra cadena de fecha nuevamente en una marca de tiempo, luego hacemos nuestro cálculo.

Olivier B.
fuente
1

Presentaría otra posible solución en Ruby. Parece que es el más pequeño y el más limpio hasta ahora:

A=2003-12-11
B=2002-10-10
DIFF=$(ruby -rdate -e "puts Date.parse('$A') - Date.parse('$B')")
echo $DIFF
Gato gris
fuente
2
Está buscando una forma en bash.
Cojones
2
No hay una forma portátil de hacerlo en el propio shell. Todas las alternativas utilizan programas externos particulares (es decir, GNU dateo algún lenguaje de programación) y honestamente creo que Ruby es una buena forma de hacerlo. Esta solución es muy corta y no utiliza bibliotecas no estándar ni otras dependencias. De hecho, creo que hay más posibilidades de tener Ruby instalado que de tener GNU date instalado.
GreyCat
1

Otra versión de Python:

python -c "from datetime import date; print date(2003, 11, 22).toordinal() - date(2002, 10, 20).toordinal()"
Ulf
fuente
1

en Unix debería tener instaladas las fechas GNU. no es necesario que se desvíe de bash. aquí está la solución encadenada considerando días, solo para mostrar los pasos. se puede simplificar y ampliar a fechas completas.

DATE=$(echo `date`)
DATENOW=$(echo `date -d "$DATE" +%j`)
DATECOMING=$(echo `date -d "20131220" +%j`)
THEDAY=$(echo `expr $DATECOMING - $DATENOW`)

echo $THEDAY 
mattymik
fuente
1

Esto supone que un mes es 1/12 de un año:

#!/usr/bin/awk -f
function mktm(datespec) {
  split(datespec, q, "-")
  return q[1] * 365.25 + q[3] * 365.25 / 12 + q[2]
}
BEGIN {
  printf "%d\n", mktm(ARGV[2]) - mktm(ARGV[1])
}
Steven Penny
fuente
0

Prueba esto:

perl -e 'use Date::Calc qw(Delta_Days); printf "%d\n", Delta_Days(2002,10,20,2003,11,22);'
Pausado hasta nuevo aviso.
fuente
0

Supongamos que sincronizamos manualmente las copias de seguridad de la base de datos de Oracle en un disco terciario. Entonces queremos eliminar las copias de seguridad antiguas en ese disco. Así que aquí hay un pequeño script de bash:

#!/bin/sh

for backup_dir in {'/backup/cmsprd/local/backupset','/backup/cmsprd/local/autobackup','/backup/cfprd/backupset','/backup/cfprd/autobackup'}
do

    for f in `find $backup_dir -type d -regex '.*_.*_.*' -printf "%f\n"`
    do

        f2=`echo $f | sed -e 's/_//g'`
        days=$(((`date "+%s"` - `date -d "${f2}" "+%s"`)/86400))

        if [ $days -gt 30 ]; then
            rm -rf $backup_dir/$f
        fi

    done

done

Modifique los directorios y el período de retención ("30 días") para adaptarlos a sus necesidades.

Alex Cherkas
fuente
Oracle coloca los conjuntos de respaldo en el Área de recuperación de Flash usando un formato como 'YYYY_MM_DD', por lo que eliminamos los guiones bajos para pasarlos a 'date -d'
Alex Cherkas
0

Para MacOS sierra (tal vez de Mac OS X yosemate),

Para obtener el tiempo de época (segundos desde 1970) de un archivo y guardarlo en una var: old_dt=`date -j -r YOUR_FILE "+%s"`

Para obtener el tiempo de época del tiempo actual new_dt=`date -j "+%s"`

Para calcular la diferencia del tiempo de dos épocas anteriores (( diff = new_dt - old_dt ))

Para comprobar si la diferencia es superior a 23 días (( new_dt - old_dt > (23*86400) )) && echo Is more than 23 days

osexp2003
fuente
0
echo $(date +%d/%h/%y) date_today
echo " The other date is 1970/01/01"
TODAY_DATE="1970-01-01"
BEFORE_DATE=$(date +%d%h%y)
echo "THE DIFFERNS IS " $(( ($(date -d $BEFORE_DATE +%s) - $(date -d $TODAY_DATE +%s)) )) SECOUND

Úselo para obtener su fecha de hoy y su segundo desde la fecha que desea. si lo quieres con días solo divídelo con 86400

echo $(date +%d/%h/%y) date_today
echo " The other date is 1970/01/01"
TODAY_DATE="1970-01-01"
BEFORE_DATE=$(date +%d%h%y)
echo "THE DIFFERNS IS " $(( ($(date -d $BEFORE_DATE +%s) - $(date -d $TODAY_DATE +%s))/ 86400)) SECOUND
Zaed Kazem
fuente