How many days have elapsed? Our intrepid shell script programmer Dave Taylor looks at how to solve date-related calculations and demonstrates a script that calculates elapsed days between any specified day in the past and the current date.
In case you haven't been reading my last few columns or, perhaps, are working for the NSA and scanning this content to identify key phrases, like “back door” and “low-level vulnerability”, we're working on a shell script that calculates the number of days between a specified date in the past and the current date.
When last we scripted, the basic functionality was coded so that the script would calculate days from the beginning date to the end of that particular year, then the number of years until the current year (accounting for leap years), followed by the current number of days into the year. The problem is, it's not working.
Here's the state of things:
$ date Mon Jul 7 09:14:37 PDT 2014 $ sh daysago.sh 7 4 2012 The date you specified -- 7-4-2012 -- is valid. Continuing... 0 days transpired between end of 2012 and beginning of this year calculated 153 days left in the specified year Calculated that 7/4/2012 was 341 days ago.
The script correctly ascertains that the current date, July 7, 2014, is 153 days into the year, but the rest of it's a hopeless muddle. Let's dig in to the code and see what's going on!
The code in my last article was fairly convoluted in terms of calculating the number of days left in the starting year subsequent to the date specified (July 4, 2012, in the above example), but there's a far easier way, highlighted in this interaction:
$ date -j 0803120010 Tue Aug 3 12:00:00 PDT 2010 $ date -j 0803120010 +%j 215
In other words, modern date commands let you specify a date (in MON DAY HOUR MIN YEAR format) and then use the %j format notation to get the day-of-the-year for that specific date. August 3, 2010, was the 215th day of the year.
Did you try that, and find that date complained about the -j flag? Good. That means you're likely using GNU date, which is far superior and is actually something we'll need for the full script to work. Test which version you have by using the --version flag:
$ date --version date (GNU coreutils) 8.4 Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Written by David MacKenzie.
How many days were in a given year? That's also easily done with a shortcut, checking the day of the year of December 31st. For example:
$ date -d 12/31/2010 +%j 365
But, 2012 was a leap year. So:
$ date -d 12/31/2012 +%j 366
Therefore the days-left-in-year calculation is simply days-in-year – day-of-the-year.
The next calculation is days/year * years between the specified date and the current year.
The first step of calculating the days left in the starting year is to create the correct date format string for the date command. Fortunately, with GNU date, it's easily done:
# build date string format for the specified starting date startdatefmt="$startmon/$startday/$startyear" calculate="$(date -d "12/31/$startyear" +%j) - $(date -d ↪$startdatefmt +%j)" echo "Calculating $calculate" daysleftinyear=$(( $calculate ))
When run as part of the script, the debugging echo statement offers useful information to help debug things:
$ sh daysago.sh 2 4 2012 The date you specified -- 2-4-2012 -- is valid. Continuing... Calculating 366 - 035 There were 337 days left in the starting year $ sh daysago.sh 2 4 2013 The date you specified -- 2-4-2013 -- is valid. Continuing... Calculating 365 - 035 There were 336 days left in the starting year
Notice that when we specified Feb 4, 2012, a leap year, there are 337 days left in the year, but when we specify the same date on the following non-leapyear, there are 336 days. Sounds right!
The next portion of the calculation is to calculate the number of days in each year between the start year and the current year, not counting either of those. Sounds like a while loop to me, so let's do this:
daysbetweenyears=0 tempyear=$(( $startyear + 1 )) while [ $tempyear -lt $thisyear ] ; do echo "intervening year: $tempyear" daysbetweenyears=$(( $daysbetweenyears + $(date -d ↪"12/31/$tempyear" +%j) )) tempyear=$(( $tempyear + 1 )) done echo "Intervening days in years between $startyear and ↪$thisyear = $daysbetweenyears"
Again, I'm adding a debugging echo statement to clarify what's going on, but we're getting pretty close:
$ sh daysago.sh 2 4 2010 The date you specified -- 2-4-2010 -- is valid. Continuing... Calculating 365 - 035 Calculated that there were 336 days left in the starting year intervening year: 2011 intervening year: 2012 intervening year: 2013 intervening days in years between 2010 and 2014 = 1096
That seems to pass the reasonable test, as there are three years between 2010 and 2014 (namely 2011, 2012 and 2013) and the back-of-envelope calculation of 3*365 = 1095.
If you're using non-GNU date, you already have realized that the string format is different and that you need to use the -j flag instead of the -d flag. The problem is, the older date command also works differently, because 1969 is the beginning of Epoch time. Look:
$ date -j 0204120060 # last two digits are year, so '60 Wed Feb 4 12:00:00 PST 2060
It interprets the two-digit “60” as 2060, not 1960. Boo!
If you're not running GNU date, you're going to have a really big problem for dates prior to 1969, and I'm going to just say “get GNU date, dude” to solve it.
We have everything we need to do the math now. We can calculate the number of days left in the start year, the number of days in intervening years, and the day-of-year number of the current date. Let's put it all together:
### DAYS IN CURRENT YEAR dayofyear=$(date +%j) # that's easy! ### NOW LET'S ADD IT ALL UP totaldays=$(( $daysleftinyear + $daysbetweenyears + $dayofyear )) echo "$totaldays days have elapsed between ↪$startmon/$startday/$startyear and today, ↪day $dayofyear of $thisyear."
That's it. Now, stripping out the debug echo statements, here's what we can ascertain:
$ sh daysago.sh 2 4 1949 23900 days have elapsed between 2/4/1949 and today, ↪day 188 of 2014. $ sh daysago.sh 2 4 1998 6003 days have elapsed between 2/4/1998 and today, ↪day 188 of 2014. $ sh daysago.sh 2 4 2013 524 days have elapsed between 2/4/2013 and today, ↪day 188 of 2014.
But look, there's still a lurking problem when it's the same year that we're calculating:
$ sh daysago.sh 2 4 2014 524 days have elapsed between 2/4/2014 and today, ↪day 188 of 2014.
That's a pretty easy edge case to debug, however, so I'm going to wrap things up here and let you enjoy trying to figure out what's not quite right in the resultant script.
Stumped? Send me an e-mail via www.linuxjournal.com/contact.