
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)
Help others find this article at Del.icio.us, Digg, Netscape, Reddit, and Simpy.
Categorized:
Shell Script Programming
(Article 7339)
Tagged: date math, scripting, shell script programming Previous: How can I display hidden files in Windows XP? Next: Are MySpace links to msplinks.com spyware? Subscribe!
Never miss another useful Q&A article again! Subscribe to AskDaveTaylor with Google Reader. Couldn't you speed up the program and make it less processor intensive by starting on the first Friday of 2007, then advancing by a weekly interval instead of a daily interval (or changing the interval from daily to weekly the first time a Friday is hit)? Posted by: Greg Bulmash at April 28, 2007 4:26 PMYou're right, Greg, that'd be a smarter way to solve this, far less processor intensive. As you say, simply step day by day until the first friday, then change the value of oneday from 60*60*24 to (oneday*7) so you have seconds in a week. That'd speed things up by, oh, 7x :-) Posted by: Dave Taylor at April 29, 2007 7:34 AMActually the Gregorian calendar repeats every 12 years, not 10. Not that it matters. Plus using PL/SQL on an Oracle database would be a better way to calculate it. I'm not just saying it because I am a PL/SQL programmer. But the average is 1.833. ; ) SQL> DECLARE PL/SQL procedure successfully completed. Elapsed: 00:00:00.02 Posted by: brian at May 1, 2007 7:46 AMI have a lot to say, but ...
I do have a comment, now that you mention it!
|
Search
Find just the answers you seek from among our 1700+ free tech support articles by using our Lijit search engine.
Help!
Subscribe to
Ask Dave Taylor!
Free Updates!
Sign up and get free weekly updates and special offers on books, seminars, workshops and more.
Articles and Reviews
Auctions and Online Shopping Blogs and RSS Feeds Building Web site traffic Business and Management Cell Phones and Mobile Phones CGI Scripts and Web Site Programming Computer and Internet Basics d) None of the Above HTML and CSS Mac OS X Help MySpace, Facebook, Twitter and Social Network Help Pay Per Click (PPC) Search Engine Optimization Shell Script Programming Sony PSP, MP3 Players, Etc. The Writing Business Unix and Linux Help Video Game Tips and Help Windows Help
Recent Entries
Join the List!
Book Links
|