Industry guru Dave Taylor offers tech support on technical and business topics, including iPhone, iPod, Microsoft Windows, Sony PSP, cellphones, online advertising, CSS, Web design, business, Unix, Linux, SEO, Mac OS X, and shell script programming.     


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.


Dave's Answer:

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
Wed Dec 31 18:00:00 CST 1969

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!
    Enter your name: and your email addr:  




Categorized: Shell Script Programming   (Article 7339, Written by )
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?




Reader Comments To Date: 7

Greg Bulmash said, on April 28, 2007 4:26 PM:

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)?

Dave Taylor said, on April 29, 2007 7:34 AM:

You'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 :-)

brian said, on May 1, 2007 7:46 AM:

Actually 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
v_current_date DATE := '13-JAN-2007';
v_ending_date DATE := '14-JAN-2019';
v_13_friday_count BINARY_INTEGER := 0;
BEGIN
dbms_output.enable(1000000);
WHILE v_current_date < v_ending_date
LOOP
IF TO_CHAR(v_current_date, 'D') = 6
THEN
v_13_friday_count := v_13_friday_count + 1;
DBMS_OUTPUT.PUT_LINE(v_current_date || ' is a Friday the 13th');
END IF;
v_current_date := ADD_MONTHS(v_current_date, 1);
END LOOP;
DBMS_OUTPUT.PUT_LINE('T 2 3 here are ' || v_13_friday_count || ' Friday the 13ths in a 12 year period.');
DBMS_OUTPUT.PUT_LINE('For an average of ' || ROUND(v_13_friday_count/12, 3) || ' per year.');
END;
/
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 13-APR-07 is a Friday the 13th
13-JUL-07 is a Friday the 13th
13-JUN-08 is a Friday the 13th
13-FEB-09 is a Friday the 13th
13-MAR-09 is a Friday the 13th
13-NOV-09 is a Friday the 13th
13-AUG-10 is a Friday the 13th
13-MAY-11 is a Friday the 13th
13-JAN-12 is a Friday the 13th
13-APR-12 is a Friday the 13th
13-JUL-12 is a Friday the 13th
13-SEP-13 is a Friday the 13th
13-DEC-13 is a Friday the 13th
13-JUN-14 is a Friday the 13th
13-FEB-15 is a Friday the 13th
13-MAR-15 is a Friday the 13th
13-NOV-15 is a Friday the 13th
13-MAY-16 is a Friday the 13th
13-JAN-17 is a Friday the 13th
13-OCT-17 is a Friday the 13th
13-APR-18 is a Friday the 13th
13-JUL-18 is a Friday the 13th
There are 22 Friday the 13ths in a 12 year period.
For an average of 1.833 per year.

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.02

ANIMOUSE said, on February 11, 2009 3:36 PM:

YOUR POINT IS.............. HOW MANY FRIEDAY THE 13 ARE IN A YEAR AND A LEAP YEAR? IF YOU DONT KNOW YOU ARE THE WORST !!!!!!!!!!!!!!!!

Claudia said, on August 13, 2010 4:16 PM:

wouldn't it be easier just to look at a calender?

julie said, on April 9, 2012 4:26 AM:

something wrong here. odds of any given month having a fri 13th is 1 in 7 once you cycle through enough times. number expected in any given 12 month stretch is therefore 12/7, PERIOD.

leap year, leap centuries, years with 3 fri 13ths - they will all wash out in the average; the answer is 12/7. that's 1.714, not 1.833.

you did an awful lot of work and came up with the wrong answer -- glad you're not doing MY homework!!

cheers

julie said, on April 9, 2012 4:39 AM:

sorry for the double post -- still trying to figure out these interwebs!

(good chance to play "spot the edits", tho)

Starbucks coffee cup I do have a lot to say, and questions of my own for that matter, but first I'd like to say thank you, Dave, for all your helpful information by buying you a cup of coffee!

I do have a comment, now that you mention it!











I will never send you any unsolicited email. Ever.






Check This Out Too...

 
Look for Answers
Need Help? Ask Dave Taylor!


Follow Me on Pinterest

Find Me on Google+
ADT on G+
© 2002 - 2013 by Dave Taylor. All Rights Reserved.

Note: This web site is for the purpose of disseminating information for educational purposes, free of charge, for the benefit of all visitors. We take great care to provide quality information. However, we do not guarantee, and accept no legal liability whatsoever arising from or connected to, the accuracy, reliability, currency or completeness of any material contained on this web site or on any linked site. Further, please note that by submitting a question or comment you're agreeing to my terms of service, which are: you relinquish any subsequent rights of ownership to your material by submitting it on this site. My lawyer says "Thanks".
"Ask Dave Taylor®" is a registered trademark of Intuitive Systems, LLC.