Python で「営業日」を計算する(祝日・休業日対応)
業務システムでは「3 営業日後」「月末の最終営業日」といった計算が頻繁に必要になります。Python には営業日計算をサポートするライブラリがいくつかありますが、それぞれ特徴が異なります。
numpy の busday 関数
NumPy には営業日計算用の関数が組み込まれています。科学計算ライブラリですが、日付計算にも使えます。
import numpy as np
from datetime import date
# 5営業日後を計算
today = date(2024, 12, 20) # 金曜日
result = np.busday_offset(today, 5)
print(result)
# 2024-12-27(土日を除いた5営業日後)
busday_offset は土日を自動的にスキップします。祝日を考慮するには holidays パラメータを指定します。
import numpy as np
from datetime import date
# 祝日を指定
holidays = [
'2024-12-23', # 天皇誕生日の振替
'2024-12-31', # 年末
'2025-01-01', # 元日
]
today = date(2024, 12, 20)
result = np.busday_offset(today, 5, holidays=holidays)
print(result)
# 2024-12-30(祝日も除外)
2 つの日付間の営業日数を数えることもできます。
import numpy as np
start = '2024-12-01'
end = '2024-12-31'
count = np.busday_count(start, end)
print(count)
# 22(12月の営業日数、祝日考慮なし)
pandas の営業日オフセット
pandas はより柔軟な営業日計算を提供します。
import pandas as pd
from datetime import date
# BusinessDay オフセット
today = pd.Timestamp(2024, 12, 20)
result = today + pd.offsets.BusinessDay(5)
print(result)
# 2024-12-27 00:00:00
pandas の強みはカスタムカレンダーを定義できる点です。
import pandas as pd
from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday
# 日本の祝日カレンダーを定義
class JapaneseHolidayCalendar(AbstractHolidayCalendar):
rules = [
Holiday('元日', month=1, day=1),
Holiday('成人の日', month=1, day=8), # 第2月曜
Holiday('建国記念の日', month=2, day=11),
# ... 他の祝日を追加
]
# カスタムカレンダーを使った営業日
from pandas.tseries.offsets import CustomBusinessDay
jpn_bd = CustomBusinessDay(calendar=JapaneseHolidayCalendar())
today = pd.Timestamp(2024, 12, 20)
result = today + 5 * jpn_bd
print(result)
ただし、日本の祝日は「春分の日」や「秋分の日」のように天文計算で決まるもの、「ハッピーマンデー」で移動するものがあり、手動での定義は現実的ではありません。
jpholiday ライブラリ
日本の祝日に特化した jpholiday ライブラリを使うと、複雑な祝日ルールを自分で実装する必要がなくなります。
import jpholiday
from datetime import date
# 特定の日が祝日かどうか
print(jpholiday.is_holiday(date(2024, 11, 3)))
# True(文化の日)
# 祝日名を取得
print(jpholiday.is_holiday_name(date(2024, 11, 3)))
# 文化の日
# 期間内の祝日一覧
holidays = jpholiday.between(date(2024, 1, 1), date(2024, 12, 31))
for d, name in holidays:
print(f"{d}: {name}")
jpholiday は振替休日や国民の休日も正しく計算してくれます。
高速だが祝日リストを自分で用意する必要がある
日本の祝日を自動計算してくれるが、営業日計算機能自体はない
営業日計算の実装
jpholiday と組み合わせて営業日計算を実装してみます。
import jpholiday
from datetime import date, timedelta
def is_business_day(d: date) -> bool:
"""営業日かどうか判定"""
# 土日チェック
if d.weekday() >= 5:
return False
# 祝日チェック
if jpholiday.is_holiday(d):
return False
return True
def add_business_days(start: date, days: int) -> date:
"""営業日を加算"""
current = start
remaining = days
direction = 1 if days >= 0 else -1
remaining = abs(remaining)
while remaining > 0:
current += timedelta(days=direction)
if is_business_day(current):
remaining -= 1
return current
# 使用例
today = date(2024, 12, 20)
result = add_business_days(today, 5)
print(result)
この実装はシンプルですが、大量の日付を処理する場合はパフォーマンスに問題が出ます。
高速化のアプローチ
営業日を事前計算してキャッシュする方法が効果的です。
import jpholiday
from datetime import date, timedelta
from functools import lru_cache
@lru_cache(maxsize=None)
def get_business_days_list(year: int) -> list[date]:
"""年間の営業日リストを生成"""
start = date(year, 1, 1)
end = date(year, 12, 31)
business_days = []
current = start
while current <= end:
if current.weekday() < 5 and not jpholiday.is_holiday(current):
business_days.append(current)
current += timedelta(days=1)
return business_days
def add_business_days_fast(start: date, days: int) -> date:
"""キャッシュを使った高速営業日計算"""
bd_list = get_business_days_list(start.year)
try:
idx = bd_list.index(start)
except ValueError:
# startが営業日でない場合、次の営業日を探す
for i, d in enumerate(bd_list):
if d > start:
idx = i
break
new_idx = idx + days
# 年をまたぐ場合の処理
while new_idx >= len(bd_list):
new_idx -= len(bd_list)
bd_list = get_business_days_list(bd_list[0].year + 1)
return bd_list[new_idx]
月末営業日の計算
「月末の最終営業日」もよく使う計算パターンです。
import jpholiday
from datetime import date, timedelta
import calendar
def last_business_day_of_month(year: int, month: int) -> date:
"""月の最終営業日を取得"""
# 月末日を取得
last_day = calendar.monthrange(year, month)[1]
d = date(year, month, last_day)
# 営業日になるまで遡る
while d.weekday() >= 5 or jpholiday.is_holiday(d):
d -= timedelta(days=1)
return d
# 使用例
for month in range(1, 13):
last_bd = last_business_day_of_month(2024, month)
print(f"2024年{month}月の最終営業日: {last_bd}")
会社独自の休業日
実務では会社独自の休業日(創立記念日、年末年始の特別休暇など)を考慮する必要があります。
import jpholiday
from datetime import date
class BusinessDayCalculator:
def __init__(self, company_holidays: list[date] = None):
self.company_holidays = set(company_holidays or [])
def is_business_day(self, d: date) -> bool:
if d.weekday() >= 5:
return False
if jpholiday.is_holiday(d):
return False
if d in self.company_holidays:
return False
return True
# 使用例
company_holidays = [
date(2024, 12, 28), # 仕事納め翌日
date(2024, 12, 30),
date(2024, 12, 31),
]
calc = BusinessDayCalculator(company_holidays)
print(calc.is_business_day(date(2024, 12, 30)))
# False(会社休業日)
高速だが祝日リストを手動管理。バッチ処理向き。
日本の祝日を自動計算。会社独自の休業日と組み合わせて使う。
営業日計算は一見単純ですが、祝日の扱い、年またぎ、うるう年など考慮すべき点が多いです。信頼性のあるライブラリを活用し、テストケースを充実させることが重要です。