Documentation Python

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で対応できない場合は、dateutilrelativedeltaを活用しましょう。

参考文献

円