Pythonで日付に特定の日数を加えたい場合、datetimeモジュールとtimedeltaを組み合わせて簡単に実現できます。この記事では、様々な日付計算の方法を解説します。
日付計算方法の比較
| 方法 | 用途 | 標準ライブラリ |
|---|---|---|
timedelta | 日・週・時間・分・秒の加減算 | ○ |
relativedelta | 月・年単位の加減算 | × (dateutil) |
pandas.DateOffset | データ分析での日付操作 | × (pandas) |
numpy.timedelta64 | 大量データの日付計算 | × (numpy) |
timedeltaの基本
日数の加算・減算
from datetime import datetime, timedelta
# 現在の日付
today = datetime.now()
print(f"今日: {today.strftime('%Y-%m-%d')}")
# 日数を加算
after_5_days = today + timedelta(days=5)
print(f"5日後: {after_5_days.strftime('%Y-%m-%d')}")
# 日数を減算
before_5_days = today - timedelta(days=5)
print(f"5日前: {before_5_days.strftime('%Y-%m-%d')}")
# 負の値でも減算可能
also_before_5_days = today + timedelta(days=-5)
print(f"5日前: {also_before_5_days.strftime('%Y-%m-%d')}")
timedeltaの引数
timedeltaは複数の単位を組み合わせて使用できます。
from datetime import datetime, timedelta
now = datetime.now()
# 週単位
one_week_later = now + timedelta(weeks=1)
print(f"1週間後: {one_week_later}")
# 複数の単位を組み合わせ
delta = timedelta(days=2, hours=3, minutes=30)
result = now + delta
print(f"2日3時間30分後: {result}")
# timedeltaの引数一覧
delta = timedelta(
days=1,
seconds=30,
microseconds=1000,
milliseconds=500,
minutes=15,
hours=2,
weeks=1
)
print(f"合計秒数: {delta.total_seconds()}")
dateオブジェクトとdatetimeオブジェクト
from datetime import date, datetime, timedelta
# dateオブジェクト(日付のみ)
today_date = date.today()
future_date = today_date + timedelta(days=30)
print(f"30日後: {future_date}") # 2024-11-22
# datetimeオブジェクト(日付と時刻)
now = datetime.now()
future_datetime = now + timedelta(days=30, hours=5)
print(f"30日5時間後: {future_datetime}")
# 特定の日付から計算
specific_date = date(2024, 1, 1)
target_date = specific_date + timedelta(days=100)
print(f"2024年1月1日の100日後: {target_date}") # 2024-04-10
月・年単位の加算(relativedelta)
標準ライブラリのtimedeltaでは月や年単位の計算ができないため、dateutilライブラリを使用します。
from datetime import datetime
from dateutil.relativedelta import relativedelta
today = datetime.now()
# 1ヶ月後
one_month_later = today + relativedelta(months=1)
print(f"1ヶ月後: {one_month_later.strftime('%Y-%m-%d')}")
# 1年後
one_year_later = today + relativedelta(years=1)
print(f"1年後: {one_year_later.strftime('%Y-%m-%d')}")
# 複合的な計算
result = today + relativedelta(years=1, months=2, days=15)
print(f"1年2ヶ月15日後: {result.strftime('%Y-%m-%d')}")
# 月末の扱い
jan_31 = datetime(2024, 1, 31)
feb_end = jan_31 + relativedelta(months=1)
print(f"1月31日の1ヶ月後: {feb_end}") # 2024-02-29(閏年)
実践的な例
期限計算
from datetime import datetime, timedelta
def calculate_deadline(start_date: datetime, business_days: int) -> datetime:
"""営業日(土日を除く)で期限を計算"""
current = start_date
days_added = 0
while days_added < business_days:
current += timedelta(days=1)
# 土曜(5)と日曜(6)を除く
if current.weekday() < 5:
days_added += 1
return current
# 使用例
start = datetime(2024, 10, 23) # 水曜日
deadline = calculate_deadline(start, 5)
print(f"開始日: {start.strftime('%Y-%m-%d (%A)')}")
print(f"5営業日後: {deadline.strftime('%Y-%m-%d (%A)')}")
日付の差分計算
from datetime import datetime, date
# 2つの日付の差を計算
date1 = date(2024, 1, 1)
date2 = date(2024, 12, 31)
diff = date2 - date1
print(f"日数差: {diff.days}日") # 365日
# datetimeの差分
dt1 = datetime(2024, 10, 23, 9, 0, 0)
dt2 = datetime(2024, 10, 25, 18, 30, 0)
diff = dt2 - dt1
print(f"差分: {diff}") # 2 days, 9:30:00
print(f"合計秒数: {diff.total_seconds()}") # 207000.0
定期的な日付の生成
from datetime import datetime, timedelta
from typing import Generator
def date_range(start: datetime, end: datetime, step_days: int = 1) -> Generator[datetime, None, None]:
"""開始日から終了日まで、指定した間隔で日付を生成"""
current = start
while current <= end:
yield current
current += timedelta(days=step_days)
# 使用例: 1週間分の日付を生成
start = datetime(2024, 10, 21)
end = datetime(2024, 10, 27)
for dt in date_range(start, end):
print(dt.strftime('%Y-%m-%d (%A)'))
# 使用例: 隔週の日付を生成
print("\n隔週:")
for dt in date_range(start, start + timedelta(days=42), step_days=14):
print(dt.strftime('%Y-%m-%d'))
年齢計算
from datetime import date
from dateutil.relativedelta import relativedelta
def calculate_age(birthdate: date, reference_date: date = None) -> int:
"""生年月日から年齢を計算"""
if reference_date is None:
reference_date = date.today()
age = relativedelta(reference_date, birthdate)
return age.years
# 使用例
birthday = date(1990, 5, 15)
age = calculate_age(birthday)
print(f"年齢: {age}歳")
# 特定の日時点での年齢
reference = date(2024, 12, 31)
age_at_year_end = calculate_age(birthday, reference)
print(f"2024年末時点の年齢: {age_at_year_end}歳")
月初・月末の計算
from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta
import calendar
def get_month_start(dt: date) -> date:
"""月初日を取得"""
return dt.replace(day=1)
def get_month_end(dt: date) -> date:
"""月末日を取得"""
# 翌月1日の前日
next_month = dt.replace(day=1) + relativedelta(months=1)
return next_month - timedelta(days=1)
def get_month_end_stdlib(dt: date) -> date:
"""月末日を取得(標準ライブラリのみ)"""
_, last_day = calendar.monthrange(dt.year, dt.month)
return dt.replace(day=last_day)
# 使用例
today = date(2024, 10, 15)
print(f"今月の月初: {get_month_start(today)}") # 2024-10-01
print(f"今月の月末: {get_month_end(today)}") # 2024-10-31
pandasでの日付計算
大量のデータを扱う場合は、pandasが効率的です。
import pandas as pd
# 日付範囲の生成
dates = pd.date_range(start='2024-01-01', periods=12, freq='ME')
print("月末の日付リスト:")
print(dates)
# DateOffsetを使った計算
base_date = pd.Timestamp('2024-10-23')
# 営業日で加算
business_day_later = base_date + pd.offsets.BDay(5)
print(f"5営業日後: {business_day_later}")
# 月末に移動
month_end = base_date + pd.offsets.MonthEnd(0)
print(f"月末: {month_end}")
よくあるエラーと対処
from datetime import datetime, timedelta
# TypeError: unsupported operand type(s)
# 文字列と加算しようとした場合
date_str = "2024-10-23"
# result = date_str + timedelta(days=5) # エラー
# 解決: 文字列をdatetimeに変換
dt = datetime.strptime(date_str, "%Y-%m-%d")
result = dt + timedelta(days=5)
print(result)
# タイムゾーンの混在に注意
from datetime import timezone
# aware datetimeとnaive datetimeを混ぜるとエラー
aware_dt = datetime.now(timezone.utc)
naive_dt = datetime.now()
# diff = aware_dt - naive_dt # TypeError
# 解決: 両方をaware、またはnaiveに統一
naive_aware = aware_dt.replace(tzinfo=None)
diff = naive_aware - naive_dt
まとめ
| 操作 | 推奨方法 |
|---|---|
| 日・時間の加減算 | timedelta(days=n, hours=n) |
| 週単位の加減算 | timedelta(weeks=n) |
| 月・年単位の加減算 | relativedelta(months=n, years=n) |
| 営業日計算 | 自作関数またはpandas.offsets.BDay |
| 大量データの処理 | pandas.DateOffset |
日付計算では、月末の扱いやタイムゾーンに注意が必要です。標準ライブラリのtimedeltaで対応できない場合は、dateutilのrelativedeltaを活用しましょう。