Jak porównać dwie daty w powłoce?


88

Jak można porównać dwie daty w skorupce?

Oto przykład, w jaki sposób chciałbym tego użyć, chociaż nie działa tak, jak jest:

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

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

Jak mogę osiągnąć pożądany efekt?


Po co chcesz zapętlać? Czy masz na myśli warunkowy, a nie pętlowy?
Mat

Dlaczego oznaczyłeś pliki ? Czy te daty faktycznie są razy w pliku?
manatwork

Sprawdź to na stackoverflow: stackoverflow.com/questions/8116503/…
slm

Odpowiedzi:


107

Nadal brakuje właściwej odpowiedzi:

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

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

Zauważ, że wymaga to daty GNU. Równoważna dateskładnia BSD date(domyślnie taka jak w OSX) todate -j -f "%F" 2014-08-19 +"%s"


10
Nie wiem, dlaczego ktoś to ocenił, jest dość dokładny i nie radzi sobie z łączeniem brzydkich strun. Konwertuj datę na uniksowy znacznik czasu (w sekundach), porównaj uniksowe znaczniki czasu.
Nicholi

Myślę, że moją główną zaletą tego rozwiązania jest to, że możesz łatwo dodawać i odejmować. Na przykład chciałem mieć limit x sekund, ale mój pierwszy pomysł ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) jest oczywiście błędny.
iGEL

Możesz dołączyć precyzję w nanosekundach dodate -d 2013-07-18 +%s%N
typelogic

32

Brakuje formatu daty dla porównania:

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

Brakuje również zapętlonych struktur, jak planujesz uzyskać więcej dat?


To nie działa z datedostarczonym z macOS.
Flimm,

date -djest niestandardowy i nie działa na typowym systemie UNIX. Na BSD próbuje nawet ustawić wartość kenel DST.
schily,

32

Można użyć standardowego porównania ciągów, aby porównać chronologiczne uporządkowanie [ciągów w formacie roku, miesiąca i dnia].

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

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

Na szczęście, gdy [ciągi znaków, które używają formatu RRRR-MM-DD] są sortowane * w kolejności alfabetycznej, są one również sortowane * w kolejności chronologicznej.

(* - posortowane lub porównane)

W tym przypadku nic szczególnego nie jest potrzebne. tak!


Gdzie jest gałąź jeszcze?
Vladimir Despotovic

To jedyne rozwiązanie, które działało dla mnie w Sierra MacOS
Vladimir Despotovic,

To jest prostsze niż moje rozwiązanie if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];… Ten format daty został zaprojektowany do sortowania przy użyciu standardowego sortowania alfanumerycznego lub -do usuwania i sortowania numerycznego.
ctrl-alt-delor

To zdecydowanie najmądrzejsza odpowiedź tutaj
Ed Randall

7

To nie jest problem pętli struktur, ale typów danych.

Te daty ( todatei cond) są łańcuchami, a nie liczbami, więc nie można użyć operatora „-ge” test. (Pamiętaj, że notacja w nawiasach kwadratowych jest równoważna poleceniu test.)

Możesz użyć innego zapisu dla swoich dat, aby były to liczby całkowite. Na przykład:

date +%Y%m%d

wygeneruje liczbę całkowitą taką jak 20130715 na 15 lipca 2013 r. Następnie możesz porównać swoje daty z „-ge” i równoważnymi operatorami.

Aktualizacja: jeśli podane są daty (np. Czytasz je z pliku w formacie 2013-07-13), możesz łatwo przetwarzać je wstępnie za pomocą tr.

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

6

Operator -gedziała tylko z liczbami całkowitymi, które nie są Twoimi datami.

Jeśli twój skrypt to skrypt bash, ksh lub zsh, możesz <zamiast tego użyć operatora. Ten operator nie jest dostępny w desce rozdzielczej ani w innych powłokach, które nie wykraczają daleko poza standard POSIX.

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

W dowolnej powłoce możesz konwertować ciągi na liczby, przestrzegając kolejności dat, po prostu usuwając myślniki.

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

Alternatywnie możesz przejść tradycyjnie i skorzystać z exprnarzędzia.

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

Ponieważ wywoływanie podprocesów w pętli może być powolne, możesz preferować wykonanie transformacji za pomocą konstrukcji przetwarzających ciąg powłoki.

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

Oczywiście, jeśli możesz pobrać daty bez myślników, będziesz mógł je porównać -ge.


Gdzie jest gałąź jeszcze w tym bashu?
Vladimir Despotovic

2
To wydaje się być najbardziej poprawną odpowiedzią. Bardzo pomocny!
Mojtaba Rezaeian

