How many Friday the 13ths are there in a given year?
I'm studying Unix, and i need help writing a script. The script requires that it outputs the number of months and years from January 2007 to December 2017 inclusive that have a Friday the thirteenth.
I generally don't help people with homework assignments (I get quite a few requests every week) but this one is pretty interesting, all in all, so let's have a shot at trying to figure out how to solve this problem with a Unix shell script.
The first step is to decide how complicated to make it: we can either use cal and parse the results or, if we're lucky, we'll have a version of the command-line date command that can take arbitrary "epoch values" and turn them into date strings. An epoch value, by the way, is the core date unit in Unix, the number of seconds since the beginning of "Unix time". That value? Well, we can let the command tell us:
$ date -r0
So one way to solve this problem is to simply feed the "date" command epoch values for each day in the date range we need, testing each to see if it's a "Friday" and a "13".
The only hard part is figuring out the epoch date for, say, high noon on January 1st, 2007.
Here's how I'd solve that:
$ date Sat Apr 28 14:35:55 CDT 2007 $ date +%s 1177788958 Now we know that epoch date 1177788958 = 28 April, 2007 at 2:23pm $ expr 60 \* 60 \* 24 86400 That's seconds in a day, the basic value to step from one day to another $ date -r `expr 1177788958 - 86400` Fri Apr 27 14:35:58 CDT 2007 Proof: you can see we now see the exact same time of day, one day earlier. Now, we can also use "date" to figure out how many days into the year we are: $ date +%j 118 Now, finally, we can just subtract 118 * 88400 to get to January 1st: $ date -r `expr 1177702558 - 10195200` Sat Dec 30 13:35:58 CST 2006 Nope. I'm guessing it's a timezone issue, so we can just recalculate... $ expr 86400 \* 116 10022400 $ expr 1177702558 - 10022400 1167680158 $ date -r 1167680158 Mon Jan 1 13:35:58 CST 2007
Okay, so that's the tough part figured out. We now know the epoch date for 1 January, 2007, mid-day. It's not at noon, but since we'll be at exactly the same time of day for every day it'll work just fine...
Now the script is actually pretty straightforward, believe it or not. As a first stab, let's just see if we can produce a week's worth of dates in a loop:
starton=1167680158 # 1 jan, 2007, midday oneday=86400 daystotest=7 # test one week to start span=$(( $oneday * $daystotest )) endon=$(( $starton + $span )) theday=$starton while [ $theday -le $endon ]; do date -r $theday theday=$(( $theday + $oneday )) done
Let's run those settings:
$ sh fridays.sh Mon Jan 1 13:35:58 CST 2007 Tue Jan 2 13:35:58 CST 2007 Wed Jan 3 13:35:58 CST 2007 Thu Jan 4 13:35:58 CST 2007 Fri Jan 5 13:35:58 CST 2007 Sat Jan 6 13:35:58 CST 2007 Sun Jan 7 13:35:58 CST 2007 Mon Jan 8 13:35:58 CST 2007
Good. Now, let's do another interim solution by counting how many Fridays there are. This we can do by just modifying the while loop:
while [ $theday -le $endon ] do if [ ! -z "$(date -r $theday | grep "^Fri ")" ] ; then echo "Match: $(date -r $theday)" matches=$(( $matches + 1 )) fi theday=$(( $theday + $oneday )) done
Now this time I'll run for 60 days by changing "daystotest" to 60, and run it:
$ sh fridays.sh Match: Fri Jan 5 13:35:58 CST 2007 Match: Fri Jan 12 13:35:58 CST 2007 Match: Fri Jan 19 13:35:58 CST 2007 Match: Fri Jan 26 13:35:58 CST 2007 Match: Fri Feb 2 13:35:58 CST 2007 Match: Fri Feb 9 13:35:58 CST 2007 Match: Fri Feb 16 13:35:58 CST 2007 Match: Fri Feb 23 13:35:58 CST 2007 Match: Fri Mar 2 13:35:58 CST 2007 Matches: 9
Good!! So now let's add one more step: a test for "13" in the grep:
while [ $theday -le $endon ] do if [ ! -z "$(date -r $theday | grep -E "^Fri.* 13 ")" ] ; then echo "Match: $(date -r $theday)" matches=$(( $matches + 1 )) fi theday=$(( $theday + $oneday )) done
As you can see, I'm using a bit of a fancy regular expression (hence the addition of the "-E" flag) to test for lines that start with "Fri" and contain space-13-space. Now we'll run this for the entire year of 2007 by changing "daystotest" to 365, and...
$ sh fridays.sh Match: Fri Apr 13 14:35:58 CDT 2007 Match: Fri Jul 13 14:35:58 CDT 2007 Matches: 2
Just about done. Now we just need to figure out how many days are in 10 years. Hopefully you can do that in your head! 3650 days:
sh fridays.sh Match: Fri Apr 13 14:35:58 CDT 2007 Match: Fri Jul 13 14:35:58 CDT 2007 Match: Fri Jun 13 14:35:58 CDT 2008 Match: Fri Feb 13 13:35:58 CST 2009 Match: Fri Mar 13 14:35:58 CDT 2009 Match: Fri Nov 13 13:35:58 CST 2009 Match: Fri Aug 13 14:35:58 CDT 2010 Match: Fri May 13 14:35:58 CDT 2011 Match: Fri Jan 13 13:35:58 CST 2012 Match: Fri Apr 13 14:35:58 CDT 2012 Match: Fri Jul 13 14:35:58 CDT 2012 Match: Fri Sep 13 14:35:58 CDT 2013 Match: Fri Dec 13 13:35:58 CST 2013 Match: Fri Jun 13 14:35:58 CDT 2014 Match: Fri Feb 13 13:35:58 CST 2015 Match: Fri Mar 13 14:35:58 CDT 2015 Match: Fri Nov 13 13:35:58 CST 2015 Match: Fri May 13 14:35:58 CDT 2016 last day tested was Fri Dec 30 13:35:58 CST 2016 Matches: 18
As you can see, I added a test to see the last day we tested, because we need to take into account leapyears. Not bad, though, we're only off one day in ten years, so I'll readjust it to 3651, but since that penultimate day is Friday, the 31st of December, 2016 can't be a Friday the 13th, so I'll stick with 18 as the answer.
Just for completeness, here's the full script "fridays.sh" for you:
#!/bin/sh # count occurences of Friday the 13th in the next four years. starton=1167680158 # 1 jan, 2007, midday oneday=86400 daystotest=3651 # a decade, compensating for leap years matches=0 span=$(( $oneday * $daystotest )) endon=$(( $starton + $span )) theday=$starton while [ $theday -le $endon ] do if [ ! -z "$(date -r $theday | grep -E "^Fri.* 13 ")" ] ; then echo "Match: $(date -r $theday)" matches=$(( $matches + 1 )) fi theday=$(( $theday + $oneday )) done echo "last day tested was $(date -r $theday)" echo "Matches: $matches" exit 0
There ya go. 18 Friday the thirteenths in ten years means, logically, that they occur, on average, 1.8 times per year. Next, we can try to figure out how often a blue moon occurs! (actually, blue moons are the second full moon in a month, so we could actually solve that one as a shell script. But I'll skip that for now)
More Useful Shell Script Programming Articles:
✔ Secretly capture screenshots on my Mac?
When I used to work on a Linux system, there was a utility we had that would let me take screen captures every...✔ Parsing "id" strings in a Shell Script?
Hello Dave. I need a Bash shell script that creates a directories with the group names automatically when user logs in to the...✔ Copy and Paste from the Mac OS X Command Line?
I am constantly running commands in Terminal.app on my MacBook and then copying and pasting the results into email messages or documents. Yes,...✔ Script to test line lengths for Twitter compatibility?
I've been tasked with writing a series of tweets for a Black Friday marketing campaign and am finding it a bit tricky because...✔ Shell script to convert lowercase to title case?
As part of a project I'm working on, I find myself deep in a Linux shell script, needing to have a subroutine that...
Let's stay in touch!
Sign up for my weekly AskDaveTaylor Newsletter and you'll receive even more tech and gadget help right to your inbox, along with exclusive news and industry updates. It's good stuff. I promise!
I do have a comment, now that you mention it!
Check This Out Too...
Look for Answers
All Our Categories
Apple iPad Help
Articles and Reviews
Auctions and Online Shopping
Blogs and Blogging
Building Web Site Traffic
Business and Management
Computer and Internet Basics
d) None of the Above
Google Gmail Help
Google Plus Help
Industry News and Trade Shows
iPhone and Cell Phone Help
iPod, Sony PSP and MP3 Player Help
Kindle Fire Help
Mac OS X Help
Pay Per Click (PPC) Advertising
Search Engine Optimization (SEO)
Shell Script Programming
Tech Support Video Help
The Writing Business
Twitter, LinkedIn and Social Network Help
Unix and Linux Help
Video Game Tips and Help
Windows PC Help
Find Me on Google+
ADT on G+