Como comparar duas datas em um shell?

88

Como duas datas podem ser comparadas em um shell?

Aqui está um exemplo de como eu gostaria de usar isso, embora não funcione como está:

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

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

Como posso alcançar o resultado desejado?

Abdul
fonte
O que você quer fazer um loop? Você quer dizer condicional em vez de loop?
Mat
Por que você marcou arquivos ? Essas datas são realmente horários de arquivo?
manatwork
Verifique isso no stackoverflow: stackoverflow.com/questions/8116503/…
slm

Respostas:

107

A resposta correta ainda está faltando:

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

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

Observe que isso requer data GNU. A datesintaxe equivalente para BSD date(como encontrada no OSX por padrão) édate -j -f "%F" 2014-08-19 +"%s"

user93501
fonte
10
Não sei por que alguém rebaixou isso, é bastante preciso e não lida com concatenar seqüências feias. Converta sua data em timestamp unix (em segundos), compare timestamps unix.
Nicholi
Penso que a principal vantagem desta solução é que você pode fazer adições ou subtrações facilmente. Por exemplo, eu queria ter um tempo limite de x segundos, mas minha primeira ideia ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) está obviamente errada.
iGEL
Você pode incluir precisão de nanossegundos comdate -d 2013-07-18 +%s%N
typelogic
32

Está faltando o formato da data para a comparação:

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

Você também está perdendo estruturas em loop, como planeja obter mais datas?

LPoblet
fonte
Isso não funciona com o datefornecido com o macOS.
Flimm
date -dnão é padrão e não funciona em um UNIX típico. No BSD, ele ainda tenta definir o valor do kenel DST.
schily
32

Pode usar uma comparação de cadeias padrão para comparar a ordem cronológica [de cadeias em um formato de ano, mês, dia].

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

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

Felizmente, quando [cadeias que usam o formato AAAA-MM-DD] são classificadas * em ordem alfabética, elas também são classificadas * em ordem cronológica.

(* - classificado ou comparado)

Nada extravagante necessário neste caso. yay!

user2533809
fonte
Onde está o ramo else aqui?
Vladimir Despotovic
Esta é a única solução que funcionou para mim no Sierra MacOS
Vladimir Despotovic
Isso é mais simples que minha solução de if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];… Esse formato de data foi projetado para ser classificado usando a classificação numérica alfa padrão ou para -ser separado e classificado numericamente.
ctrl-alt-Delor
Esta é de longe a resposta mais inteligente aqui
Ed Randall
7

Este não é um problema de estruturas em loop, mas de tipos de dados.

Essas datas ( todatee cond) são cadeias de caracteres, não números, portanto você não pode usar o operador "-ge" de test. (Lembre-se de que a notação entre colchetes é equivalente ao comando test.)

O que você pode fazer é usar uma notação diferente para suas datas, para que sejam números inteiros. Por exemplo:

date +%Y%m%d

produzirá um número inteiro como 20130715 para 15 de julho de 2013. Em seguida, você pode comparar suas datas com "-ge" e operadores equivalentes.

Atualização: se suas datas são fornecidas (por exemplo, você as lê de um arquivo no formato 13/07/2013), você pode pré-processá-las facilmente com tr.

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

O operador -getrabalha apenas com números inteiros, que não são suas datas.

Se o seu script for um script bash ou ksh ou zsh, você poderá usar o <operador. Este operador não está disponível no painel ou em outros shells que não vão muito além do padrão POSIX.

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

Em qualquer shell, você pode converter as seqüências de caracteres em números, respeitando a ordem das datas, simplesmente removendo os traços.

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

Como alternativa, você pode ir tradicional e usar o exprutilitário.

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

Como a invocação de subprocessos em um loop pode ser lenta, você pode preferir fazer a transformação usando construções de processamento de cadeia 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

Obviamente, se você puder recuperar as datas sem os hífens, poderá compará-las -ge.

Gilles
fonte
Onde está o ramo else nessa festança?
Vladimir Despotovic
2
Esta parece ser a resposta mais correta. Muito útil!
Mojtaba Rezaeian
1

