Comment comparer deux dates dans un shell?

88

Comment comparer deux dates dans un shell?

Voici un exemple de la façon dont j'aimerais utiliser ceci, 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 atteindre le résultat souhaité?

Abdul
la source
Qu'est-ce que vous voulez faire une boucle? Voulez-vous dire conditionnel plutôt que boucle?
Mat
Pourquoi avez-vous tagué des fichiers ? Ces dates sont-elles réellement des temps de fichier?
Manatwork
Découvrez ceci sur stackoverflow: stackoverflow.com/questions/8116503/…
slm

Réponses:

107

La bonne réponse est toujours manquante:

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 une date GNU. La datesyntaxe équivalente pour BSD date(comme dans OSX par défaut) est la suivante:date -j -f "%F" 2014-08-19 +"%s"

utilisateur93501
la source
10
Donno, pourquoi quelqu'un a baissé la note, c'est assez précis et ne traite pas de la concaténation de chaînes laides. Convertissez votre date en timestamp Unix (en secondes), comparez les timestamps Unix.
Nicholi
Je pense que le principal avantage de cette solution est que vous pouvez facilement faire des additions ou des soustractions. Par exemple, je voulais avoir un délai d'attente de x secondes, mais ma première idée ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) est évidemment fausse.
iGEL
Vous pouvez inclure une précision en nanosecondes avecdate -d 2013-07-18 +%s%N
typelogic
32

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 comptez-vous obtenir plus de dates?

LPoblet
la source
Cela ne fonctionne pas avec le dateMacOS fourni.
Flimm
date -dest non standard et ne fonctionne pas sous UNIX typique. Sur BSD, il tente même de définir la valeur kenel DST.
Schily
32

Peut utiliser une comparaison de chaînes standard pour comparer l'ordre chronologique [des chaînes dans un format année, mois et 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é ou comparé)

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

utilisateur2533809
la source
Où est la branche else ici?
Vladimir Despotovic
C’est la seule solution qui a fonctionné pour moi sur Sierra MacOS
Vladimir Despotovic
C’est plus simple que ma solution de if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];… Ce format de date a été conçu pour être trié à l’aide du tri alphanumérique standard, ou pour -être épuré et trié numériquement.
ctrl-alt-delor
C'est de loin la réponse la plus intelligente ici
Ed Randall
7

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

Ces dates ( todateet cond) sont des chaînes, pas des nombres, vous ne pouvez donc pas utiliser l'opérateur "-ge" de test. (Rappelez-vous que la notation entre crochets est équivalente à la commande test.)

Ce que vous pouvez faire est d’utiliser une notation différente pour vos dates afin qu’elles soient des entiers. Par exemple:

date +%Y%m%d

produira un nombre entier comme 20130715 pour le 15 juillet 2013. Vous pourrez ensuite comparer vos dates avec les opérateurs "-ge" et équivalents.

Mise à jour: si vos dates sont indiquées (par exemple, si 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
sergut
la source
6

L'opérateur -gene fonctionne qu'avec des entiers, ce que vos dates ne sont pas.

Si votre script est un script bash, ksh ou zsh, vous pouvez utiliser l' <opérateur à la place. Cet opérateur n'est pas disponible dans les tirets ni dans d'autres shells ne dépassant pas 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

Alternativement, vous pouvez aller traditionnel et utiliser l' exprutilitaire.

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

Comme l’appel de sous-processus dans une boucle peut être lent, vous préférerez peut-être effectuer la transformation à l’aide de structures de traitement de chaîne de commande.

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 traits d'union, vous pourrez les comparer -ge.

Gilles
la source
Où est la branche else dans cette bash?
Vladimir Despotovic
2
Cela semble être la réponse la plus correcte. Très utile!
Mojtaba Rezaeian
1

Je convertis les chaînes en horodatages Unix (secondes depuis le 1.1.1970 0: 0: 0). Ceux-ci peuvent comparer facilement

unix_todate=$(date -d "${todate}" "+%s")
unix_cond=$(date -d "${cond}" "+%s")
if [ ${unix_todate} -ge ${unix_cond} ]; then
   echo "over condition"
fi
InuSasha
la source
Cela ne fonctionne pas avec datecelui fourni avec macOS.
Flimm
Semble être le même que unix.stackexchange.com/a/170982/100397 qui a été posté quelque temps avant le vôtre
roaima
1

Il existe également cette méthode dans l'article intitulé: Calcul simple de la date et de l'heure dans BASH de unix.com.

Ces fonctions sont un extrait d'un script de 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"
slm
la source
Cela ne fonctionne pas avec datecelui fourni avec macOS.
Flimm
Si vous installez gdate sur MacOS via brew, ceci peut être facilement modifié pour fonctionner en passant dateà gdate.
slm
1
Cette réponse m'a été très utile dans mon cas. Ça devrait être mieux!
Mojtaba Rezaeian
1

réponse spécifique

Comme j'aime bien réduire les fourches et permettre beaucoup de trucs, voici mon but:

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

Bien maintenant:

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

Cela va repeupler les deux variables $todateet $cond, en utilisant un seul fork, avec une sortie date -f -qui prend stdio pour lire une date par ligne.

Enfin, vous pourriez casser votre boucle avec

((todate>=cond))&&break

Ou en 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 le script intégré de printf qui pourrait rendre la date et l'heure en secondes (voir man bash;-)

Ce script utilise un seul fork.

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

Cela créera un sous-processus dédié (un seul fork):

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
}

Note 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 la 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 shell-connector:

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 pareil: .shcontient le script de test complet s'il n'a pas été acheté)

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
}
F. Hauri
la source
Le profilage bash contient une bonne démonstration de la façon dont l’utilisation date -f -pourrait réduire les besoins en ressources.
F. Hauri
0

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

Une approche que vous pouvez utiliser si le séparateur est garanti -:

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.

Josh McGee
la source
0

Vous pouvez également utiliser la fonction intégrée de mysql pour comparer les dates. Cela 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 $DBUSERet $DBPASSWORDvariables en fonction de la vôtre.

Abid Khan
la source
0

FWIW: sur Mac, il semble que l'insertion d'une date telle que "2017-05-05" dans la date sera ajoutée à la date et l'heure actuelle, et vous obtiendrez une valeur différente chaque fois que vous la convertirez en heure de début. pour obtenir une heure d'époque plus cohérente pour des dates fixes, incluez des valeurs factices 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 date fournie avec macOS .... testée sur macOS 10.12.6.

s84
la source
0

Répondant à @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

datetimeconversions entières vers epoch, la réponse de @ mike-q à l' adresse https://stackoverflow.com/questions/10990949/convert-date-time-string-stro--poch-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 est évaluée de manière incorrecte comme suit 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
Victoria Stuart
la source
0

La raison pour laquelle cette comparaison ne fonctionne pas bien avec une chaîne de date comportant un trait d'union est que le shell suppose qu'un nombre précédé de 0 est un nombre octal, dans ce cas le mois "07". Diverses solutions ont été proposées, mais la plus rapide et la plus simple consiste à supprimer les traits d'union. Bash a une fonctionnalité de substitution de chaîne qui rend cela rapide et facile, alors 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
John McNulty
la source