¿Cómo comparar dos fechas en un shell?


88

¿Cómo se pueden comparar dos fechas en un shell?

Aquí hay un ejemplo de cómo me gustaría usar esto, aunque no funciona como está:

todate=2013-07-18
cond=2013-07-15

if [ $todate -ge $cond ];
then
    break
fi                           

¿Cómo puedo lograr el resultado deseado?


¿Para qué quieres hacer un bucle? ¿Quieres decir condicional en lugar de bucle?
Mat

¿Por qué etiquetaste archivos ? ¿Son esas fechas realmente tiempos de archivo?
manatwork

Mira esto en stackoverflow: stackoverflow.com/questions/8116503/…
slm

Respuestas:


107

Todavía falta la respuesta correcta:

todate=$(date -d 2013-07-18 +%s)
cond=$(date -d 2014-08-19 +%s)

if [ $todate -ge $cond ];
then
    break
fi  

Tenga en cuenta que esto requiere la fecha GNU. La datesintaxis equivalente para BSD date(como se encuentra en OSX por defecto) esdate -j -f "%F" 2014-08-19 +"%s"


10
No sé por qué alguien rechazó esto, es bastante preciso y no se ocupa de concatenar cuerdas feas. Convierta su fecha en marca de tiempo de Unix (en segundos), compare las marcas de tiempo de Unix.
Nicholi

Creo que mi principal ventaja de esta solución es que puedes hacer sumas o restas fácilmente. Por ejemplo, quería tener un tiempo de espera de x segundos, pero mi primera idea ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) obviamente es incorrecta.
iGEL

Puede incluir precisión de nanosegundos condate -d 2013-07-18 +%s%N
typelogic

32

Te falta el formato de fecha para la comparación:

#!/bin/bash

todate=$(date -d 2013-07-18 +"%Y%m%d")  # = 20130718
cond=$(date -d 2013-07-15 +"%Y%m%d")    # = 20130715

if [ $todate -ge $cond ]; #put the loop where you need it
then
 echo 'yes';
fi

También te faltan estructuras en bucle, ¿cómo planeas obtener más fechas?


Esto no funciona con el dateenviado con macOS.
Flimm

date -dno es estándar y no funciona en un UNIX típico. En BSD incluso intenta establecer el valor de kenel DST.
schily

32

Puede usar una comparación de cadenas estándar para comparar el orden cronológico [de cadenas en un formato de año, mes y día].

date_a=2013-07-18
date_b=2013-07-15

if [[ "$date_a" > "$date_b" ]] ;
then
    echo "break"
fi

Afortunadamente, cuando [las cadenas que usan el formato AAAA-MM-DD] se ordenan * en orden alfabético, también se ordenan * en orden cronológico.

(* - ordenado o comparado)

No se necesita nada lujoso en este caso. ¡Hurra!


¿Dónde está la rama más aquí?
Vladimir Despotovic

Esta es la única solución que funcionó para mí en Sierra MacOS
Vladimir Despotovic

Esto es más simple que mi solución de if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];... Este formato de fecha fue diseñado para ser ordenado usando un orden alfanumérico estándar, o para -ser despojado y ordenado numéricamente.
ctrl-alt-delor

Esta es, de lejos, la respuesta más inteligente aquí
Ed Randall,

7

Este no es un problema de estructuras en bucle sino de tipos de datos.

Esas fechas ( todatey cond) son cadenas, no números, por lo que no puede usar el operador "-ge" de test. (Recuerde que la notación de corchetes es equivalente al comando test).

Lo que puede hacer es usar una notación diferente para sus fechas para que sean enteros. Por ejemplo:

date +%Y%m%d

producirá un número entero como 20130715 para el 15 de julio de 2013. Luego puede comparar sus fechas con "-ge" y operadores equivalentes.

Actualización: si se proporcionan sus fechas (por ejemplo, si las está leyendo desde un archivo en formato 2013-07-13), puede procesarlas fácilmente con tr.

$ echo "2013-07-15" | tr -d "-"
20130715

6

El operador -gesolo funciona con números enteros, que no son sus fechas.

Si su script es un script bash o ksh o zsh, puede usar el <operador en su lugar. Este operador no está disponible en tablero u otros shells que no van mucho más allá del estándar POSIX.

if [[ $cond < $todate ]]; then break; fi

