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é?
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"
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!
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?
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
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
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.
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.
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
}
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))
}
# 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"
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
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.
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
.
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
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.