Bagaimana cara membandingkan dua tanggal di shell?

88

Bagaimana dua kurma dapat dibandingkan dalam sebuah shell?

Berikut adalah contoh bagaimana saya ingin menggunakan ini, meskipun tidak berfungsi apa adanya:

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

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

Bagaimana saya bisa mencapai hasil yang diinginkan?

Abdul
sumber
Untuk apa Anda ingin mengulang? Apakah maksud Anda conditional daripada loop?
Mat
Mengapa Anda menandai file ? Apakah tanggal-tanggal itu sebenarnya adalah waktu pengajuan?
manatwork
Lihat ini di stackoverflow: stackoverflow.com/questions/8116503/…
slm

Jawaban:

107

Jawaban yang benar masih hilang:

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

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

Perhatikan bahwa ini memerlukan tanggal GNU. dateSintaks yang setara untuk BSD date(seperti ditemukan di OSX secara default) adalahdate -j -f "%F" 2014-08-19 +"%s"

pengguna93501
sumber
10
Donno mengapa seseorang menurunkan ini, itu cukup akurat dan tidak berurusan dengan string buruk. Ubah tanggal Anda menjadi cap waktu unix (dalam detik), bandingkan cap waktu unix.
Nicholi
Saya pikir, saya keuntungan utama dari solusi ini adalah, bahwa Anda dapat melakukan penambahan atau pengurangan dengan mudah. Misalnya, saya ingin memiliki batas waktu x detik, tetapi ide pertama saya ( timeout=$(`date +%Y%m%d%H%M%S` + 500)) jelas salah.
iGEL
Anda dapat memasukkan ketepatan nanoseconds dengandate -d 2013-07-18 +%s%N
typelogic
32

Anda tidak memiliki format tanggal untuk perbandingan:

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

Anda juga kehilangan struktur pengulangan, bagaimana Anda berencana untuk mendapatkan lebih banyak tanggal?

LPoblet
sumber
Ini tidak berfungsi dengan yang datedikirimkan bersama macOS.
Flimm
date -dtidak standar dan tidak bekerja pada UNIX yang khas. Pada BSD bahkan mencoba untuk mengatur nilai kenel DST.
schily
32

Dapat menggunakan perbandingan string standar untuk membandingkan urutan kronologis [string dalam format tahun, bulan, hari].

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

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

Untungnya, ketika [string yang menggunakan format YYYY-MM-DD] diurutkan * dalam urutan abjad, mereka juga diurutkan * dalam urutan kronologis.

(* - diurutkan atau dibandingkan)

Tidak ada yang dibutuhkan dalam kasus ini. yay!

pengguna2533809
sumber
Di mana cabang lain di sini?
Vladimir Despotovic
Ini adalah satu-satunya solusi yang bekerja untuk saya di Sierra MacOS
Vladimir Despotovic
Ini lebih sederhana daripada solusi saya if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];... Format tanggal ini dirancang untuk diurutkan menggunakan pengurutan angka alpha standar, atau untuk -dilucuti dan diurutkan secara numerik.
ctrl-alt-delor
Ini adalah jawaban yang paling cerdas di sini
Ed Randall
7

Ini bukan masalah struktur pengulangan tetapi tipe data.

Tanggal tersebut ( todatedan cond) adalah string, bukan angka, sehingga Anda tidak dapat menggunakan operator "-ge" dari test. (Ingat bahwa tanda kurung siku sama dengan perintah test.)

Apa yang dapat Anda lakukan adalah menggunakan notasi yang berbeda untuk tanggal Anda sehingga bilangan bulat. Sebagai contoh:

date +%Y%m%d

akan menghasilkan bilangan bulat seperti 20130715 untuk 15 Juli 2013. Kemudian Anda dapat membandingkan tanggal Anda dengan "-ge" dan operator yang setara.

Pembaruan: jika tanggal Anda diberikan (mis. Anda membacanya dari file dalam format 2013-07-13) maka Anda dapat dengan mudah memprosesnya dengan tr.

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

Operator -gehanya bekerja dengan bilangan bulat, yang tanggal Anda tidak.

Jika skrip Anda adalah skrip bash atau ksh atau zsh, Anda dapat menggunakan <operator sebagai gantinya. Operator ini tidak tersedia dalam dash atau shell lain yang tidak jauh melampaui standar POSIX.

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

Dalam shell apa pun, Anda dapat mengonversi string menjadi angka sambil menghormati urutan tanggal hanya dengan menghapus tanda hubung.

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

Atau, Anda bisa menggunakan tradisional dan menggunakan exprutilitas.

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

Karena menjalankan subproses dalam satu loop bisa lambat, Anda mungkin lebih suka melakukan transformasi menggunakan konstruksi pemrosesan string 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

Tentu saja, jika Anda dapat mengambil tanggal tanpa tanda hubung di tempat pertama, Anda akan dapat membandingkannya -ge.

Gilles
sumber
Di mana cabang lain di pesta ini?
Vladimir Despotovic
2
Ini sepertinya jawaban yang paling benar. Sangat membantu!
Mojtaba Rezaeian
1

