111

A="2002-20-10"
B="2003-22-11"

How to find the difference in days between two dates?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Tree
  • 9,532
  • 24
  • 64
  • 83
  • 10
    As explained below, but your dates are the wrong way round: they need to be yyyy-mm-dd – Peter Flynn Nov 08 '17 at 09:12
  • Many answers use the -d option of (GNU-)date. I want to add that this is NOT a part of POSIX date, therefore less portable. As long as the OP is not working on Unix distributions like Solaris, but only "common Linux", he or she should be good tho. A respective tag would help imho. – Cadoiz Jun 04 '20 at 17:45

26 Answers26

82

The bash way - convert the dates into %y%m%d format and then you can do this straight from the command line:

echo $(( ($(date --date="031122" +%s) - $(date --date="021020" +%s) )/(60*60*24) ))
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
techjacker
  • 1,348
  • 2
  • 13
  • 13
  • 11
    This doesn't necessarily need the dates to be in `%y%m%d` format. E.g. gnu date will accept the `%Y-%m-%d` format the OP used. In general, ISO-8601 is a good choice (and the OP's format is one such format). Formats with 2 digit years are better avoided. – mc0e Jun 16 '15 at 11:52
  • 8
    For the difference from **today**, one may like to use `days_diff=$(( (\`date -d $B +%s\` - \`date -d "00:00" +%s\`) / (24*3600) ))`. note that `$days_diff` will be an integer (i.e. no decimals) – Julien Jan 07 '16 at 10:41
  • 1
    This is dependent on the variant of `date` installed. It works for GNU `date`. It doesn't work with POSIX `date`. This probably isn't a problem for most readers, but it's worth noting. – mc0e Mar 01 '18 at 15:01
  • If you really want POSIX portability, check this: https://unix.stackexchange.com/a/7220/43835 – mc0e Mar 01 '18 at 15:05
  • 3
    Just to combat confusion: OP's format is **not** `%Y-%m-%d`, until we introduce `August 2.0` and `October 2.0` OP's format is `%Y-%d-%m`. Relevant xkcd: https://xkcd.com/1179/ – confetti Aug 14 '19 at 17:56
  • This answer does not work on macOS with Bash 5.1: `date: illegal option -- - [...] bash: ( - )/(60*60*24) : syntax error: operand expected (error token is ")/(60*60*24) ")` – Marlon Richert Jun 10 '21 at 08:20
  • On 32 bit Linux, this does not work for dates > Epochalypse. – weberjn Feb 26 '23 at 17:32
56

If you have GNU date, it allows to print the representation of an arbitrary date (-d option). In this case convert the dates to seconds since EPOCH, subtract and divide by 24*3600.

