Calculating the Day of the Week with Zeller’s Congruence in C

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.

Zeller's Congruence

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

ZIP File
GitHub

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.

Leave a Reply

Your email address will not be published. Required fields are marked *