In this post I'll write an implementation in Python of Zeller's Congruence, a simple and elegant little formula to carry out the seemingly complex task of calculating the day of the week (Monday, Tuesday etc.) from a date.

## Christian Zeller and His Clever Little Formula

The formula was developed by German mathematician Christian Zeller (24 June 1822, a Monday, to 31 May 1899, a Wednesday). I have always been impressed that a task which you might think highly complex and solvable only by some sort of brute-force iteration can actually be reduced to a short and elegant formula. Of course you wouldn't use the code in this post in production applications as Python provides the functionality already, but I hope you agree it is a worthwhile programming exercise.

There are several versions of the formula, including two sub-versions of each, one for the Gregorian calendar and one for the Julian. This is the Gregorian version of the formula I will be implementing, in an image stolen from Wikipedia.

Note that the symbols like an elongated letter L and it's mirror-image twin denote "floor", ie the result of the term is rounded down to the nearest integer. The full Wikipedia article is here and is worth at least skimming.

Rewritten in a more computer-friendly way we get

Zeller's Congruence

h=(q+((13*(m+1))/5)+Y+(Y/4)-(Y/100)+(Y/400))%7

## Starting to Code

Create a new folder and within it create the following empty files. You can also download the source code as a zip or clone/download from Github if you prefer.

- zeller.py
- main.py

Source Code Links

Open zeller.py and type or copy/paste the imports and first function.

zeller.py part 1

import datetime

import random

import calendar

import math

def showdatesanddays():

"""

Creates a selection of random dates and runs the Zeller Algorithm on them,

printing out the date, and then the day according to Python and Zeller.

"""

# Zeller gives a value 0 to 6 representing Saturday to Friday

zellerdays = ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]

for i in range(0, 19):

d = datetime.date.fromtimestamp(random.randint(1, 1000000000))

print("Date: " + str(d))

print("Python: " + calendar.day_name[d.weekday()])

print("Zeller: " + zellerdays[zellergregorian(d)])

print("------------------")

The showdatesanddays function firstly creates a list of day names starting with Saturday. These correspond with the values returned by Zeller's Algorithm. We need to do this as "Python Weeks" start on a Monday.

After that we enter a for loop which generates a number of random dates using the fromtimestamp and randint functions. The fromtimestamp function creates a date from the number of seconds from 1/1/1970, and using 1 billion as the upper limit of randint will create dates between 1/1/1970 and 9/9/2001, which is adequate to demonstrate the algorithm.

After that we just need to print out the date and the corresponding day name using Python's calendar.day_name list. Finally we print out the day name again from the zellerdays list, using a call to our zellergregorian function as the list index.

We can now implement that function so enter or paste it into zeller.py.

zeller.py part 2

def zellergregorian(d):

"""

Runs the Zeller algorithm on the given date

and returns the day index 0 to 6 for Saturday to Friday.

"""

q = d.day

m = d.month

Y = d.year

# adjust month to run from 3 to 14 from March to February

if m <= 2:

m+= 12

# and also adjust year if January or February

if d.month <= 2:

Y -= 1

h = (q + math.floor((13 * (m + 1)) / 5) + Y + math.floor(Y / 4) - math.floor(Y / 100) + math.floor(Y / 400)) % 7

return h

In zellergregorian we first declare three variables for day, month and year, setting them to the values from the date function argument. Of course we could just use the values in the date directly but I wanted to use variable names in the formula which matched those used by Herr Zeller. (I don't know why day is called "q"!)

We then need to adjust month and year if the month of our date is January or February as the algorithm uses years running from March to February, indexed 3 to 14.

Finally we calculate the day index - if you examine this line carefully you will see it implements the algorithm given above exactly. Note the use of the math.floor function to round down various terms to the nearest integer.

Now open main.py and after a line to print the heading we call showdatesanddays.

main.py

import zeller

def main():

"""

Zeller's Congruence calculates the day of the week from the given date.

"""

print("-----------------------------------")

print("| codedrome.com |")

print("| Zeller's Congruence: |")

print("| Calculating the Day of the Week |")

print("-----------------------------------")

zeller.showdatesanddays()

main()

Now we can run the program with this command...

Run

python3.7 main.py

...which will give us this output.

Program Output (partial)

-----------------------------------

| codedrome.com |

| Zeller's Congruence: |

| Calculating the Day of the Week |

-----------------------------------

Date: 1983-05-07

Python: Saturday

Zeller: Saturday

------------------

Date: 1981-02-25

Python: Wednesday

Zeller: Wednesday

------------------

Date: 1977-12-04

Python: Sunday

Zeller: Sunday

------------------

Date: 1974-08-04

Python: Sunday

Zeller: Sunday

------------------

Date: 2000-08-22

Python: Tuesday

Zeller: Tuesday

------------------

Date: 1996-07-03

Python: Wednesday

Zeller: Wednesday

------------------

You will be pleased to see that Python and Zeller days are the same for all dates.