Pythonうるう年を判定・カウント・列挙する方法【完全解説】
Pythonでうるう年を判定、カウント、列挙する方法を詳しく解説します。標準ライブラリのcalendarモジュールを使用した基本的な方法から、カスタム実装まで幅広くカバーします。
うるう年の基本概念
うるう年は4年に1度発生しますが、以下のルールに従います:
- 4で割り切れる年はうるう年
- ただし、100で割り切れる年は平年
- ただし、400で割り切れる年はうるう年
import calendar
# 基本的なうるう年判定
print(f"2024年: {calendar.isleap(2024)}") # True
print(f"2023年: {calendar.isleap(2023)}") # False
print(f"2000年: {calendar.isleap(2000)}") # True (400で割り切れる)
print(f"1900年: {calendar.isleap(1900)}") # False (100で割り切れるが400では割り切れない)
うるう年の判定方法
calendarモジュールを使用
import calendar
def check_leap_year_calendar(year):
"""calendarモジュールを使用したうるう年判定"""
return calendar.isleap(year)
# 使用例
years = [2020, 2021, 2022, 2023, 2024]
for year in years:
is_leap = check_leap_year_calendar(year)
print(f"{year}年: {'うるう年' if is_leap else '平年'}")
手動実装による判定
def is_leap_year_manual(year):
"""うるう年判定の手動実装"""
if year % 400 == 0:
return True
elif year % 100 == 0:
return False
elif year % 4 == 0:
return True
else:
return False
def is_leap_year_compact(year):
"""うるう年判定のコンパクト実装"""
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
# 検証用テスト
test_years = [1600, 1700, 1800, 1900, 2000, 2020, 2021, 2024]
print("=== うるう年判定の検証 ===")
for year in test_years:
calendar_result = calendar.isleap(year)
manual_result = is_leap_year_manual(year)
compact_result = is_leap_year_compact(year)
print(f"{year}: calendar={calendar_result}, manual={manual_result}, compact={compact_result}")
詳細な判定結果を返す関数
def analyze_leap_year(year):
"""うるう年の詳細な分析結果を返す"""
analysis = {
'year': year,
'is_leap': calendar.isleap(year),
'divisible_by_4': year % 4 == 0,
'divisible_by_100': year % 100 == 0,
'divisible_by_400': year % 400 == 0,
'reason': ''
}
if analysis['divisible_by_400']:
analysis['reason'] = '400で割り切れるためうるう年'
elif analysis['divisible_by_100']:
analysis['reason'] = '100で割り切れるが400で割り切れないため平年'
elif analysis['divisible_by_4']:
analysis['reason'] = '4で割り切れるためうるう年'
else:
analysis['reason'] = '4で割り切れないため平年'
return analysis
# 使用例
special_years = [1600, 1700, 1900, 2000, 2024]
for year in special_years:
result = analyze_leap_year(year)
print(f"{year}年: {result['reason']}")
うるう年のカウント
期間内のうるう年をカウント
import calendar
def count_leap_years(start_year, end_year):
"""指定期間内のうるう年をカウント"""
count = 0
leap_years = []
for year in range(start_year, end_year + 1):
if calendar.isleap(year):
count += 1
leap_years.append(year)
return {
'count': count,
'leap_years': leap_years,
'period': f"{start_year}-{end_year}",
'total_years': end_year - start_year + 1
}
# 使用例
result = count_leap_years(2000, 2030)
print(f"2000-2030年のうるう年: {result['count']}年")
print(f"うるう年: {result['leap_years']}")
効率的なうるう年カウント
def count_leap_years_efficient(start_year, end_year):
"""数学的計算による効率的なうるう年カウント"""
def count_divisible(start, end, divisor):
"""指定した除数で割り切れる年の数をカウント"""
return end // divisor - (start - 1) // divisor
# 4で割り切れる年の数
divisible_by_4 = count_divisible(start_year, end_year, 4)
# 100で割り切れる年の数(平年になる)
divisible_by_100 = count_divisible(start_year, end_year, 100)
# 400で割り切れる年の数(うるう年になる)
divisible_by_400 = count_divisible(start_year, end_year, 400)
# うるう年の総数
leap_count = divisible_by_4 - divisible_by_100 + divisible_by_400
return {
'count': leap_count,
'divisible_by_4': divisible_by_4,
'divisible_by_100': divisible_by_100,
'divisible_by_400': divisible_by_400
}
# 使用例と検証
start, end = 1900, 2100
efficient_result = count_leap_years_efficient(start, end)
normal_result = count_leap_years(start, end)
print(f"効率的計算: {efficient_result['count']}年")
print(f"通常計算: {normal_result['count']}年")
print(f"結果一致: {efficient_result['count'] == normal_result['count']}")
うるう年の列挙
基本的な列挙
import calendar
def list_leap_years(start_year, end_year):
"""指定範囲のうるう年を列挙"""
leap_years = []
for year in range(start_year, end_year + 1):
if calendar.isleap(year):
leap_years.append(year)
return leap_years
def list_leap_years_generator(start_year, end_year):
"""ジェネレータを使用したうるう年列挙"""
for year in range(start_year, end_year + 1):
if calendar.isleap(year):
yield year
# 使用例
leap_years_list = list_leap_years(2020, 2040)
print(f"2020-2040年のうるう年: {leap_years_list}")
# ジェネレータの使用
print("ジェネレータでの取得:")
for leap_year in list_leap_years_generator(2020, 2030):
print(f" {leap_year}年")
詳細情報付きの列挙
import calendar
from datetime import date
def enumerate_leap_years_detailed(start_year, end_year):
"""うるう年の詳細情報を含む列挙"""
leap_years_info = []
for year in range(start_year, end_year + 1):
if calendar.isleap(year):
# 2月29日の曜日を取得
feb_29 = date(year, 2, 29)
weekday_names = ['月', '火', '水', '木', '金', '土', '日']
weekday = weekday_names[feb_29.weekday()]
leap_info = {
'year': year,
'feb_29_weekday': weekday,
'days_in_year': 366,
'years_since_last': None
}
# 前回のうるう年からの年数を計算
if leap_years_info:
leap_info['years_since_last'] = year - leap_years_info[-1]['year']
leap_years_info.append(leap_info)
return leap_years_info
# 使用例
detailed_leap_years = enumerate_leap_years_detailed(2020, 2032)
print("=== うるう年詳細情報 ===")
for info in detailed_leap_years:
years_info = f" ({info['years_since_last']}年後)" if info['years_since_last'] else ""
print(f"{info['year']}年: 2月29日は{info['feb_29_weekday']}曜日{years_info}")
特定条件でのうるう年列挙
import calendar
def find_leap_years_by_condition(start_year, end_year, condition_func):
"""特定条件を満たすうるう年を検索"""
matching_years = []
for year in range(start_year, end_year + 1):
if calendar.isleap(year) and condition_func(year):
matching_years.append(year)
return matching_years
# 条件関数の例
def is_century_year(year):
"""世紀年(100で割り切れる年)かどうか"""
return year % 100 == 0
def ends_with_digit(year, digit):
"""指定した数字で終わる年かどうか"""
return year % 10 == digit
# 使用例
# 世紀年のうるう年
century_leap_years = find_leap_years_by_condition(1600, 2400, is_century_year)
print(f"世紀年のうるう年: {century_leap_years}")
# 4で終わるうるう年
leap_years_ending_4 = find_leap_years_by_condition(2000, 2100, lambda y: ends_with_digit(y, 4))
print(f"4で終わるうるう年: {leap_years_ending_4}")
実用的な応用例
年齢計算におけるうるう年考慮
import calendar
from datetime import date, timedelta
def calculate_age_with_leap_days(birth_year, birth_month, birth_day, target_date=None):
"""うるう年を考慮した年齢計算"""
if target_date is None:
target_date = date.today()
birth_date = date(birth_year, birth_month, birth_day)
# 基本年齢計算
age = target_date.year - birth_date.year
if target_date.month < birth_date.month or \
(target_date.month == birth_date.month and target_date.day < birth_date.day):
age -= 1
# 経験したうるう日の数を計算
leap_days_experienced = 0
for year in range(birth_year, target_date.year + 1):
if calendar.isleap(year):
leap_day = date(year, 2, 29)
if birth_date <= leap_day <= target_date:
leap_days_experienced += 1
# 総日数計算
total_days = (target_date - birth_date).days
return {
'age_years': age,
'total_days': total_days,
'leap_days_experienced': leap_days_experienced,
'birth_date': birth_date,
'target_date': target_date
}
# 使用例
age_info = calculate_age_with_leap_days(2000, 2, 28)
print(f"年齢: {age_info['age_years']}歳")
print(f"総日数: {age_info['total_days']}日")
print(f"経験したうるう日: {age_info['leap_days_experienced']}日")
カレンダー期間の計算
import calendar
from datetime import date, timedelta
def calculate_period_with_leap_info(start_date, end_date):
"""期間内のうるう年情報を含む計算"""
total_days = (end_date - start_date).days
# 期間内のうるう年を取得
leap_years = []
leap_days_in_period = 0
for year in range(start_date.year, end_date.year + 1):
if calendar.isleap(year):
leap_day = date(year, 2, 29)
if start_date <= leap_day <= end_date:
leap_years.append(year)
leap_days_in_period += 1
# 平均年長計算
years_span = end_date.year - start_date.year + 1
average_year_length = total_days / years_span if years_span > 0 else 0
return {
'start_date': start_date,
'end_date': end_date,
'total_days': total_days,
'years_span': years_span,
'leap_years_in_period': leap_years,
'leap_days_in_period': leap_days_in_period,
'average_year_length': round(average_year_length, 2)
}
# 使用例
start = date(2020, 1, 1)
end = date(2030, 12, 31)
period_info = calculate_period_with_leap_info(start, end)
print(f"期間: {period_info['start_date']} ~ {period_info['end_date']}")
print(f"総日数: {period_info['total_days']}日")
print(f"期間内のうるう年: {period_info['leap_years_in_period']}")
print(f"うるう日の数: {period_info['leap_days_in_period']}日")
print(f"平均年長: {period_info['average_year_length']}日")
2月29日生まれの人の誕生日計算
import calendar
from datetime import date
def get_birthday_dates_for_leap_day_born(birth_year, target_years):
"""2月29日生まれの人の各年の誕生日を計算"""
if not calendar.isleap(birth_year):
raise ValueError("誕生年がうるう年ではありません")
birthday_info = []
for year in target_years:
if calendar.isleap(year):
# うるう年の場合は2月29日
birthday = date(year, 2, 29)
is_actual_birthday = True
else:
# 平年の場合は2月28日または3月1日
# 一般的には2月28日が使用される
birthday = date(year, 2, 28)
is_actual_birthday = False
age = year - birth_year
birthday_info.append({
'year': year,
'birthday': birthday,
'age': age,
'is_actual_birthday': is_actual_birthday,
'is_leap_year': calendar.isleap(year)
})
return birthday_info
# 使用例
birth_year = 2000 # うるう年
target_years = list(range(2020, 2030))
birthday_schedule = get_birthday_dates_for_leap_day_born(birth_year, target_years)
print("=== 2月29日生まれの誕生日スケジュール ===")
for info in birthday_schedule:
actual_note = "実際の誕生日" if info['is_actual_birthday'] else "代替日"
print(f"{info['year']}年 ({info['age']}歳): {info['birthday']} ({actual_note})")
うるう年の統計分析
import calendar
def analyze_leap_year_statistics(start_year, end_year):
"""うるう年の統計分析"""
total_years = end_year - start_year + 1
leap_years = []
intervals = []
# うるう年の収集
for year in range(start_year, end_year + 1):
if calendar.isleap(year):
leap_years.append(year)
# うるう年間の間隔計算
for i in range(1, len(leap_years)):
intervals.append(leap_years[i] - leap_years[i-1])
# 統計計算
leap_count = len(leap_years)
leap_percentage = (leap_count / total_years) * 100
statistics = {
'period': f"{start_year}-{end_year}",
'total_years': total_years,
'leap_years_count': leap_count,
'leap_percentage': round(leap_percentage, 2),
'leap_years': leap_years,
'intervals': intervals,
'average_interval': round(sum(intervals) / len(intervals), 2) if intervals else 0,
'min_interval': min(intervals) if intervals else 0,
'max_interval': max(intervals) if intervals else 0
}
return statistics
# 使用例
stats = analyze_leap_year_statistics(1900, 2100)
print(f"=== {stats['period']} うるう年統計 ===")
print(f"総年数: {stats['total_years']}年")
print(f"うるう年数: {stats['leap_years_count']}年")
print(f"うるう年割合: {stats['leap_percentage']}%")
print(f"平均間隔: {stats['average_interval']}年")
print(f"間隔範囲: {stats['min_interval']}-{stats['max_interval']}年")
うるう年カレンダージェネレータ
import calendar
class LeapYearCalendar:
def __init__(self):
self.leap_years_cache = {}
def is_leap_year(self, year):
"""キャッシュ付きうるう年判定"""
if year not in self.leap_years_cache:
self.leap_years_cache[year] = calendar.isleap(year)
return self.leap_years_cache[year]
def get_february_info(self, year):
"""2月の情報を取得"""
is_leap = self.is_leap_year(year)
days_in_feb = 29 if is_leap else 28
return {
'year': year,
'is_leap_year': is_leap,
'days_in_february': days_in_feb,
'february_calendar': calendar.monthcalendar(year, 2)
}
def compare_februaries(self, years):
"""複数年の2月を比較"""
comparison = []
for year in years:
feb_info = self.get_february_info(year)
comparison.append(feb_info)
return comparison
def next_leap_year(self, from_year):
"""指定年以降の次のうるう年を取得"""
year = from_year + 1
while not self.is_leap_year(year):
year += 1
return year
def previous_leap_year(self, from_year):
"""指定年以前の前のうるう年を取得"""
year = from_year - 1
while year > 0 and not self.is_leap_year(year):
year -= 1
return year if year > 0 else None
# 使用例
leap_cal = LeapYearCalendar()
# 2月の比較
years_to_compare = [2023, 2024, 2025]
feb_comparison = leap_cal.compare_februaries(years_to_compare)
print("=== 2月の比較 ===")
for feb_info in feb_comparison:
leap_status = "うるう年" if feb_info['is_leap_year'] else "平年"
print(f"{feb_info['year']}年: {feb_info['days_in_february']}日 ({leap_status})")
# 次のうるう年検索
current_year = 2023
next_leap = leap_cal.next_leap_year(current_year)
prev_leap = leap_cal.previous_leap_year(current_year)
print(f"\n{current_year}年の前後のうるう年:")
print(f"前: {prev_leap}年")
print(f"次: {next_leap}年")
パフォーマンス比較
import calendar
import time
def performance_test():
"""うるう年判定のパフォーマンステスト"""
test_years = list(range(1, 10000))
# calendar.isleap()のテスト
start_time = time.time()
for year in test_years:
calendar.isleap(year)
calendar_time = time.time() - start_time
# 手動実装のテスト
start_time = time.time()
for year in test_years:
year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
manual_time = time.time() - start_time
print(f"=== パフォーマンステスト({len(test_years)}年分)===")
print(f"calendar.isleap(): {calendar_time:.4f}秒")
print(f"手動実装: {manual_time:.4f}秒")
print(f"速度比: {calendar_time / manual_time:.2f}倍")
# パフォーマンステストの実行
performance_test()
まとめ
- 判定方法:
calendar.isleap():標準的な方法- 手動実装:カスタマイズが必要な場合
- カウント:効率的な数学的計算が可能
- 列挙:ジェネレータを活用したメモリ効率化
- 実用例:年齢計算、誕生日管理、期間計算
- 応用:統計分析、カレンダー生成、パフォーマンス最適化
うるう年の理解と適切な処理により、正確な日付計算アプリケーションが開発できます。
■プロンプトだけでオリジナルアプリを開発・公開してみた!!
■AI時代の第一歩!「AI駆動開発コース」はじめました!
テックジム東京本校で先行開始。
■テックジム東京本校
「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。
<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。
<月1開催>放送作家による映像ディレクター養成講座
<オンライン無料>ゼロから始めるPython爆速講座

