Day of the Year: Date Parsing and Leap Year Logic
Given a date string in the format "YYYY-MM-DD", return the day number of the year. For example, "2019-01-09" is the 9th day of 2019, so the answer is 9. And "2019-02-10" is the 41st day (31 days in January plus 10 in February).
This is LeetCode 1154: Day of the Year, an easy problem that tests your ability to work with date arithmetic and handle leap years correctly. It is a clean exercise in parsing strings into numbers and summing up month lengths.
Why this problem matters
Date manipulation shows up constantly in real software, from scheduling systems to financial calculations. This problem distills the core logic into its simplest form: given a date, compute an ordinal day number. It forces you to recall how many days each month has, understand the leap year rules, and chain together small arithmetic steps without off-by-one errors.
The skills you practice here (string parsing, table lookups, conditional logic) transfer directly to problems involving coordinate conversion, base-number systems, and prefix sum calculations.
The key insight
You do not need a date library. You just need an array of month lengths: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]. The day of the year is the sum of all complete months before the current one, plus the current day. If the year is a leap year and the month is after February, add 1 to account for the extra day.
The algorithm:
- Parse the string to extract
year,month, anddayas integers. - Check if the year is a leap year.
- Sum the days of months 1 through
month - 1. - Add
dayto that sum. - If it is a leap year and the month is past February, add 1.
That is it. No complex data structures, no recursion, just a lookup table and a loop.
The solution
def dayOfYear(date: str) -> int:
year, month, day = map(int, date.split("-"))
days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
is_leap = (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
total = sum(days_in_month[:month - 1]) + day
if is_leap and month > 2:
total += 1
return total
Let's walk through what each piece does.
The first line parses the date string by splitting on "-" and converting each part to an integer. This gives you year, month, and day in one clean step.
The days_in_month array stores the number of days in each month for a non-leap year. January is at index 0, February at index 1, and so on. This is your lookup table.
The leap year check follows the standard rules: a year is a leap year if it is divisible by 4, except for century years, which must also be divisible by 400. So 2000 is a leap year, but 1900 is not.
The sum(days_in_month[:month - 1]) expression adds up all the days in the months that come before the current one. If the month is March (3), it sums index 0 (January, 31) and index 1 (February, 28), giving 59. Then you add the current day.
Finally, if it is a leap year and the month is after February (month > 2), you add 1 to account for February having 29 days instead of 28. You only add the extra day when the month is past February, because if the date is in January or February, the leap day has not occurred yet.
The leap year rule has three parts: divisible by 4, not divisible by 100, unless divisible by 400. A common mistake is checking only year % 4 == 0 and forgetting the century exception. The year 1900 is not a leap year, but 2000 is. Make sure your condition handles both.
Visual walkthrough
Let's trace through the example "2019-03-01" step by step. Watch how parsing, leap year checking, and prefix summing combine to produce the answer.
Step 1: Parse the date string "2019-03-01"
Split on "-" to extract year, month, and day as integers.
Step 2: Determine if the year is a leap year
A year is a leap year if divisible by 4, but not by 100, unless also divisible by 400. 2019 is not divisible by 4, so Feb = 28.
Step 3: Sum days of all completed months before March
Month 3 is March, so sum months 1 and 2: 31 + 28 = 59 days completed.
Step 4: Add the current day to get the final answer
59 + 1 = 60. The date "2019-03-01" is the 60th day of the year.
The process is mechanical: parse, check leap year, sum previous months, add the day. For "2019-03-01", that gives 31 + 28 + 1 = 60. Since 2019 is not a leap year, no adjustment is needed.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Month sum | O(1) | O(1) |
Time is O(1). Even though you loop over the months before the current one, the maximum number of months is 12. This is a fixed constant, so the work does not grow with input size. String parsing is also bounded by the fixed 10-character date format.
Space is O(1). The days_in_month array has exactly 12 entries, and you use a handful of integer variables. Nothing scales with input.
The building blocks
1. String parsing to numeric values
The pattern of splitting a formatted string and converting its parts to integers:
year, month, day = map(int, date.split("-"))
This is the same idea behind parsing IP addresses ("192.168.1.1".split(".")), version numbers, or any delimited string. The pattern is always split, then convert. You will see it in problems like "Compare Version Numbers" (LeetCode 165) and "Validate IP Address" (LeetCode 468).
2. Leap year determination
The conditional check that encodes calendar rules:
is_leap = (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
This is a specific instance of the broader pattern of encoding domain rules as boolean expressions. The key is getting the precedence right: the year % 100 != 0 exception applies only to the year % 4 case, while the year % 400 case overrides everything. Parentheses make the logic explicit and prevent bugs.
Edge cases
Before submitting, think through these scenarios:
- January 1 (
"2019-01-01"): no previous months to sum. The answer is simply1. - December 31 in a non-leap year (
"2019-12-31"): sum all 12 months. The answer is 365. - December 31 in a leap year (
"2000-12-31"): sum all 12 months plus the leap day. The answer is 366. - February 29 in a leap year (
"2000-02-29"): sum January (31) and add 29. The answer is 60. Since month is 2 (not greater than 2), the leap adjustment does not apply. But wait,days_in_month[:1]gives only January (31), and day is 29, so total = 31 + 29 = 60. This is correct because the leap day is already included in thedayvalue itself. - Century year that is not a leap year (
"1900-03-01"): 1900 is divisible by 4 and by 100, but not by 400. It is not a leap year. February has 28 days. The answer is 31 + 28 + 1 = 60. - Century year that is a leap year (
"2000-03-01"): 2000 is divisible by 400, so it is a leap year. The answer is 31 + 28 + 1 + 1 = 61 (the extra +1 from the leap adjustment).
From understanding to recall
You have walked through the solution and it makes sense. Parse the string, check for leap year, sum the months, add the day. Simple. But in an interview, the details matter: do you add 1 when the month equals 2, or only when it is greater than 2? Do you slice [:month - 1] or [:month]? Is 1900 a leap year or not?
These are recall gaps, not understanding gaps. You know the logic, but under pressure, the small details slip. Spaced repetition closes that gap. You practice writing the month-sum calculation and the leap year check from memory at increasing intervals. After a few rounds, the code flows out automatically.
The date parsing and leap year patterns are two of roughly 60 reusable building blocks that cover hundreds of LeetCode problems. Learning them individually and drilling them with spaced repetition is far more effective than grinding random problems and hoping they stick.
Related posts
- Excel Sheet Column Number - Another math conversion problem where you map between two representations (column letters to numbers)
- Excel Sheet Column Title - The reverse direction of math conversion, turning numbers back into column letters
- Add Binary - String-to-number conversion with carry logic, practicing the same parse-and-compute pattern
CodeBricks breaks the Day of the Year LeetCode problem into its string parsing and leap year building blocks, then drills them independently with spaced repetition. You type each piece from scratch until the pattern is automatic. When a date arithmetic question shows up in your interview, you do not think about it. You just write it.