×   Home   Blog   Newsletter   Privacy   Contact Us   About

Zeller`s Congruence

Have you ever wondered how computers (and people) determine the day of the week for an arbitrary date? Do you know on which day you were born? On what day of the week will Halloween fall on in three years time? If your girlfriend's birthday is January 19th, what day of the week does it fall on next year?…
In today’s society, we’d probably answer these questions with a quick reference to an online calendar, or maybe Excel, (or, if you are a programmer, with a function call). But if you wanted to go old-school, and calculate this by hand, how would you go about doing it?
This problem was studied by the German mathematician, Julius Christian Johannes Zeller, who published an elegant algorithm for the calculation in 1882. This algorithm is named after him as Zeller’s Congruence.

The formula

I’ll start by showing Zeller’s formula, then we’ll break it down and explain it:
In this formula, Y is the year, m is the month, and d is the day of the month. For example, 9th August 1967: Y=1967, m=8, d=9.
At first glance it appears quite complex, but in reality, it’s pretty simple and elegant. The key component is the observation that the day of the week progresses in a very predictable way based on the day, month, and year.
In this formula the ⌈x⌉ is symbology for the ceiling function, and the ⌊x⌋ is symbology for the floor function. The ceiling function returns the least integer that is greater than or equal to x, and the floor function returns the greatest integer less than or equal to x (essentially 'snapping' the input to the integer above, or below, of the input respectively).

How it works

The first thing to note is the modulos 7 operator wrapping the function. There are only seven days in the week, and our answer must be one of these. Days of the week wrap around continuously. The mod 7 operation returns a value 0–6 which we can map to the contiguous days of the week (more later). If you are not familiar with the modulos operator, the easiest way to think of it is is what the remainder would be if, in this case, a number were divided by 7. All we are concerned about is the modulos.
The next thing to note is that a year has 365 days (ignore about leap years for just a moment). 365 does not divide perfectly by seven.
365 mod 7 = 1. This means that the day of the week advances by one day each year. If January 19th is Saturday this year, then next year it will be a Sunday, and the year after that a Monday … (again ignoring leap years). What this means is that there is direct linear correlation between the year, and the output. For every +1 year, output increases +1 (modulo 7). If you look at the formula, you can see that the third term in the bracket is just Y.
If you familiar with the Gregorian Calendar, you will know that the orbital period of the Earth around the Sun is not 365 days, but closer to 365.2425 days (for more details on the background of this, read this article about Friday 13th). To account for this longer period, every four years (a leap year), we add an extra day in February, to make 366 days. This extra date advances the day along one* so if August 9th were a Tuesday this year, if the next year were a leap year, rather than it being a Wednesday, it would be a Thursday. It's a little more complex than this because with just a leap year every four years, we'd get to 365.25 days, which is not good enough, so if the year is divisible by 100, we skip the leap year, unless it is divisible by 400, in which case we do make it a leap year. (Again, for details of why, see the article indicated above).
These complex leap year rules account for the last three terms in the formula. For every multiple of four years, we advance on one. For every multiple of 100, we back-off one, and for every multiple of 400 we add one back on. Here the floor function is simply doing the equivalent of INT(x) function.
*It advances on one extra day only after February. For the first two months of the year, it still only advances on the one. We'll fix this later.

Dealing with the days and months

We've accounted for the years, now let's deal with the days and months, and pesky issue of the first two months of a leap year.
The day issue is trivial; for every +1 day, the output advances +1 (modulo 7). Every day we advance, the day of the week moves to the next day! This is the first term in the equation.
To solve the month issue, Zeller's brilliant idea was to imagine starting a year in March instead of January. (Another way to think of this is making January the equivalent of the 13th month of the previous year, and February the 14th month). With the problem child of February now right at the end of the 'year', we don't have to worry about the variance of its days.
With this shift complete, the number of days in each month are as follows: {31,30,31,30,31,31,30,31,30,31,31,28/29}.
However, we're only concerned with the modulos of the number of days, and these are {3,2,3,2,3,3,2,3,2,3,3,0}.
If you look closely, they alternate at first, but then there is a double 3, then the sequence repeats. If we look at a graph of their cummulative sums, we get the following chart. For every 5 months advancing, the cummulative count increases by 13. We can approximate this with a line of gradient 13/5, and use the ceiling function to snap to the answer we want.
This accounts for the second term in the fomula, we subtract two from the month to shift it back, then multiply by the gradient to get the approximation for the offset, and use the ceiling function to snap it up to the integer.
(In Zeller's original paper, he used the gradient of 26/10 months, which you can see is the same thing).
The ceiling function was not standard in all early computer languages, and the floor function is just the equivalent of the INT(x) function (truncating the decimal; essentially casting a float into an integer), so the Zeller function is also sometimes quoted using the following equivalent alternative fraction for the month component so that all parts use the floor function:
All that we need to do now is convert the output to the correct day of the week. Using the following mapping, we can convert the modulos answer into a day of the week.

0 = Saturday
1 = Sunday
2 = Monday
3 = Tuesday
4 = Wednesday
5 = Thursday
6 = Friday


The origins of this slightly non-standard start of the week at Saturday is just the offset to ensure that the answer to the equation starts at the correct day of the week. (Cycles of days repeat every 400 years, and 1/1/2000 is a Saturday).
Putting it all in code:

function zeller(month, day, year) {
   
   if (month < 3) {month += 12; year -= 1;}
   
   var z = (day + 
             parseInt(((month + 1) * 13) / 5) + 
             year + 
             parseInt(year / 4) -  
             parseInt(year / 100) +  
             parseInt(year / 400)
            ) % 7;
			
   return z;
}

Advertisement:

Try it out

Here is code to allow you to find out the day of week for a date. (Note, this is a simple implementation of the code above, and there is no error checking on the input to confirm it is valid).

 

Julian Calendar

The Zeller formula above is for the Gregorian Calendar, which is what most of the World uses today. The Gregorian calendar came into effect in around 1582, promulgated by Pope Gregory XIII. Again, for more background, check out this reference. Here is an equivalent formula that can be used for the Julian calender. There is no accomodation for the complex leap years, and there is an offset to set the day correctly.

Doomsday

I'm sure there are talented people who might be able to perform the above calculations in their heads, but mental date calculators typically use the Doomsday Rule system as it is easier (once you've memorized the necessary key data).
The Doomsday algorithm for mental calculation was devised by John Conway, yes, that John Conway in 1973 based on inspiration from Lewis Carroll. Yes, that Lewis Carroll.
The system takes advantage of each year having a certain day of the week, called the doomsday, upon which certain easy-to-remember dates fall; for example, 4/4, 6/6, 8/8, 10/10, 12/12, and the last day of February, all occur on the same day of the week in any year.
Applying the Doomsday algorithm involves four steps:
  1. Determination of the anchor day for the century.
  2. Calculation of the doomsday for the year from the anchor day.
  3. Selection of the closest date out of those that always fall on the doomsday.
  4. Count of the number of days between this date and the date in question. (Which can be done modulo 7).
There's an excellent summary of the system here. Good Luck!