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?


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"


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?


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!


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

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.


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

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"

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
}

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.


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.


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.


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

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.