Skip to main content

The Leap Year Rule, Why 1900 Was Not a Leap Year

How the divisible-by-4, century, and 400-year rules decide a leap year, why Feb 29 only sometimes appears, and the off-by-one bugs the rule causes in code.

Published By Li Lei
#leap year #calendar #date math #programming

The Leap Year Rule, Why 1900 Was Not a Leap Year

Most people learn one half of the leap year rule in grade school: every fourth year gets an extra day, February 29. That half is correct often enough that you can go decades without it ever failing you. Then you hit a year ending in 00, and the simple version quietly breaks. The year 1900 had only 28 days in February. The year 2000 had 29. They are both divisible by 4, both divisible by 100, and they behave differently. Knowing exactly why is the difference between a calendar that stays aligned with the seasons and one that drifts, and between date code that ships and date code that pages you at 2 a.m.

The Actual Rule, in One Line

Here is the complete rule, no hand-waving. A year is a leap year if it is divisible by 4, except that century years (those divisible by 100) are not leap years unless they are also divisible by 400. So 2000 is a leap year because it divides evenly by 400, but 1900 is not, because it divides by 100 and fails the 400 test.

Written as a single boolean check, it reads:

isLeap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0)

Three divisibility tests, evaluated in the right order. The % 4 test catches the common case. The % 100 test pulls century years out. The % 400 test puts the special centuries back in. Skip any one of them and you get a calendar that is wrong every hundred years.

Why this baroque arrangement instead of a flat "every four years"? Earth takes about 365.2422 days to orbit the Sun, not a tidy 365. Adding one day every four years overcorrects slightly, because 0.2422 is a little less than 0.25. Dropping three leap days every four centuries (skipping 100, 200, and 300 but keeping 400) shaves off the excess. The result keeps the Gregorian calendar accurate to roughly one day in 3,200 years, which is close enough that nobody reading this will ever need the next correction.

A Worked Example, 2024 Against 1900 Against 2000

Numbers make the rule concrete, so run three years through it by hand.

2024. Is it divisible by 4? 2024 ÷ 4 = 506, exact, yes. Is it a century year, divisible by 100? 2024 ÷ 100 = 20.24, no. The century exception never triggers, so the % 4 result stands. 2024 is a leap year, 366 days, February has 29 days.

1900. Divisible by 4? 1900 ÷ 4 = 475, exact, yes, so the naive rule wants to call it leap. But it is a century year: 1900 ÷ 100 = 19, exact. Now the 400 test decides: 1900 ÷ 400 = 4.75, not whole. The century exception fires and is not rescued. 1900 is a common year, 365 days, February has 28 days. This is the year that breaks "every four years" for anyone who never learned the second half of the rule.

2000. Divisible by 4? Yes, 500. Century year? Yes, 2000 ÷ 100 = 20. The 400 test: 2000 ÷ 400 = 5, exact. The century exception is overridden. 2000 is a leap year, 366 days, February 29 exists. People who watched the millennium roll over got a leap day that year, while their grandparents in 1900 did not, even though both years look identical to the divide-by-4 shortcut.

If you want to verify any of this without arithmetic in your head, type the year into the Leap Year Checker. It lays out all three divisibility checks as little yes/no marks, tells you the day count, and shows the previous and next leap years so the gaps are visible.

The Feb 29 Quirk and the Eight-Year Gap

The leap day itself is a strange piece of the calendar. February 29 exists in 366-day years and vanishes in 365-day ones, which means a date that is perfectly valid in 2024 is meaningless in 2023. Anyone born on February 29, sometimes called a leapling, gets a real birthday only about once every four years. Someone born on 2024-02-29 next sees a genuine February 29 in 2028; in the off years, calendars and ticketing systems either roll them to February 28 or to March 1, and the two choices disagree.

The century skip also stretches the rhythm. Leap years usually march four apart: 2020, 2024, 2028, 2032. But across a skipped century the gap becomes eight years. Between 1896 and 1904 there was no leap year at all, because 1900 was dropped. That eight-year stretch is the single most under-tested span in date code, precisely because most test suites only sample years near the present, where the gap is always four.

The Off-by-One Bugs This Causes in Code

I have shipped at least one of these bugs, and I have reviewed several more. The pattern is always the same: someone implements the easy half of the rule and the hard half waits quietly for a century year or a leap February.

The classic is the half-finished check. A developer writes year % 4 == 0 and stops, because every test year they thought of, 2020, 2024, 2016, passes. The code looks correct for 99% of the years it will ever see. Then a financial report dated in February 2100 computes a day count that is off by one, because 2100 is not actually a leap year and the code thinks it is. The fix is never clever, it is just the full three-part rule, but you have to know the full rule exists to write it.

The mirror-image bug is the over-correction: someone hears "centuries are not leap years" and special-cases every 00 year to 365 days. Now 2000 and 2400 come back wrong, because they are leap years. You only avoid both bugs by running the 400 check before you decide a century year is common.

Then there are the date-arithmetic off-by-ones that have nothing to do with computing isLeap and everything to do with forgetting February changes length. A deadline counted as "60 days from January 15" lands a day early or late if you assumed February has 28 days in a year where it has 29. A loop that fills a year's worth of days hard-coded to 365 silently drops December 31 in leap years. When I am counting days across a February, I sanity-check the span with the days until date calculator rather than trusting a hand-written + 28, and I confirm the year's leap status first so I know whether February contributes 28 or 29.

How to Test Date Code So It Survives

If you write any function that touches years, treat the leap year rule as a small but adversarial spec, and pick test cases that actually exercise it. The minimum set is: a plain leap year (2024), a plain common year (2023), a century non-leap (1900 or 2100), and a divisible-by-400 leap (2000 or 2400). Those four cover all three branches of the rule, and the two century cases are the ones a naive implementation fails.

Add a span that crosses a skipped century, like 1896 to 1904, so any code that assumes a four-year cadence trips immediately. Add a date-arithmetic case that walks through a leap February, because that is where the length-of-month bugs hide rather than in the isLeap function itself. Birthday and age math is a good real-world stress test here, and the age calculator is handy for spot-checking how a leapling's age advances across years where their birthday does and does not exist.

The leap year rule is tiny, four words of arithmetic, and that smallness is exactly why it gets shipped half-done. Learn both halves once, keep the four canonical test years in your head, and the bug that bit everyone from grade-school students to satellite engineers stops being able to bite you.


Made by Toolora · Updated 2026-06-13