7

I'd like to be able to start with a year, and calculate occurrences of Friday the 13th. A brute force solution is easy and obvious. I have something slightly better, but I have no doubt that someone else can come up with an elegant algorithm for this.

Perhaps a little trickier, I'd be interested in giving the program a month, and have it find the next year in which that month has a Friday the 13th.

Feel free to use pseudo code, but I expect people will vote more for working code samples in you favorite language.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Adam Davis
  • 91,931
  • 60
  • 264
  • 330

7 Answers7

11

Any month that starts with a Sunday has a Friday on the thirteenth. There are only 14 combinations possible knowing what day the first of the year is on (with or without leap year, and sun-sat). You should just calculate it once and get it over with. You'd only check 14*12 possible months to start out with, well with in reason.

resultant table element (from 2009, 2010):

[Thursday,false] => Feb, March, Nov
[Friday,false] => Aug

to fill the table you have a generic month Jan(31),Feb(28).. and then iterate with a seed of each day of the week, noting months that start with sunday, and also with a leap year and without. Pretty straight forward, and once done, you can share it with us :)

nlucaroni
  • 47,556
  • 6
  • 64
  • 86
  • 2
    This is the fastest table method, afaict. There are fourteen types of weekdays - 7 for non leap years, 7 for leap years, and 12 months. A 2D or 3D array provides a very quick answer once you know what day of the week Jan 1 falls on, and whether it's a leap year - both are easy to calculate. – Adam Davis Feb 13 '09 at 16:33
  • For everyone who wants the whole table, this is for non-leap years: `[[4, 7], [9, 12], [6], [2, 3, 11], [8], [5], [1, 10]]` and this is for leap years: `[[9, 12], [6], [3, 11], [2, 8], [5], [10], [1, 4, 7]]`. The values inside the array are 1-based indices of months and the outer array starts from Monday – illright Oct 26 '20 at 08:34
10

Since your brute force algorithm is apparently the intuitive day-by-day iteration option, perhaps you haven't considered the Doomsday Algorithm. It would allow you to simply check if that 13th is a Friday. As far as I know, it is the most efficient solution to the problem.

Devin Jeanpierre
  • 92,913
  • 4
  • 55
  • 79
3

Here's some example PHP code that goes through a pretty straight forward loop of the dates in a range. I would modify this to check the 13th of each month for Fridayness, rather than checking every Friday for 13thness, as they do in the example.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
2

One thing I noticed is that the first of the month falls on a Sunday during months with a Friday the 13th. You can probably leverage this to make it easier to calculate.

Powerlord
  • 87,612
  • 17
  • 125
  • 175
1
initialize startDate to 13th of the month given in the current year
while (true) {
    if (startDate.dayOfWeek == Date.FRIDAY)
         break;
    else
         startDate.year ++;
}
return startDate.year;
Elie
  • 13,693
  • 23
  • 74
  • 128
1

This is how I would do it:

  • Assume year is known and is an integer.

  • Loop from 1 to 12

    • Create date with loop index, year and 13 for the day

If you want to start with a month and year (you have to assume some sort of year), your algorithm becomes

  • Assume year is known and an integer

  • Assume month is known and is an integer

  • Loop

    • Create date with index of loop as year, known month variable, and 13 for the day

    • Determine day of week as per established algorithms

    • If day of week calculate above is Friday, return date, else

    • Else increment year by 1

casperOne
  • 73,706
  • 19
  • 184
  • 253
0

I use PowerShell 7.1 x64 on Windows 10, I am also interested in this, though my programming skill is not advanced enough to develop an independent and complex algorithm (without built-in) to solve this, currently I can do this:

$start=Read-Host "Input start year"
$end=Read-Host "Input end year"
$years=$start..$end
$blackfriday=@()
foreach ($year in $years) {
    $blackfriday+=1..12 | foreach { [datetime]"$year-$_-13" } | where { $_.DayOfWeek -eq 'Friday'} | foreach {$_.tostring("yyyy, MMMM dd, dddd")}
}
$blackfriday

Update: Now I have this, by brute forcing year-month-13 to convert date to days, and get the results where days mod 7 equal to 5, I have used all the advanced algorithms found on Wikipedia: https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week And I found out they don't work well with PowerShell, don't know if it's just me or Wikipedia got it wrong, anyway here is my script, I have fc compared the results of the script with builtin and this script, and it shows"FC: no differences encountered":

$months=@(
[PSCustomObject]@{Month='January';Days=31}
[PSCustomObject]@{Month='February';Days=28}
[PSCustomObject]@{Month='March';Days=31}
[PSCustomObject]@{Month='April';Days=30}
[PSCustomObject]@{Month='May';Days=31}
[PSCustomObject]@{Month='June';Days=30}
[PSCustomObject]@{Month='July';Days=31}
[PSCustomObject]@{Month='August';Days=31}
[PSCustomObject]@{Month='September';Days=30}
[PSCustomObject]@{Month='October';Days=31}
[PSCustomObject]@{Month='November';Days=30}
[PSCustomObject]@{Month='December';Days=31}
)
function BlackFriday {
    param(
    [Parameter(ValueFromPipeline=$true, Mandatory=$true, Position=0)] [int64] $start,
    [Parameter(ValueFromPipeline=$true, Mandatory=$true, Position=1)] [int64] $end
    )
    $years=$start..$end
    $blackfriday=@()
    foreach ($year in $years) {
        $array=1..12
        foreach ($arra in $array) {
            $month=0
            for ($i=0;$i -lt $arra-1;$i++) {
                $month+=$months[$i].Days
            }
            [int]$ye=$year
            if ($arra -le 2) { $ye-=1}
            if ($ye % 4 -eq 0) {$leap=$ye/4}
            else {while ($ye % 4 -ne 0) {$ye-=1}
            $leap=$ye/4}
            if ($ye % 100 -eq 0) {$century=$ye/100}
            else {while ($ye % 100 -ne 0) {$ye-=4}
            $century=$ye/100}
            if ($ye % 400 -eq 0) {$cycle=$ye/400}
            else {while ($ye % 400 -ne 0) {$ye-=100}
            $cycle=$ye/400}
            $leap=$leap-$century+$cycle
            $date=[int64](($year-1)*365+$month+13+$leap)
            if ($date % 7 -eq 5) {
                $name=$months[$arra-1].Month
                $blackfriday+=[string]"$year, $name 13, Friday"
            }
        }
    }
    $blackfriday
}
$start=Read-Host "Input start year"
$end=Read-Host "Input end year"
BlackFriday $start $end