Eu converto as Strings em unix-timestamps (segundos desde 1.1.1970 0: 0: 0). Estes podem ser comparados facilmente

unix_todate=$(date -d "${todate}" "+%s")
unix_cond=$(date -d "${cond}" "+%s")
if [ ${unix_todate} -ge ${unix_cond} ]; then
   echo "over condition"
fi
InuSasha
fonte
Isso não funciona com o dateque acompanha o macOS.
Flimm
Parece ser o mesmo que unix.stackexchange.com/a/170982/100397 que foi publicado algum tempo antes de vocês
roaima
1

Também existe este método no artigo intitulado: Calibração simples de data e hora no BASH do unix.com.

Essas funções são um trecho de um script nesse segmento!

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"
slm
fonte
Isso não funciona com o dateque acompanha o macOS.
Flimm
Se você instalar o gdate no MacOS via brew, isso poderá ser facilmente modificado para funcionar, alterando datepara gdate.
Slm
1
Esta resposta foi muito útil no meu caso. Deve aumentar!
Mojtaba Rezaeian
1

resposta específica do

Como gosto de reduzir os garfos e o permitem muitos truques, existe o meu objetivo:

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

Bem agora:

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

Isso irá preencher novamente as duas variáveis $todatee $cond, usando apenas um fork, com exceção do date -f -que leva o stdio para ler uma data por linha.

Finalmente, você pode quebrar seu ciclo com

((todate>=cond))&&break

Ou como uma função :

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
}

Usando o do , printfque pode renderizar data e hora com segundos a partir da época (consulte man bash;-)

Este script usa apenas um garfo.

Alternativa com garfos limitados e função de leitor de data

Isso criará um subprocesso dedicado (apenas um 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

Como entrada e saída estão abertas, a entrada quino pode ser excluída.

A função:

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

Nota Para evitar garfos inúteis como todate=$(myDate 2013-07-18), a variável deve ser definida pela própria função. E para permitir sintaxe livre (com ou sem aspas para a cadeia de dados), o nome da variável deve ser o último argumento.

Data da comparação:

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

pode render:

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

se estiver fora de um loop.

Ou use a função bash do conector do shell:

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

ou

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

(Que não são exatamente iguais: .shcontém script de teste completo, se não for fornecido)

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
fonte
O perfil de bash contém uma boa demonstração de como o uso date -f -pode reduzir os requisitos de recursos.
21717 F. F. Hauri
0

datas são strings, não inteiros; você não pode compará-los com operadores aritméticos padrão.

uma abordagem que você pode usar, se for garantido que o separador é -:

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

Isso funciona já em bash 2.05b.0(1)-release.

Josh McGee
fonte
0

Você também pode usar a função embutida do mysql para comparar as datas. Dá o resultado em 'dias'.

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

Basta inserir os valores $DBUSERe $DBPASSWORDvariáveis ​​de acordo com o seu.

Abid Khan
fonte
0

FWIW: no Mac, parece que alimentar apenas uma data como "2017-05-05" adicionará a hora atual à data e você terá um valor diferente cada vez que a converter para a época. para obter um tempo de época mais consistente para datas fixas, inclua valores fictícios por horas, minutos e segundos:

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

Isso pode ser uma raridade limitada à versão da data fornecida com o macOS .... testada no macOS 10.12.6.

s84
fonte
0

Fazendo eco da resposta do @Gilles ("O operador -ge funciona apenas com números inteiros"), aqui está um exemplo.

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

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

datetimeconversões de número inteiro para epoch, de acordo com a resposta de @ mike-q em 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"é maior que (mais recente que) "$old_date", mas essa expressão é avaliada incorretamente como False:

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

... mostrado explicitamente, aqui:

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

Comparações de números inteiros:

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
fonte
0

A razão pela qual essa comparação não funciona bem com uma string de data hifenizada é que o shell assume que um número com 0 inicial é um octal, nesse caso, o mês "07". Foram propostas várias soluções, mas a mais rápida e fácil é retirar os hífens. O Bash possui um recurso de substituição de string que torna isso rápido e fácil, então a comparação pode ser realizada como uma expressão aritmética:

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

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