Doing Date Math on the Command Line, Part I

If you’ve ever used a spreadsheet, you’ve probably used or seen
functions for doing date math—in other words, taking one date and adding some number
of days or months to it to get a new date, or taking two
dates and finding the number days between them.
The same thing can be done from the command line using
the lowly date command, possibly with a little
help from Bash’s arithmetic.

The two most important features of the date command to
understand for doing date math are the +FORMAT option
and the –date option.
The formatting option allows you to format the date in different ways
or get just certain parts of the date, such as the year,
the month, the day and so on:

$ date ‘+%Y’
2018
$ date ‘+%m’
09
$ date ‘+%d’
16

There are a few dozen formatting options; see the date(1)
man page for more.

The second option, the –date (or -d) option, allows you to specify
the date to use, rather than the current date:

$ date –date ‘March 1, 2015’ “+%Y”
2015
$ date –date=’March 1, 2015′ “+%m”
03
$ date -d ‘March 1, 2015’ “+%d”
01

The interesting part of the –date option is that it allows you to
specify date strings in a variety of ways. For example, given the
above date of “March 1, 2015”, you can calculate dates relative to it:

$ date –date ‘March 1, 2015 +7 days’
Sun Mar 8 00:00:00 MST 2015
$ date –date ‘March 1, 2015 -1 year’
Sat Mar 1 00:00:00 MST 2014
$ date –date ‘March 1, 2015 +12 days +12 hours +15 minutes’
Fri Mar 13 12:15:00 MST 2015

You also can use strings that refer to previous or upcoming days
of the week (relative to the current date):

$ date –date ‘last Monday’
Mon Sep 10 00:00:00 MST 2018
$ date –date ‘next Monday’
Mon Sep 17 00:00:00 MST 2018

Note, however, that combining a date and things like “next Monday” does not work.
The date(1) man page has a bit more information about the types
of date strings you can specify, however, the GNU Info date page
has much more complete documentation.

So now that you’ve seen the basics of doing date math, let’s use it
to do something.
This example comes from some recent scripts I was working on
while organizing Linux Journal’s online archives.
Each month we publish a new issue.
Each issue has an issue number that’s one more than the previous issue.
Issue numbers are easy to deal with when specifying arguments
to a script, but readers tend to think more in terms of months and years.
So in a couple places I needed to generate months and years from issue numbers.

To not complicate the example, let’s ignore a couple breaks in
LJ‘s publishing history.
So, let’s use issue #284 from March 2018 as the starting point
and make all calculations relative to it.
The script takes one or more issue numbers and prints out the month
and year corresponding to that issue number:

$ cat date1.sh
1 for inum in $*
2 do
3 [[ “$inum” =~ ^[0-9]$ ]] || { echo “Invalid issue number: $inum”; continue; }
4
5 month=$(date –date=”March 1, 2018 +$((inum – 284)) month” +’%B’)
6 year=$(date –date=”March 1, 2018 +$((inum – 284)) month” +’%Y’)
7 printf “Issue %d is from %s, %sn” $inum $month $year
8 done

$ bash date1.sh 284 285
Issue 284 is from March, 2018
Issue 285 is from April, 2018

Lines 5 and 6 calculate the number of issues
that have elapsed since issue 284 using Bash’s arithmetic
expression syntax: $((inum – 284)).
That result then is used to add that many months onto the
date of the first issue, which gives the date
of the issue in question.
Then the date command’s format options provides the month and year
(the %B and %Y format specifiers, respectively).
Those values next are used to print out the month and year of the issue.
And just in case you were wondering, issue #1000 will be coming out
in November 2077.

One thing that can you trip you up when doing date math is that
if you use dates near the end of the month when adding or
subtracting months, you can end up with unexpected results.
This is because the date command uses the same value for the
length of each month when it adds months and ignores the fact
that not all months are the same length.
So, for example, look what happens if you change the reference
date in the script above from the start of the month to the end of the month:

$ cat date2.sh
5 month=$(date –date=”March 31, 2018 +$((inum – 284)) month” +’%B’)
6 year=$(date –date=”March 31, 2018 +$((inum – 284)) month” +’%Y’)

$ bash date2.sh 284 285
Issue 284 is from March, 2018
Issue 285 is from May, 2018

This incorrectly labels issue 285 as being in May rather
than April.
You can verify this if you run the effective date command
by itself:

$ date –date=”March 31, 2018 +1 month”
Tue May 1 00:00:00 MST 2018

Since April has only 30 days, you end up skipping to the first of May.

The other common type of date math that I mentioned above is calculating
the difference between two dates.
Unfortunately, the date command doesn’t support this directly.
To do that, you need to convert the dates in question into the number
of seconds since the epoch (using the %s format specifier),
and then subtract the values and divide by the number of seconds
in a day (86,400) to get the days in between:

$ cat date3.sh
start_date=”March 1, 2018″
end_date=”March 31, 2018″

sdate=$(date –date=”$start_date” ‘+%s’)
edate=$(date –date=”$end_date” ‘+%s’)
days=$(( (edate – sdate) / 86400 ))
echo “$days days between $start_date and $end_date”

$ bash date3.sh
30 days between March 1, 2018 and March 31, 2018

In part two of this article, I plan to tackle how to get a
specific previous day of the week relative to a specific date
(which, as I noted above, is not directly doable with the date command).
In other words, I want to be able to do something that effectively
does the following:

$ date –date ‘March 1, 2015 previous Monday’ # won’t work
date: invalid date ‘March 1 2015 previous Monday’

Source

Leave a Reply

Your email address will not be published. Required fields are marked *

WP2Social Auto Publish Powered By : XYZScripts.com