Skip to main content

Appendix C. Leap Years

This appendix details the rules and implementation of leap year calculations.

Leap Year Rules

The leap year rules in the Gregorian calendar are designed to make the average year length close to the actual period of Earth's orbit around the Sun (approximately 365.2422 days).

Three Rules

A leap year must satisfy one of the following conditions:

  1. Years divisible by 4 but not by 100 are leap years
  2. Years divisible by 400 are leap years

In pseudocode:

if (year % 400 == 0) then
return LEAP_YEAR
else if (year % 100 == 0) then
return NOT_LEAP_YEAR
else if (year % 4 == 0) then
return LEAP_YEAR
else
return NOT_LEAP_YEAR

Implementation Examples

C Language

int is_leap_year(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

// Or a clearer version
int is_leap_year_verbose(int year) {
if (year % 400 == 0) {
return 1; // Divisible by 400: leap year
}
if (year % 100 == 0) {
return 0; // Divisible by 100 but not 400: common year
}
if (year % 4 == 0) {
return 1; // Divisible by 4 but not 100: leap year
}
return 0; // Otherwise: common year
}

Python

def is_leap_year(year):
"""Determine if year is a leap year"""
return (year % 400 == 0) or (year % 4 == 0 and year % 100 != 0)

# Or use Python calendar module
import calendar
print(calendar.isleap(2000)) # True

JavaScript

function isLeapYear(year) {
return (year % 400 === 0) || (year % 4 === 0 && year % 100 !== 0);
}

Java

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

// Or use Java 8+ GregorianCalendar
import java.util.GregorianCalendar;
boolean isLeap = GregorianCalendar.isLeapYear(2000);

Historical Leap Year Examples

Recent Leap Years (21st Century)

2000: Leap year ✅ (divisible by 400)
2001: Common year ❌
2002: Common year ❌
2003: Common year ❌
2004: Leap year ✅ (divisible by 4, not by 100)
2008: Leap year ✅
2012: Leap year ✅
2016: Leap year ✅
2020: Leap year ✅
2024: Leap year ✅
2028: Leap year ✅

Century Years (Special Cases)

1600: Leap year ✅ (divisible by 400)
1700: Common year ❌ (divisible by 100 but not 400)
1800: Common year ❌ (divisible by 100 but not 400)
1900: Common year ❌ (divisible by 100 but not 400)
2000: Leap year ✅ (divisible by 400)
2100: Common year ❌ (divisible by 100 but not 400)
2200: Common year ❌
2300: Common year ❌
2400: Leap year ✅ (divisible by 400)

Days in Each Month

Understanding leap years is crucial for determining days in each month:

def days_in_month(year, month):
"""Return number of days in specified year and month"""
days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

if month == 2 and is_leap_year(year):
return 29
return days_per_month[month - 1]

# Examples
print(days_in_month(2000, 2)) # 29 (leap year February)
print(days_in_month(1900, 2)) # 28 (common year February)
print(days_in_month(2024, 4)) # 30 (April always 30 days)

Date Validation

def is_valid_date(year, month, day):
"""Validate if date is valid"""
if month < 1 or month > 12:
return False

if day < 1:
return False

max_day = days_in_month(year, month)
if day > max_day:
return False

return True

# Tests
print(is_valid_date(2000, 2, 29)) # True (leap year)
print(is_valid_date(1900, 2, 29)) # False (common year)
print(is_valid_date(2002, 4, 31)) # False (April has only 30 days)

History of Leap Years

Julian vs Gregorian Calendar

Julian Calendar:

  • Introduced by Julius Caesar in 46 BC
  • Simple rule: leap year every 4 years
  • Average year length: 365.25 days
  • Problem: about 11 minutes longer than actual solar year

Gregorian Calendar:

  • Introduced by Pope Gregory XIII in 1582
  • Modified rule: century years must be divisible by 400
  • Average year length: 365.2425 days
  • Closer to actual solar year (365.2422 days)

Adoption of Gregorian Calendar

Different countries adopted the Gregorian calendar at different times:

1582: Italy, Spain, Portugal
1752: Great Britain and colonies (including US)
1917: Russia
1923: Greece

Why Do We Need Leap Years?

Earth's actual orbital period around the Sun is approximately 365.2422 days, not exactly 365 days.

Consequences without leap years:
Annual error: 0.2422 days ≈ 5 hours 48 minutes 46 seconds
After 4 years: about 1 day
After 100 years: about 24 days
After 400 years: about 97 days

Seasons would gradually drift!

Gregorian Calendar Accuracy:

Leap years in 400 years: 97
(Years divisible by 4 in 400 years: 100
Minus divisible by 100: 4
Plus divisible by 400: 1
= 100 - 4 + 1 = 97)

Total days in 400 years: 365 × 400 + 97 = 146,097 days
Average per year: 146,097 ÷ 400 = 365.2425 days

Error: 365.2425 - 365.2422 = 0.0003 days/year
Would take about 3,333 years to accumulate 1 day error

RFC 3339 Implementation Requirements

When implementing RFC 3339, leap years must be handled correctly:

# Validate RFC 3339 date-time
def validate_rfc3339_date(year, month, day):
"""Validate date portion of RFC 3339 format"""
# Year range check
if year < 0 or year > 9999:
return False, "Year must be 0000-9999"

# Month check
if month < 1 or month > 12:
return False, "Month must be 01-12"

# Day check (considering leap years)
if not is_valid_date(year, month, day):
max_day = days_in_month(year, month)
return False, f"Day must be 01-{max_day:02d} for {year}-{month:02d}"

return True, "Valid date"

# Tests
print(validate_rfc3339_date(2000, 2, 29)) # (True, 'Valid date')
print(validate_rfc3339_date(1900, 2, 29)) # (False, '...')

Key Point: Correctly implementing leap year rules is crucial for RFC 3339 compliance. Incorrect leap year determination can lead to accepting invalid dates or rejecting valid dates.