# Calculate the day of week of any date... in your head

If you want to really impress people at cocktail parties — and by impress I mean freak them out with your uncanny, weird, rainman-like borderline Asperger's-syndrome abilities — compute the day of week of any date in your head. With a little bit of memorization and some basic computational arithmetic skill, you can figure out that, for example, Jul. 4 1776 fell on a Thursday, or that your friend's birthday will fall on a Sunday three years from now.

I started working this out when my kids were little, as I was driving with them cross-country — my son kept asking me on what day of the week he was born. I knew the date of his birth, but I couldn't for the life of me remember what day it actually fell on. I remembered that it happend during the week, since I had to leave work when my wife told me she had gone into labor, but that's all I could come up with. Since I was in the car, with no (convenient) access to a calendar — and nothing particularly better to do with myself — I started trying to work it out in my head.

Although I didn't figure it out on that car trip, I worked out the basic algorithm that I'll present here, since it makes a sort of an interesting computational problem as well. Conceptually, it's a simple problem:

- Figure out how many days occurred between the target date and the current date.
- Divide this by 7 and compute the remainder R
- If the target date is in the past, count backwards from the current day of the week by R days; if in the future, count forwards.

The first key observation is that there are
52 weeks in a year, but 365 days in a (non-leap) year. Since 52 * 7 = 364,
that means that there's one "extra" day in each year. So, if today is
Thursday, Mar. 15, 2012, then Mar. 15, 2013 will fall on a Friday. Mar. 15,
2014 will fall on a Saturday. Mar. 15, 2015 will fall on a Sunday, and so on.
So, if you want to figure out what day a given date fell on (or falls on),
first figure out what day that date will fall on *this* year, and then
add or subtract one day for each intervening year.

Leap years make this a bit more complicated, since you have to add one day for each intervening leap year... but that's not impossible to work out in your head, either. Just add an extra day for each multiple of four that occurs between the current year and the target year. For instance, today is Monday, Jan. 9, 2012. What day did Jan. 9, 1941 fall on? Well, for starters, 2012 - 1941 = 71; you can figure this out in your head easily. How many leap years occured in the intervening 71 years? Well, 71 / 4 = 17, so there were 17 leap years in between. Now, add that 17 to 71 to get 88. Now it's a simple matter of figuring out the remainder of 88 / 7 to figure out how many days of the week to "subtract" from the current one to figure out what day Jan. 9, 1941 fell on. Dividing by 7, you get a remainder of 4 - so subtract four days from the current day of Monday to determine that Jan. 9, 1941 fell on a Thursday.

One thing to be aware of if you're computing dates *way* into the future
or *way* in the past; years that are multiples of 100 are
*not* leap years, even though they're evenly divisible by 4... unless they
are divisible by 400. So you have to again subtract 1 for each multiple of 100 that occurs between the source and target year, excluding the multiples of
400: 2400, 1600, 1200, ... This won't be difficult unless you're trying to
compute back to the dark ages, in which case you should probably be aware
that the current leap year rules didn't go into effect until 1582. If you
need to compute that far back, just get a calendar.

So, algorithmically speaking, the offset between the same day on different years is given by:

[ ( target year - source year ) + ( target year - source year ) / 4 ) ] % 7

One last adjustment: if the target or source year *is* a leap year
— is evenly divisble by 4 — you have to add one day if the
earlier date falls in January or February, and add one day if the later
date falls after February. Obviously, you'll only ever have to adjust one
in this case, so it's not too difficult to keep track of this. It's also easy
to tell if a date falls on a leap year - if the last two digits are evenly
divisible by 4, then it's a leap year. Since 76 is evenly divisible by 4,
for instance, 1776 was a leap year (and so was 1876, 1976, and so will be
2076, etc.).

Now, if you want to extend that out to any arbitrary date, first figure out
what day your chosen date will fall on in *this* year, and then perform
the computation to figure out the difference. Unfortunately, figuring out
what day a given date falls on in the current year is a bit harder than
figuring out the difference between two dates in different years. It would
be trivial if each month had exactly 30 days; then you could just compute
the difference in months and multiply by 2 (since 30 % 7 = 2). The actual
differences are given by the table below.

Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec | |

Jan | 0 | 3 | 3 | 6 | 1 | 4 | 6 | 2 | 5 | 0 | 3 | 5 |

Feb | 3 | 0 | 0 | 3 | 5 | 1 | 3 | 6 | 2 | 4 | 0 | 2 |

Mar | 3 | 0 | 0 | 3 | 5 | 1 | 3 | 6 | 2 | 4 | 0 | 2 |

Apr | 6 | 3 | 3 | 0 | 2 | 5 | 0 | 3 | 6 | 1 | 4 | 6 |

May | 1 | 5 | 5 | 2 | 0 | 3 | 5 | 1 | 4 | 6 | 2 | 4 |

Jun | 4 | 1 | 1 | 5 | 3 | 0 | 2 | 5 | 1 | 3 | 6 | 1 |

