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

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:

$dateSat Apr 28 14:35:55 CDT 2007 $date +%s1177788958Now we know that epoch date 1177788958 = 28 April, 2007 at 2:23pm$expr 60 \* 60 \* 2486400That'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 2007Proof: 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 +%j118Now, finally, we can just subtract 118 * 88400 to get to January 1st:$date -r `expr 1177702558 - 10195200`Sat Dec 30 13:35:58 CST 2006Nope. I'm guessing it's a timezone issue, so we can just recalculate...$expr 86400 \* 11610022400 $expr 1177702558 - 100224001167680158 $date -r 1167680158Mon 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.shMon 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.shMatch: 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.shMatch: 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)

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

(good chance to play “spot the edits”, tho)

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

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

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

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

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

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