首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 更多程式設計知識

Python 取得月份天數完整指南:calendar 模組、datetime 計算與效能比較

     編輯:更多程式設計知識

取得指定年月的天數是常見需求。Python 標準庫提供 `calendar.monthrange`、手動計算等多種方式。本文完整解析各方法原理、效能比較、邊界情況與最佳實務。

標準庫方法:calendar.monthrange (推薦)


import calendar

# 回傳 (weekday_of_first_day, number_of_days)
# weekday: 0=Monday ... 6=Sunday
result = calendar.monthrange(2024, 2)  # 閏年二月
print(result)  # (3, 29) -> 週四開始, 29天

# 直接取得天數
days = calendar.monthrange(2024, 2)[1]
print(f"2024年2月有 {days} 天")  # 29

# 常見年份測試
for year in [2023, 2024, 2000, 1900, 2025]:
    days = calendar.monthrange(year, 2)[1]
    print(f"{year}年2月: {days}天")
# 2023: 28, 2024: 29, 2000: 29, 1900: 28, 2025: 28

替代方法:datetime 計算


from datetime import date, timedelta

def days_in_month_datetime(year, month):
    """利用下個月第一天減去一天"""
    if month == 12:
        next_month = date(year + 1, 1, 1)
    else:
        next_month = date(year, month + 1, 1)
    
    last_day = next_month - timedelta(days=1)
    return last_day.day

# 測試
print(days_in_month_datetime(2024, 2))  # 29
print(days_in_month_datetime(2023, 2))  # 28

手動計算:閏年規則


def is_leap_year(year):
    """格里曆閏年規則"""
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

def days_in_month_manual(year, month):
    if month == 2:
        return 29 if is_leap_year(year) else 28
    elif month in {4, 6, 9, 11}:
        return 30
    else:
        return 31

# 查表法 (最快)
_MONTH_DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

def days_in_month_lookup(year, month):
    if month == 2 and is_leap_year(year):
        return 29
    return _MONTH_DAYS[month]

效能基準測試


import timeit
import calendar
from datetime import date, timedelta

def bench_calendar():
    return calendar.monthrange(2024, 2)[1]

def bench_datetime():
    next_month = date(2025, 3, 1)
    return (next_month - timedelta(days=1)).day

def bench_manual():
    year, month = 2024, 2
    return 29 if (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)) else 28

def bench_lookup():
    year, month = 2024, 2
    if month == 2 and (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)):
        return 29
    return _MONTH_DAYS[month]

# 執行測試
iterations = 1_000_000
print(f"calendar.monthrange: {timeit.timeit(bench_calendar, number=iterations):.3f}s")
print(f"datetime 計算:      {timeit.timeit(bench_datetime, number=iterations):.3f}s")
print(f"手動閏年判斷:      {timeit.timeit(bench_manual, number=iterations):.3f}s")
print(f"查表法:            {timeit.timeit(bench_lookup, number=iterations):.3f}s")

# 典型結果 (相對速度):
# 查表法:       1.0x (基準)
# 手動閏年判斷: 1.2x
# calendar:     3.5x (函數呼叫開銷)
# datetime:     8.0x (物件建立開銷)

進階功能:calendar 模組其他實用函數


import calendar

# 1. 月曆矩陣 (每週一列,0 表示該月外的日期)
cal = calendar.monthcalendar(2024, 2)
print("2024年2月月曆:")
for week in cal:
    print(week)
# [[0, 0, 0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], ...]

# 2. 文字月曆
print(calendar.month(2024, 2))
#     February 2024
# Mo Tu We Th Fr Sa Su
#                1  2  3  4
#  5  6  7  8  9 10 11
# ...

# 3. 完整年曆
# print(calendar.calendar(2024))

# 4. 判斷閏年
print(calendar.isleap(2024))  # True
print(calendar.isleap(1900))  # False

# 5. 取得月份名稱
print(calendar.month_name[2])    # 'February'
print(calendar.month_abbr[2])    # 'Feb'

# 6. 取得星期名稱
print(calendar.day_name[0])      # 'Monday'
print(calendar.day_abbr[0])      # 'Mon'

# 7. 設定週首日 (預設 0=Monday)
calendar.setfirstweekday(calendar.SUNDAY)
cal = calendar.monthcalendar(2024, 2)  # 現在週日為第一欄

實戰應用:日期範圍迭代


from datetime import date, timedelta
import calendar

def iterate_month_days(year, month):
    """產生該月所有日期"""
    days = calendar.monthrange(year, month)[1]
    for day in range(1, days + 1):
        yield date(year, month, day)

def get_month_range(year, month):
    """取得該月第一天與最後一天 (date 物件)"""
    first = date(year, month, 1)
    last_day = calendar.monthrange(year, month)[1]
    last = date(year, month, last_day)
    return first, last

# 使用範例
first, last = get_month_range(2024, 2)
print(f"2024-02 範圍: {first} 至 {last}")

# 批次處理該月所有工作日
workdays = [d for d in iterate_month_days(2024, 2) if d.weekday() < 5]
print(f"2024-02 工作日數: {len(workdays)}")

時區感知版本 (Python 3.9+ zoneinfo)


from datetime import datetime
from zoneinfo import ZoneInfo
import calendar

def days_in_month_tz(year, month, tz_name="Asia/Taipei"):
    """考慮時區的月份天數 (極少數情況跨日)"""
    tz = ZoneInfo(tz_name)
    # 該月第一天 00:00
    start = datetime(year, month, 1, tzinfo=tz)
    # 下個月第一天 00:00
    if month == 12:
        end = datetime(year + 1, 1, 1, tzinfo=tz)
    else:
        end = datetime(year, month + 1, 1, tzinfo=tz)
    
    # 計算天數差 (通常與無時區版相同)
    diff = end - start
    return diff.days

# 正常情況下結果相同
print(days_in_month_tz(2024, 2))  # 29

方法選擇指南

場景推薦方法理由
一般應用、可讀性優先calendar.monthrange標準庫、語意清晰、功能豐富
高效能、大量呼叫 (百萬級)查表法 + 內聯閏年判斷無函數呼叫、無物件建立、最快
已使用 datetime 物件datetime 計算法程式碼一致性、物件導向
需同時取得月初星期calendar.monthrange一次取得雙資訊
教學/展示閏年邏輯手動計算邏輯透明、易理解
跨時區精確計算zoneinfo + datetime處理夏令時間邊界情況

常見邊界情況測試


import calendar

# 1. 歷史年份 (格里曆 1582 年起)
print(calendar.monthrange(1582, 10))  # (4, 31) - 格里曆採用月份

# 2. 極大年份
print(calendar.monthrange(9999, 12))  # (4, 31)

# 3. 無效參數
try:
    calendar.monthrange(2024, 13)
except Exception as e:
    print(f"預期錯誤: {type(e).__name__}")  # Illegal month value

# 4. 閏秒不影響天數計算
# 閏秒只影響時間精度,不影響日期

# 5. 不同曆法 (需第三方庫)
# 伊斯蘭曆、農曆、波斯曆等需使用 convertdate、hijri-converter 等庫

掌握 `calendar.monthrange` 核心用法,理解替代方案效能特性,能根據應用場景選擇最適合的月份天數計算策略。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved