web-dev-qa-db-fra.com

Comment comparer deux dates dans un shell?

Comment comparer deux dates dans un shell?

Voici un exemple de la façon dont j'aimerais l'utiliser, bien que cela ne fonctionne pas tel quel:

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

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

Comment puis-je obtenir le résultat souhaité?

95
Abdul

La bonne réponse manque toujours:

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

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

Notez que cela nécessite GNU date. La syntaxe équivalente date pour BSD date (comme celle trouvée par défaut dans OSX) est date -j -f "%F" 2014-08-19 +"%s"

122
user93501

Peut utiliser une comparaison de chaînes standard pour comparer l'ordre chronologique [des chaînes au format année, mois, jour].

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

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

Heureusement, lorsque [les chaînes qui utilisent le format AAAA-MM-JJ] sont triées * par ordre alphabétique, elles sont également triées * par ordre chronologique.

(* - triés ou comparés)

Rien d'extraordinaire nécessaire dans ce cas. Yay!

36
user2533809

Il vous manque le format de date pour la comparaison:

#!/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

Il vous manque également des structures en boucle, comment prévoyez-vous d'obtenir plus de dates?

32
LPoblet

Ce n'est pas un problème de structures en boucle mais de types de données.

Ces dates (todate et cond) sont des chaînes et non des nombres, vous ne pouvez donc pas utiliser l'opérateur "-ge" de test. (N'oubliez pas que la notation entre crochets équivaut à la commande test.)

Ce que vous pouvez faire, c'est utiliser une notation différente pour vos dates afin qu'elles soient entières. Par exemple:

date +%Y%m%d

produira un entier comme 20130715 pour le 15 juillet 2013. Ensuite, vous pouvez comparer vos dates avec "-ge" et des opérateurs équivalents.

Mise à jour: si vos dates sont données (par exemple, vous les lisez à partir d'un fichier au format 2013-07-13), vous pouvez les prétraiter facilement avec tr.

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

L'opérateur -ge ne fonctionne qu'avec des entiers, contrairement à vos dates.

Si votre script est un script bash ou ksh ou zsh, vous pouvez utiliser le < opérateur à la place. Cet opérateur n'est pas disponible dans les tirets ou autres shells qui ne vont pas beaucoup au-delà de la norme POSIX.

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

Dans n'importe quel shell, vous pouvez convertir les chaînes en nombres tout en respectant l'ordre des dates en supprimant simplement les tirets.

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

Vous pouvez également opter pour le traditionnel et utiliser l'utilitaire expr.

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

Comme l'appel de sous-processus dans une boucle peut être lent, vous pouvez préférer effectuer la transformation à l'aide de constructions de traitement de chaîne 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

Bien sûr, si vous pouvez récupérer les dates sans les tirets en premier lieu, vous pourrez les comparer avec -ge.

Je convertis les chaînes en horodatages Unix (secondes depuis le 1.1.1970 0: 0: 0). Ceux-ci peuvent être comparés facilement

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

bash réponse spécifique

Comme j'aime réduire les fourches et bash permettre beaucoup de trucs, il y a mon but:

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

Bien maintenant:

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

Cela remplira à nouveau les deux variables $todate Et $cond, En utilisant une seule fourche, avec une sortie de date -f - Qui prendra stdio pour en lire une date par ligne.

Enfin, vous pouvez rompre votre boucle avec

((todate>=cond))&&break

Ou comme une fonction -:

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
}

Utiliser bash 's printf intégré qui pourrait rendre la date et l'heure avec secondes d'Epoch (voir man bash ;-)

Ce script n'utilise qu'une seule fourche.

Alternative avec fourches limitées et fonction de lecture de date

Cela créera un sous-processus dédié (une seule fourche):

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

Comme l'entrée et la sortie sont ouvertes, l'entrée fifo peut être supprimée.

La fonction:

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

Nota Afin d'éviter des fourches inutiles comme todate=$(myDate 2013-07-18), la variable doit être définie par la fonction elle-même. Et pour permettre une syntaxe libre (avec ou sans guillemets à la chaîne de données), le nom de la variable doit être le dernier argument .

Puis date de comparaison:

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

peut rendre:

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 en dehors d'une boucle.

Ou utilisez la fonction bash du connecteur Shell:

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

ou

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

(Ce qui n'est pas exactement le même: .sh Contient un script de test complet s'il n'est pas fourni)

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
}
1
F. Hauri

Il y a aussi cette méthode de l'article intitulé: Calcul simple de la date et de l'heure en BASH de unix.com.

Ces fonctions sont un extrait d'un script dans ce fil!

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

Usage

# 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"
1
slm

En écho à la réponse @Gilles ("L'opérateur -ge ne fonctionne qu'avec des entiers"), voici un exemple.

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

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

datetime conversions entières en Epoch, selon la réponse de @ mike-q à 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" est supérieur à (plus récent que) "$old_date", mais cette expression n'est pas correctement évaluée comme False:

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

... montré explicitement, ici:

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

Comparaisons entières:

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
Victoria Stuart

Vous pouvez également utiliser la fonction intégrée de mysql pour comparer les dates. Il donne le résultat en "jours".

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

Insérez simplement les valeurs de $DBUSER et $DBPASSWORD variables selon la vôtre.

0
Abid Khan

les dates sont des chaînes, pas des entiers; vous ne pouvez pas les comparer avec les opérateurs arithmétiques standard.

une approche que vous pouvez utiliser, si le séparateur est garanti d'être -:

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

Cela fonctionne aussi loin que bash 2.05b.0(1)-release.

0
Josh McGee

La raison pour laquelle cette comparaison ne fonctionne pas bien avec une chaîne de date avec trait d'union est que le shell suppose qu'un nombre avec un 0 en tête est un octal, dans ce cas le mois "07". Plusieurs solutions ont été proposées, mais la plus rapide et la plus simple consiste à retirer les traits d'union. Bash a une fonction de substitution de chaîne qui rend cela rapide et facile, puis la comparaison peut être effectuée comme une expression arithmétique:

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

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
0
John McNulty

FWIW: sur Mac, il semble que le fait d'introduire une date comme "05/05/2017" ajoutera l'heure actuelle à la date et vous obtiendrez une valeur différente chaque fois que vous la convertirez en heure Epoch. pour obtenir un temps Epoch plus cohérent pour les dates fixes, incluez des valeurs fictives pour les heures, les minutes et les secondes:

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

Cela peut être une bizarrerie limitée à la version de la date livrée avec macOS .... testée sur macOS 10.12.6.

0
s84