Day of the Week: Zeller's Congruence in Code
Given a date as three integers, day, month, and year, return the day of the week for that date. The answer is one of "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", or "Saturday".
This is LeetCode 1185: Day of the Week, and it is a direct application of a well-known mathematical formula. The problem tests whether you can translate a formula into code cleanly, handle a few tricky adjustments, and avoid off-by-one errors in index mapping.
Why this problem matters
Calendar arithmetic shows up more often than you might expect. Scheduling systems, date pickers, and financial applications all need to compute weekdays from dates. Beyond the practical use, this problem is a great exercise in translating a mathematical formula into working code. You need to handle special cases (January and February get shifted), manage integer division carefully, and map a numeric result back to a string. These are the same skills you use whenever you implement any formula-based algorithm.
The key insight
You do not need to simulate anything or count days from a reference point. Mathematicians solved the "what day is this date?" problem centuries ago. Zeller's congruence is a closed-form formula that takes a year, month, and day and produces a number representing the day of the week. The only trick is that January and February are treated as months 13 and 14 of the previous year, so you need to adjust the inputs before plugging them into the formula.
The formula itself involves integer division and modular arithmetic, both of which Python handles cleanly. Once you adjust the inputs, it is a single expression.
The solution
def day_of_the_week(day: int, month: int, year: int) -> str:
if month <= 2:
month += 12
year -= 1
k = year % 100
j = year // 100
h = (day + (13 * (month + 1)) // 5 + k + k // 4 + j // 4 - 2 * j) % 7
days = ["Saturday", "Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday"]
return days[h]
Let's walk through what each piece does.
The first block handles the January/February adjustment. Zeller's formula treats January and February as months 13 and 14 of the preceding year. So if the input month is 1 or 2, we add 12 to the month and subtract 1 from the year. For all other months (3 through 12), we leave the inputs unchanged.
Next, we split the year into two parts: k is the year within the century (the last two digits), and j is the century (the first two digits). For example, 2019 gives k = 19 and j = 20.
The core formula computes h, which is a value from 0 to 6. Zeller's congruence maps 0 to Saturday, 1 to Sunday, 2 to Monday, and so on through 6 for Friday. The formula accounts for the irregular distribution of days across months and the leap year rules all in one expression.
Finally, we use h as an index into a list of day names. Since the formula's output starts at Saturday for 0, our list starts with "Saturday".
You could also solve this problem using Python's built-in datetime module with datetime.date(year, month, day).strftime("%A"). That is perfectly valid for production code, but in an interview setting the point is to demonstrate the formula. Knowing Zeller's congruence shows you understand the math, not just the standard library.
Visual walkthrough
Let's trace through the example input (day=31, month=8, year=2019) step by step through Zeller's congruence. The expected output is "Saturday".
Step 1: Adjust January and February
Computation
month = 8, year = 2019 Since month > 2, no adjustment needed. Adjusted: month = 8, year = 2019
If month were 1 or 2, we would add 12 to month and subtract 1 from year.
Step 2: Extract century and year-of-century
Computation
k = year % 100 = 2019 % 100 = 19 j = year // 100 = 2019 // 100 = 20
k is the year within the century (19), j is the century (20).
Step 3: Apply Zeller's congruence
Computation
h = (day + ⌊13*(month+1)/5⌋ + k + ⌊k/4⌋ + ⌊j/4⌋ - 2*j) % 7 h = (31 + ⌊13*9/5⌋ + 19 + ⌊19/4⌋ + ⌊20/4⌋ - 2*20) % 7 h = (31 + 23 + 19 + 4 + 5 - 40) % 7 h = 42 % 7 h = 0
Zeller's formula returns h where 0 = Saturday, 1 = Sunday, ..., 6 = Friday.
Step 4: Map h to the day name
Computation
days = ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] h = 0 → days[0] = "Saturday"
The result is Saturday, which matches August 31, 2019.
Step 5: Handle negative remainders
Computation
In Python, % always returns non-negative, so (h % 7) is safe. In other languages, use ((h % 7) + 7) % 7 to guarantee a value in [0, 6].
Python's modulo operator makes this a non-issue, but it is worth noting for other languages.
The walkthrough confirms that each step produces a clean intermediate value, and the final result maps directly to the correct day. The formula works for any valid Gregorian calendar date, which is all the problem guarantees.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Zeller's congruence | O(1) | O(1) |
Time is O(1). The formula uses a fixed number of arithmetic operations regardless of the input values. There are no loops, no iterations, and no recursion.
Space is O(1). We store a handful of integer variables and a fixed-size list of seven strings. Nothing grows with input size.
The building blocks
1. Modular arithmetic for cyclic mapping
The % 7 operation at the end of Zeller's formula maps any integer to one of seven buckets (0 through 6). This is the same technique you use whenever you need to cycle through a fixed set of values:
h = (some_large_expression) % 7
result = options[h]
You see this pattern in hashing, round-robin scheduling, circular buffer indexing, and many other problems where you need to wrap around a fixed range.
2. Input normalization before computation
The January/February adjustment is an example of normalizing inputs before feeding them into a formula:
if month <= 2:
month += 12
year -= 1
Many math problems require you to transform the input into a form the formula expects. Recognizing when you need to do this, and doing it before the main computation rather than trying to patch the result afterward, keeps the code clean and correct.
Edge cases
- January and February dates: The month/year adjustment is critical. January 1, 2000 must be treated as month 13 of year 1999 for the formula to work.
- Leap year dates: February 29 on a leap year. The formula handles leap years through the
k/4andj/4terms, so no special case is needed. - Century boundaries: Year 1900 vs. year 2000. The century term
jchanges, and thej/4term accounts for the 400-year leap year cycle. - Earliest valid dates: The problem guarantees dates between 1971 and 2100, so you do not need to worry about pre-Gregorian calendar dates.
- End-of-month dates: Days like the 31st. The formula does not care about whether a month has 28, 30, or 31 days, since the problem guarantees the input is a valid date.
From understanding to recall
Zeller's congruence is elegant, but it has several pieces that are easy to mix up. Is it 13 * (month + 1) / 5 or 13 * month / 5? Does h = 0 mean Sunday or Saturday? Do you subtract 1 from the year for January, or add 1? These details matter, and getting one wrong means a wrong answer.
The way to lock in a formula like this is repetition with spacing. You write it from memory today, again in two days, again in a week. Each time you recall it, the neural pathway strengthens. After a few rounds, the adjustment for January/February, the split into k and j, and the day-name mapping are automatic.
This is especially valuable for math problems in interviews. You will not always get Zeller's congruence specifically, but you will get problems that require translating a formula into code with careful attention to edge cases. The skill of memorizing and reproducing a formula accurately under pressure transfers directly.
Related posts
- Palindrome Number - Another math problem where digit manipulation and careful modular arithmetic produce a clean O(1)-space solution
- Reverse Integer - Digit extraction with mod 10 and integer division, the same arithmetic building blocks that power Zeller's formula
- Fizz Buzz - Modular arithmetic for classification, mapping numbers to categories just like mapping dates to weekdays
CodeBricks breaks Day of the Week into its modular arithmetic and input normalization building blocks, then drills them independently with spaced repetition. You type Zeller's formula from scratch until the adjustment rules, the index mapping, and the edge cases are automatic. When a math problem shows up in your interview, you do not second-guess the formula. You just write it.