Jul | 6 | 3 | 3 | 0 | 5 | 2 | 0 | 3 | 6 | 1 | 4 | 6 |

Aug | 2 | 6 | 6 | 3 | 1 | 5 | 3 | 0 | 3 | 5 | 1 | 3 |

Sep | 5 | 2 | 2 | 6 | 4 | 1 | 6 | 3 | 0 | 2 | 5 | 0 |

Oct | 0 | 4 | 4 | 1 | 6 | 3 | 1 | 5 | 2 | 0 | 3 | 5 |

Nov | 3 | 0 | 0 | 4 | 2 | 6 | 4 | 1 | 5 | 3 | 0 | 2 |

Dec | 5 | 2 | 2 | 6 | 4 | 1 | 6 | 3 | 0 | 5 | 2 | 0 |

If you can memorize that behemoth, you don't need my help computing the day that a given date falls on... is there a way to simplify this calculation?

Well, actually, it's easier to just simplify the problem. One thing that
makes this computation difficult is that I'm trying to compute the number
of days between today and the target date. But I don't actually need to do
that - all I'm really interested in is the day that the target date falls on.
If I can remember what day January 1st falls on in this year (Sunday, in 2012),
then I can just figure the offset between *that* day and the
date that I'm interested in. Now I only have to remember the very first row
of table 1.

Still - that's 12 pretty randomly distributed numbers... not easy to memorize.
Remember earlier when I mentioned that this would be easy if every month
had 30 days? Then I could just multiply the ordinal of the target month by
2 and that would give me my offset, mod 7. Although months have varying numbers
of days, it's much easier to memorize how wrong *this* calculation is than
it is to memorize the actual differences themselves.

Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec |

0 | 1 | -1 | 0 | 0 | 1 | 1 | 2 | 3 | 3 | 4 | 4 |

Now, given a date in the current year, multiply its (zero-based) month ordinal by 2 and then add the number in table 2, which is easy to memorize. This gives you the "month offset" - and since your reference date is Jan. 1, the day offset is just the day of the month. Add these two numbers, compute the remainder modulo 7, and you've got your offset to figure out what day a given date falls in in the current year. Don't forget to account for leap year if the current year is one, too.

So let's say you want to figure out what day Sep. 25, 2012 falls on, knowing that Jan. 1 2012 falls on a Sunday. 8 * 2 = 16 + 3 (from table 2) = 19 + 1 since 2012 is a leap year = 20, which leaves a remainder of 6 modulo 7. Therefore, Sep. 1 of 2012 must fall on a Saturday. The 25th is 24 days after that; 24 leaves a remainder of 3 mod 7, so Sep. 25 of 2012 must then fall on a Tuesday. Just don't forget to subtract 1 from the month and day to account for the fact that all of these computation are "zero-based". If you're a C or Java programmer, you shouldn't have any trouble keeping track of that.

In fact, it's not even necessary to keep track of what day January 1st falls on in the current year - all you need is a known reference date, and you can compute everything relative to that date, accounting for the year as shown above. I just remember that Jan. 1, 2000 was a Saturday, and I can compute everything else forward from there. Using the year 2000 as a reference year makes my year calculations easy to keep track of, since it's easy to subtract from.

So, let's put all of this together. What day will Jul. 4, 2019 fall on? M = 6, D = 3, Y = 19. The year offset is 19 + ( 19 / 4 ) = 23 % 7 = 2, which you can figure out in your head easily. Add one since the "source year" of 2000 was a leap year to get 3. The month offset is (6*2)+1=13%7=6 and the day offset is, of course, D=3. 6+3+3=12; 12%7=4, so Jul. 4 2019 is five days more than the reference date of Saturday, Jan. 1, 2000: Thursday, Jul. 4 2019.

Here it is in code:

static int adjustment[] = { 0, 1, -1, 0, 0, 1, 1, 2, 3, 3, 4, 4 }; static int day_offset( int y, int m, int d ) { int year_offset, month_offset, day_offset; // Add 1 if y > 2000 to account for the fact that 2000 was a leap year. year_offset = ( y - 2000 ) + ( ( y - 2000 ) / 4 ) + ( y > 2000 ); // Account for wrongly computed leap years year_offset -= ( y - 2000 ) / 100; // Add back 1 year if the target year is a leap year but the target // day is after the 29th (in other words, the leap day hasn't happened // yet). if ( y % 100 ) { year_offset += ( y < 2000 ) && !( y % 4 ) && ( m > 2 ); year_offset -= ( y > 2000 ) && !( y % 4 ) && ( m < 3 ); } month_offset = ( ( m - 1 ) * 2 ) + adjustment[ m - 1 ]; day_offset = d - 1; return ( year_offset + month_offset + day_offset ) % 7; }This isn't necessarily the most efficient way to do this on a computer, but encapsulates an algorithm that, with a bit of practice, you can memorize and repeat in your head next time you need to know if Christmas will fall on a weekend this year or not.

afekz, 2014-09-17