How to calculate a leap year in Python

Learn how to calculate a leap year in Python. Discover different methods, tips, real-world uses, and how to debug common errors.

How to calculate a leap year in Python
Published on: 
Tue
Feb 24, 2026
Updated on: 
Mon
Apr 6, 2026
The Replit Team

A leap year calculation in Python is a classic programming exercise. This task helps you master conditional logic with operators like and, or, and not for specific date-based rules.

In this article, you'll explore techniques to check for leap years. You'll find practical tips, see real-world applications, and get debugging advice to help you handle dates correctly in your projects.

Using basic if conditions to check leap years

year = 2024
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
print(f"{year} is a leap year")
else:
print(f"{year} is not a leap year")--OUTPUT--2024 is a leap year

This approach uses a single, compound if statement to apply the leap year rules directly. The logic relies on the modulo operator (%) to check for divisibility, combining the conditions with and and or.

A year is considered a leap year if either of these two conditions is true:

  • It is evenly divisible by 4, but not by 100.
  • It is evenly divisible by 400.

This structure correctly handles century years. For example, 1900 is not a leap year, but 2000 is, and this logic captures that distinction efficiently.

Basic techniques for leap year calculation

That initial example combines several key techniques, from using the % operator to structuring boolean logic, which you can scale to check entire ranges for leap years.

Using the % operator to check divisibility

def is_leap(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)

print(is_leap(2000)) # Divisible by 400
print(is_leap(1900)) # Divisible by 100 but not 400
print(is_leap(2024)) # Divisible by 4 but not 100--OUTPUT--True
False
True

The is_leap function packages the logic cleanly. It relies on the modulo operator (%) to find the remainder of a division. For instance, if year % 4 equals zero, you know the year is evenly divisible by four. Learn more about using the modulo operator for various calculations.

The function’s return statement uses a single boolean expression to check if a year is:

  • Divisible by 4 but not by 100, or
  • Divisible by 400.

This compact structure correctly evaluates all the rules at once, making the code both efficient and easy to read. For more insights on building logical conditions in Python, see dedicated guides on logical operators.

Using boolean expressions for clearer logic

def is_leap_year(year):
divisible_by_4 = year % 4 == 0
divisible_by_100 = year % 100 == 0
divisible_by_400 = year % 400 == 0
return divisible_by_4 and (not divisible_by_100 or divisible_by_400)

print(f"2020: {is_leap_year(2020)}, 2100: {is_leap_year(2100)}")--OUTPUT--2020: True, 2100: False

This version of the is_leap_year function boosts clarity by assigning each condition to a descriptive boolean variable. Storing the results of checks like year % 4 == 0 in variables such as divisible_by_4 makes the code's purpose immediately obvious and easier to read.

The final return statement then combines these variables to evaluate the leap year rules. A year qualifies if it is:

  • Divisible by 4, and
  • Either not divisible by 100 or is divisible by 400.

This technique makes complex logic much easier to follow and debug.

Finding leap years in a range

def leap_years_between(start, end):
return [year for year in range(start, end + 1)
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)]

print(leap_years_between(1995, 2024))--OUTPUT--[1996, 2000, 2004, 2008, 2012, 2016, 2020, 2024]

A list comprehension offers a powerful and Pythonic way to find all leap years in a range. The leap_years_between function packs a loop and a conditional check into one readable line.

  • It iterates through every year from start to end using range(start, end + 1).
  • The if clause filters this sequence, keeping only the years that meet the leap year criteria.
  • Finally, it returns a new list containing just the leap years.

Advanced leap year implementations

While manual checks are great for learning, Python's built-in modules offer more robust and efficient ways to handle leap year calculations for real-world applications.

Using Python's built-in calendar module

import calendar

for year in [2000, 1900, 2020, 2023]:
is_leap = calendar.isleap(year)
print(f"{year}: {'Leap year' if is_leap else 'Not a leap year'}")--OUTPUT--2000: Leap year
1900: Not a leap year
2020: Leap year
2023: Not a leap year

Python's calendar module offers a straightforward way to check for leap years without manual logic. The calendar.isleap() function does all the heavy lifting for you.

  • You simply pass a year to the function.
  • It returns True if it's a leap year and False otherwise.

This approach is cleaner and less prone to errors, as the complex rules are handled internally. It's a reliable method that's already part of Python's standard library, so you don't have to reinvent the wheel.

Analyzing leap centuries with list comprehensions

import calendar