En cualquier shell, puede convertir las cadenas en números respetando el orden de las fechas simplemente eliminando los guiones.

if [ "$(echo "$todate" | tr -d -)" -ge "$(echo "$cond" | tr -d -)" ]; then break; fi

Alternativamente, puede ir tradicional y usar la exprutilidad.

if expr "$todate" ">=" "$cond" > /dev/null; then break; fi

Como invocar subprocesos en un bucle puede ser lento, puede preferir hacer la transformación utilizando construcciones de procesamiento de cadenas de shell.

todate_num=${todate%%-*}${todate#*-}; todate_num=${todate_num%%-*}${todate_num#*-}
cond_num=${cond%%-*}${cond#*-}; cond_num=${cond_num%%-*}${cond_num#*-}
if [ "$todate_num" -ge "$cond_num" ]; then break; fi

Por supuesto, si puede recuperar las fechas sin los guiones en primer lugar, podrá compararlos con -ge.


¿Dónde está la rama más en esta fiesta?
Vladimir Despotovic

2
Esta parece ser la respuesta más correcta. ¡Muy útil!
Mojtaba Rezaeian

1

Convierto las cadenas en marcas de tiempo unix (segundos desde 1.1.1970 0: 0: 0). Estos pueden compararse fácilmente

unix_todate=$(date -d "${todate}" "+%s")
unix_cond=$(date -d "${cond}" "+%s")
if [ ${unix_todate} -ge ${unix_cond} ]; then
   echo "over condition"
fi

Esto no funciona con el dateque viene con macOS.
Flimm

Parece ser lo mismo que unix.stackexchange.com/a/170982/100397 que fue publicado algún tiempo antes que el tuyo
roaima

1

También existe este método del artículo titulado: Calculo simple de fecha y hora en BASH de unix.com.

¡Estas funciones son un extracto de un script en ese hilo!

date2stamp () {
    date --utc --date "$1" +%s
}

dateDiff (){
    case $1 in
        -s)   sec=1;      shift;;
        -m)   sec=60;     shift;;
        -h)   sec=3600;   shift;;
        -d)   sec=86400;  shift;;
        *)    sec=86400;;
    esac
    dte1=$(date2stamp $1)
    dte2=$(date2stamp $2)
    diffSec=$((dte2-dte1))
    if ((diffSec < 0)); then abs=-1; else abs=1; fi
    echo $((diffSec/sec*abs))
}

Uso

# calculate the number of days between 2 dates
    # -s in sec. | -m in min. | -h in hours  | -d in days (default)
    dateDiff -s "2006-10-01" "2006-10-31"
    dateDiff -m "2006-10-01" "2006-10-31"
    dateDiff -h "2006-10-01" "2006-10-31"
    dateDiff -d "2006-10-01" "2006-10-31"
    dateDiff  "2006-10-01" "2006-10-31"

Esto no funciona con el dateque viene con macOS.
Flimm

Si instala gdate en MacOS a través de brew, esto puede modificarse fácilmente para que funcione cambiando datea gdate.
slm

1
Esta respuesta fue muy útil en mi caso. ¡Debería calificar!
Mojtaba Rezaeian

1

respuesta específica

Como me gusta reducir los tenedores y permiten muchos trucos, ahí está mi propósito:

todate=2013-07-18
cond=2013-07-15

Bien ahora:

{ read todate; read cond ;} < <(date -f - +%s <<<"$todate"$'\n'"$cond")

Esto volverá a llenar ambas variables $todatey $cond, usando solo una bifurcación, con una salida date -f -que toma el estándar para leer una fecha por línea.

Finalmente, podrías romper tu ciclo con

((todate>=cond))&&break

O como una función :

myfunc() {
    local todate cond
    { read todate
      read cond
    } < <(
      date -f - +%s <<<"$1"$'\n'"$2"
    )
    ((todate>=cond))&&return
    printf "%(%a %d %b %Y)T older than %(%a %d %b %Y)T...\n" $todate $cond
}

Uso de 's incorporado printfque podría representar la fecha y hora con segundos de época (ver man bash;-)

Este script solo usa una bifurcación.

Alternativa con horquillas limitadas y función de lector de fecha

Esto creará un subproceso dedicado (solo una bifurcación):

mkfifo /tmp/fifo
exec 99> >(exec stdbuf -i 0 -o 0 date -f - +%s >/tmp/fifo 2>&1)
exec 98</tmp/fifo
rm /tmp/fifo

