Skip to main content

Appendix C. Leap Years (闰年)

本附录详细说明闰年的计算规则和实现。

闰年规则

格里高利历中的闰年规则旨在使平均年长度接近地球绕太阳公转的实际周期(约365.2422天)。

三条规则

闰年必须同时满足以下条件之一:

  1. 能被4整除但不能被100整除的年份是闰年
  2. 能被400整除的年份是闰年

用伪代码表示:

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

实现示例

C语言

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

// 或者更清晰的版本
int is_leap_year_verbose(int year) {
if (year % 400 == 0) {
return 1; // 能被400整除:闰年
}
if (year % 100 == 0) {
return 0; // 能被100整除但不能被400整除:平年
}
if (year % 4 == 0) {
return 1; // 能被4整除但不能被100整除:闰年
}
return 0; // 其他:平年
}

Python

def is_leap_year(year):
"""判断是否为闰年"""
return (year % 400 == 0) or (year % 4 == 0 and year % 100 != 0)

# 或使用Python calendar模块
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);
}

// 或使用Java 8+的GregorianCalendar
import java.util.GregorianCalendar;
boolean isLeap = GregorianCalendar.isLeapYear(2000);

历史闰年示例

近期闰年 (21世纪)

2000: 闰年 ✅ (能被400整除)
2001: 平年 ❌
2002: 平年 ❌
2003: 平年 ❌
2004: 闰年 ✅ (能被4整除,不能被100整除)
2008: 闰年 ✅
2012: 闰年 ✅
2016: 闰年 ✅
2020: 闰年 ✅
2024: 闰年 ✅
2028: 闰年 ✅

世纪年(特殊情况)

1600: 闰年 ✅ (能被400整除)
1700: 平年 ❌ (能被100整除但不能被400整除)
1800: 平年 ❌ (能被100整除但不能被400整除)
1900: 平年 ❌ (能被100整除但不能被400整除)
2000: 闰年 ✅ (能被400整除)
2100: 平年 ❌ (能被100整除但不能被400整除)
2200: 平年 ❌
2300: 平年 ❌
2400: 闰年 ✅ (能被400整除)

每月天数

了解闰年对于确定每月天数至关重要:

def days_in_month(year, 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]

# 示例
print(days_in_month(2000, 2)) # 29 (闰年二月)
print(days_in_month(1900, 2)) # 28 (平年二月)
print(days_in_month(2024, 4)) # 30 (四月总是30天)

验证日期有效性

def is_valid_date(year, month, day):
"""验证日期是否有效"""
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

# 测试
print(is_valid_date(2000, 2, 29)) # True (闰年)
print(is_valid_date(1900, 2, 29)) # False (平年)
print(is_valid_date(2002, 4, 31)) # False (四月只有30天)

闰年的历史

儒略历 vs 格里高利历

儒略历 (Julian Calendar):

  • 由尤利乌斯·凯撒在公元前46年引入
  • 简单规则:每4年一闰
  • 平均年长度:365.25天
  • 问题:比实际太阳年长约11分钟

格里高利历 (Gregorian Calendar):

  • 由教皇格里高利十三世在1582年引入
  • 修正规则:世纪年需能被400整除
  • 平均年长度:365.2425天
  • 更接近实际太阳年(365.2422天)

采用格里高利历

不同国家在不同时间采用格里高利历:

1582: 意大利、西班牙、葡萄牙
1752: 英国及殖民地(包括美国)
1917: 俄罗斯
1923: 希腊

为什么需要闰年?

地球绕太阳公转的实际周期约为 365.2422天,而不是整整365天。

没有闰年的后果:
每年误差: 0.2422天 ≈ 5小时48分46秒
4年后累计: 约1天
100年后累计: 约24天
400年后: 约97天

季节将逐渐漂移!

格里高利历的精度:

400年中的闰年数: 97个
(400年能被4整除的: 100个
减去能被100整除的: 4个
加回能被400整除的: 1个
= 100 - 4 + 1 = 97个)

400年总天数: 365 × 400 + 97 = 146,097天
平均每年: 146,097 ÷ 400 = 365.2425天

误差: 365.2425 - 365.2422 = 0.0003天/年
需要约3,333年才会累计1天误差

RFC 3339的实现要求

在实现RFC 3339时,必须正确处理闰年:

# 验证RFC 3339日期时间
def validate_rfc3339_date(year, month, day):
"""验证RFC 3339格式的日期部分"""
# 年份范围检查
if year < 0 or year > 9999:
return False, "Year must be 0000-9999"

# 月份检查
if month < 1 or month > 12:
return False, "Month must be 01-12"

# 日期检查(考虑闰年)
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"

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

关键要点: 正确实现闰年规则对于RFC 3339合规性至关重要。错误的闰年判断会导致接受无效日期或拒绝有效日期。