1

Przekształcam ciągi znaków na uniksowe znaczniki czasu (sekundy od 1.1.1970 0: 0: 0). Można to łatwo porównać

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

To nie działa w dateprzypadku systemu MacOS.
Flimm,

Wygląda na to, że jest taki sam, jak unix.stackexchange.com/a/170982/100397, który został opublikowany jakiś czas przed twoim
roaima

1

Istnieje również ta metoda z artykułu zatytułowanego: Proste obliczanie daty i godziny w BASH z unix.com.

Te funkcje są fragmentem skryptu w tym wątku!

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

Stosowanie

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

To nie działa w dateprzypadku systemu MacOS.
Flimm,

Jeśli zainstalujesz gdate na MacOS przez brew, można to łatwo zmodyfikować do pracy, zmieniając datena gdate.
slm

1
Ta odpowiedź była bardzo pomocna w mojej sprawie. To powinno wzrosnąć!
Mojtaba Rezaeian

1

specyficzna odpowiedź

Ponieważ lubię redukować widelce, a pozwala na wiele lew, oto mój cel:

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

Więc teraz:

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

Spowoduje to ponowne wypełnienie obu zmiennych $todatei $cond, używając tylko jednego rozwidlenia, ouptut, date -f -który zajmie stdio do odczytu jednej daty po linii.

Wreszcie możesz przerwać pętlę

((todate>=cond))&&break

Lub jako funkcja :

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
}

Korzystanie z wbudowanego , printfktóre może renderować datę i czas w sekundach z epoki (patrz man bash;-)

Ten skrypt używa tylko jednego widelca.

Alternatywnie z ograniczonymi widłami i funkcją czytnika daty

Spowoduje to utworzenie dedykowanego podprocesu (tylko jeden rozwidlenie):

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

Ponieważ wejście i wyjście są otwarte, wpis fifo może zostać usunięty.

Funkcja:

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

Uwaga Aby zapobiec niepotrzebnym rozwidleniom todate=$(myDate 2013-07-18), zmienna ma być ustawiana przez samą funkcję. Aby umożliwić dowolną składnię (z cudzysłowami do datestring lub bez), nazwa zmiennej musi być ostatnim argumentem.

Następnie porównanie dat:

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

może renderować:

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

jeśli poza pętlą.

Lub użyj funkcji bash-shell-connector:

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

lub

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

(Które nie są dokładnie takie same: .shzawierają pełny skrypt testowy, jeśli nie są pozyskiwane)

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
}

Profilowanie bash zawiera dobrą demonstrację tego, jak użycie date -f -może zmniejszyć wymagania dotyczące zasobów.
F. Hauri

0

daty są łańcuchami, a nie liczbami całkowitymi; nie można ich porównywać ze standardowymi operatorami arytmetycznymi.

można zastosować jedno podejście, jeśli gwarantuje się, że separatorem jest -:

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

Działa to już w przeszłości bash 2.05b.0(1)-release.


0

Możesz także użyć wbudowanej funkcji mysql do porównania dat. Daje wynik w „dniach”.

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

Po prostu wstaw wartości $DBUSERi $DBPASSWORDzmienne zgodnie z twoimi.


0

FWIW: na Macu wydaje się, że wprowadzenie do daty tylko daty „2017-05-05” spowoduje dodanie bieżącej godziny do tej daty, a otrzymasz inną wartość za każdym razem, gdy zmienisz ją na czas z epoki. aby uzyskać bardziej spójny czas dla ustalonych dat, uwzględnij wartości pozorne dla godzin, minut i sekund:

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

Może to być osobliwość ograniczona do wersji daty dostarczonej z macOS .... testowanej na macOS 10.12.6.


0

Echoing @Gilles odpowiedź („Operator -ge działa tylko z liczbami całkowitymi”), oto przykład.

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

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

datetimekonwersje liczb całkowitych na epoch, według odpowiedzi @ mike-q na 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"jest większa niż (nowsza niż) "$old_date", ale to wyrażenie niepoprawnie ocenia jako False:

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

... pokazane wyraźnie tutaj:

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

Porównanie liczb całkowitych:

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

Powodem, dla którego to porównanie nie działa dobrze z dzielonym ciągiem daty jest to, że powłoka przyjmuje liczbę z wiodącym 0, to ósemka, w tym przypadku miesiąc „07”. Zaproponowano różne rozwiązania, ale najszybszym i najłatwiejszym jest usunięcie łączników. Bash ma funkcję podstawiania ciągów, która sprawia, że ​​jest to szybkie i łatwe, a następnie porównanie można wykonać jako wyrażenie arytmetyczne:

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

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.