Como la entrada y la salida están abiertas, se puede eliminar la entrada de fifo.

La función:

myDate() {
    local var="${@:$#}"
    shift
    echo >&99 "${@:1:$#-1}"
    read -t .01 -u 98 $var
}

Nota Para evitar tenedores inútiles como todate=$(myDate 2013-07-18), la variable debe ser establecida por la función misma. Y para permitir la sintaxis libre (con o sin comillas para la cadena de fechas), el nombre de la variable debe ser el último argumento.

Entonces la fecha de comparación:

myDate 2013-07-18        todate
myDate Mon Jul 15 2013   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

puede representar:

To: Thu Jul 18 00:00:00 2013 > Cond: Mon Jul 15 00:00:00 2013
bash: break: only meaningful in a `for', `while', or `until' loop

si fuera de un bucle

O use la función bash de conector de shell:

wget https://github.com/F-Hauri/Connector-bash/raw/master/shell_connector.bash

o

wget https://f-hauri.ch/vrac/shell_connector.sh

(Que no son exactamente iguales: .shcontienen un script de prueba completo si no se obtiene)

source shell_connector.sh
newConnector /bin/date '-f - +%s' @0 0

myDate 2013-07-18        todate
myDate "Mon Jul 15 2013"   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

Profiling bash contiene una buena demostración de cómo usar date -f -podría reducir el requisito de recursos.
F. Hauri

0

las fechas son cadenas, no enteros; no puede compararlos con operadores aritméticos estándar.

Un enfoque que puede utilizar, si se garantiza que el separador sea -:

IFS=-
read -ra todate <<<"$todate"
read -ra cond <<<"$cond"
for ((idx=0;idx<=numfields;idx++)); do
  (( todate[idx] > cond[idx] )) && break
done
unset IFS

Esto funciona tan atrás como bash 2.05b.0(1)-release.


0

También puede usar la función incorporada de mysql para comparar las fechas. Da el resultado en 'días'.

from_date="2015-01-02"
date_today="2015-03-10"
diff=$(mysql -u${DBUSER} -p${DBPASSWORD} -N -e"SELECT DATEDIFF('${date_today}','${from_date}');")

Simplemente inserte los valores $DBUSERy $DBPASSWORDvariables de acuerdo con los suyos.


0

FWIW: en Mac, parece que alimentar solo una fecha como "2017-05-05" agregará la hora actual a la fecha y obtendrá un valor diferente cada vez que la convierta en tiempo de época. para obtener un tiempo de época más consistente para fechas fijas, incluya valores ficticios para horas, minutos y segundos:

date -j -f "%Y-%m-%d %H:%M:%S" "2017-05-05 00:00:00" +"%s"

Esto puede ser una rareza limitada a la versión de fecha que se envió con macOS ... probado en macOS 10.12.6.


0

Haciendo eco de la respuesta @Gilles ("El operador -ge solo funciona con enteros"), aquí hay un ejemplo.

curr_date=$(date +'%Y-%m-%d')
echo "$curr_date"
2019-06-20

old_date="2019-06-19"
echo "$old_date"
2019-06-19

datetimeconversiones enteras a epoch, según la respuesta de @ mike-q en https://stackoverflow.com/questions/10990949/convert-date-time-string-to-epoch-in-bash :

curr_date_int=$(date -d "${curr_date}" +"%s")
echo "$curr_date_int"
1561014000

old_date_int=$(date -d "${old_date}" +"%s")
echo "$old_date_int"
1560927600

"$curr_date"es mayor que (más reciente que) "$old_date", pero esta expresión se evalúa incorrectamente como False:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; fi

... se muestra explícitamente aquí:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; else echo 'bar'; fi
bar

Comparaciones enteras:

if [[ "$curr_date_int" -ge "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -gt "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; fi

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; else echo 'bar'; fi
bar

0

La razón por la que esta comparación no funciona bien con una cadena de fecha con guiones es que el shell supone que un número con un 0 inicial es un octal, en este caso el mes "07". Se han propuesto varias soluciones, pero la más rápida y fácil es eliminar los guiones. Bash tiene una función de sustitución de cadenas que hace que esto sea rápido y fácil, luego la comparación se puede realizar como una expresión aritmética:

todate=2013-07-18
cond=2013-07-15

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.