Saya mengubah Strings menjadi cap waktu unix (detik sejak 1.1.1970 0: 0: 0). Ini dapat dibandingkan dengan mudah

unix_todate=$(date -d "${todate}" "+%s")
unix_cond=$(date -d "${cond}" "+%s")
if [ ${unix_todate} -ge ${unix_cond} ]; then
   echo "over condition"
fi
InuSasha
sumber
Ini tidak berfungsi dengan dateyang dikirimkan bersama macOS.
Flimm
Tampaknya sama dengan unix.stackexchange.com/a/170982/100397 yang telah diposting beberapa waktu sebelum Anda
roaima
1

Ada juga metode ini dari artikel berjudul: Perhitungan tanggal dan waktu sederhana dalam BASH dari unix.com.

Fungsi-fungsi ini adalah kutipan dari skrip di utas itu!

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

Pemakaian

# 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
sumber
Ini tidak berfungsi dengan dateyang dikirimkan bersama macOS.
Flimm
Jika Anda menginstal gdate pada MacOS melalui minuman, ini dapat dengan mudah dimodifikasi agar berfungsi dengan mengubahnya datemenjadi gdate.
slm
1
Jawaban ini sangat membantu dalam kasus saya. Itu harus naik tingkat!
Mojtaba Rezaeian
1

jawaban spesifik

Karena saya suka mengurangi garpu dan mengizinkan banyak trik, ada tujuan saya:

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

Baik sekarang:

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

Ini akan mengisi kembali kedua variabel $todatedan $cond, menggunakan hanya satu garpu, dengan ouptut date -f -yang mengambil stdio untuk membaca satu tanggal per baris.

Akhirnya, Anda bisa memutus perulangan dengan

((todate>=cond))&&break

Atau sebagai fungsi :

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
}

Menggunakan built-in printf yang dapat membuat waktu tanggal dengan detik dari zaman (lihat man bash;-)

Script ini hanya menggunakan satu garpu.

Alternatif dengan garpu terbatas dan fungsi pembaca tanggal

Ini akan membuat subproses khusus (hanya satu garpu):

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

Ketika input dan output terbuka, entri fifo dapat dihapus.

Fungsi:

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

Nota Untuk mencegah garpu yang tidak berguna seperti todate=$(myDate 2013-07-18), variabel harus diatur oleh fungsi sendiri. Dan untuk mengizinkan sintaks gratis (dengan atau tanpa tanda kutip untuk datestring), nama variabel harus menjadi argumen terakhir .

Kemudian perbandingan tanggal:

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

dapat membuat:

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

jika di luar loop.

Atau gunakan fungsi bash shell-connector:

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

atau

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

(Wich tidak persis sama: .shmengandung skrip tes lengkap jika tidak bersumber)

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
sumber
Memprofilkan bash mengandung demonstrasi yang baik untuk bagaimana menggunakan date -f -dapat mengurangi kebutuhan sumber daya.
F. Hauri
0

tanggal adalah string, bukan bilangan bulat; Anda tidak dapat membandingkannya dengan operator aritmatika standar.

satu pendekatan yang dapat Anda gunakan, jika separator dijamin -:

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

Ini bekerja sejauh bash 2.05b.0(1)-release.

Josh McGee
sumber
0

Anda juga dapat menggunakan fungsi bawaan mysql untuk membandingkan tanggal. Ini memberikan hasil dalam 'hari'.

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

Cukup masukkan nilai $DBUSERdan $DBPASSWORDvariabel sesuai dengan milik Anda.

Abid Khan
sumber
0

FWIW: pada Mac, sepertinya memberi makan hanya tanggal seperti "2017-05-05" menjadi tanggal akan menambahkan waktu saat ini ke tanggal dan Anda akan mendapatkan nilai yang berbeda setiap kali Anda mengubahnya menjadi waktu zaman. untuk mendapatkan waktu zaman yang lebih konsisten untuk tanggal tetap, sertakan nilai dummy untuk jam, menit, dan detik:

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

Ini mungkin keanehan terbatas pada versi tanggal yang dikirimkan bersama macOS .... diuji pada macOS 10.12.6.

s84
sumber
0

Echoing @Gilles answer ("Operator -ge hanya bekerja dengan bilangan bulat"), berikut adalah contohnya.

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

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

datetimekonversi integer menjadi epoch, per jawaban @ mike-q di 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"lebih besar dari (lebih baru dari) "$old_date", tetapi ungkapan ini salah mengevaluasi sebagai False:

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

... ditampilkan secara eksplisit, di sini:

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

Perbandingan bilangan bulat:

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

Alasan mengapa perbandingan ini tidak bekerja dengan baik dengan string tanggal ditulis dgn tanda penghubung adalah shell mengasumsikan angka dengan 0 terkemuka adalah oktal, dalam hal ini bulan "07". Ada berbagai solusi yang diusulkan tetapi cara tercepat dan termudah adalah menghapus tanda hubung. Bash memiliki fitur substitusi string yang membuat ini cepat dan mudah maka perbandingan dapat dilakukan sebagai ekspresi aritmatika:

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

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