Skip to content

Leap Year



Why we need leap years

Analogy

  • Our calendar marks a year as exactly 365 days. But the solar system does not keep time in round numbers.
  • It takes Earth approximately 365.2422 days—roughly 365 days, 5 hours, 48 minutes, and 46 seconds—to complete one orbit around the sun.
  • If we ignored those extra ~6 hours, our calendar would drift by about one day every four years. After a single century, seasons would shift by nearly a month, with summer drifting into autumn and winter.
  • Adding one day (February 29) every four years fixes most of the drift. But that slight overcorrection adds up over centuries, requiring two extra rules to keep human calendars in sync with the stars.

Definition

A leap year is a calendar year containing 366 days instead of 365, with an added 29th day in February.

In software, determining a leap year requires checking divisibility by 4, with special rules for century years divisible by 100 and 400.


The three Gregorian rules

Adding a leap day every 4 years adds 24 hours every 4 years (an average of 6 extra hours per year). But the solar excess is only 5 hours, 48 minutes, and 46 seconds.

That means adding a leap day every 4 years overshoots the solar orbit by about 11 minutes each year. Over 400 years, that small surplus accumulates to roughly three extra days.

The Gregorian calendar uses three filters to correct the error:

  1. The 4-year rule: Any year evenly divisible by 4 is a leap year.
  2. The 100-year exception: If the year is also divisible by 100 (a century year like 1800 or 1900), it is not a leap year. This removes three surplus days every four centuries.
  3. The 400-year correction: If the year is divisible by 400 (like 1600 or 2000), it is a leap year after all. This restores one day to fine-tune the calendar.
The 3-tier decision cascadeyear % 4 == 0?NoCommon (365d)Yesyear % 100 == 0?NoLeap Year (366d)Yes (Century)year % 400 == 0?NoCommon (365d)YesLeap Year (366d)Tracing example years2024Leap Year (366d)÷4 is true, ÷100 is false→ 29 days in February1900Common Year (365d)÷100 is true, but ÷400 is false→ 28 days in February2000Leap Year (366d)÷100 is true and ÷400 is true→ 29 days in February(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)

The Java implementation

In Java, all three rules combine into a single boolean expression:

Code
public class LeapYear {  public static boolean isLeapYear(int year) {      return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);  }}

This expression leverages short-circuit evaluation:

  • For an ordinary year like 2023: 2023 % 4 == 0 evaluates to false. Because && requires both sides to be true, Java never checks year % 100. It moves directly to the right side of ||, sees 2023 % 400 == 0 is false, and returns false.
  • For a normal leap year like 2024: 2024 % 4 == 0 is true, and 2024 % 100 != 0 is true. The left side of || is satisfied, so Java never evaluates the right side at all and immediately returns true.
  • For a century year like 1900: 1900 % 100 != 0 is false, failing the left side. The right side 1900 % 400 == 0 is also false, correctly identifying 1900 as a common year.
  • For a quad-century like 2000: The left side fails because 2000 is divisible by 100, but the right side 2000 % 400 == 0 succeeds, correctly returning true.

The if-else pattern: checking the exception first

If you prefer explicit if statements instead of a compound boolean expression, you can write the checks by handling the most specific condition first:

Code
public static boolean isLeapYear(int year) {  if (year % 400 == 0) {      return true;  }  if (year % 100 == 0) {      return false;  }  return year % 4 == 0;}

Because each return immediately exits the function, testing year % 400 == 0 first guarantees that no 400-year exception can be accidentally rejected by the 100-year check below it.


Common mistakes to watch for

Watch out

  • Checking only year % 4 == 0: This is the most common bug in date-handling code. It causes years like 1900, 2100, and 2200 to be falsely treated as leap years.
  • Confusing AND and OR: Writing year % 4 == 0 || year % 400 == 0 && year % 100 != 0 changes the evaluation grouping. Always group the century check explicitly with parentheses: (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0).

Quick check


Key takeaway

Divisible by 4 adds an extra day; century years take three of them back; every 400 years restores one. In code: (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0).