Today I'll write an implementation in C 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 Christian Zeller, a 19th Century German mathematician, and I have always been impressed that a task which you might think is solvable only by some sort of brute-force iteration can actually be achieved so simply.
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, within it create a new empty file called zeller.c, and paste in the following code. You can also download the source as a zip, or clone/download the Github repository if you prefer.
Source Code Links
zeller.c
#include<stdio.h> #include<math.h> #include<time.h> #include<locale.h> #include<stdlib.h> //-------------------------------------------------------- // FUNCTION PROTOTYPES //-------------------------------------------------------- void showdatesanddays(void); int zellergregorian(struct tm* date); //-------------------------------------------------------- // FUNCTION main //-------------------------------------------------------- int main(void) { puts("-----------------------"); puts("| codedrome.com |"); puts("| Zeller's Congruence |"); puts("-----------------------\n"); showdatesanddays(); return EXIT_SUCCESS; } //-------------------------------------------------------- // FUNCTION showdatesanddays //-------------------------------------------------------- void showdatesanddays() { // set of day names corresponding to Zeller's h value char* daynames[] = {"Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"}; char datestring[64]; char daystring[10]; struct tm date = {}; int h; srand(time(NULL)); // generate 20 random dates for(int i = 0; i < 20; i++) { date.tm_year = (rand() % 2017) - 1900; // in tm, the year is based on 1900 date.tm_mon = rand() % 11; date.tm_mday = rand() % 365; // mktime will sort out tm_mday, eg 32 January becomes 1 February // normalize date and set other fields mktime(&date); strftime(datestring, 64, "%d %B %Y", &date); strftime(daystring, 10, "%A", &date); //h = zellergregorian(&date); printf("Date %s\n", datestring); printf("Day (C library) %s\n", daystring); //printf("Day (Zeller) %s\n\n", daynames[h]); } }
Basically what we are doing here is creating a tm struct (part of the standard library in time.h) to hold the component parts of a date and time, then iteratively setting it to 20 random dates. The dates and the corresponding day names are then printed out.
As you can see a couple of lines are commented out. These call the yet-to-be-implemented zellergregorian function and then print out the name of the day it returns right underneath the C library generated day for us to compare.
Let's compile and run the code we have so far. In the terminal run
Compiling
gcc zeller.c -std=c11 -lm -o zeller
to compile, and then
Running
./zeller
to run the program. You should get something like this. The dates will be different, and I am only showing the first few here.
Program Output
----------------------- | codedrome.com | | Zeller's Congruence | ----------------------- Date 19 July 748 Day (C library) Monday Date 17 June 1371 Day (C library) Monday Date 12 November 1660 Day (C library) Friday Date 18 November 499 Day (C library) Wednesday
Now paste the zellergregorian function into zeller.c.
zeller.c
//-------------------------------------------------------- // FUNCTION zellergregorian //-------------------------------------------------------- int zellergregorian(struct tm* pdate) { int h = 0; // day of week, Saturday = 0 int q = pdate->tm_mday; // day of month int m = pdate->tm_mon; // month, 3 to 14 = March to February int Y = 1900 + pdate->tm_year; // tm_year is 1900-based // adjust month to run from 3 to 14 for March to February if(m <= 1) m+= 13; else m+= 1; // and also adjust year if January or February if(pdate->tm_mon <= 1) Y--; // Calculate h as per Herr Zeller // No need to use floor() as these are all ints h = (q + ((13 * (m + 1)) / 5) + Y + (Y / 4) - (Y / 100) + (Y / 400)) % 7; return h; }
Firstly we create and initialize a few variables.
- q is set unchanged to tm_mday
- m is set to tm_mon and then adjusted for March through February to equal 3 to 14 respectively
- Y is set to tm_year + 1900, as it is 1900-based. It is then decremented for January and February
After that all we need to do is evaluate the formula and return the result. Go back to main and uncomment the two commented lines of code, then build and run. This time we'll also see Herr Zeller's opinion of what day of the week it is.
Program Output
----------------------- | codedrome.com | | Zeller's Congruence | ----------------------- Date 01 December 1869 Day (C library) Wednesday Day (Zeller) Wednesday Date 02 June 890 Day (C library) Friday Day (Zeller) Friday Date 27 October 1265 Day (C library) Tuesday Day (Zeller) Tuesday Date 03 January 1980 Day (C library) Thursday Day (Zeller) Thursday
Obviously you wouldn't use this code in production as the C library already provides the same functionality, but it is an interesting and worthwhile programming exercise nonetheless.
Please follow CodeDrome on Twitter to keep up with future posts.