76

How can two dates be compared in a shell?

Here is an example of how I would like to use this, though it does not work as-is:

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

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

How can I achieve the desired result?

11 Answers 11

88

The right answer is still missing:

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

if [ $todate -ge $cond ];
then
    break
fi  
  • 9
    Donno why someone downvoted this, it's pretty accurate and doesn't deal with concatenating ugly strings. Convert your date to unix timestamp (in seconds), compare unix timestamps. – Nicholi May 2 '15 at 23:16
  • 4
    This doesn't work with the date shipped with macOS. – Flimm Dec 8 '16 at 9:49
  • 7
    @Flimm With date on macOS, the equivalent is date -j -f "%F" 2014-08-19 +"%s". – Kusalananda Jan 30 '17 at 15:33
  • I think, me main advantage of this solution is, that you can do additions or subtractions easily. For example, I wanted to have a timeout of x seconds, but my first idea (timeout=$(`date +%Y%m%d%H%M%S` + 500)) is obviously wrong. – iGEL Jul 20 '18 at 9:59
  • 1
    date -d is non standard and does not work on a typical UNIX. On BSD it even attempts to set the kenel value DST.... – schily Dec 7 '18 at 10:13
28

You are missing the date format for the comparison:

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

You are missing looping structures too, how are you planning to get more dates?

  • This doesn't work with the date shipped with macOS. – Flimm Dec 8 '16 at 9:49
  • date -d is non standard and does not work on a typical UNIX. On BSD it even attempts to set the kenel value DST. – schily Dec 7 '18 at 10:13
22

Can use a standard string comparison to compare the chronological ordering [of strings in a year, month, day format].

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

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

Thankfully, when [strings that use the YYYY-MM-DD format] are sorted* in alphabetical order, they are also sorted* in chronological order.

(* - sorted or compared)

Nothing fancy needed in this case. yay!

  • Where is the else branch here? – Vladimir Despotovic Dec 15 '16 at 14:12
  • This is the only solution that worked for me on Sierra MacOS – Vladimir Despotovic Dec 15 '16 at 14:22
  • This is simpler that my solution of if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ]; … This date format was designed to be sorted using standard alpha numeric sort, or for - to be stripped and sorted numerically. – ctrl-alt-delor Apr 8 '17 at 17:59
7

This is not a problem of looping structures but of data types.

Those dates (todate and cond) are strings, not numbers, so you cannot use the "-ge" operator of test. (Remember that square bracket notation is equivalent to the command test.)

What you can do is use a different notation for your dates so that they are integers. For example:

date +%Y%m%d

will produce an integer like 20130715 for July 15th, 2013. Then you can compare your dates with "-ge" and equivalent operators.

Update: if your dates are given (e.g. you are reading them from a file in 2013-07-13 format) then you can preprocess them easily with tr.

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

The operator -ge only works with integers, which your dates aren't.

If your script is a bash or ksh or zsh script, you can use the < operator instead. This operator is not available in dash or other shells that don't go much beyond the POSIX standard.

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

In any shell, you can convert the strings to numbers while respecting the order of dates simply by removing the dashes.

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

Alternatively, you can go traditional and use the expr utility.

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

As invoking subprocesses in a loop can be slow, you may prefer to do the transformation using shell string processing constructs.

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

Of course, if you can retrieve the dates without the hyphens in the first place, you'll be able to compare them with -ge.

  • Where is the else branch in this bash? – Vladimir Despotovic Dec 15 '16 at 14:12
  • This seems to be the most correct answer. Very helpful! – Mojtaba Rezaeian Oct 21 '18 at 0:50
1

I convert the Strings into unix-timestamps (seconds since 1.1.1970 0:0:0). These can compared easily

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

There is also this method from the article titled: Simple date and time calulation in BASH from unix.com.

These functions are an excerpt from a script in that thread!

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"
  • This doesn't work with the date that ships with macOS. – Flimm Dec 8 '16 at 9:51
  • If you install gdate on MacOS via brew this can be easily modified to work by changing date to gdate. – slm Oct 8 '18 at 20:40
  • 1
    This answer was very helpful on my case. It should rate up! – Mojtaba Rezaeian Oct 21 '18 at 0:40
0

dates are strings, not integers; you can't compare them with standard arithmetic operators.

one approach you can use, if the separator is guaranteed to be -:

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

This works as far back as bash 2.05b.0(1)-release.

0

You can also use mysql's builtin function to compare the dates. It gives the result in 'days'.

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

Just insert the values of $DBUSER and $DBPASSWORD variables according to yours.

0

specific answer

As I like to reduce forks and do permit a lot of tricks, there is my purpose:

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

Well, now:

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

This will re-populate both variables $todate and $cond, using only one fork, with ouptut of date -f - wich take stdio for reading one date by line.

Finally, you could break your loop with

((todate>=cond))&&break

Or as a function:

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
}

Using 's builtin printf wich could render date time with seconds from epoch (see man bash;-)

This script only use one fork.

  • Profiling bash contain a good demonstration for how using date -f - could reduce ressources requirement. – F. Hauri Feb 13 '17 at 0:02
0

FWIW: on Mac, it seems feeding just a date like "2017-05-05" into date will append the current time to the date and you will get different value each time you convert it to epoch time. to get a more consistent epoch time for fixed dates, include dummy values for hours, minutes, and seconds:

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

This may be a oddity limited to the version of date that shipped with macOS.... tested on macOS 10.12.6.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.