centuries = [1700, 1800, 1900, 2000, 2100]
leap_centuries = [year for year in centuries if calendar.isleap(year)]
regular_centuries = [year for year in centuries if not calendar.isleap(year)]

print(f"Leap centuries: {leap_centuries}")
print(f"Non-leap centuries: {regular_centuries}")--OUTPUT--Leap centuries: [2000]
Non-leap centuries: [1700, 1800, 1900, 2100]

This example combines list comprehensions with the calendar.isleap() function to efficiently sort century years. It creates two new lists by filtering the original centuries list based on the function's output.

  • The leap_centuries list collects years where calendar.isleap(year) returns True.
  • The regular_centuries list uses not calendar.isleap(year) to gather the remaining years.

This neatly demonstrates the specific rule for centuries. A century year is only a leap year if it's divisible by 400, which is why only 2000 makes the cut in this example.

Using datetime to validate leap years

from datetime import datetime, timedelta

def is_leap_using_datetime(year):
feb28 = datetime(year, 2, 28)
next_day = feb28 + timedelta(days=1)
return next_day.day == 29

for year in [2020, 2021, 2022, 2024]:
print(f"{year}: {is_leap_using_datetime(year)}")--OUTPUT--2020: True
2021: False
2022: False
2024: True

This clever trick uses Python's datetime module to sidestep manual logic. The is_leap_using_datetime function checks for a leap year by seeing if February 29th actually exists for a given year. This technique is useful when comparing dates in Python for various applications.

  • It starts by creating a datetime object for February 28th.
  • Next, it adds one day using timedelta(days=1).
  • The function then checks if the resulting day is the 29th. If it is, the year is a leap year. Otherwise, the date would have rolled over to March 1st, confirming it's not.

Move faster with Replit

Replit is an AI-powered development platform that comes with all Python dependencies pre-installed. This means you can skip setup and start coding instantly, moving directly from learning a technique to applying it. Instead of piecing together functions like calendar.isleap(), you can use Agent 4 to build complete applications directly from a description.

You can describe the tool you need, and the Agent will handle the code, testing, and deployment. For example, you could create:

  • A project timeline planner that projects deadlines across multiple years, accurately adjusting for the extra day in years like 2024 and 2028.
  • A historical date validator that checks if a date like "February 29, 1900" was a valid calendar day.
  • A subscription renewal calculator that projects future billing dates, correctly handling the extra day in leap years.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Even with simple logic, a few common pitfalls can trip you up when calculating leap years in Python.

Forgetting to group conditions with parentheses is a frequent mistake. Python's operator precedence rules mean that and is evaluated before or, which can break your formula. Without parentheses, an expression like year % 4 == 0 and year % 100 != 0 or year % 400 == 0 is interpreted differently than intended, leading to incorrect results for certain years. Always wrap your conditions to ensure the logic is evaluated in the correct order.

Another common issue is performing an incomplete check when validating dates containing February 29th. It’s not enough to just see if the day is 29; you must also confirm the year itself is a leap year. For example, the date "February 29, 1900" is invalid because 1900 was a century year not divisible by 400. A robust validation function checks both the date and the year's leap status together.

Failing to handle invalid inputs can easily crash your program. If your function expects an integer for the year but receives a string or a float, it will raise a TypeError. To build more resilient code, you should always include error handling with try-except blocks to catch invalid data types and manage them without breaking the application. This is especially important when securing AI-generated code.

Forgetting parentheses in leap year formula

Python's operator precedence can easily trip you up. When you forget parentheses, the and condition is checked before or, altering your logic. This makes the formula fail for century years like 1900, as the following code demonstrates.

def is_leap_year(year):
# Missing parentheses causes incorrect logic
return year % 4 == 0 and year % 100 != 0 or year % 400 == 0

print(is_leap_year(1900)) # Incorrectly returns True

Without parentheses, the and operator binds the first two checks together, which breaks the formula for century years. The final or condition then misfires, causing the error. The code below shows how to enforce the correct order.

def is_leap_year(year):
# Proper parentheses for correct order of operations
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)

print(is_leap_year(1900)) # Correctly returns False

The corrected is_leap_year function uses parentheses to enforce the proper order of operations. By grouping (year % 4 == 0 and year % 100 != 0), you force Python to evaluate this condition as a single unit first. This is crucial because it prevents the final or year % 400 == 0 check from misfiring on its own. Always group conditions this way when mixing and and or operators to ensure your logic is evaluated as intended. Tools for code repair can help identify and fix such logic errors automatically.

Incomplete leap year check for February 29th