Example using GNU date (from https://stackoverflow.com/a/9008871/215713):

let DIFF=($(date +%s -d 20210131)-$(date +%s -d 20210101))/86400
echo $DIFF
30

This also works with dates in other formats, for example "2021-01-31".

Other answers suggest ways to do it that don't require GNU date.

rfay
  • 9,963
  • 1
  • 47
  • 89
  • 1
    @Tree: I believe there is no portable way to subtract dates short of implementing it yourself — it's not that hard, the only thing of interest are leap years. But nowadays everyone wants to subtract dates, as it seems: Have a look at http://unix.stackexchange.com/questions/1825/tool-in-unix-to-subtract-dates :) –  Feb 09 '11 at 20:29
  • 18
    For lazyweb's sake, I think this is what user332325 meant: ```echo "( `date -d $B +%s` - `date -d $A +%s`) / (24*3600)" | bc -l ``` – Pablo Mendes May 06 '15 at 16:43
  • 2
    What does portable mean in this context? – htellez Feb 20 '16 at 00:22
  • 1
    @htellez something that works on multiple platforms (windows, linux etc..) – Anubis Feb 28 '18 at 12:50
  • For bash one liner based on this answer see: https://stackoverflow.com/a/49728059/1485527 – jschnasse Apr 09 '18 at 08:08
  • @Sumudu and @ htellez I think, user332325 means that the `-d` option of (GNU-)`date` is NOT a part of POSIX date, therefore less portable. As long as the OP is not working on Unix distributions like Solaris, but only "common Linux", this works. If not, check user332325's link above! @ jschnasse's answer also uses `date -d` – Cadoiz Jun 04 '20 at 17:54
  • This answer does not work on macOS in Bash 5.1: `date: illegal time format [...] bash: let: DIFF=(-)/86400: syntax error: operand expected (error token is ")/86400")` – Marlon Richert Jun 10 '21 at 08:18
34

tl;dr

date_diff=$(( ($(date -d "2015-03-11 UTC" +%s) - $(date -d "2015-03-05 UTC" +%s)) / (60*60*24) ))

Watch out! Many of the bash solutions here are broken for date ranges which span the date when daylight savings time begins (where applicable). This is because the $(( math )) construct does a 'floor'/truncation operation on the resulting value, returning only the whole number. Let me illustrate:

DST started March 8th this year in the US, so let's use a date range spanning that:

start_ts=$(date -d "2015-03-05" '+%s')
end_ts=$(date -d "2015-03-11" '+%s')

Let's see what we get with the double parentheses:

echo $(( ( end_ts - start_ts )/(60*60*24) ))

Returns '5'.

Doing this using 'bc' with more accuracy gives us a different result:

echo "scale=2; ( $end_ts - $start_ts )/(60*60*24)" | bc

Returns '5.95' - the missing 0.05 being the lost hour from the DST switchover.

So how should this be done correctly?
I would suggest using this instead:

printf "%.0f" $(echo "scale=2; ( $end_ts - $start_ts )/(60*60*24)" | bc)

Here, the 'printf' rounds the more accurate result calculated by 'bc', giving us the correct date range of '6'.

Edit: highlighting the answer in a comment from @hank-schultz below, which I have been using lately:

date_diff=$(( ($(date -d "2015-03-11 UTC" +%s) - $(date -d "2015-03-05 UTC" +%s) )/(60*60*24) ))

This should also be leap second safe as long as you always subtract the earlier date from the later one, since leap seconds will only ever add to the difference - truncation effectively rounds down to the correct result.

x-yuri
  • 16,722
  • 15
  • 114
  • 161
evan_b
  • 1,079
  • 1
  • 11
  • 22
  • 9
    If instead, you specify the timezone for both the start and the end (and make sure they are the same), then you also no longer have this problem, like so: `echo $(( ($(date --date="2015-03-11 UTC" +%s) - $(date --date="2015-03-05 UTC" +%s) )/(60*60*24) ))`, which returns 6, instead of 5. – Hank Schultz Jun 23 '15 at 20:40
  • 1
    Ah, one of the answerers thought about the inaccuracies of the basic solution repeated by others in variants. Thumbs up! How about leap seconds? It is leap-second-safe? There are precedents of software returning off-by-one-**day** result if run at certain times, ref [problems associated with the leap second](https://en.wikipedia.org/wiki/Leap_second#Examples_of_problems_associated_with_the_leap_second). – Stéphane Gourichon Nov 20 '15 at 12:29
  • Woops, the "wrong" computation you give as example correctly returns 6 here (Ubuntu 15.04 AMD64, GNU date version 8.23). Perhaps your example is timezone-dependant? My timezone is "Europe/Paris". – Stéphane Gourichon Nov 20 '15 at 12:34
  • Same good result for the "wrong" computation with GNU date 2.0 on MSYS, same timezone. – Stéphane Gourichon Nov 20 '15 at 12:35
  • 1
    @StéphaneGourichon, your timezone's daylight savings change looks to have happened on March 29, 2015 - so try a date range which includes that. – evan_b Dec 04 '15 at 22:22
  • @evan_b you're right. Replacing 11 with 30 yields 24 days with integer arithmetic, 24.95 with bc, and an error with printf because locale uses a comma for decimal separator instead of dot. Prefixing with `LC_ALL=C` (with bash, might be different with other shells) yields the correct number 25. – Stéphane Gourichon Dec 05 '15 at 22:16
  • This answer does not work on macOS with Bash 5.1: `bash: ( - ) / (60*60*24) : syntax error: operand expected (error token is ") / (60*60*24) ")` – Marlon Richert Jun 10 '21 at 08:21
21

And in python

$python -c "from datetime import date; print (date(2003,11,22)-date(2002,10,20)).days"
398
Fred Laurent
  • 1,537
  • 1
  • 12
  • 14
  • 2
    @mouviciel not really. Python is not always present. – mc0e Jun 16 '15 at 11:57
  • 2
    @mc0e in that context, nothing is always present – Anubis Feb 28 '18 at 12:53
  • 8
    @Anubis the OP specified tags as `bash` and `shell`, so I think we can assume that we're talking about a *nix system. Within such systems, `echo` and `date` are reliably present, but python is not. – mc0e Mar 01 '18 at 14:53
  • 2
    @mc0e yeah that makes sense. Even-though python2 is available in most *nix systems, when it comes to low-end devices (embedded etc..) we will have to survive only with primary tools. – Anubis Mar 02 '18 at 10:06
20

This works for me:

A="2002-10-20"
B="2003-11-22"
echo $(( ($(date -d $B +%s) - $(date -d $A +%s)) / 86400 )) days

Prints

398 days

What is happening?

  1. Provide valid time string in A and B
  2. Use date -d to handle time strings
  3. Use date %s to convert time strings to seconds since 1970 (unix epoche)
  4. Use bash parameter expansion to subtract seconds
  5. divide by seconds per day (86400=60*60*24) to get difference as days
  6. ! DST is not taken into account ! See this answer at unix.stackexchange!
jschnasse
  • 8,526
  • 6
  • 32
  • 72
  • This answer does not work on macOS with Bash 5.1: `bash: ( - ) / 86400 : syntax error: operand expected (error token is ") / 86400 ")` – Marlon Richert Jun 10 '21 at 08:22
  • Sorry to hear that. Maybe remove the whitespace to `)/86400` does fix it? – jschnasse Jun 10 '21 at 18:24
  • Sorry, it appears I pasted the wrong error message. The real problem is that on macOS, `date -d` sets the kernel's value for daylight saving time. It does not accept a date in that position. – Marlon Richert Jun 16 '21 at 20:30
  • Maybe use `-jf` instead? If it works, I would add that variant to my answer. Initally I posted my answer because most of the others didn´t explain why they use certain parameters. Also the info about DST I referenced in the link was not prominent available. So my claim is not to provide a plattform independent solution. But together we can work towards that. – jschnasse Jun 17 '21 at 06:28
  • Concerning MacOS and gdate: coreutils is in [Homebrew](https://brew.sh/). Just install `brew install coreutils` and you've got gdate. Maybe worth a try. – jschnasse Jun 17 '21 at 12:26
11

Here's the MacOS X / *BSD version for your convenience.

$ A="2002-20-10"; B="2003-22-11";
$ echo $(((`date -jf %Y-%d-%m "$B" +%s` - `date -jf %Y-%d-%m "$A" +%s`)/86400))

nJoy!

nickl-
  • 8,417
  • 4
  • 42
  • 56
  • 1
    -bash: ( - )/86400: syntax error: operand expected (error token is ")/86400") – cavalcade Nov 01 '16 at 00:56
  • 1
    @cavalcade - that is because you haven't done `A="2002-20-10"; B="2003-22-11"` – RAM237 Jul 05 '17 at 13:43
  • Thanks, despite it works for this particular case, I would suggest using `echo $(((\`date -jf "%Y-%d-%m" "$B" +%s\` - \`date -jf "%Y-%d-%m" "$A" +%s\`)/86400))`, i.e. wrapping both formats and variables into double quotes, otherwise may fail on different date format (e.g. `%b %d, %Y`/`Jul 5, 2017`) – RAM237 Jul 05 '17 at 13:46
  • @RAM237 Thats what I get for not paying attention *headsmack* Well spotted, thanks – cavalcade May 16 '18 at 12:54
  • 1
    ... Y-d-m? Oh my God. I thought I'd never see the day. – hraban Oct 03 '21 at 15:42
7

If the option -d works in your system, here's another way to do it. There is a caveat that it wouldn't account for leap years since I've considered 365 days per year.

date1yrs=`date -d "20100209" +%Y`
date1days=`date -d "20100209" +%j`
date2yrs=`date +%Y`
date2days=`date +%j`
diffyr=`expr $date2yrs - $date1yrs`
diffyr2days=`expr $diffyr \* 365`
diffdays=`expr $date2days - $date1days`
echo `expr $diffyr2days + $diffdays`
Ruchi
  • 679
  • 3
  • 13
5

Even if you don't have GNU date, you'll probably have Perl installed:

use Time::Local;
sub to_epoch {
  my ($t) = @_; 
  my ($y, $d, $m) = ($t =~ /(\d{4})-(\d{2})-(\d{2})/);
  return timelocal(0, 0, 0, $d+0, $m-1, $y-1900);
}
sub diff_days {
  my ($t1, $t2) = @_; 
  return (abs(to_epoch($t2) - to_epoch($t1))) / 86400;
}
print diff_days("2002-20-10", "2003-22-11"), "\n";

This returns 398.041666666667 -- 398 days and one hour due to daylight savings.


The question came back up on my feed. Here's a more concise method using a Perl bundled module

days=$(perl -MDateTime -le '
    sub parse_date { 
        @f = split /-/, shift;
        return DateTime->new(year=>$f[0], month=>$f[2], day=>$f[1]); 
    }
    print parse_date(shift)->delta_days(parse_date(shift))->in_units("days");
' $A $B)
echo $days   # => 398
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
3

Here's my working approach using zsh. Tested on OSX:

# Calculation dates
## A random old date
START_DATE="2015-11-15"
## Today's date
TODAY=$(date +%Y-%m-%d)

# Import zsh date mod
zmodload zsh/datetime

# Calculate number of days
DIFF=$(( ( $(strftime -r %Y-%m-%d $TODAY) - $(strftime -r %Y-%m-%d $START_DATE) ) / 86400 ))
echo "Your value: " $DIFF

Result:

Your value:  1577

Basically, we use strftime reverse (-r) feature to transform our date string back to a timestamp, then we make our calculation.

Olivier B.
  • 91
  • 2
2

Using mysql command

$ echo "select datediff('2013-06-20 18:12:54+08:00', '2013-05-30 18:12:54+08:00');"  | mysql -N

Result: 21

NOTE: Only the date parts of the values are used in the calculation

Reference: http://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_datediff

bruce
  • 425
  • 3
  • 3
2

This is the simplest i managed to get working on centos 7:

OLDDATE="2018-12-31"
TODAY=$(date -d $(date +%Y-%m-%d) '+%s')
LINUXDATE=$(date -d "$OLDDATE" '+%s')
DIFFDAYS=$(( ($TODAY - $LINUXDATE) / (60*60*24) ))

echo $DIFFDAYS
Gotxi
  • 41
  • 1
2

Command line bash solution

echo $((($(date +%s -d 20210131)-$(date +%s -d 20210101))/86400)) days

will print

30 days
Anatolii Shuba
  • 4,614
  • 1
  • 16
  • 17
1

Use the shell functions from http://cfajohnson.com/shell/ssr/ssr-scripts.tar.gz; they work in any standard Unix shell.

date1=2012-09-22
date2=2013-01-31
. date-funcs-sh
_date2julian "$date1"
jd1=$_DATE2JULIAN
_date2julian "$date2"
echo $(( _DATE2JULIAN - jd1 ))

See the documentation at http://cfajohnson.com/shell/ssr/08-The-Dating-Game.shtml

jaypal singh
  • 74,723
  • 23
  • 102
  • 147
1

on unix you should have GNU dates installed. you do not need to deviate from bash. here is the strung out solution considering days, just to show the steps. it can be simplified and extended to full dates.

DATE=$(echo `date`)
DATENOW=$(echo `date -d "$DATE" +%j`)
DATECOMING=$(echo `date -d "20131220" +%j`)
THEDAY=$(echo `expr $DATECOMING - $DATENOW`)

echo $THEDAY 
mattymik
  • 11
  • 3
1

This assumes that a month is 1/12 of a year:

#!/usr/bin/awk -f
function mktm(datespec) {
  split(datespec, q, "-")
  return q[1] * 365.25 + q[3] * 365.25 / 12 + q[2]
}
BEGIN {
  printf "%d\n", mktm(ARGV[2]) - mktm(ARGV[1])
}
Zombo
  • 1
  • 62
  • 391
  • 407
1

I'd submit another possible solution in Ruby. Looks like it's the be smallest and cleanest looking one so far:

A=2003-12-11
B=2002-10-10
DIFF=$(ruby -rdate -e "puts Date.parse('$A') - Date.parse('$B')")
echo $DIFF
GreyCat
  • 16,622
  • 18
  • 74
  • 112
  • 2
    He is looking for a way in bash. – Cojones Mar 07 '12 at 11:26
  • 2
    There is no portable way to do it in shell itself. All alternatives use particular external programs (i.e. GNU `date` or some scripting language) and I honestly think that Ruby is a *good* way to go here. This solution is very short and does not use any non-standard libraries or other dependencies. In fact, I think that there's a higher chance of having Ruby installed than one would have GNU date installed. – GreyCat Mar 08 '12 at 21:14
1

If you have datediff command (for example dateutils package in some Linux distributions),

datediff 2003-11-22 2002-10-20

This gives an integer as number of days (398). The input should conform to some standard date like ISO format: YYYY-MM-DD

Yusuf N
  • 311
  • 1
  • 2
1

Another Python version:

python -c "from datetime import date; print date(2003, 11, 22).toordinal() - date(2002, 10, 20).toordinal()"
Ulf
  • 19
  • 1
0

Assume we rsync Oracle DB backups to a tertiary disk manually. Then we want to delete old backups on that disk. So here is a small bash script:

#!/bin/sh

for backup_dir in {'/backup/cmsprd/local/backupset','/backup/cmsprd/local/autobackup','/backup/cfprd/backupset','/backup/cfprd/autobackup'}
do

    for f in `find $backup_dir -type d -regex '.*_.*_.*' -printf "%f\n"`
    do

        f2=`echo $f | sed -e 's/_//g'`
        days=$(((`date "+%s"` - `date -d "${f2}" "+%s"`)/86400))

        if [ $days -gt 30 ]; then
            rm -rf $backup_dir/$f
        fi

    done

done

Modify the dirs and retention period ("30 days") to suit your needs.

Community
  • 1
  • 1
Oleksii Cherkas
  • 351
  • 3
  • 4
  • Oracle puts backup sets in Flash Recovery Area using format like 'YYYY_MM_DD' so we delete the underscores for passing it to 'date -d' – Oleksii Cherkas Feb 18 '13 at 08:52
0

For MacOS sierra (maybe from Mac OS X yosemate),

To get epoch time(Seconds from 1970) from a file, and save it to a var: old_dt=`date -j -r YOUR_FILE "+%s"`

To get epoch time of current time new_dt=`date -j "+%s"`

To calculate difference of above two epoch time (( diff = new_dt - old_dt ))

To check if diff is more than 23 days (( new_dt - old_dt > (23*86400) )) && echo Is more than 23 days

osexp2000
  • 2,910
  • 30
  • 29
0

Give this a try:

perl -e 'use Date::Calc qw(Delta_Days); printf "%d\n", Delta_Days(2002,10,20,2003,11,22);'
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
0
echo $(date +%d/%h/%y) date_today
echo " The other date is 1970/01/01"
TODAY_DATE="1970-01-01"
BEFORE_DATE=$(date +%d%h%y)
echo "THE DIFFERNS IS " $(( ($(date -d $BEFORE_DATE +%s) - $(date -d $TODAY_DATE +%s)) )) SECOUND

use it to get your date today and your sec since the date you want. if you want it with days just divide it with 86400

echo $(date +%d/%h/%y) date_today
echo " The other date is 1970/01/01"
TODAY_DATE="1970-01-01"
BEFORE_DATE=$(date +%d%h%y)
echo "THE DIFFERNS IS " $(( ($(date -d $BEFORE_DATE +%s) - $(date -d $TODAY_DATE +%s))/ 86400)) SECOUND
Dulaj Kulathunga
  • 1,248
  • 2
  • 9
  • 19
0

Instead of hard coding the expiration to a set amount of days when generating self-signed certificates, I wanted to have them expire just before the Y2k38 problem kicks in (on 19 January 2028). But, OpenSSL only allow the expiration to be set using -days, which is the number of days from the current date. I ended up using this:

openssl req -newkey rsa -new -x509 \
  -days $((( $((2**31)) - $(date +%s))/86400-1)) \
  -nodes -out new.crt -keyout new.key -subj '/CN=SAML_SP/'
0

for zsh (Only works if dates in the same year)

echo $(($(date -d "today" +%j) - $(date -d "08 Jan 2021" +%j) )) days
zzapper
  • 4,743
  • 5
  • 48
  • 45
0

Using postgresql psql command

$ echo "select '2003-2-11'::date - '2002-2-10'::date;" | psql -A -t

Result: 366


If you want to display the accompanying unit of time in words, you could use the age function:
$ echo "select age('2003-2-11'::date, '2002-2-10'::date);" | psql -A -t

Result: 1 year 1 day

justsomeguy
  • 513
  • 4
  • 11
0
pip install diffdate
diffdate 10-20-2002 11-22-2003
Fractale
  • 1,503
  • 3
  • 19
  • 34