A simple check for divisibility by four isn't enough to identify a leap year correctly. This logic fails on the special rule for century years, causing errors for years like 1900. The following days_in_february function demonstrates this exact pitfall.

def days_in_february(year):
# Incomplete check - only checks divisibility by 4
if year % 4 == 0:
return 29
return 28

print(days_in_february(1900)) # Incorrectly returns 29

The days_in_february function's logic is too simple, as it only checks for divisibility by four. This causes it to fail on century years like 1900. The corrected version below applies the complete rule for an accurate check.

def days_in_february(year):
# Complete leap year formula
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
return 29
return 28

print(days_in_february(1900)) # Correctly returns 28

The corrected days_in_february function now implements the full leap year logic, checking if a year is divisible by 4 but not 100, unless it's also divisible by 400. This is crucial for correctly identifying non-leap century years like 1900. You should use this complete check whenever you're validating dates or building calendar-based features to avoid errors with historical or future dates, ensuring your application handles time-sensitive data accurately.

Failing to handle invalid date inputs

It's easy to forget that February has 29 days in a leap year, which can lead to bugs in date validation. A function might incorrectly flag a valid date like "February 29, 2020" as an error. The following validate_date function demonstrates this exact problem.

def validate_date(day, month, year):
days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
# Bug: Doesn't adjust February for leap years
return 1 <= month <= 12 and 1 <= day <= days_in_month[month]

print(validate_date(29, 2, 2020)) # Should be valid but returns False

The validate_date function's hardcoded days_in_month list doesn't account for leap years, causing it to reject valid dates like February 29th. The following code integrates a proper check to resolve this issue.

def validate_date(day, month, year):
days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
days_in_month[2] = 29
return 1 <= month <= 12 and 1 <= day <= days_in_month[month]

print(validate_date(29, 2, 2020)) # Correctly returns True

The corrected validate_date function now dynamically adjusts for leap years. It first checks if the input year is a leap year and, if so, updates February's day count to 29 before validating the date.

This simple change ensures that valid dates like "February 29, 2020" are correctly accepted. It's a crucial detail to remember when building any feature that handles date inputs—like event schedulers or data entry forms—to prevent rejecting valid user data.

Real-world applications

Beyond avoiding common errors, this logic is essential for building real-world applications that validate dates and perform date arithmetic.

Validating dates with the is_leap function

You can reuse your is_leap function to build a more robust date validator that correctly handles dates like February 29th.

def is_leap(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)

def validate_date(date_str):
day, month, year = map(int, date_str.split('/'))
days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

if month == 2 and is_leap(year):
return 1 <= day <= 29
return 1 <= month <= 12 and 1 <= day <= days_in_month[month]

print(validate_date("29/2/2020")) # Valid leap year date
print(validate_date("29/2/2021")) # Invalid date

The validate_date function checks if a date string is valid by reusing the is_leap logic. It first parses the date string into day, month, and year integers. The function's key strength is its special handling for February.

  • If the month is February and is_leap(year) returns true, it correctly validates day 29.
  • For all other dates, it falls back to a standard check using a list of days per month.

This modular approach makes the validation logic clean and reliable, correctly identifying dates like "29/2/2020" as valid.

Implementing date arithmetic with the next_day function

You can build on the is_leap logic to perform date arithmetic, creating a next_day function that correctly handles rollovers into the next month or year.

def is_leap(year):
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)

def next_day(year, month, day):
days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if month == 2 and is_leap(year):
days_in_month[2] = 29

if day < days_in_month[month]:
return year, month, day + 1
elif month < 12:
return year, month + 1, 1
else:
return year + 1, 1, 1

print(next_day(2020, 2, 28)) # Leap year: Feb 29
print(next_day(2023, 2, 28)) # Non-leap year: Mar 1

The next_day function calculates the following calendar day, building on the is_leap logic to ensure date arithmetic is accurate. Similar techniques are essential when calculating age in Python, where leap years affect precision.

  • It first checks if the year is a leap year, updating February's day count to 29 if needed.
  • If the current day isn't the last of the month, it simply increments the day.
  • Otherwise, it rolls over to the first day of the next month or—if it's December 31st—to the next year.

This modular approach makes the function robust for handling all date transitions, and it's well-suited for automated self-testing.

Get started with Replit

Now, turn this logic into a tool. Tell Replit Agent to "build an age-in-days calculator that accounts for leap years" or "create a business day counter between two dates."

The Agent writes the code, tests for errors, and deploys the app for you, transforming your description into a working tool. Start building with Replit.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.